返回提交历史
Modified
Common/ReaperCombatEntities.cs
+68
-1
Added
Common/ReaperDeathUltimateFreezeSystem.cs
+49
-0
Modified
Common/ReaperUltimateVisualSystem.cs
+18
-4
Modified
Projectiles/ReaperActionControllerProjectile.cs
+21
-5
XFEstudio/DeathMod
锁定死神大招斩击中心并冻结战场
f3fbb66
代码差异
4 个文件
+156
-10
@@ -44,6 +44,17 @@ public sealed class ReaperCombatPlayer : ModPlayer
44
44
45
45
public bool BloodFrenzyActive => bloodFrenzyLatched;
46
46
internal int BloodShieldAmount => bloodShield;
47
internal bool FreezesWorldForDeathUltimate
48
{
49
get
50
{
51
if (ultimateForm != ReaperFormId.Death || ultimateTimer <= 0)
52
return false;
53
int elapsed = ReaperDeathUltimateGeometry.Duration - ultimateTimer;
54
return elapsed >= ReaperDeathUltimateGeometry.FirstCutTick
55
&& elapsed <= ReaperDeathUltimateGeometry.ShatterTick;
56
}
57
}
47
58
48
59
public override void Initialize()
49
60
{
@@ -565,7 +576,7 @@ public sealed class ReaperCombatPlayer : ModPlayer
565
576
ReaperFormId.Frost => 120,
566
577
ReaperFormId.Soul => 116,
567
578
ReaperFormId.Void => 220,
568
ReaperFormId.Death => 170,
579
ReaperFormId.Death => ReaperDeathUltimateGeometry.Duration,
569
580
_ => 0
570
581
};
571
582
}
@@ -620,6 +631,11 @@ public sealed class ReaperCombatGlobalNPC : GlobalNPC
620
631
private Vector2 frostStoredVelocity;
621
632
private short deathUltimateStopTimer;
622
633
private Vector2 deathUltimateStoredVelocity;
634
private bool deathWorldFreezeCaptured;
635
private Vector2 deathWorldFreezeVelocity;
636
private float deathWorldFreezeRotation;
637
private Rectangle deathWorldFreezeFrame;
638
private double deathWorldFreezeFrameCounter;
623
639
624
640
public override void SetDefaults(NPC entity)
625
641
{
@@ -657,10 +673,44 @@ public sealed class ReaperCombatGlobalNPC : GlobalNPC
657
673
frostStoredVelocity = Vector2.Zero;
658
674
deathUltimateStopTimer = 0;
659
675
deathUltimateStoredVelocity = Vector2.Zero;
676
deathWorldFreezeCaptured = false;
677
deathWorldFreezeVelocity = Vector2.Zero;
678
deathWorldFreezeRotation = 0f;
679
deathWorldFreezeFrame = Rectangle.Empty;
680
deathWorldFreezeFrameCounter = 0d;
660
681
}
661
682
662
683
public override bool PreAI(NPC npc)
663
684
{
685
if (ReaperDeathUltimateFreezeSystem.IsWorldFrozen)
686
{
687
if (!deathWorldFreezeCaptured)
688
{
689
deathWorldFreezeCaptured = true;
690
deathWorldFreezeVelocity = npc.velocity;
691
deathWorldFreezeRotation = npc.rotation;
692
deathWorldFreezeFrame = npc.frame;
693
deathWorldFreezeFrameCounter = npc.frameCounter;
694
if (Main.netMode != NetmodeID.MultiplayerClient)
695
npc.netUpdate = true;
696
}
697
deathUltimateStopTimer = 0;
698
deathUltimateStoredVelocity = Vector2.Zero;
699
npc.velocity = Vector2.Zero;
700
npc.rotation = deathWorldFreezeRotation;
701
npc.frame = deathWorldFreezeFrame;
702
npc.frameCounter = deathWorldFreezeFrameCounter;
703
return false;
704
}
705
if (deathWorldFreezeCaptured)
706
{
707
npc.velocity = deathWorldFreezeVelocity;
708
deathWorldFreezeCaptured = false;
709
deathWorldFreezeVelocity = Vector2.Zero;
710
if (Main.netMode != NetmodeID.MultiplayerClient)
711
npc.netUpdate = true;
712
}
713
664
714
bool bossLike = IsBossLike(npc);
665
715
if (deathUltimateStopTimer > 0)
666
716
{
@@ -701,6 +751,17 @@ public sealed class ReaperCombatGlobalNPC : GlobalNPC
701
751
return true;
702
752
}
703
753
754
public override void FindFrame(NPC npc, int frameHeight)
755
{
756
if (!deathWorldFreezeCaptured
757
|| !ReaperDeathUltimateFreezeSystem.IsWorldFrozen)
758
{
759
return;
760
}
761
npc.frame = deathWorldFreezeFrame;
762
npc.frameCounter = deathWorldFreezeFrameCounter;
763
}
764
704
765
internal void RecordDeathUltimateCut(NPC npc, int owner, int actionId, int damageDone)
705
766
{
706
767
if (owner < 0 || owner >= Main.maxPlayers || damageDone <= 0)
@@ -712,6 +773,12 @@ public sealed class ReaperCombatGlobalNPC : GlobalNPC
712
773
}
713
774
deathUltimateAccumulatedDamage[owner] = (int)Math.Min(int.MaxValue / 4L,
714
775
deathUltimateAccumulatedDamage[owner] + (long)damageDone);
776
if (ReaperDeathUltimateFreezeSystem.IsWorldFrozen)
777
{
778
npc.velocity = Vector2.Zero;
779
npc.netUpdate = true;
780
return;
781
}
715
782
if (deathUltimateStopTimer <= 0)
716
783
deathUltimateStoredVelocity = npc.velocity;
717
784
deathUltimateStopTimer = Math.Max(deathUltimateStopTimer, (short)180);
@@ -0,0 +1,49 @@
1
using Terraria;
2
using Terraria.ModLoader;
3
4
namespace DeathMod.Common;
5
6
/// <summary>
7
/// Resolves the authoritative Death Reaper world-stop once per game tick. NPC
8
/// hooks query this cached value, so a full battlefield does not rescan every
9
/// player once for every NPC.
10
/// </summary>
11
internal sealed class ReaperDeathUltimateFreezeSystem : ModSystem
12
{
13
private static ulong cachedTick = ulong.MaxValue;
14
private static bool cachedFrozen;
15
16
internal static bool IsWorldFrozen
17
{
18
get
19
{
20
if (Main.gameMenu)
21
return false;
22
if (cachedTick == Main.GameUpdateCount)
23
return cachedFrozen;
24
25
cachedTick = Main.GameUpdateCount;
26
cachedFrozen = false;
27
foreach (Player player in Main.ActivePlayers)
28
{
29
if (!player.dead && player.GetModPlayer<ReaperCombatPlayer>()
30
.FreezesWorldForDeathUltimate)
31
{
32
cachedFrozen = true;
33
break;
34
}
35
}
36
return cachedFrozen;
37
}
38
}
39
40
public override void OnWorldLoad() => ResetCache();
41
public override void OnWorldUnload() => ResetCache();
42
public override void Unload() => ResetCache();
43
44
private static void ResetCache()
45
{
46
cachedTick = ulong.MaxValue;
47
cachedFrozen = false;
48
}
49
}
@@ -261,8 +261,11 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
261
261
origin = viewport * 0.5f - state.Aim * 180f;
262
262
}
263
263
Vector2 focus = state.FocusWorld - Main.screenPosition;
264
if (focus.X < -presentationMargin || focus.X > viewport.X + presentationMargin
265
|| focus.Y < -presentationMargin || focus.Y > viewport.Y + presentationMargin)
264
if (state.Form != ReaperFormId.Death
265
&& (focus.X < -presentationMargin
266
|| focus.X > viewport.X + presentationMargin
267
|| focus.Y < -presentationMargin
268
|| focus.Y > viewport.Y + presentationMargin))
266
269
{
267
270
focus = viewport * 0.5f;
268
271
}
@@ -1032,7 +1035,7 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
1032
1035
Array.Sort(deathMirrorAngles);
1033
1036
1034
1037
float eased = Ease(shatter);
1035
float radius = viewport.Length() * 1.35f;
1038
float radius = GetViewportCoverRadius(focus, viewport) * 1.08f;
1036
1039
Color shardColor = Color.White
1037
1040
* (opacity * MathHelper.Lerp(1f, 0.48f, eased));
1038
1041
int vertex = 0;
@@ -1117,11 +1120,22 @@ public sealed class ReaperUltimateVisualSystem : ModSystem
1117
1120
float rotation)
1118
1121
=> origin + (point - origin).RotatedBy(rotation);
1119
1122
1123
private static float GetViewportCoverRadius(Vector2 focus, Vector2 viewport)
1124
{
1125
float radius = focus.Length();
1126
radius = Math.Max(radius, Vector2.Distance(focus,
1127
new Vector2(viewport.X, 0f)));
1128
radius = Math.Max(radius, Vector2.Distance(focus,
1129
new Vector2(0f, viewport.Y)));
1130
radius = Math.Max(radius, Vector2.Distance(focus, viewport));
1131
return Math.Max(1f, radius);
1132
}
1133
1120
1134
private static void DrawDeathUltimateCutCracks(SpriteBatch batch,
1121
1135
Texture2D pixel, in UltimateVisualState state, Vector2 focus,
1122
1136
Vector2 viewport, float shatter, float opacity)
1123
1137
{
1124
float radius = viewport.Length() * 0.72f;
1138
float radius = GetViewportCoverRadius(focus, viewport) * 1.04f;
1125
1139
for (int cut = 0; cut < ReaperDeathUltimateGeometry.CutCount; cut++)
1126
1140
{
1127
1141
int cutTick = ReaperDeathUltimateGeometry.GetCutTick(cut);
@@ -20,6 +20,7 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
20
20
private SickleCombatSnapshot snapshot;
21
21
private Vector2 aim;
22
22
private Vector2 specialTargetOffset;
23
private Vector2 ultimateFocusWorld;
23
24
private bool ultimate;
24
25
private bool configured;
25
26
private int timer;
@@ -73,7 +74,7 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
73
74
return -1;
74
75
Projectile projectile = Main.projectile[index];
75
76
if (projectile.ModProjectile is ReaperActionControllerProjectile controller)
76
controller.Configure(snapshot, requestedTarget, ultimate);
77
controller.Configure(snapshot, requestedTarget, ultimate, player.Center);
77
78
projectile.netUpdate = true;
78
79
return index;
79
80
}
@@ -199,6 +200,7 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
199
200
snapshot.Write(writer);
200
201
writer.WriteVector2(aim);
201
202
writer.WriteVector2(specialTargetOffset);
203
writer.WriteVector2(ultimateFocusWorld);
202
204
writer.Write(ultimate);
203
205
writer.Write(Math.Max(0, timer));
204
206
writer.Write(Math.Max(0, chargeFrames));
@@ -226,6 +228,7 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
226
228
SickleCombatSnapshot incomingSnapshot = SickleCombatSnapshot.Read(reader);
227
229
Vector2 incomingAim = reader.ReadVector2().SafeNormalize(Vector2.UnitX);
228
230
Vector2 incomingTargetOffset = reader.ReadVector2();
231
Vector2 incomingUltimateFocusWorld = reader.ReadVector2();
229
232
bool incomingUltimate = reader.ReadBoolean();
230
233
int incomingTimer = Math.Max(0, reader.ReadInt32());
231
234
int incomingChargeFrames = Math.Max(0, reader.ReadInt32());
@@ -250,6 +253,7 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
250
253
snapshot = incomingSnapshot;
251
254
aim = incomingAim;
252
255
specialTargetOffset = incomingTargetOffset;
256
ultimateFocusWorld = incomingUltimateFocusWorld;
253
257
ultimate = incomingUltimate;
254
258
timer = incomingTimer;
255
259
chargeFrames = incomingChargeFrames;
@@ -300,7 +304,8 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
300
304
end = reader.ReadVector2();
301
305
}
302
306
303
private void Configure(in SickleCombatSnapshot value, Vector2 direction, bool isUltimate)
307
private void Configure(in SickleCombatSnapshot value, Vector2 direction,
308
bool isUltimate, Vector2 playerCenter)
304
309
{
305
310
snapshot = value;
306
311
aim = direction.SafeNormalize(Vector2.UnitX);
@@ -312,6 +317,12 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
312
317
specialTargetOffset *= 2000f / targetDistance;
313
318
else if (targetDistance < 2f)
314
319
specialTargetOffset = aim * 240f;
320
ultimateFocusWorld = playerCenter + specialTargetOffset;
321
if (!float.IsFinite(ultimateFocusWorld.X)
322
|| !float.IsFinite(ultimateFocusWorld.Y))
323
{
324
ultimateFocusWorld = playerCenter + aim * 240f;
325
}
315
326
ultimate = isUltimate;
316
327
configured = true;
317
328
timer = 0;
@@ -732,9 +743,14 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
732
743
_ => 1
733
744
};
734
745
Vector2 origin = player.MountedCenter;
735
Vector2 focus = snapshot.Form is ReaperFormId.Void or ReaperFormId.Death
736
? player.Center + specialTargetOffset
737
: origin + aim * 180f;
746
Vector2 focus = snapshot.Form switch
747
{
748
// Death locks the selected point in world space at cast start. Player
749
// movement and camera movement must not drag the cut intersection.
750
ReaperFormId.Death => ultimateFocusWorld,
751
ReaperFormId.Void => player.Center + specialTargetOffset,
752
_ => origin + aim * 180f
753
};
738
754
ReaperUltimateVisualSystem.ReportUltimate(player, snapshot.Form, aim,
739
755
focus, Projectile.identity, timer, visualDuration);
740
756
switch (snapshot.Form)