返回提交历史
Modified
Common/DeathDomainCrescentVisualSystem.cs
+9
-5
Modified
Common/ReaperCombatDefinitions.cs
+2
-0
Modified
Common/ReaperCombatService.cs
+7
-4
Modified
Common/ReaperCrescentPrimitiveTextureSystem.cs
+8
-4
Modified
Common/ReaperUltimateVisualSystem.cs
+5
-32
Modified
Projectiles/ReaperActionControllerProjectile.cs
+9
-6
Modified
Projectiles/ReaperDeathEchoScytheProjectile.cs
+32
-0
Modified
Projectiles/ReaperStrikeProjectile.cs
+44
-2
Modified
Projectiles/SickleSwingProjectile.cs
+26
-16
XFEstudio/DeathMod
重制死神蓄速右键与世界斩痕
23908db
代码差异
9 个文件
+142
-69
@@ -20,7 +20,8 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
20
20
private const int DepthBands = 18;
21
21
private const float HalfSweep = ReaperCombatRegistry.DeathPrimaryHalfSweep;
22
22
private const float OuterRadiusRatio = 0.91f;
23
private const float MaximumThicknessRatio = 0.405f;
23
private const float MaximumThicknessRatio =
24
ReaperCombatRegistry.StandardCrescentThicknessRatio;
24
25
25
26
private static readonly Dictionary<long, CrescentDrawState> drawStates = [];
26
27
private static BasicEffect? effect;
@@ -35,11 +36,12 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
35
36
float Opacity,
36
37
int SwingDirection,
37
38
float LifeProgress,
39
bool PreserveInterior,
38
40
ulong UpdateTick);
39
41
40
42
internal static void Record(int owner, int identity, Vector2 center,
41
43
float rotation, float radius, float opacity, int swingDirection,
42
float lifeProgress)
44
float lifeProgress, bool preserveInterior = false)
43
45
{
44
46
if (Main.dedServ || radius <= 1f || opacity <= 0.001f)
45
47
return;
@@ -47,7 +49,8 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
47
49
long key = ((long)owner << 32) | (uint)identity;
48
50
drawStates[key] = new CrescentDrawState(center, rotation, radius,
49
51
opacity, swingDirection < 0 ? -1 : 1,
50
MathHelper.Clamp(lifeProgress, 0f, 1f), Main.GameUpdateCount);
52
MathHelper.Clamp(lifeProgress, 0f, 1f), preserveInterior,
53
Main.GameUpdateCount);
51
54
}
52
55
53
56
public override void PostUpdateEverything()
@@ -184,8 +187,9 @@ internal sealed class DeathDomainCrescentVisualSystem : ModSystem
184
187
Vector2 world = state.Center + rotated;
185
188
Vector2 screen = world - Main.screenPosition;
186
189
187
float integrity = ReaperCrescentPrimitiveTextureSystem
188
.SampleDeathInteriorIntegrity(progress, depth, erosion);
190
float integrity = state.PreserveInterior ? 1f
191
: ReaperCrescentPrimitiveTextureSystem
192
.SampleDeathInteriorIntegrity(progress, depth, erosion);
189
193
float depthFade = MathHelper.Lerp(0.96f, 0.78f, depth);
190
194
float alpha = state.Opacity * revealAlpha * capAlpha
191
195
* integrity * depthFade;
@@ -132,7 +132,9 @@ public static class ReaperCombatRegistry
132
132
// scales the weapon sprite; it must never be reused as an attack-range
133
133
// multiplier.
134
134
public const float DeathWeaponDrawScale = 2.58f;
135
public const float DeathPrimaryMaximumTempo = 5.2f;
135
136
public const float CrescentOuterRadiusRatio = 0.91f;
137
public const float StandardCrescentThicknessRatio = 0.455f;
136
138
public const float DeathPrimaryHalfSweep = 2.43f;
137
139
// The 100x160 Death sprite reaches roughly 406 world pixels from its handle
138
140
// at the presentation scale above. A 447px crescent places its 91% hot rim
@@ -72,13 +72,13 @@ public static class ReaperCombatService
72
72
73
73
phase = TakeNextPrimaryPhase(player, snapshot, out deathTempo);
74
74
int baseDuration = ReaperCombatRegistry.GetUseTime(snapshot.Form, snapshot.Stage, phase);
75
int minimumDuration = snapshot.Form == ReaperFormId.Death ? 6 : 12;
75
int minimumDuration = snapshot.Form == ReaperFormId.Death ? 3 : 12;
76
76
int authoritativeDuration = Math.Max(minimumDuration,
77
77
(int)Math.Ceiling(baseDuration / Math.Max(0.1f,
78
78
snapshot.AttackSpeedMultiplier * deathTempo)));
79
79
ReaperCombatSystem.ActivePrimaryProjectile[owner] = projectile.whoAmI;
80
80
ReaperCombatSystem.HasActivePrimaryProjectile[owner] = true;
81
int minimumAuthorizationGap = snapshot.Form == ReaperFormId.Death ? 4 : 8;
81
int minimumAuthorizationGap = snapshot.Form == ReaperFormId.Death ? 2 : 8;
82
82
ReaperCombatSystem.NextPrimaryAuthorizationTick[owner] = now
83
83
+ (ulong)Math.Max(minimumAuthorizationGap, authoritativeDuration - 1);
84
84
return true;
@@ -113,7 +113,9 @@ public static class ReaperCombatService
113
113
return phase;
114
114
}
115
115
116
internal const int DeathPrimaryMaximumChain = 12;
116
// Death now takes twice as many uninterrupted attacks to reach full tempo,
117
// but the completed cadence is itself twice as fast as the old cap.
118
internal const int DeathPrimaryMaximumChain = 24;
117
119
private const ulong DeathPrimaryChainResetFrames = 46UL;
118
120
119
121
internal static float GetDeathPrimaryTempo(Player player)
@@ -131,7 +133,8 @@ public static class ReaperCombatService
131
133
{
132
134
float progress = MathHelper.Clamp((chain - 1f) / (DeathPrimaryMaximumChain - 1f), 0f, 1f);
133
135
progress = progress * progress * (3f - 2f * progress);
134
return MathHelper.Lerp(1f, 2.6f, progress);
136
return MathHelper.Lerp(1f,
137
ReaperCombatRegistry.DeathPrimaryMaximumTempo, progress);
135
138
}
136
139
137
140
public static bool TryStartSpecial(Player player, Vector2 aim)
@@ -355,7 +355,8 @@ internal sealed class ReaperCrescentPrimitiveTextureSystem : ModSystem
355
355
{
356
356
float normalizedX = (x + 0.5f) / MaterialTextureSize * 2f - 1f;
357
357
CrescentSample sample = SampleCrescent(normalizedX, normalizedY,
358
halfSweep: 2.43f, maximumThickness: 0.405f,
358
halfSweep: 2.43f, maximumThickness:
359
ReaperCombatRegistry.StandardCrescentThicknessRatio,
359
360
innerWarp: 0.015f, asymmetry: 0.22f, antialias);
360
361
if (sample.Body <= 0.001f)
361
362
continue;
@@ -477,7 +478,8 @@ internal sealed class ReaperCrescentPrimitiveTextureSystem : ModSystem
477
478
{
478
479
float normalizedX = (x + 0.5f) / TextureSize * 2f - 1f;
479
480
CrescentSample sample = SampleCrescent(normalizedX, normalizedY,
480
halfSweep: 2.43f, maximumThickness: 0.405f,
481
halfSweep: 2.43f, maximumThickness:
482
ReaperCombatRegistry.StandardCrescentThicknessRatio,
481
483
innerWarp: 0.018f, asymmetry: 0.22f, antialias);
482
484
if (sample.Body <= 0.001f)
483
485
continue;
@@ -531,7 +533,8 @@ internal sealed class ReaperCrescentPrimitiveTextureSystem : ModSystem
531
533
{
532
534
float normalizedX = (x + 0.5f) / TextureSize * 2f - 1f;
533
535
CrescentSample sample = SampleCrescent(normalizedX, normalizedY,
534
halfSweep: 2.43f, maximumThickness: 0.405f,
536
halfSweep: 2.43f, maximumThickness:
537
ReaperCombatRegistry.StandardCrescentThicknessRatio,
535
538
innerWarp: 0.018f, asymmetry: 0.22f, antialias);
536
539
if (sample.Body <= 0.001f)
537
540
continue;
@@ -675,7 +678,8 @@ internal sealed class ReaperCrescentPrimitiveTextureSystem : ModSystem
675
678
{
676
679
float normalizedX = (x + 0.5f) / TextureSize * 2f - 1f;
677
680
CrescentSample sample = SampleCrescent(normalizedX, normalizedY,
678
halfSweep: 2.43f, maximumThickness: 0.405f,
681
halfSweep: 2.43f, maximumThickness:
682
ReaperCombatRegistry.StandardCrescentThicknessRatio,
679
683
innerWarp: 0.018f, asymmetry: 0.22f, antialias);
680
684
if (sample.Body <= 0.001f)
681
685
continue;
@@ -287,8 +287,7 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
287
287
time, primary, secondary, state.Aim, state.ActionId);
288
288
break;
289
289
case ReaperFormId.Death:
290
DrawDeath(batch, pixel, focus, viewport, progress, opacity, time,
291
state.ActionId);
290
DrawDeath(batch, pixel, focus, viewport, progress, opacity, time);
292
291
break;
293
292
}
294
293
}
@@ -881,43 +880,17 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
881
880
Vector2 viewport,
882
881
float progress,
883
882
float opacity,
884
float time,
885
int actionId)
883
float time)
886
884
{
887
// New Death ultimate: eighteen independent world cuts cross the complete
888
// viewport, remain suspended, then break together. Do not call the old
889
// six-gate/six-form requiem here; that was the presentation the redesign
890
// explicitly replaced.
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.
891
888
Rectangle screen = new(0, 0, (int)Math.Ceiling(viewport.X),
892
889
(int)Math.Ceiling(viewport.Y));
893
890
float suspended = Envelope(progress, 0.08f, 0.14f, 0.87f, 0.92f) * opacity;
894
891
batch.Draw(pixel, screen, new Color(2, 0, 4) * (0.46f * suspended));
895
892
DrawGlow(batch, focus, 220f, new Color(120, 0, 38) * (suspended * 0.22f));
896
893
897
float reach = Math.Max(viewport.X, viewport.Y) * 1.72f;
898
for (int index = 0; index < 18; index++)
899
{
900
float eventProgress = (20f + index * 6f) / 170f;
901
float reveal = Reveal(progress, eventProgress - 0.018f,
902
eventProgress + 0.022f);
903
float hold = 1f - Reveal(progress, 0.865f, 0.92f);
904
float visibility = reveal * hold * opacity;
905
if (visibility <= 0.001f)
906
continue;
907
908
uint hash = unchecked((uint)(actionId * 747796405 + index * 2891336453));
909
hash ^= hash >> 16;
910
hash *= 0x7FEB352Du;
911
hash ^= hash >> 15;
912
float angle = (hash & 0x00FFFFFFu) / 16777215f * MathHelper.Pi
913
- MathHelper.PiOver2;
914
Vector2 center = focus;
915
float width = 13f + index % 4 * 2.2f;
916
float completion = Ease(reveal);
917
ReaperCrescentPrimitiveTextureSystem.DrawDeathDomainBlade(center,
918
angle, reach, width + 11f, visibility, completion);
919
}
920
921
894
float shatter = Envelope(progress, 0.87f, 0.89f, 0.985f, 1f) * opacity;
922
895
if (shatter <= 0.001f)
923
896
return;
@@ -173,9 +173,12 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
173
173
if ((weaponEffects & SpriteEffects.FlipVertically) != 0)
174
174
anchor.Y = texture.Height - anchor.Y;
175
175
176
// Two restrained colored echoes make the special pose readable without
177
// turning it into an extra damaging projectile.
178
for (int echo = 2; echo >= 1; echo--)
176
// Death's held right-click pose is deliberately motionless. Its branch
177
// scythes own all movement and entrance animation, so even decorative
178
// weapon echoes would read as another hand swing here.
179
int heldEchoCount = !ultimate && snapshot.Form == ReaperFormId.Death
180
? 0 : 2;
181
for (int echo = heldEchoCount; echo >= 1; echo--)
179
182
{
180
183
float offset = (0.018f + visualCharge * 0.014f) * echo;
181
184
Main.EntitySpriteDraw(texture, grip - Main.screenPosition, null,
@@ -1149,11 +1152,11 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
1149
1152
ReaperFormId.Blood => -2.25f,
1150
1153
ReaperFormId.Frost => -1.82f,
1151
1154
ReaperFormId.Soul => -2.7f,
1152
ReaperFormId.Death => -1.45f
1153
+ (float)Math.Sin(timer * 0.08f) * 0.18f,
1155
ReaperFormId.Death => -1.45f,
1154
1156
_ => -2f
1155
1157
};
1156
float tremor = visualCharge >= 0.98f
1158
float tremor = snapshot.Form != ReaperFormId.Death
1159
&& visualCharge >= 0.98f
1157
1160
? (float)Math.Sin(Main.GlobalTimeWrappedHourly * 48f) * 0.025f
1158
1161
: 0f;
1159
1162
visualWeaponAngle = aimAngle + (drawBack + tremor) * facing;
@@ -14,6 +14,7 @@ namespace DeathMod.Projectiles;
14
14
public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
15
15
{
16
16
private const int HistoryLength = 16;
17
private const int SummonRiftPointCount = 11;
17
18
private static readonly ReaperFormId[] BranchForms =
18
19
[
19
20
ReaperFormId.Bone, ReaperFormId.Blood, ReaperFormId.Infernal,
@@ -21,6 +22,8 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
21
22
];
22
23
23
24
private readonly Vector2[] tipHistory = new Vector2[HistoryLength];
25
private readonly Vector2[] summonRiftPoints =
26
new Vector2[SummonRiftPointCount];
24
27
private SickleCombatSnapshot snapshot;
25
28
private ReaperFormId branchForm;
26
29
private Vector2 summonOrigin;
@@ -84,6 +87,8 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
84
87
if (!configured)
85
88
return;
86
89
age++;
90
if (Main.netMode != NetmodeID.Server && age <= 20)
91
RecordSummonRift();
87
92
float travel = Smooth01(MathHelper.Clamp(age / 11f, 0f, 1f));
88
93
Vector2 arrival = strikeFocus - aim * 42f;
89
94
focus = Vector2.Lerp(summonOrigin, arrival, travel);
@@ -199,6 +204,7 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
199
204
focus = Vector2.Lerp(summonOrigin, strikeFocus - incomingAim * 42f,
200
205
Smooth01(MathHelper.Clamp(incomingAge / 11f, 0f, 1f)));
201
206
aim = incomingAim;
207
BuildSummonRiftPath();
202
208
age = incomingAge;
203
209
struck = incomingStruck;
204
210
residualSpawned = incomingResidualSpawned;
@@ -222,6 +228,7 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
222
228
strikeFocus = targetFocus;
223
229
focus = origin;
224
230
aim = (targetFocus - origin).SafeNormalize(Vector2.UnitX);
231
BuildSummonRiftPath();
225
232
actionId = parentActionId;
226
233
age = 0;
227
234
validHistory = 0;
@@ -260,6 +267,31 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
260
267
validHistory = Math.Min(HistoryLength, validHistory + 1);
261
268
}
262
269
270
private void BuildSummonRiftPath()
271
{
272
Vector2 axis = aim.RotatedBy(MathHelper.PiOver2);
273
Vector2 normal = aim;
274
for (int index = 0; index < summonRiftPoints.Length; index++)
275
{
276
float progress = index / (float)(summonRiftPoints.Length - 1);
277
float signed = progress * 2f - 1f;
278
float bend = (float)Math.Sin(progress * MathHelper.Pi) * 10f;
279
summonRiftPoints[index] = summonOrigin + axis * signed * 62f
280
- normal * bend;
281
}
282
}
283
284
private void RecordSummonRift()
285
{
286
float appear = Smooth01(age / 3f);
287
float vanish = 1f - Smooth01((age - 11f) / 9f);
288
float opacity = appear * vanish;
289
if (opacity <= 0.001f)
290
return;
291
DeathDomainTrailVisualSystem.Record(Projectile.owner,
292
Projectile.identity, summonRiftPoints, 39f, opacity);
293
}
294
263
295
private void DrawBranchTrail(float opacity)
264
296
{
265
297
for (int index = 1; index < validHistory; index++)
@@ -95,10 +95,11 @@ public sealed class ReaperStrikeProjectile : ModProjectile
95
95
if (!configured)
96
96
return;
97
97
age++;
98
Projectile.timeLeft = Math.Max(2, delay + 9 - age);
98
int visualLifetime = GetVisualLifetime();
99
Projectile.timeLeft = Math.Max(2, visualLifetime + 1 - age);
99
100
if (age == Math.Max(1, delay) && Main.netMode != NetmodeID.Server)
100
101
SpawnBurst();
101
if (age >= delay + 8)
102
if (age >= visualLifetime)
102
103
Projectile.Kill();
103
104
}
104
105
@@ -116,7 +117,15 @@ public sealed class ReaperStrikeProjectile : ModProjectile
116
117
public override bool PreDraw(ref Color lightColor)
117
118
{
118
119
if (!configured || age < delay || age > delay + 7)
120
{
121
if (!IsDeathUltimateWorldCut)
122
return false;
123
}
124
if (IsDeathUltimateWorldCut)
125
{
126
DrawDeathUltimateWorldCut();
119
127
return false;
128
}
120
129
// Void circle strikes are the invisible collision sweep of the held
121
130
// weapon. Its exact blade-tip tear is already drawn by the shared trail;
122
131
// a conventional purple ring would contradict the cut-out background.
@@ -231,6 +240,39 @@ public sealed class ReaperStrikeProjectile : ModProjectile
231
240
DrawLine(start, end, Math.Max(2f, width * 0.22f), core * opacity);
232
241
}
233
242
243
private bool IsDeathUltimateWorldCut => configured
244
&& snapshot.Form == ReaperFormId.Death
245
&& hitKind == ReaperHitKind.Ultimate
246
&& shape == ReaperStrikeShape.Line;
247
248
private int GetVisualLifetime()
249
{
250
if (!IsDeathUltimateWorldCut)
251
return delay + 8;
252
// Every cut remains fixed in the world until the controller's frame-150
253
// shatter. Cut N is born at 20 + N*6, hence this remaining lifetime.
254
return Math.Max(18, 132 - phase * 6);
255
}
256
257
private void DrawDeathUltimateWorldCut()
258
{
259
Vector2 direction = Projectile.velocity.SafeNormalize(Vector2.UnitX);
260
Vector2 worldCenter = Projectile.Center + direction * length * 0.5f;
261
float completion = Smooth01(age / 4f);
262
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);
268
}
269
270
private static float Smooth01(float value)
271
{
272
value = MathHelper.Clamp(value, 0f, 1f);
273
return value * value * (3f - 2f * value);
274
}
275
234
276
private static void DrawLine(Vector2 start, Vector2 end, float drawWidth, Color color)
235
277
{
236
278
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end, color, drawWidth);
@@ -53,7 +53,7 @@ public sealed class SickleSwingProjectile : ModProjectile
53
53
internal bool ServerValidated => serverValidated;
54
54
internal float DeathTempo => deathTempo;
55
55
internal bool DeathSpaceBreak => snapshot.Form == ReaperFormId.Death
56
&& deathTempo >= 2.58f;
56
&& deathTempo >= ReaperCombatRegistry.DeathPrimaryMaximumTempo - 0.01f;
57
57
private int ItemType => (int)Projectile.ai[2];
58
58
private int SwingDirection => Math.Sign(Projectile.ai[0]) == 0 ? 1 : Math.Sign(Projectile.ai[0]);
59
59
private int VisualStage => snapshot.Form == ReaperFormId.Death ? 3 : snapshot.StageNumber;
@@ -71,7 +71,8 @@ public sealed class SickleSwingProjectile : ModProjectile
71
71
snapshot = value;
72
72
phase = Math.Max(0, attackPhase);
73
73
deathTempo = snapshot.Form == ReaperFormId.Death
74
? MathHelper.Clamp(primaryDeathTempo, 1f, 2.6f)
74
? MathHelper.Clamp(primaryDeathTempo, 1f,
75
ReaperCombatRegistry.DeathPrimaryMaximumTempo)
75
76
: 1f;
76
77
configured = true;
77
78
Projectile.damage = Math.Max(1, (int)Math.Round(snapshot.Damage * ReaperCombatRegistry.GetPrimaryDamageMultiplier(snapshot, phase)));
@@ -98,7 +99,10 @@ public sealed class SickleSwingProjectile : ModProjectile
98
99
Projectile.tileCollide = false;
99
100
Projectile.ignoreWater = true;
100
101
Projectile.penetrate = -1;
101
Projectile.ownerHitCheck = true;
102
// Reaper blades intentionally cut through terrain. Their authoritative
103
// swept geometry is sufficient; Terraria's owner line-of-sight gate
104
// would otherwise reject a valid hit whenever a tile is between them.
105
Projectile.ownerHitCheck = false;
102
106
Projectile.hide = true;
103
107
Projectile.timeLeft = 120;
104
108
Projectile.DamageType = DamageClass.Melee;
@@ -269,12 +273,10 @@ public sealed class SickleSwingProjectile : ModProjectile
269
273
return false;
270
274
float width = ReaperCombatRegistry.GetCollisionWidth(snapshot);
271
275
float reach;
272
float innerReachRatio;
273
276
if (snapshot.Form == ReaperFormId.Death)
274
277
{
275
278
reach = ReaperCombatRegistry.DeathPrimaryCollisionReach;
276
279
width = 74f;
277
innerReachRatio = 0.48f;
278
280
}
279
281
else if (snapshot.Form == ReaperFormId.Void)
280
282
{
@@ -283,7 +285,6 @@ public sealed class SickleSwingProjectile : ModProjectile
283
285
// damage edge travels through the same arc shown to the player.
284
286
reach = GetVoidVisibleReach();
285
287
width = Math.Max(width, GetVoidPrimaryWidth());
286
innerReachRatio = 0.28f;
287
288
}
288
289
else if (snapshot.Form == ReaperFormId.Blood)
289
290
{
@@ -300,20 +301,27 @@ public sealed class SickleSwingProjectile : ModProjectile
300
301
ReaperStage.StageII => IsComboFinisher ? 28f : 24f,
301
302
_ => IsComboFinisher ? 34f : 28f
302
303
};
303
innerReachRatio = 0.20f;
304
304
}
305
305
else
306
306
{
307
307
reach = Vector2.Distance(grip, GetBladeTip(grip, weaponAngle));
308
innerReachRatio = 0.18f;
309
308
}
310
return CheckSweptBladeCollision(targetHitbox, reach, width,
311
innerReachRatio);
309
return CheckSweptBladeCollision(targetHitbox, reach, width);
312
310
}
313
311
314
312
private bool CheckSweptBladeCollision(Rectangle targetHitbox, float reach,
315
float width, float innerReachRatio)
313
float width)
316
314
{
315
// The old inner-radius exclusion left a very large blind spot (over 200
316
// pixels for Death). Cover the handle area explicitly, then sweep every
317
// blade sample all the way from the grip to the visible cutting edge.
318
float closeRadius = Math.Clamp(width * 0.65f, 32f, 64f);
319
Vector2 closest = new(
320
MathHelper.Clamp(grip.X, targetHitbox.Left, targetHitbox.Right),
321
MathHelper.Clamp(grip.Y, targetHitbox.Top, targetHitbox.Bottom));
322
if (Vector2.DistanceSquared(grip, closest) <= closeRadius * closeRadius)
323
return true;
324
317
325
float angularDelta = MathHelper.WrapAngle(weaponAngle
318
326
- previousCollisionAngle);
319
327
float sweptDistance = Math.Abs(angularDelta) * reach;
@@ -322,14 +330,13 @@ public sealed class SickleSwingProjectile : ModProjectile
322
330
// without changing the total damage or adding extra attack events.
323
331
int samples = Math.Clamp((int)Math.Ceiling(sweptDistance
324
332
/ Math.Max(5f, width * 0.48f)), 1, 72);
325
float innerReach = MathHelper.Clamp(innerReachRatio, 0f, 0.9f) * reach;
326
333
float collisionPoint = 0f;
327
334
for (int sample = 0; sample <= samples; sample++)
328
335
{
329
336
float angle = previousCollisionAngle
330
337
+ angularDelta * (sample / (float)samples);
331
338
Vector2 direction = angle.ToRotationVector2();
332
Vector2 start = grip + direction * innerReach;
339
Vector2 start = grip;
333
340
Vector2 end = grip + direction * reach;
334
341
if (Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(),
335
342
targetHitbox.Size(), start, end, width, ref collisionPoint))
@@ -406,7 +413,8 @@ public sealed class SickleSwingProjectile : ModProjectile
406
413
int incomingPhase = reader.ReadByte();
407
414
int incomingTimer = reader.ReadInt16();
408
415
float incomingAimRotation = reader.ReadSingle();
409
float incomingDeathTempo = MathHelper.Clamp(reader.ReadSingle(), 1f, 2.6f);
416
float incomingDeathTempo = MathHelper.Clamp(reader.ReadSingle(), 1f,
417
ReaperCombatRegistry.DeathPrimaryMaximumTempo);
410
418
bool incomingInfernalMoveReady = reader.ReadBoolean();
411
419
Vector2 incomingInfernalMoveStart = incomingInfernalMoveReady
412
420
? reader.ReadVector2()
@@ -603,7 +611,7 @@ public sealed class SickleSwingProjectile : ModProjectile
603
611
float primaryDeathTempo)
604
612
{
605
613
int baseDuration = ReaperCombatRegistry.GetUseTime(value.Form, value.Stage, attackPhase);
606
int minimumDuration = value.Form == ReaperFormId.Death ? 6 : 12;
614
int minimumDuration = value.Form == ReaperFormId.Death ? 3 : 12;
607
615
float tempo = value.Form == ReaperFormId.Death ? primaryDeathTempo : 1f;
608
616
return Math.Max(minimumDuration, (int)Math.Ceiling(baseDuration
609
617
/ Math.Max(0.1f, value.AttackSpeedMultiplier * tempo)));
@@ -1007,7 +1015,7 @@ public sealed class SickleSwingProjectile : ModProjectile
1007
1015
out float reach, out int direction);
1008
1016
DeathDomainCrescentVisualSystem.Record(Projectile.owner,
1009
1017
Projectile.identity, center, rotation, reach, visibility * 0.92f,
1010
direction, animationProgress);
1018
direction, animationProgress, DeathSpaceBreak);
1011
1019
}
1012
1020
1013
1021
private void GetDeathPrimaryCrescentGeometry(out Vector2 center,
@@ -1527,6 +1535,8 @@ public sealed class SickleSwingProjectile : ModProjectile
1527
1535
1528
1536
private float GetDeathCrescentVisibility()
1529
1537
{
1538
if (DeathSpaceBreak)
1539
return Smooth01(animationProgress / 0.08f);
1530
1540
float terminalFade = 1f - Smooth01((animationProgress - 0.96f) / 0.04f);
1531
1541
return Smooth01(animationProgress / 0.13f) * terminalFade;
1532
1542
}