返回提交历史
Modified
Common/DeathDomainBackdropTextureSystem.cs
+86
-0
Modified
Common/DeathDomainCrescentVisualSystem.cs
+55
-26
Modified
Common/DeathDomainTrailVisualSystem.cs
+87
-26
Modified
Common/DeathDomainVisualSystem.cs
+40
-13
Modified
Common/ReaperUltimateVisualSystem.cs
+8
-24
Modified
Common/ReaperVfxDirector.cs
+11
-0
Modified
Projectiles/ReaperActionControllerProjectile.cs
+0
-20
Modified
Projectiles/ReaperDeathDomainRiftProjectile.cs
+3
-1
Modified
Projectiles/ReaperStrikeProjectile.cs
+49
-8
Modified
Projectiles/SickleSwingProjectile.cs
+1
-1
XFEstudio/DeathMod
统一死神领域刀光并重制终焉斩击
981411c
代码差异
10 个文件
+340
-119
@@ -1,3 +1,4 @@
1
using DeathMod.Items;
1
2
using Microsoft.Xna.Framework;
2
3
using Microsoft.Xna.Framework.Graphics;
3
4
using System;
@@ -16,6 +17,9 @@ internal sealed class DeathDomainBackdropTextureSystem : ModSystem
16
17
{
17
18
internal const int LayerWidth = 4096;
18
19
internal const int LayerHeight = 1024;
20
internal const float ReferenceDomainDiameter =
21
(160f + DeathNecklace.RadiusGrowthPerLevel
22
* (DeathNecklace.MaxCoreLevel - 1)) * 2f;
19
23
20
24
private static readonly Texture2D?[] layers = new Texture2D?[3];
21
25
@@ -25,6 +29,88 @@ internal sealed class DeathDomainBackdropTextureSystem : ModSystem
25
29
return layers[index] ??= CreateLayer(index);
26
30
}
27
31
32
/// <summary>
33
/// Maps a world point into the exact parallax plate framing used by the Death
34
/// Necklace. Weapon crescents and persistent wounds use this instead of raw
35
/// texture-size UVs, so all three layers have the same scale, anchor and
36
/// parallax as the domain rather than forming a differently scaled collage.
37
/// </summary>
38
internal static Vector2 GetWorldUv(int layer, Vector2 world,
39
Vector2 anchorWorld, Vector2 apertureSize)
40
{
41
apertureSize.X = Math.Max(1f, apertureSize.X);
42
apertureSize.Y = Math.Max(1f, apertureSize.Y);
43
GetSourceFrame(layer, anchorWorld, apertureSize,
44
out float sourceOffsetX, out float sourceOffsetY,
45
out float viewWidth, out float viewHeight);
46
Vector2 normalized = (world - anchorWorld) / apertureSize
47
+ new Vector2(0.5f);
48
return new Vector2(
49
(sourceOffsetX + normalized.X * viewWidth) / LayerWidth,
50
(sourceOffsetY + normalized.Y * viewHeight) / LayerHeight);
51
}
52
53
internal static Vector2 GetWorldUv(int layer, Vector2 world,
54
Vector2 anchorWorld)
55
=> GetWorldUv(layer, world, anchorWorld,
56
new Vector2(ReferenceDomainDiameter));
57
58
/// <summary>
59
/// Uses the active owner's Death Necklace aperture when one exists. A
60
/// descended domain is sampled against exactly the current viewport; a
61
/// circular domain uses the same reference aperture as the necklace. Every
62
/// weapon mask belonging to that owner therefore reveals the same parallax
63
/// pixel at the same world position.
64
/// </summary>
65
internal static Vector2 GetMatchingDomainUv(int layer, Vector2 world,
66
int owner, Vector2 fallbackAnchor)
67
{
68
Vector2 anchor = fallbackAnchor;
69
Vector2 aperture = new(ReferenceDomainDiameter);
70
if (owner >= 0 && owner < Main.maxPlayers
71
&& Main.player[owner] is Player player && player.active)
72
{
73
anchor = player.Center;
74
DeathDomainVisualSystem.TryGetMatchingBackdropAperture(owner,
75
out anchor, out aperture);
76
}
77
return GetWorldUv(layer, world, anchor, aperture);
78
}
79
80
internal static float GetLayerOpacity(int layer)
81
=> layer switch { 0 => 1f, 1 => 0.9f, _ => 0.96f };
82
83
/// <summary>Returns the necklace's source rectangle for a world aperture.</summary>
84
internal static void GetSourceFrame(int layer, Vector2 worldCenter,
85
Vector2 apertureSize, out float sourceOffsetX,
86
out float sourceOffsetY, out float viewWidth, out float viewHeight)
87
{
88
layer = Math.Clamp(layer, 0, layers.Length - 1);
89
float referenceViewWidth = 1024f
90
* (layer switch { 0 => 0.68f, 1 => 0.9f, _ => 0.84f });
91
float referenceViewHeight = 512f
92
* (layer switch { 0 => 0.78f, 1 => 0.9f, _ => 0.86f });
93
float horizontalScale = Math.Max(0.001f,
94
apertureSize.X / ReferenceDomainDiameter);
95
float verticalScale = Math.Max(0.001f,
96
apertureSize.Y / ReferenceDomainDiameter);
97
viewWidth = Math.Min(LayerWidth,
98
referenceViewWidth * horizontalScale);
99
viewHeight = Math.Min(LayerHeight,
100
referenceViewHeight * verticalScale);
101
102
float worldWidth = Math.Max(1f, Main.maxTilesX * 16f);
103
float worldHeight = Math.Max(1f, Main.maxTilesY * 16f);
104
float worldX = MathHelper.Clamp(worldCenter.X / worldWidth, 0f, 1f);
105
float worldY = MathHelper.Clamp(worldCenter.Y / worldHeight, 0f, 1f);
106
float travelFactor = layer switch { 0 => 0.34f, 1 => 0.66f, _ => 1f };
107
float horizontalTravel = Math.Max(0f, LayerWidth - viewWidth)
108
* travelFactor;
109
sourceOffsetX = (LayerWidth - viewWidth - horizontalTravel) * 0.5f
110
+ horizontalTravel * worldX;
111
sourceOffsetY = worldY * Math.Max(0f, LayerHeight - viewHeight);
112
}
113
28
114
/// <summary>
29
115
/// Samples the exact three procedural plates used by the necklace domain and
30
116
/// composites them in premultiplied-alpha order. Death weapon masks use this
@@ -21,19 +21,25 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
21
21
private const int ResidualLifetimeFrames = 30;
22
22
private const float HalfSweep = ReaperCombatRegistry.DeathPrimaryHalfSweep;
23
23
private const float OuterRadiusRatio = 0.91f;
24
private const float MaximumThicknessRatio =
25
ReaperCombatRegistry.StandardCrescentThicknessRatio;
24
// The outside blade edge stays on the weapon tip. Additional size grows only
25
// toward the wielder, as requested, instead of increasing attack reach.
26
private const float MaximumThicknessRatio = 0.585f;
26
27
27
28
private static readonly Dictionary<long, CrescentDrawState> drawStates = [];
28
29
private static long nextVisualInstanceId;
29
30
private static BasicEffect? effect;
30
private static VertexPositionColorTexture[] vertices =
31
new VertexPositionColorTexture[(AngularSegments + 1) * (DepthBands + 1)];
31
private static VertexPositionColorTexture[][] layerVertices =
32
[
33
new VertexPositionColorTexture[(AngularSegments + 1) * (DepthBands + 1)],
34
new VertexPositionColorTexture[(AngularSegments + 1) * (DepthBands + 1)],
35
new VertexPositionColorTexture[(AngularSegments + 1) * (DepthBands + 1)]
36
];
32
37
private static short[] indices = CreateIndices();
33
38
private static VertexPositionColorTexture[] rimVertices =
34
39
new VertexPositionColorTexture[AngularSegments * 6];
35
40
36
41
private readonly record struct CrescentDrawState(
42
int Owner,
37
43
Vector2 Center,
38
44
float Rotation,
39
45
float Radius,
@@ -56,7 +62,8 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
56
62
return nextVisualInstanceId;
57
63
}
58
64
59
internal static void Record(long visualInstanceId, Vector2 center,
65
internal static void Record(long visualInstanceId, int owner,
66
Vector2 center,
60
67
float rotation, float radius, float opacity, int swingDirection,
61
68
float lifeProgress, bool preserveInterior = false)
62
69
{
@@ -83,7 +90,7 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
83
90
return;
84
91
}
85
92
86
drawStates[visualInstanceId] = new CrescentDrawState(center, rotation,
93
drawStates[visualInstanceId] = new CrescentDrawState(owner, center, rotation,
87
94
radius, opacity, swingDirection < 0 ? -1 : 1,
88
95
MathHelper.Clamp(lifeProgress, 0f, 1f), preserveInterior,
89
96
Main.GameUpdateCount);
@@ -120,7 +127,7 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
120
127
nextVisualInstanceId = 0;
121
128
BasicEffect? oldEffect = effect;
122
129
effect = null;
123
vertices = [];
130
layerVertices = [];
124
131
indices = [];
125
132
rimVertices = [];
126
133
if (oldEffect is not null && !Main.dedServ)
@@ -170,6 +177,7 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
170
177
for (int layer = 0; layer < 3; layer++)
171
178
{
172
179
effect.Texture = DeathDomainBackdropTextureSystem.GetLayer(layer);
180
VertexPositionColorTexture[] vertices = layerVertices[layer];
173
181
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
174
182
{
175
183
pass.Apply();
@@ -201,16 +209,13 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
201
209
float progress = segment / (float)AngularSegments;
202
210
float angle = MathHelper.Lerp(-HalfSweep, HalfSweep, progress)
203
211
* state.SwingDirection;
204
float taper = (float)Math.Pow(Math.Max(0f,
205
Math.Sin(progress * MathHelper.Pi)), 0.67f);
206
taper *= 1f + 0.22f * (progress * 2f - 1f);
212
float taper = GetCrescentTaper(progress);
207
213
// The textured interior and the separately drawn hot blade edge must
208
214
// share this exact boundary. Noise belongs inside the material;
209
215
// perturbing the outer radius creates a visible air gap at the rim.
210
216
float outerRadius = state.Radius * OuterRadiusRatio;
211
float innerDistortion = ((float)Math.Sin(progress * 23f + 0.7f)
212
+ (float)Math.Sin(progress * 47f - 1.4f) * 0.42f)
213
* state.Radius * 0.018f * taper;
217
float innerDistortion = GetInnerBoundaryNoise(state, progress,
218
taper);
214
219
float thickness = state.Radius * MaximumThicknessRatio * taper;
215
220
float innerRadius = outerRadius - thickness + innerDistortion;
216
221
float revealAlpha = 1f - SmoothStep(reveal - 0.024f,
@@ -241,13 +246,19 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
241
246
// lower alpha; depth never lets the ordinary world bleed through.
242
247
float alpha = revealAlpha * capAlpha * integrity;
243
248
244
// World-space UVs are deliberately independent of the current
245
// crescent angle, radius, and animation frame. Only the mask
246
// moves; the Death Domain scenery behind it never rotates.
247
Vector2 uv = new(world.X / DeathDomainBackdropTextureSystem.LayerWidth,
248
world.Y / DeathDomainBackdropTextureSystem.LayerHeight);
249
vertices[vertexIndex++] = new VertexPositionColorTexture(
250
new Vector3(screen, 0f), Color.White * alpha, uv);
249
// Each parallax layer uses the exact framing and scale of the
250
// Death Necklace. The mask moves, but its domain does not rotate.
251
for (int layer = 0; layer < 3; layer++)
252
{
253
Vector2 uv = DeathDomainBackdropTextureSystem.GetMatchingDomainUv(
254
layer, world, state.Owner, state.Center);
255
layerVertices[layer][vertexIndex] =
256
new VertexPositionColorTexture(
257
new Vector3(screen, 0f), Color.White * (alpha
258
* DeathDomainBackdropTextureSystem
259
.GetLayerOpacity(layer)), uv);
260
}
261
vertexIndex++;
251
262
}
252
263
}
253
264
}
@@ -391,13 +402,9 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
391
402
return false;
392
403
}
393
404
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);
405
float taper = GetCrescentTaper(progress);
397
406
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;
407
float innerDistortion = GetInnerBoundaryNoise(state, progress, taper);
401
408
float innerRadius = outerRadius
402
409
- state.Radius * MaximumThicknessRatio * taper
403
410
+ innerDistortion;
@@ -413,6 +420,28 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
413
420
|| preferOtherAtSharedBoundary;
414
421
}
415
422
423
private static float GetCrescentTaper(float progress)
424
{
425
float taper = (float)Math.Pow(Math.Max(0f,
426
Math.Sin(progress * MathHelper.Pi)), 0.67f);
427
return taper * (1f + 0.22f * (progress * 2f - 1f));
428
}
429
430
private static float GetInnerBoundaryNoise(CrescentDrawState state,
431
float progress, float taper)
432
{
433
// Continuous, layered wave noise keeps the centre-facing edge organic
434
// without the disconnected saw teeth produced by per-segment randomness.
435
float seed = state.Rotation * 1.37f
436
+ state.Center.X * 0.0013f + state.Center.Y * 0.0019f;
437
float wave = (float)Math.Sin(progress * MathHelper.TwoPi * 5f + seed)
438
+ (float)Math.Sin(progress * MathHelper.TwoPi * 11f
439
- seed * 0.71f) * 0.46f
440
+ (float)Math.Sin(progress * MathHelper.TwoPi * 23f
441
+ seed * 1.19f) * 0.19f;
442
return wave * state.Radius * 0.032f * taper;
443
}
444
416
445
private static float GetResidualProgress(CrescentDrawState state)
417
446
{
418
447
ulong age = Main.GameUpdateCount > state.UpdateTick
@@ -21,22 +21,29 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
21
21
new VertexPositionColorTexture[MaximumPoints * 2];
22
22
private static VertexPositionColorTexture[] mergedOutlineVertices =
23
23
new VertexPositionColorTexture[4096];
24
private static VertexPositionColorTexture[] mergedBackdropVertices =
25
new VertexPositionColorTexture[4096];
24
private static VertexPositionColorTexture[][] mergedBackdropVertices =
25
[
26
new VertexPositionColorTexture[4096],
27
new VertexPositionColorTexture[4096],
28
new VertexPositionColorTexture[4096]
29
];
26
30
private static readonly short[] indices = CreateIndices();
27
31
private static BasicEffect? effect;
28
32
29
33
private readonly record struct TrailDrawState(
34
int Owner,
30
35
IReadOnlyList<Vector2> Points,
31
36
float Width,
32
37
float Opacity,
33
38
bool MergeOverlappingRims,
39
float Fracture,
40
int Seed,
34
41
Vector4 Bounds,
35
42
ulong UpdateTick);
36
43
37
44
internal static void Record(int owner, int identity,
38
45
IReadOnlyList<Vector2> points, float width, float opacity,
39
bool mergeOverlappingRims = false)
46
bool mergeOverlappingRims = false, float fracture = 0f)
40
47
{
41
48
if (Main.dedServ || points.Count < 2 || width <= 0.5f
42
49
|| opacity <= 0.001f)
@@ -53,13 +60,15 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
53
60
drawStates[key] = existing with
54
61
{
55
62
Opacity = opacity,
63
Fracture = MathHelper.Clamp(fracture, 0f, 1f),
56
64
UpdateTick = Main.GameUpdateCount
57
65
};
58
66
return;
59
67
}
60
68
61
drawStates[key] = new TrailDrawState(points, width, opacity,
62
mergeOverlappingRims,
69
drawStates[key] = new TrailDrawState(owner, points, width, opacity,
70
mergeOverlappingRims, MathHelper.Clamp(fracture, 0f, 1f),
71
unchecked(identity * 397 ^ owner * 7919),
63
72
CalculateTrailBounds(points, width * 1.72f),
64
73
Main.GameUpdateCount);
65
74
}
@@ -141,10 +150,13 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
141
150
DrawSolidRibbon(graphicsDevice, state, state.Width * 1.17f,
142
151
new Color(255, 187, 178, 235)
143
152
* (state.Opacity * 0.88f));
144
BuildVertices(state, state.Width, Color.White * state.Opacity);
145
153
effect.TextureEnabled = true;
146
154
for (int layer = 0; layer < 3; layer++)
147
155
{
156
BuildVertices(state, state.Width,
157
Color.White * (state.Opacity
158
* DeathDomainBackdropTextureSystem
159
.GetLayerOpacity(layer)), layer);
148
160
effect.Texture = DeathDomainBackdropTextureSystem.GetLayer(layer);
149
161
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
150
162
{
@@ -184,7 +196,8 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
184
196
{
185
197
pass.Apply();
186
198
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList,
187
mergedBackdropVertices, 0, backdropVertexCount / 3);
199
mergedBackdropVertices[layer], 0,
200
backdropVertexCount / 3);
188
201
}
189
202
}
190
203
}
@@ -211,6 +224,8 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
211
224
Color color = baseColor * (state.Opacity * opacityMultiplier);
212
225
for (int segment = 0; segment < pointCount - 1; segment++)
213
226
{
227
if (!IsSegmentVisible(state, segment, pointCount))
228
continue;
214
229
Vector2 start = state.Points[segment];
215
230
Vector2 end = state.Points[segment + 1];
216
231
Vector2 startNormal = GetTrailNormal(state.Points, segment,
@@ -273,9 +288,10 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
273
288
continue;
274
289
}
275
290
int pointCount = Math.Min(MaximumPoints, state.Points.Count);
276
Color color = Color.White * state.Opacity;
277
291
for (int segment = 0; segment < pointCount - 1; segment++)
278
292
{
293
if (!IsSegmentVisible(state, segment, pointCount))
294
continue;
279
295
Vector2 start = state.Points[segment];
280
296
Vector2 end = state.Points[segment + 1];
281
297
Vector2 startNormal = GetTrailNormal(state.Points, segment,
@@ -292,26 +308,41 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
292
308
Vector2 rightStart = start + startNormal * startHalfWidth;
293
309
Vector2 leftEnd = end - endNormal * endHalfWidth;
294
310
Vector2 rightEnd = end + endNormal * endHalfWidth;
295
EnsureBatchCapacity(ref mergedBackdropVertices,
296
vertexIndex + 6);
297
WriteBackdropBatchVertex(ref vertexIndex, leftStart, color);
298
WriteBackdropBatchVertex(ref vertexIndex, rightStart, color);
299
WriteBackdropBatchVertex(ref vertexIndex, leftEnd, color);
300
WriteBackdropBatchVertex(ref vertexIndex, leftEnd, color);
301
WriteBackdropBatchVertex(ref vertexIndex, rightStart, color);
302
WriteBackdropBatchVertex(ref vertexIndex, rightEnd, color);
311
for (int layer = 0; layer < 3; layer++)
312
EnsureBatchCapacity(ref mergedBackdropVertices[layer],
313
vertexIndex + 6);
314
Vector2 anchor = GetBackgroundAnchor(state);
315
for (int layer = 0; layer < 3; layer++)
316
{
317
int layerVertexIndex = vertexIndex;
318
Color color = Color.White * (state.Opacity
319
* DeathDomainBackdropTextureSystem
320
.GetLayerOpacity(layer));
321
WriteBackdropBatchVertex(layer, state.Owner,
322
ref layerVertexIndex, leftStart, anchor, color);
323
WriteBackdropBatchVertex(layer, state.Owner,
324
ref layerVertexIndex, rightStart, anchor, color);
325
WriteBackdropBatchVertex(layer, state.Owner,
326
ref layerVertexIndex, leftEnd, anchor, color);
327
WriteBackdropBatchVertex(layer, state.Owner,
328
ref layerVertexIndex, leftEnd, anchor, color);
329
WriteBackdropBatchVertex(layer, state.Owner,
330
ref layerVertexIndex, rightStart, anchor, color);
331
WriteBackdropBatchVertex(layer, state.Owner,
332
ref layerVertexIndex, rightEnd, anchor, color);
333
}
334
vertexIndex += 6;
303
335
}
304
336
}
305
337
return vertexIndex;
306
338
}
307
339
308
private static void WriteBackdropBatchVertex(ref int vertexIndex,
309
Vector2 world, Color color)
340
private static void WriteBackdropBatchVertex(int layer, int owner,
341
ref int vertexIndex, Vector2 world, Vector2 anchor, Color color)
310
342
{
311
Vector2 uv = new(
312
world.X / DeathDomainBackdropTextureSystem.LayerWidth,
313
world.Y / DeathDomainBackdropTextureSystem.LayerHeight);
314
WriteBatchVertex(mergedBackdropVertices, ref vertexIndex, world,
343
Vector2 uv = DeathDomainBackdropTextureSystem.GetMatchingDomainUv(
344
layer, world, owner, anchor);
345
WriteBatchVertex(mergedBackdropVertices[layer], ref vertexIndex, world,
315
346
color, uv);
316
347
}
317
348
@@ -345,6 +376,30 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
345
376
Math.Sin(MathHelper.Clamp(progress, 0f, 1f) * MathHelper.Pi)),
346
377
0.34f);
347
378
379
private static bool IsSegmentVisible(TrailDrawState state, int segment,
380
int pointCount)
381
{
382
if (state.Fracture <= 0.001f)
383
return true;
384
uint hash = unchecked((uint)(state.Seed * 747796405
385
+ segment * 2891336453));
386
hash ^= hash >> 16;
387
hash *= 0x7FEB352Du;
388
hash ^= hash >> 15;
389
float random = (hash & 0x00FFFFFFu) / 16777215f;
390
float along = (segment + 0.5f) / Math.Max(1f, pointCount - 1f);
391
float ripple = 0.5f + 0.5f * (float)Math.Sin(
392
along * MathHelper.TwoPi * 5f + state.Seed * 0.017f);
393
float breakThreshold = 0.10f + random * 0.68f + ripple * 0.16f;
394
return Smooth01(state.Fracture) < breakThreshold;
395
}
396
397
private static float Smooth01(float value)
398
{
399
value = MathHelper.Clamp(value, 0f, 1f);
400
return value * value * (3f - 2f * value);
401
}
402
348
403
private static Vector4 CalculateTrailBounds(
349
404
IReadOnlyList<Vector2> points, float width)
350
405
{
@@ -372,6 +427,10 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
372
427
&& bounds.X <= right && bounds.Y <= bottom;
373
428
}
374
429
430
private static Vector2 GetBackgroundAnchor(TrailDrawState state)
431
=> new((state.Bounds.X + state.Bounds.Z) * 0.5f,
432
(state.Bounds.Y + state.Bounds.W) * 0.5f);
433
375
434
private static void DrawSolidRibbon(GraphicsDevice graphicsDevice,
376
435
TrailDrawState state, float width, Color color)
377
436
{
@@ -389,7 +448,7 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
389
448
}
390
449
391
450
private static int BuildVertices(TrailDrawState state, float width,
392
Color color)
451
Color color, int backdropLayer = -1)
393
452
{
394
453
int count = Math.Min(MaximumPoints, state.Points.Count);
395
454
for (int index = 0; index < count; index++)
@@ -408,9 +467,11 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
408
467
{
409
468
Vector2 world = point + normal * (side == 0 ? -halfWidth : halfWidth);
410
469
Vector2 screen = world - Main.screenPosition;
411
Vector2 uv = new(
412
world.X / DeathDomainBackdropTextureSystem.LayerWidth,
413
world.Y / DeathDomainBackdropTextureSystem.LayerHeight);
470
Vector2 uv = backdropLayer >= 0
471
? DeathDomainBackdropTextureSystem.GetMatchingDomainUv(
472
backdropLayer, world, state.Owner,
473
GetBackgroundAnchor(state))
474
: Vector2.Zero;
414
475
vertices[index * 2 + side] = new VertexPositionColorTexture(
415
476
new Vector3(screen, 0f), color, uv);
416
477
}
@@ -72,6 +72,37 @@ internal class DeathDomainVisualSystem : ModSystem
72
72
ClearAnimationState();
73
73
}
74
74
75
/// <summary>
76
/// Supplies the same aperture currently used to draw an owner's necklace
77
/// domain. Weapon masks query this instead of re-deriving full-screen state,
78
/// so future domain unlock rules cannot make the two backgrounds diverge.
79
/// </summary>
80
internal static bool TryGetMatchingBackdropAperture(int owner,
81
out Vector2 anchorWorld, out Vector2 apertureSize)
82
{
83
anchorWorld = owner >= 0 && owner < Main.maxPlayers
84
&& Main.player[owner].active
85
? Main.player[owner].Center
86
: Main.screenPosition
87
+ new Vector2(Main.screenWidth, Main.screenHeight) * 0.5f;
88
apertureSize = new Vector2(
89
DeathDomainBackdropTextureSystem.ReferenceDomainDiameter);
90
if (owner < 0 || owner >= Main.maxPlayers)
91
return false;
92
93
DeathDomainVisualSystem instance = ModContent
94
.GetInstance<DeathDomainVisualSystem>();
95
if (instance.domainAnimation[owner] <= 0.001f)
96
return false;
97
if (instance.cachedFullScreen[owner])
98
{
99
anchorWorld = Main.screenPosition
100
+ new Vector2(Main.screenWidth, Main.screenHeight) * 0.5f;
101
apertureSize = new Vector2(Main.screenWidth, Main.screenHeight);
102
}
103
return true;
104
}
105
75
106
public override void PostDrawTiles()
76
107
{
77
108
Player localPlayer = Main.LocalPlayer;
@@ -793,19 +824,15 @@ internal class DeathDomainVisualSystem : ModSystem
793
824
out float viewWidth,
794
825
out float viewHeight)
795
826
{
796
float referenceViewWidth = 1024f * (layer switch { 0 => 0.68f, 1 => 0.9f, _ => 0.84f });
797
float referenceViewHeight = 512f * (layer switch { 0 => 0.78f, 1 => 0.9f, _ => 0.86f });
798
viewWidth = Math.Min(texture.Width, referenceViewWidth * horizontalScale);
799
viewHeight = Math.Min(texture.Height, referenceViewHeight * verticalScale);
800
801
float worldWidth = Math.Max(1f, Main.maxTilesX * 16f);
802
float worldHeight = Math.Max(1f, Main.maxTilesY * 16f);
803
float worldX = MathHelper.Clamp(worldCenter.X / worldWidth, 0f, 1f);
804
float worldY = MathHelper.Clamp(worldCenter.Y / worldHeight, 0f, 1f);
805
float travelFactor = layer switch { 0 => 0.34f, 1 => 0.66f, _ => 1f };
806
float horizontalTravel = Math.Max(0f, texture.Width - viewWidth) * travelFactor;
807
sourceOffsetX = (texture.Width - viewWidth - horizontalTravel) * 0.5f + horizontalTravel * worldX;
808
sourceOffsetY = worldY * Math.Max(0f, texture.Height - viewHeight);
827
_ = texture;
828
DeathDomainBackdropTextureSystem.GetSourceFrame(layer, worldCenter,
829
new Vector2(
830
DeathDomainBackdropTextureSystem.ReferenceDomainDiameter
831
* horizontalScale,
832
DeathDomainBackdropTextureSystem.ReferenceDomainDiameter
833
* verticalScale),
834
out sourceOffsetX, out sourceOffsetY,
835
out viewWidth, out viewHeight);
809
836
}
810
837
811
838
private static void DrawDomainRippleWaves(SpriteBatch batch, Vector2 center,
@@ -400,10 +400,9 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
400
400
// letterbox reads as one cinematic composition and cannot resemble a
401
401
// misplaced child viewport.
402
402
403
float heartbeat = Pulse(progress, 0.985f, 0.018f) * opacity;
404
if (heartbeat > 0.001f)
405
batch.Draw(pixel, new Rectangle(0, 0, (int)Math.Ceiling(viewport.X), (int)Math.Ceiling(viewport.Y)),
406
Color.White * (heartbeat * 0.16f));
403
// Final impacts are expressed by world geometry, sound and camera
404
// response. A full-viewport white heartbeat obscured those details and
405
// became a one-frame white screen when several attacks overlapped.
407
406
}
408
407
409
408
private static void DrawBone(
@@ -689,7 +688,6 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
689
688
{
690
689
// A cool gray veil mutes the world without changing Terraria's shared shader state.
691
690
batch.Draw(pixel, viewportBounds, new Color(102, 128, 148) * (0.12f * opacity));
692
batch.Draw(pixel, viewportBounds, Color.White * (Pulse(progress, 0.91f, 0.055f) * 0.10f * opacity));
693
691
694
692
float reach = Math.Max(viewport.X, viewport.Y) * 0.68f;
695
693
for (int index = 0; index < 4; index++)
@@ -882,9 +880,10 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
882
880
float opacity,
883
881
float time)
884
882
{
885
// The eighteen damaging projectiles now own world-space, domain-filled
886
// wounds. This interface layer supplies atmosphere and the final
887
// shatter only; drawing another set here would pin cuts to the camera.
883
// The eighteen damaging projectiles own the world-space, domain-filled
884
// cuts and their track-aligned shatter. This layer is atmosphere only;
885
// screen-space blades or radial shards would drift with the camera and
886
// would not correspond to any preceding cut.
888
887
Rectangle screen = new(0, 0, (int)Math.Ceiling(viewport.X),
889
888
(int)Math.Ceiling(viewport.Y));
890
889
float suspended = Envelope(progress, 0.08f, 0.14f, 0.87f, 0.92f) * opacity;
@@ -892,22 +891,7 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
892
891
DrawGlow(batch, focus, 220f, new Color(120, 0, 38) * (suspended * 0.22f));
893
892
894
893
float shatter = Envelope(progress, 0.87f, 0.89f, 0.985f, 1f) * opacity;
895
if (shatter <= 0.001f)
896
return;
897
float scale = MathHelper.Clamp(Math.Min(viewport.X, viewport.Y) * 0.50f, 330f, 620f);
898
DrawReaperSilhouette(batch, pixel, focus - Vector2.UnitY * scale * 0.22f,
899
scale, new Color(2, 0, 3), new Color(205, 18, 56), shatter * 0.78f);
900
for (int shard = 0; shard < 20; shard++)
901
{
902
float angle = shard * MathHelper.TwoPi / 20f + time * 0.025f;
903
Vector2 direction = angle.ToRotationVector2();
904
float inner = 26f + shard % 4 * 8f;
905
float outer = MathHelper.Lerp(inner, scale * 0.68f,
906
Ease(shatter)) * (0.76f + shard % 3 * 0.10f);
907
DrawLayeredLine(batch, pixel, focus + direction * inner,
908
focus + direction * outer, new Color(35, 0, 14),
909
new Color(255, 66, 98), 9f, 1.5f, shatter * 0.72f);
910
}
894
batch.Draw(pixel, screen, new Color(18, 0, 8) * (shatter * 0.12f));
911
895
}
912
896
913
897
private static void DrawDeathLegacy(
@@ -148,6 +148,17 @@ public sealed class ReaperVfxDirector : ModSystem
148
148
opacity = MathHelper.Clamp(opacity, 0f, MaximumFlashOpacity);
149
149
colorFrames = Math.Clamp(colorFrames, 0, 30);
150
150
151
// Bright full-viewport overlays read as a white-screen defect when rapid
152
// melee or passive events overlap. Keep their camera punch and vignette,
153
// but suppress the bright color plane entirely.
154
Vector3 rgb = color.ToVector3();
155
float luminance = rgb.X * 0.2126f + rgb.Y * 0.7152f + rgb.Z * 0.0722f;
156
if (luminance >= 0.64f)
157
{
158
opacity = 0f;
159
colorFrames = 0;
160
}
161
151
162
if (strength >= shakeStrength || shakeFrames <= 1)
152
163
{
153
164
shakeStrength = strength;
@@ -156,8 +156,6 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
156
156
DrawSpecialStoryboard(player, grip);
157
157
else if (snapshot.Form == ReaperFormId.Infernal)
158
158
DrawInfernalUltimateRoute(TextureAssets.MagicPixel.Value);
159
else if (snapshot.Form == ReaperFormId.Death)
160
DrawDeathUltimateSwingCrescent(grip);
161
159
162
160
Texture2D texture = ModContent.Request<Texture2D>(
163
161
ReaperCombatRegistry.GetTexturePath(snapshot.Form, snapshot.Stage)).Value;
@@ -1287,24 +1285,6 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
1287
1285
}
1288
1286
}
1289
1287
1290
private void DrawDeathUltimateSwingCrescent(Vector2 grip)
1291
{
1292
if (timer < 20 || timer >= 128)
1293
return;
1294
int cut = Math.Clamp((timer - 20) / 6, 0, 17);
1295
float local = MathHelper.Clamp(((timer - 20) % 6) / 5f, 0f, 1f);
1296
int facing = Math.Abs(aim.X) > 0.05f ? Math.Sign(aim.X) : 1;
1297
int direction = (cut & 1) == 0 ? facing : -facing;
1298
float visibility = SmoothStep(MathHelper.Clamp(local / 0.22f, 0f, 1f))
1299
* (1f - SmoothStep(MathHelper.Clamp((local - 0.76f) / 0.24f, 0f, 1f)));
1300
float radius = ReaperCombatRegistry.DeathPrimaryCrescentRadius;
1301
Vector2 center = grip;
1302
float rotation = aim.ToRotation();
1303
ReaperCrescentPrimitiveTextureSystem.DrawDeathDomainCrescent(center,
1304
rotation, radius, visibility * 0.94f, 3, direction,
1305
Main.GlobalTimeWrappedHourly, local);
1306
}
1307
1308
1288
private void DrawSpecialStoryboard(Player player, Vector2 grip)
1309
1289
{
1310
1290
Texture2D pixel = TextureAssets.MagicPixel.Value;
@@ -108,9 +108,11 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
108
108
float opacity = GetOpacity();
109
109
if (Main.netMode != NetmodeID.Server)
110
110
{
111
float fracture = Smooth01(MathHelper.Clamp(
112
(30f - Projectile.timeLeft) / 30f, 0f, 1f));
111
113
DeathDomainTrailVisualSystem.Record(Projectile.owner,
112
114
Projectile.identity, points, TrailWidth, opacity,
113
mergeOverlappingRims: true);
115
mergeOverlappingRims: true, fracture: fracture);
114
116
}
115
117
if (Main.netMode != NetmodeID.MultiplayerClient)
116
118
TryHarvestIntersectingEnemies();
@@ -12,6 +12,8 @@ namespace DeathMod.Projectiles;
12
12
13
13
public sealed class ReaperStrikeProjectile : ModProjectile
14
14
{
15
private const int DeathCutPointCount = 65;
16
private readonly Vector2[] deathCutPoints = new Vector2[DeathCutPointCount];
15
17
private SickleCombatSnapshot snapshot;
16
18
private ReaperHitKind hitKind;
17
19
private ReaperStrikeShape shape;
@@ -96,6 +98,8 @@ public sealed class ReaperStrikeProjectile : ModProjectile
96
98
return;
97
99
age++;
98
100
int visualLifetime = GetVisualLifetime();
101
if (IsDeathUltimateWorldCut && Main.netMode != NetmodeID.Server)
102
RecordDeathUltimateWorldCut(visualLifetime);
99
103
Projectile.timeLeft = Math.Max(2, visualLifetime + 1 - age);
100
104
if (age == Math.Max(1, delay) && Main.netMode != NetmodeID.Server)
101
105
SpawnBurst();
@@ -123,7 +127,7 @@ public sealed class ReaperStrikeProjectile : ModProjectile
123
127
}
124
128
if (IsDeathUltimateWorldCut)
125
129
{
126
DrawDeathUltimateWorldCut();
130
DrawDeathUltimateShatterFragments();
127
131
return false;
128
132
}
129
133
// Void circle strikes are the invisible collision sweep of the held
@@ -254,17 +258,54 @@ public sealed class ReaperStrikeProjectile : ModProjectile
254
258
return Math.Max(18, 132 - phase * 6);
255
259
}
256
260
257
private void DrawDeathUltimateWorldCut()
261
private void RecordDeathUltimateWorldCut(int lifetime)
258
262
{
259
263
Vector2 direction = Projectile.velocity.SafeNormalize(Vector2.UnitX);
260
Vector2 worldCenter = Projectile.Center + direction * length * 0.5f;
264
for (int index = 0; index < deathCutPoints.Length; index++)
265
{
266
deathCutPoints[index] = Projectile.Center + direction * length
267
* (index / (float)(deathCutPoints.Length - 1));
268
}
261
269
float completion = Smooth01(age / 4f);
270
float fracture = Smooth01(MathHelper.Clamp(
271
(age - (lifetime - 12f)) / 12f, 0f, 1f));
272
float terminalFade = 1f - Smooth01(MathHelper.Clamp(
273
(fracture - 0.78f) / 0.22f, 0f, 1f));
274
DeathDomainTrailVisualSystem.Record(Projectile.owner,
275
Projectile.identity, deathCutPoints, Math.Max(54f, width * 2.5f),
276
completion * terminalFade * 0.98f,
277
mergeOverlappingRims: true, fracture: fracture);
278
}
279
280
private void DrawDeathUltimateShatterFragments()
281
{
262
282
int lifetime = GetVisualLifetime();
263
float fade = Smooth01((lifetime - age) / 10f);
264
float opacity = completion * fade * 0.96f;
265
ReaperCrescentPrimitiveTextureSystem.DrawDeathDomainBlade(
266
worldCenter - Main.screenPosition, direction.ToRotation(), length,
267
Math.Max(38f, width * 2.05f), opacity, completion);
283
float fracture = Smooth01(MathHelper.Clamp(
284
(age - (lifetime - 12f)) / 12f, 0f, 1f));
285
if (fracture <= 0.001f)
286
return;
287
288
Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX);
289
Vector2 normal = axis.RotatedBy(MathHelper.PiOver2);
290
for (int shard = 0; shard < 7; shard++)
291
{
292
uint hash = unchecked((uint)(Projectile.identity * 747796405
293
+ shard * 2891336453 + phase * 97));
294
hash ^= hash >> 16;
295
float along = 0.08f + (hash & 0xFFFFu) / 65535f * 0.84f;
296
float side = (shard & 1) == 0 ? -1f : 1f;
297
float tilt = 0.22f + ((hash >> 16) & 0xFFu) / 255f * 0.48f;
298
Vector2 origin = Projectile.Center + axis * length * along;
299
Vector2 direction = (normal * side + axis * tilt)
300
.SafeNormalize(normal * side);
301
float branchLength = (22f + shard % 3 * 9f)
302
* MathHelper.Lerp(0.35f, 1f, fracture);
303
Vector2 end = origin + direction * branchLength;
304
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(origin, end,
305
new Color(30, 0, 14) * (fracture * 0.84f), 5.5f);
306
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(origin, end,
307
new Color(255, 34, 82) * (fracture * 0.78f), 1.7f);
308
}
268
309
}
269
310
270
311
private static float Smooth01(float value)
@@ -1023,7 +1023,7 @@ public sealed class SickleSwingProjectile : ModProjectile
1023
1023
GetDeathPrimaryCrescentGeometry(out Vector2 center, out float rotation,
1024
1024
out float reach, out int direction);
1025
1025
DeathDomainCrescentVisualSystem.Record(deathCrescentVisualInstanceId,
1026
center, rotation, reach, visibility * 0.92f,
1026
Projectile.owner, center, rotation, reach, visibility * 0.92f,
1027
1027
direction, animationProgress, DeathSpaceBreak);
1028
1028
}
1029
1029