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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

重制死神蓄速右键与世界斩痕

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

代码差异

9 个文件 +142 -69
Modified Common/DeathDomainCrescentVisualSystem.cs +9 -5
@@ -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;
Modified Common/ReaperCombatDefinitions.cs +2 -0
@@ -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
Modified Common/ReaperCombatService.cs +7 -4
@@ -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)
Modified Common/ReaperCrescentPrimitiveTextureSystem.cs +8 -4
@@ -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;
Modified Common/ReaperUltimateVisualSystem.cs +5 -32
@@ -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;
Modified Projectiles/ReaperActionControllerProjectile.cs +9 -6
@@ -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;
Modified Projectiles/ReaperDeathEchoScytheProjectile.cs +32 -0
@@ -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++)
Modified Projectiles/ReaperStrikeProjectile.cs +44 -2
@@ -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);
Modified Projectiles/SickleSwingProjectile.cs +26 -16
@@ -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 }