XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/DeathMod

修复死神刀光世界残留与重叠融合

affed65
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

2 个文件 +184 -40
Modified Common/DeathDomainCrescentVisualSystem.cs +175 -38
@@ -25,12 +25,13 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
25 25 ReaperCombatRegistry.StandardCrescentThicknessRatio;
26 26
27 27 private static readonly Dictionary<long, CrescentDrawState> drawStates = [];
28 private static long nextVisualInstanceId;
28 29 private static BasicEffect? effect;
29 30 private static VertexPositionColorTexture[] vertices =
30 31 new VertexPositionColorTexture[(AngularSegments + 1) * (DepthBands + 1)];
31 32 private static short[] indices = CreateIndices();
32 33 private static VertexPositionColorTexture[] rimVertices =
33 new VertexPositionColorTexture[(AngularSegments + 1) * 2];
34 new VertexPositionColorTexture[AngularSegments * 6];
34 35
35 36 private readonly record struct CrescentDrawState(
36 37 Vector2 Center,
@@ -42,16 +43,48 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
42 43 bool PreserveInterior,
43 44 ulong UpdateTick);
44 45
45 internal static void Record(int owner, int identity, Vector2 center,
46 /// <summary>
47 /// Allocates a client-local identity for one physical swing. Projectile
48 /// identities and slots can be reused before a residual crescent expires at
49 /// maximum attack speed, so neither is safe as the draw-state key.
50 /// </summary>
51 internal static long AllocateVisualInstanceId()
52 {
53 nextVisualInstanceId++;
54 if (nextVisualInstanceId <= 0)
55 nextVisualInstanceId = 1;
56 return nextVisualInstanceId;
57 }
58
59 internal static void Record(long visualInstanceId, Vector2 center,
46 60 float rotation, float radius, float opacity, int swingDirection,
47 61 float lifeProgress, bool preserveInterior = false)
48 62 {
49 if (Main.dedServ || radius <= 1f || opacity <= 0.001f)
63 if (Main.dedServ || visualInstanceId <= 0 || radius <= 1f
64 || opacity <= 0.001f)
65 return;
66
67 if (drawStates.TryGetValue(visualInstanceId,
68 out CrescentDrawState existing))
69 {
70 // A slash is a cut made in world space, not an aura attached to the
71 // player. Keep its first center, orientation and size immutable;
72 // subsequent frames only advance the reveal frontier and refresh its
73 // residual clock. Already drawn material therefore stays exactly
74 // where the blade passed even if the player moves during the swing.
75 drawStates[visualInstanceId] = existing with
76 {
77 Opacity = opacity,
78 LifeProgress = Math.Max(existing.LifeProgress,
79 MathHelper.Clamp(lifeProgress, 0f, 1f)),
80 PreserveInterior = existing.PreserveInterior || preserveInterior,
81 UpdateTick = Main.GameUpdateCount
82 };
50 83 return;
84 }
51 85
52 long key = ((long)owner << 32) | (uint)identity;
53 drawStates[key] = new CrescentDrawState(center, rotation, radius,
54 opacity, swingDirection < 0 ? -1 : 1,
86 drawStates[visualInstanceId] = new CrescentDrawState(center, rotation,
87 radius, opacity, swingDirection < 0 ? -1 : 1,
55 88 MathHelper.Clamp(lifeProgress, 0f, 1f), preserveInterior,
56 89 Main.GameUpdateCount);
57 90 }
@@ -75,11 +108,16 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
75 108 drawStates.Remove(key);
76 109 }
77 110
78 public override void OnWorldUnload() => drawStates.Clear();
111 public override void OnWorldUnload()
112 {
113 drawStates.Clear();
114 nextVisualInstanceId = 0;
115 }
79 116
80 117 public override void Unload()
81 118 {
82 119 drawStates.Clear();
120 nextVisualInstanceId = 0;
83 121 BasicEffect? oldEffect = effect;
84 122 effect = null;
85 123 vertices = [];
@@ -114,13 +152,13 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
114 152 graphicsDevice.RasterizerState = RasterizerState.CullNone;
115 153 graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
116 154
117 foreach (CrescentDrawState state in drawStates.Values)
155 foreach ((long visualInstanceId, CrescentDrawState state) in drawStates)
118 156 {
119 157 if (Main.GameUpdateCount > state.UpdateTick + ResidualLifetimeFrames)
120 158 continue;
121 159 BuildVertices(state);
122 160 DrawBackdropLayers(graphicsDevice);
123 DrawStableRim(graphicsDevice, state);
161 DrawStableRim(graphicsDevice, visualInstanceId, state);
124 162 }
125 163 }
126 164
@@ -215,7 +253,7 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
215 253 }
216 254
217 255 private static void DrawStableRim(GraphicsDevice graphicsDevice,
218 CrescentDrawState state)
256 long visualInstanceId, CrescentDrawState state)
219 257 {
220 258 if (effect is null)
221 259 return;
@@ -225,57 +263,156 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
225 263 if (opacity <= 0.001f)
226 264 return;
227 265
228 DrawRimStrip(graphicsDevice, state, state.Radius * 0.030f,
266 DrawRimStrip(graphicsDevice, visualInstanceId, state,
267 state.Radius * 0.030f,
229 268 new Color(238, 5, 60) * (opacity * 0.82f));
230 DrawRimStrip(graphicsDevice, state, state.Radius * 0.0125f,
269 DrawRimStrip(graphicsDevice, visualInstanceId, state,
270 state.Radius * 0.0125f,
231 271 new Color(255, 225, 220) * (opacity * 0.96f));
232 272 }
233 273
234 274 private static void DrawRimStrip(GraphicsDevice graphicsDevice,
235 CrescentDrawState state, float width, Color color)
275 long visualInstanceId, CrescentDrawState state, float width,
276 Color color)
236 277 {
237 278 if (effect is null)
238 279 return;
239 280
240 281 float reveal = Smooth01(state.LifeProgress);
241 float rotationCos = (float)Math.Cos(state.Rotation);
242 float rotationSin = (float)Math.Sin(state.Rotation);
243 282 float outerRadius = state.Radius * OuterRadiusRatio;
244 283 int vertexIndex = 0;
245 for (int segment = 0; segment <= AngularSegments; segment++)
284 for (int segment = 0; segment < AngularSegments; segment++)
246 285 {
247 float progress = segment / (float)AngularSegments;
248 float angle = MathHelper.Lerp(-HalfSweep, HalfSweep, progress)
249 * state.SwingDirection;
250 float revealAlpha = 1f - SmoothStep(reveal - 0.024f,
251 reveal + 0.010f, progress);
252 float capAlpha = SmoothStep(0f, 0.018f, progress)
253 * SmoothStep(0f, 0.018f, 1f - progress);
254 Color vertexColor = color * (revealAlpha * capAlpha);
255 Vector2 radial = angle.ToRotationVector2();
256 for (int side = 0; side < 2; side++)
257 {
258 float localRadius = outerRadius - side * width;
259 Vector2 local = radial * localRadius;
260 Vector2 rotated = new(
261 local.X * rotationCos - local.Y * rotationSin,
262 local.X * rotationSin + local.Y * rotationCos);
263 Vector2 screen = state.Center + rotated - Main.screenPosition;
264 rimVertices[vertexIndex++] = new VertexPositionColorTexture(
265 new Vector3(screen, 0f), vertexColor, Vector2.Zero);
266 }
286 float startProgress = segment / (float)AngularSegments;
287 float endProgress = (segment + 1f) / AngularSegments;
288 float middleProgress = (startProgress + endProgress) * 0.5f;
289 Vector2 overlapProbe = GetCrescentPoint(state, middleProgress,
290 outerRadius - width * 0.55f);
291 if (IsCoveredByAnotherCrescent(overlapProbe, visualInstanceId,
292 width * 0.78f))
293 continue;
294
295 Color startColor = color * GetRimVertexAlpha(startProgress, reveal);
296 Color endColor = color * GetRimVertexAlpha(endProgress, reveal);
297 Vector2 outerStart = GetCrescentPoint(state, startProgress,
298 outerRadius);
299 Vector2 innerStart = GetCrescentPoint(state, startProgress,
300 outerRadius - width);
301 Vector2 outerEnd = GetCrescentPoint(state, endProgress,
302 outerRadius);
303 Vector2 innerEnd = GetCrescentPoint(state, endProgress,
304 outerRadius - width);
305
306 WriteRimVertex(ref vertexIndex, outerStart, startColor);
307 WriteRimVertex(ref vertexIndex, innerStart, startColor);
308 WriteRimVertex(ref vertexIndex, outerEnd, endColor);
309 WriteRimVertex(ref vertexIndex, outerEnd, endColor);
310 WriteRimVertex(ref vertexIndex, innerStart, startColor);
311 WriteRimVertex(ref vertexIndex, innerEnd, endColor);
267 312 }
268 313
314 if (vertexIndex < 3)
315 return;
316
269 317 effect.TextureEnabled = false;
270 318 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
271 319 {
272 320 pass.Apply();
273 graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip,
274 rimVertices, 0, rimVertices.Length - 2);
321 graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList,
322 rimVertices, 0, vertexIndex / 3);
275 323 }
276 324 effect.TextureEnabled = true;
277 325 }
278 326
327 private static float GetRimVertexAlpha(float progress, float reveal)
328 {
329 float revealAlpha = 1f - SmoothStep(reveal - 0.024f,
330 reveal + 0.010f, progress);
331 float capAlpha = SmoothStep(0f, 0.018f, progress)
332 * SmoothStep(0f, 0.018f, 1f - progress);
333 return revealAlpha * capAlpha;
334 }
335
336 private static Vector2 GetCrescentPoint(CrescentDrawState state,
337 float progress, float radius)
338 {
339 float angle = MathHelper.Lerp(-HalfSweep, HalfSweep, progress)
340 * state.SwingDirection + state.Rotation;
341 return state.Center + angle.ToRotationVector2() * radius;
342 }
343
344 private static void WriteRimVertex(ref int vertexIndex, Vector2 world,
345 Color color)
346 {
347 rimVertices[vertexIndex++] = new VertexPositionColorTexture(
348 new Vector3(world - Main.screenPosition, 0f), color, Vector2.Zero);
349 }
350
351 private static bool IsCoveredByAnotherCrescent(Vector2 world,
352 long currentVisualInstanceId, float sharedBoundaryTolerance)
353 {
354 foreach ((long otherId, CrescentDrawState other) in drawStates)
355 {
356 if (otherId == currentVisualInstanceId
357 || Main.GameUpdateCount
358 > other.UpdateTick + ResidualLifetimeFrames)
359 {
360 continue;
361 }
362
363 if (ContainsVisibleDomain(other, world,
364 preferOtherAtSharedBoundary: otherId > currentVisualInstanceId,
365 sharedBoundaryTolerance))
366 {
367 return true;
368 }
369 }
370 return false;
371 }
372
373 private static bool ContainsVisibleDomain(CrescentDrawState state,
374 Vector2 world, bool preferOtherAtSharedBoundary,
375 float sharedBoundaryTolerance)
376 {
377 Vector2 delta = world - state.Center;
378 float rotationCos = (float)Math.Cos(state.Rotation);
379 float rotationSin = (float)Math.Sin(state.Rotation);
380 Vector2 local = new(
381 delta.X * rotationCos + delta.Y * rotationSin,
382 -delta.X * rotationSin + delta.Y * rotationCos);
383 float radius = local.Length();
384 float angle = MathHelper.WrapAngle(local.ToRotation());
385 float progress = state.SwingDirection > 0
386 ? (angle + HalfSweep) / (HalfSweep * 2f)
387 : (HalfSweep - angle) / (HalfSweep * 2f);
388 if (progress is < 0f or > 1f
389 || progress > Smooth01(state.LifeProgress) + 0.010f)
390 {
391 return false;
392 }
393
394 float taper = (float)Math.Pow(Math.Max(0f,
395 Math.Sin(progress * MathHelper.Pi)), 0.67f);
396 taper *= 1f + 0.22f * (progress * 2f - 1f);
397 float outerRadius = state.Radius * OuterRadiusRatio;
398 float innerDistortion = ((float)Math.Sin(progress * 23f + 0.7f)
399 + (float)Math.Sin(progress * 47f - 1.4f) * 0.42f)
400 * state.Radius * 0.018f * taper;
401 float innerRadius = outerRadius
402 - state.Radius * MaximumThicknessRatio * taper
403 + innerDistortion;
404 if (radius < innerRadius - 1f || radius > outerRadius + 1f)
405 return false;
406
407 // Strictly interior overlap always removes the internal edge. Coincident
408 // or near-coincident rims use instance order so exactly one copy remains;
409 // without this tolerance, both ribbons would classify each other's
410 // inward probe as interior and erase the shared exterior entirely.
411 float boundaryDepth = outerRadius - radius;
412 return boundaryDepth > sharedBoundaryTolerance
413 || preferOtherAtSharedBoundary;
414 }
415
279 416 private static float GetResidualProgress(CrescentDrawState state)
280 417 {
281 418 ulong age = Main.GameUpdateCount > state.UpdateTick
Modified Projectiles/SickleSwingProjectile.cs +9 -2
@@ -40,6 +40,7 @@ public sealed class SickleSwingProjectile : ModProjectile
40 40 private Vector2 authoritativeVelocity;
41 41 private int authoritativeSwingDirection;
42 42 private int validHistorySamples;
43 private long deathCrescentVisualInstanceId;
43 44 private float animationProgress;
44 45 private float previousCollisionAngle;
45 46 private bool collisionAngleInitialized;
@@ -74,6 +75,7 @@ public sealed class SickleSwingProjectile : ModProjectile
74 75 ? MathHelper.Clamp(primaryDeathTempo, 1f,
75 76 ReaperCombatRegistry.DeathPrimaryMaximumTempo)
76 77 : 1f;
78 deathCrescentVisualInstanceId = 0;
77 79 configured = true;
78 80 Projectile.damage = Math.Max(1, (int)Math.Round(snapshot.Damage * ReaperCombatRegistry.GetPrimaryDamageMultiplier(snapshot, phase)));
79 81 Projectile.originalDamage = snapshot.Damage;
@@ -1013,10 +1015,15 @@ public sealed class SickleSwingProjectile : ModProjectile
1013 1015 float visibility = GetDeathCrescentVisibility();
1014 1016 if (visibility <= 0.001f)
1015 1017 return;
1018 if (deathCrescentVisualInstanceId <= 0)
1019 {
1020 deathCrescentVisualInstanceId = DeathDomainCrescentVisualSystem
1021 .AllocateVisualInstanceId();
1022 }
1016 1023 GetDeathPrimaryCrescentGeometry(out Vector2 center, out float rotation,
1017 1024 out float reach, out int direction);
1018 DeathDomainCrescentVisualSystem.Record(Projectile.owner,
1019 Projectile.identity, center, rotation, reach, visibility * 0.92f,
1025 DeathDomainCrescentVisualSystem.Record(deathCrescentVisualInstanceId,
1026 center, rotation, reach, visibility * 0.92f,
1020 1027 direction, animationProgress, DeathSpaceBreak);
1021 1028 }
1022 1029