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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

重构死神镰刀专属成长与技能树交互

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

代码差异

13 个文件 +497 -104
Modified Common/ReaperCombatDefinitions.cs +44 -2
@@ -37,6 +37,11 @@ public readonly record struct SickleCombatSnapshot(
37 37 byte Skill3,
38 38 byte ArmorPenetrationLevel,
39 39 byte AttackSpeedLevel,
40 byte DeathInvocationSlashLevel,
41 byte DeathTempoCapLevel,
42 byte DeathRangeLevel,
43 byte DeathSpaceBreakLevel,
44 byte DeathRiftWidthLevel,
40 45 float AttackSpeedMultiplier,
41 46 byte CritChance,
42 47 int Damage,
@@ -51,6 +56,16 @@ public readonly record struct SickleCombatSnapshot(
51 56 && Skill2 > 0
52 57 && AttackSpeedMultiplier > ReaperDefinitions.GetAttackSpeedMultiplier(AttackSpeedLevel) + 0.001f;
53 58
59 public int DeathInvocationSlashCount
60 => ReaperDefinitions.GetDeathInvocationSlashCount(DeathInvocationSlashLevel);
61 public float DeathMaximumTempo
62 => ReaperDefinitions.GetDeathTempoCap(DeathTempoCapLevel);
63 public float DeathRangeMultiplier
64 => ReaperDefinitions.GetDeathRangeMultiplier(DeathRangeLevel);
65 public bool DeathSpaceBreakUnlocked => DeathSpaceBreakLevel > 0;
66 public float DeathRiftWidth
67 => ReaperDefinitions.GetDeathRiftWidth(DeathRiftWidthLevel);
68
54 69 public int GetSkillLevel(int index) => index switch
55 70 {
56 71 0 => Skill1,
@@ -68,6 +83,11 @@ public readonly record struct SickleCombatSnapshot(
68 83 writer.Write(Skill3);
69 84 writer.Write(ArmorPenetrationLevel);
70 85 writer.Write(AttackSpeedLevel);
86 writer.Write(DeathInvocationSlashLevel);
87 writer.Write(DeathTempoCapLevel);
88 writer.Write(DeathRangeLevel);
89 writer.Write(DeathSpaceBreakLevel);
90 writer.Write(DeathRiftWidthLevel);
71 91 writer.Write(AttackSpeedMultiplier);
72 92 writer.Write(CritChance);
73 93 writer.Write(Damage);
@@ -84,7 +104,12 @@ public readonly record struct SickleCombatSnapshot(
84 104 reader.ReadByte(),
85 105 reader.ReadByte(),
86 106 reader.ReadByte(),
87 MathHelper.Clamp(reader.ReadSingle(), 0.1f, 4f),
107 reader.ReadByte(),
108 reader.ReadByte(),
109 reader.ReadByte(),
110 reader.ReadByte(),
111 reader.ReadByte(),
112 MathHelper.Clamp(reader.ReadSingle(), 0.1f, 6f),
88 113 reader.ReadByte(),
89 114 Math.Max(1, reader.ReadInt32()),
90 115 Math.Max(0f, reader.ReadSingle()));
@@ -119,6 +144,11 @@ public readonly record struct SickleCombatSnapshot(
119 144 (byte)progression.GetSkillLevel(form, 2),
120 145 (byte)progression.GetCommonNodeLevel(ReaperCommonNode.ArmorPenetration),
121 146 (byte)attackSpeedLevel,
147 (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathInvocationSlashes), 0, byte.MaxValue),
148 (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathTempoCap), 0, byte.MaxValue),
149 (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathRange), 0, byte.MaxValue),
150 (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathSpaceBreak), 0, byte.MaxValue),
151 (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathRiftWidth), 0, byte.MaxValue),
122 152 attackSpeedMultiplier,
123 153 (byte)Math.Clamp(player.GetWeaponCrit(player.HeldItem), 0, 100),
124 154 Math.Max(1, damage),
@@ -132,7 +162,7 @@ public static class ReaperCombatRegistry
132 162 // scales the weapon sprite; it must never be reused as an attack-range
133 163 // multiplier.
134 164 public const float DeathWeaponDrawScale = 2.58f;
135 public const float DeathPrimaryMaximumTempo = 5.2f;
165 public const float DeathPrimaryMaximumTempo = 3f;
136 166 public const float CrescentOuterRadiusRatio = 0.91f;
137 167 public const float StandardCrescentThicknessRatio = 0.455f;
138 168 public const float DeathPrimaryHalfSweep = 2.43f;
@@ -145,6 +175,18 @@ public static class ReaperCombatRegistry
145 175 * CrescentOuterRadiusRatio;
146 176 public const float DeathPrimaryCollisionReach = 430f;
147 177
178 public static float GetDeathWeaponDrawScale(in SickleCombatSnapshot snapshot)
179 => DeathWeaponDrawScale * snapshot.DeathRangeMultiplier;
180
181 public static float GetDeathPrimaryCrescentRadius(in SickleCombatSnapshot snapshot)
182 => DeathPrimaryCrescentRadius * snapshot.DeathRangeMultiplier;
183
184 public static float GetDeathPrimaryBladeReach(in SickleCombatSnapshot snapshot)
185 => DeathPrimaryBladeReach * snapshot.DeathRangeMultiplier;
186
187 public static float GetDeathPrimaryCollisionReach(in SickleCombatSnapshot snapshot)
188 => DeathPrimaryCollisionReach * snapshot.DeathRangeMultiplier;
189
148 190 public static ReaperFormId ResolveUsableForm(ReaperProgressionState progression)
149 191 {
150 192 ReaperFormId form = progression.CurrentForm;
Modified Common/ReaperCombatService.cs +13 -6
@@ -72,10 +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 float effectiveAttackSpeed = snapshot.AttackSpeedMultiplier * deathTempo;
76 if (snapshot.Form == ReaperFormId.Death)
77 effectiveAttackSpeed = Math.Min(effectiveAttackSpeed, snapshot.DeathMaximumTempo);
75 78 int minimumDuration = snapshot.Form == ReaperFormId.Death ? 3 : 12;
76 79 int authoritativeDuration = Math.Max(minimumDuration,
77 80 (int)Math.Ceiling(baseDuration / Math.Max(0.1f,
78 snapshot.AttackSpeedMultiplier * deathTempo)));
81 effectiveAttackSpeed)));
79 82 ReaperCombatSystem.ActivePrimaryProjectile[owner] = projectile.whoAmI;
80 83 ReaperCombatSystem.HasActivePrimaryProjectile[owner] = true;
81 84 int minimumAuthorizationGap = snapshot.Form == ReaperFormId.Death ? 2 : 8;
@@ -108,7 +111,8 @@ public static class ReaperCombatService
108 111 int chain = continuing ? ReaperCombatSystem.DeathPrimaryChain[owner] + 1 : 1;
109 112 ReaperCombatSystem.DeathPrimaryChain[owner] = (byte)Math.Min(DeathPrimaryMaximumChain, chain);
110 113 ReaperCombatSystem.LastDeathPrimaryTick[owner] = now;
111 deathTempo = GetDeathTempo(ReaperCombatSystem.DeathPrimaryChain[owner]);
114 deathTempo = GetDeathTempo(ReaperCombatSystem.DeathPrimaryChain[owner],
115 snapshot.DeathMaximumTempo);
112 116 }
113 117 return phase;
114 118 }
@@ -126,15 +130,18 @@ public static class ReaperCombatService
126 130 {
127 131 return 1f;
128 132 }
129 return GetDeathTempo(ReaperCombatSystem.DeathPrimaryChain[player.whoAmI]);
133 int capLevel = player.GetModPlayer<MyPlayer>().ReaperProgression
134 .GetCommonNodeLevel(ReaperCommonNode.DeathTempoCap);
135 return GetDeathTempo(ReaperCombatSystem.DeathPrimaryChain[player.whoAmI],
136 ReaperDefinitions.GetDeathTempoCap(capLevel));
130 137 }
131 138
132 private static float GetDeathTempo(int chain)
139 private static float GetDeathTempo(int chain, float maximumTempo)
133 140 {
134 141 float progress = MathHelper.Clamp((chain - 1f) / (DeathPrimaryMaximumChain - 1f), 0f, 1f);
135 142 progress = progress * progress * (3f - 2f * progress);
136 return MathHelper.Lerp(1f,
137 ReaperCombatRegistry.DeathPrimaryMaximumTempo, progress);
143 return MathHelper.Lerp(1f, MathHelper.Clamp(maximumTempo, 1f,
144 ReaperCombatRegistry.DeathPrimaryMaximumTempo), progress);
138 145 }
139 146
140 147 public static bool TryStartSpecial(Player player, Vector2 aim)
Modified Common/ReaperProgressionDefinitions.cs +74 -8
@@ -42,7 +42,16 @@ public enum ReaperCommonNode : byte
42 42 Crit = 2,
43 43 ArmorPenetration = 3,
44 44 SpecialCooldown = 4,
45 EnergyGain = 5
45 EnergyGain = 5,
46
47 // Final-form upgrades live on the Death branch of the same character tree.
48 // Values are appended because the enum is serialized and sent over network.
49 DeathDamage = 6,
50 DeathInvocationSlashes = 7,
51 DeathTempoCap = 8,
52 DeathRange = 9,
53 DeathSpaceBreak = 10,
54 DeathRiftWidth = 11
46 55 }
47 56
48 57 /// <summary>
@@ -76,7 +85,13 @@ public enum ReaperNodeId : byte
76 85 CommonCrit = 66,
77 86 CommonArmorPenetration = 67,
78 87 CommonSpecialCooldown = 68,
79 CommonEnergyGain = 69
88 CommonEnergyGain = 69,
89 DeathDamage = 80,
90 DeathInvocationSlashes = 81,
91 DeathTempoCap = 82,
92 DeathRange = 83,
93 DeathSpaceBreak = 84,
94 DeathRiftWidth = 85
80 95 }
81 96
82 97 /// <summary>
@@ -202,7 +217,13 @@ public static class ReaperDefinitions
202 217 new(ReaperNodeId.CommonCrit, ReaperFormId.Base, -1, ReaperCommonNode.Crit, ReaperStage.StageI),
203 218 new(ReaperNodeId.CommonArmorPenetration, ReaperFormId.Base, -1, ReaperCommonNode.ArmorPenetration, ReaperStage.StageI),
204 219 new(ReaperNodeId.CommonSpecialCooldown, ReaperFormId.Base, -1, ReaperCommonNode.SpecialCooldown, ReaperStage.StageI),
205 new(ReaperNodeId.CommonEnergyGain, ReaperFormId.Base, -1, ReaperCommonNode.EnergyGain, ReaperStage.StageI)
220 new(ReaperNodeId.CommonEnergyGain, ReaperFormId.Base, -1, ReaperCommonNode.EnergyGain, ReaperStage.StageI),
221 new(ReaperNodeId.DeathDamage, ReaperFormId.Death, -1, ReaperCommonNode.DeathDamage, ReaperStage.StageIII),
222 new(ReaperNodeId.DeathInvocationSlashes, ReaperFormId.Death, -1, ReaperCommonNode.DeathInvocationSlashes, ReaperStage.StageIII),
223 new(ReaperNodeId.DeathTempoCap, ReaperFormId.Death, -1, ReaperCommonNode.DeathTempoCap, ReaperStage.StageIII),
224 new(ReaperNodeId.DeathRange, ReaperFormId.Death, -1, ReaperCommonNode.DeathRange, ReaperStage.StageIII),
225 new(ReaperNodeId.DeathSpaceBreak, ReaperFormId.Death, -1, ReaperCommonNode.DeathSpaceBreak, ReaperStage.StageIII),
226 new(ReaperNodeId.DeathRiftWidth, ReaperFormId.Death, -1, ReaperCommonNode.DeathRiftWidth, ReaperStage.StageIII)
206 227 ];
207 228
208 229 private static readonly IReadOnlyList<ReaperNodeDefinition> ReadOnlyNodeDefinitions = Array.AsReadOnly(NodeDefinitions);
@@ -292,20 +313,47 @@ public static class ReaperDefinitions
292 313
293 314 public static int GetCommonNodeMaxLevel(ReaperCommonNode node)
294 315 {
295 return node == ReaperCommonNode.Damage ? int.MaxValue : IsCommonNode(node) ? 5 : 0;
316 return node switch
317 {
318 ReaperCommonNode.Damage => 10,
319 ReaperCommonNode.DeathDamage => int.MaxValue,
320 ReaperCommonNode.DeathInvocationSlashes => 5,
321 ReaperCommonNode.DeathTempoCap => 5,
322 ReaperCommonNode.DeathRange => 5,
323 ReaperCommonNode.DeathSpaceBreak => 1,
324 ReaperCommonNode.DeathRiftWidth => 5,
325 _ => IsCommonNode(node) ? 5 : 0
326 };
296 327 }
297 328
298 329 public static float GetAttackSpeedMultiplier(int level)
299 330 => 1f + Math.Max(0, level) * AttackSpeedBonusPerLevel;
300 331
301 public static bool IsUnlimitedCommonNode(ReaperCommonNode node) => node == ReaperCommonNode.Damage;
332 public static bool IsUnlimitedCommonNode(ReaperCommonNode node)
333 => node == ReaperCommonNode.DeathDamage;
334
335 public static bool IsDeathNode(ReaperCommonNode node)
336 => node is >= ReaperCommonNode.DeathDamage and <= ReaperCommonNode.DeathRiftWidth;
337
338 public static int GetDeathInvocationSlashCount(int level)
339 => 1 + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathInvocationSlashes));
340
341 public static float GetDeathTempoCap(int level)
342 => 1.5f + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathTempoCap)) * 0.3f;
343
344 public static float GetDeathRangeMultiplier(int level)
345 => 0.5f + Math.Clamp(level, 0,
346 GetCommonNodeMaxLevel(ReaperCommonNode.DeathRange)) * 0.10f;
347
348 public static float GetDeathRiftWidth(int level)
349 => 60f + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathRiftWidth)) * 12f;
302 350
303 351 public static ReaperProgressionCost? GetCommonNodeCost(ReaperCommonNode node, int targetLevel)
304 352 {
305 353 if (!IsCommonNode(node) || targetLevel < 1 || targetLevel > GetCommonNodeMaxLevel(node))
306 354 return null;
307 355
308 if (node == ReaperCommonNode.Damage)
356 if (node is ReaperCommonNode.Damage or ReaperCommonNode.DeathDamage)
309 357 {
310 358 return targetLevel switch
311 359 {
@@ -317,7 +365,22 @@ public static class ReaperDefinitions
317 365 6 or 7 => ReaperProgressionCost.Essence(1),
318 366 8 or 9 => ReaperProgressionCost.Essence(2),
319 367 10 => ReaperProgressionCost.Essence(3),
320 _ => ReaperProgressionCost.Essence(Math.Min(999, 3 + (targetLevel - 11) / 5))
368 _ when node == ReaperCommonNode.DeathDamage
369 => ReaperProgressionCost.Essence(Math.Min(999, 3 + (targetLevel - 11) / 5)),
370 _ => null
371 };
372 }
373
374 if (IsDeathNode(node))
375 {
376 return targetLevel switch
377 {
378 1 => ReaperProgressionCost.Essence(3),
379 2 => ReaperProgressionCost.Essence(5),
380 3 => ReaperProgressionCost.Essence(8),
381 4 => ReaperProgressionCost.Essence(12),
382 5 => ReaperProgressionCost.Essence(18),
383 _ => null
321 384 };
322 385 }
323 386
@@ -337,6 +400,9 @@ public static class ReaperDefinitions
337 400 if (!IsCommonNode(node) || targetLevel <= 0)
338 401 return ReaperStage.Locked;
339 402
403 if (IsDeathNode(node))
404 return ReaperStage.StageIII;
405
340 406 if (node == ReaperCommonNode.Damage)
341 407 {
342 408 if (targetLevel <= 3)
@@ -366,6 +432,6 @@ public static class ReaperDefinitions
366 432
367 433 public static bool IsCommonNode(ReaperCommonNode node)
368 434 {
369 return node is >= ReaperCommonNode.Damage and <= ReaperCommonNode.EnergyGain;
435 return node is >= ReaperCommonNode.Damage and <= ReaperCommonNode.DeathRiftWidth;
370 436 }
371 437 }
Modified Common/ReaperProgressionService.cs +11 -0
@@ -193,6 +193,17 @@ public static class ReaperProgressionService
193 193
194 194 MyPlayer modPlayer = player.GetModPlayer<MyPlayer>();
195 195 ReaperProgressionState progression = modPlayer.ReaperProgression;
196 if (ReaperDefinitions.IsDeathNode(node) && !progression.DeathFormUnlocked)
197 {
198 failure = ReaperProgressionFailure.DeathFormLocked;
199 return false;
200 }
201 if (node == ReaperCommonNode.DeathRiftWidth
202 && progression.GetCommonNodeLevel(ReaperCommonNode.DeathSpaceBreak) <= 0)
203 {
204 failure = ReaperProgressionFailure.SkillLocked;
205 return false;
206 }
196 207 if (progression.GetCommonNodeLevel(node) == int.MaxValue)
197 208 {
198 209 failure = ReaperProgressionFailure.Maxed;
Modified Common/ReaperProgressionState.cs +25 -3
@@ -69,11 +69,11 @@ public sealed class ReaperBranchState
69 69 /// </summary>
70 70 public sealed class ReaperProgressionState
71 71 {
72 public const int CurrentDataVersion = 5;
72 public const int CurrentDataVersion = 6;
73 73 public const string DefaultSaveKey = "ReaperProgression";
74 74
75 75 private const int BranchCount = 6;
76 private const int CommonNodeCount = 6;
76 private const int CommonNodeCount = 12;
77 77 private const int SoulSealStageCount = 3;
78 78
79 79 private readonly ReaperBranchState[] branches = new ReaperBranchState[BranchCount];
@@ -346,12 +346,25 @@ public sealed class ReaperProgressionState
346 346 ? tag.Get<int[]>("NodeLevelsInt")
347 347 : Array.ConvertAll(tag.GetByteArray("NodeLevels"), value => (int)value);
348 348 int nodeCount = Math.Min(nodeIds.Length, nodeLevels.Length);
349 int migratedDeathDamage = 0;
349 350 for (int index = 0; index < nodeCount; index++)
350 351 {
351 352 ReaperNodeId nodeId = (ReaperNodeId)nodeIds[index];
353 if (nodeId == ReaperNodeId.CommonDamage
354 && nodeLevels[index] > ReaperDefinitions.GetCommonNodeMaxLevel(ReaperCommonNode.Damage))
355 {
356 migratedDeathDamage = Math.Max(migratedDeathDamage,
357 nodeLevels[index] - ReaperDefinitions.GetCommonNodeMaxLevel(ReaperCommonNode.Damage));
358 }
352 359 if (ReaperDefinitions.TryGetNode(nodeId, out _))
353 360 SetNodeLevel(nodeId, nodeLevels[index]);
354 361 }
362 if (migratedDeathDamage > 0)
363 {
364 SetCommonNodeLevel(ReaperCommonNode.DeathDamage,
365 Math.Max(GetCommonNodeLevel(ReaperCommonNode.DeathDamage),
366 migratedDeathDamage));
367 }
355 368 }
356 369 else
357 370 {
@@ -367,8 +380,17 @@ public sealed class ReaperProgressionState
367 380 }
368 381
369 382 if (tag.ContainsKey("CommonNodesInt"))
370 LoadClampedArray(tag.Get<int[]>("CommonNodesInt"), commonNodeLevels, index =>
383 {
384 int[] loadedCommon = tag.Get<int[]>("CommonNodesInt");
385 int migratedDeathDamage = loadedCommon.Length > (int)ReaperCommonNode.Damage
386 ? Math.Max(0, loadedCommon[(int)ReaperCommonNode.Damage]
387 - ReaperDefinitions.GetCommonNodeMaxLevel(ReaperCommonNode.Damage))
388 : 0;
389 LoadClampedArray(loadedCommon, commonNodeLevels, index =>
371 390 ReaperDefinitions.GetCommonNodeMaxLevel((ReaperCommonNode)index));
391 if (migratedDeathDamage > 0)
392 SetCommonNodeLevel(ReaperCommonNode.DeathDamage, migratedDeathDamage);
393 }
372 394 else
373 395 LoadClampedArray(Array.ConvertAll(tag.GetByteArray("CommonNodes"), value => (int)value), commonNodeLevels, index =>
374 396 ReaperDefinitions.GetCommonNodeMaxLevel((ReaperCommonNode)index));
Modified Items/NormalSickle.cs +2 -0
@@ -267,6 +267,8 @@ public class NormalSickle : ModItem
267 267 ReaperStage stage = ReaperDefinitions.IsBranchForm(form) ? state.GetStage(form) : ReaperStage.StageIII;
268 268 float multiplier = ReaperCombatRegistry.GetBaseDamage(form, stage) / (float)BaseSickleDamage;
269 269 multiplier *= 1f + state.GetCommonNodeLevel(ReaperCommonNode.Damage) * 0.03f;
270 if (form == ReaperFormId.Death)
271 multiplier *= 1f + state.GetCommonNodeLevel(ReaperCommonNode.DeathDamage) * 0.03f;
270 272 if (form == ReaperFormId.Blood && player.GetModPlayer<ReaperCombatPlayer>().BloodFrenzyActive)
271 273 {
272 274 int level = state.GetSkillLevel(ReaperFormId.Blood, 1);
Modified Localization/en-US.hjson +34 -2
@@ -330,13 +330,14 @@ Mods: {
330 330 ResourceSealI: Seal I {0}
331 331 ResourceSealII: Seal II {0}
332 332 ResourceSealIII: Seal III {0}
333 PanHint: Drag empty space with Left Mouse, or drag with Middle Mouse
333 PanHint: Drag empty space or use Middle Mouse to pan; scroll to zoom
334 334 ResetView: Recenter
335 335 CategoryBase: Equipped Weapon · Shared Shell
336 336 CategoryFinal: Final Form · Automatic Mastery Reward
337 337 CategoryStage: Form Stage
338 338 CategorySkill: "{0} · Unique Skill"
339 339 CategoryCommon: Character · Common Node
340 CategoryDeathUpgrade: Final Form · Death Scythe Growth
340 341 BaseAlwaysAvailable: Always available; select an illuminated form with the form wheel
341 342 UltimateLabel: Ultimate: {0}
342 343 DeathMasteryProgress: Stage III branches {0}/6 | Lv.3 unique skills {1}/18
@@ -374,6 +375,7 @@ Mods: {
374 375 CommonUnlimitedLevel: Lv.{0}/∞ | Costs {1}
375 376 CompletedLevel: Lv.{0}/{1} | Complete
376 377 CommonStageLocked: Requires any branch at Stage {0}
378 DeathUpgradeLocked: Unlock the Death Scythe first; rift width also requires Sever Death.
377 379 Unavailable: Unavailable
378 380 CostSouls: "{0} character souls"
379 381 CostEssence: "{0} Soul Essence"
@@ -559,7 +561,7 @@ Mods: {
559 561 CommonNodes: {
560 562 Damage: {
561 563 Name: Soulblade Strength
562 Description: All sickle-form damage +3% per level, with no level cap.
564 Description: All sickle-form damage +3% per level, capped at level 10. Unlimited growth now belongs to the Death branch.
563 565 }
564 566
565 567 AttackSpeed: {
@@ -586,6 +588,36 @@ Mods: {
586 588 Name: Soul-Energy Resonance
587 589 Description: Ultimate energy gain +20% per level, up to +100%.
588 590 }
591
592 DeathDamage: {
593 Name: Endless Death Edge
594 Description: Death Scythe damage +3% per level with no level cap. Legacy Soulblade levels above 10 migrate here.
595 }
596
597 DeathInvocationSlashes: {
598 Name: Sixfold Reprise
599 Description: Each summoned branch scythe performs one additional slash per level, up to six slashes each.
600 }
601
602 DeathTempoCap: {
603 Name: Terminal Acceleration
604 Description: Raises both primary-chain and held-special speed caps from 150% to at most 300%.
605 }
606
607 DeathRange: {
608 Name: Nether Reach
609 Description: Death Scythe size, slash geometry, collision and special targeting begin at 50% of the former default; each level restores 10%, reaching exactly the former range at max level.
610 }
611
612 DeathSpaceBreak: {
613 Name: Sever Death
614 Description: Unlocks the original trait: at the current primary speed cap, the blade tip leaves a persistent Death Domain rift in world space.
615 }
616
617 DeathRiftWidth: {
618 Name: Domain Expansion
619 Description: Raises Sever Death rift width from 60 to at most 120 world pixels.
620 }
589 621 }
590 622 }
591 623
Modified Localization/zh-Hans.hjson +34 -2
@@ -330,13 +330,14 @@ Mods: {
330 330 ResourceSealI: 初醒 {0}
331 331 ResourceSealII: 破界 {0}
332 332 ResourceSealIII: 统御 {0}
333 PanHint: 左键拖动空白处,或按住鼠标中键拖动画布
333 PanHint: 左键拖动空白处或按住中键平移;滚轮缩放技能树
334 334 ResetView: 归中
335 335 CategoryBase: 装备武器 · 通用外壳
336 336 CategoryFinal: 终局形态 · 自动精通奖励
337 337 CategoryStage: 形态阶段
338 338 CategorySkill: "{0} · 独特技能"
339 339 CategoryCommon: 人物 · 通用节点
340 CategoryDeathUpgrade: 终局形态 · 死神镰刀成长
340 341 BaseAlwaysAvailable: 始终可用;已点亮的形态通过形态轮盘切换
341 342 UltimateLabel: 大招:{0}
342 343 DeathMasteryProgress: 阶段 III 分支 {0}/6 | Lv.3 独特技能 {1}/18
@@ -374,6 +375,7 @@ Mods: {
374 375 CommonUnlimitedLevel: Lv.{0}/∞ | 消耗 {1}
375 376 CompletedLevel: Lv.{0}/{1} | 已圆满
376 377 CommonStageLocked: 需任意分支达到阶段 {0}
378 DeathUpgradeLocked: 需先解锁死神镰刀;裂缝宽度还需先点亮“斩开死亡”
377 379 Unavailable: 尚不可用
378 380 CostSouls: "{0} 人物灵魂"
379 381 CostEssence: "{0} 灵魂精华"
@@ -559,7 +561,7 @@ Mods: {
559 561 CommonNodes: {
560 562 Damage: {
561 563 Name: 魂刃强度
562 Description: 每级使所有镰刀形态伤害 +3%,可以无限升级。
564 Description: 每级使所有镰刀形态伤害 +3%,最高 10 级;无限成长已转移至死神镰刀分支。
563 565 }
564 566
565 567 AttackSpeed: {
@@ -586,6 +588,36 @@ Mods: {
586 588 Name: 魂能共鸣
587 589 Description: 每级大招能量获取 +20%,满级共 +100%。
588 590 }
591
592 DeathDamage: {
593 Name: 无尽死锋
594 Description: 仅提高死神镰刀伤害,每级 +3%,没有成长上限。旧版魂刃强度超过 10 级的部分会迁移到此节点。
595 }
596
597 DeathInvocationSlashes: {
598 Name: 六道复斩
599 Description: 每级使右键召出的每一把分支镰刀额外斩击 1 次,满级每把连续斩击 6 次。
600 }
601
602 DeathTempoCap: {
603 Name: 终焉加速
604 Description: 同时提高左键连斩与右键召唤的攻速上限;由基础 150% 提高至最高 300%。
605 }
606
607 DeathRange: {
608 Name: 冥界延展
609 Description: 死神镰刀、刀光、碰撞与右键索敌范围从旧版默认值的 50% 起步;每级恢复 10%,满级恰好达到旧版默认范围。
610 }
611
612 DeathSpaceBreak: {
613 Name: 斩开死亡
614 Description: 解锁原有特性:左键达到当前攻速上限时,刀尖会在世界中留下死亡领域裂缝。
615 }
616
617 DeathRiftWidth: {
618 Name: 领域创宽
619 Description: 提高“斩开死亡”留下的领域裂缝宽度,由 60 提高至最高 120 世界像素。
620 }
589 621 }
590 622 }
591 623
Modified Projectiles/ReaperActionControllerProjectile.cs +14 -6
@@ -607,16 +607,21 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
607 607 if (deathInvocationCooldown > 0)
608 608 return;
609 609
610 float acceleration = MathHelper.Clamp(chargeFrames / 600f, 0f, 1f);
610 // Reach the configured cap in roughly 2.5 seconds instead of ten. The
611 // cap itself is character progression and applies to both Death attacks.
612 float acceleration = MathHelper.Clamp(chargeFrames / 150f, 0f, 1f);
611 613 acceleration = acceleration * acceleration * (3f - 2f * acceleration);
614 float invocationTempo = MathHelper.Lerp(1f,
615 snapshot.DeathMaximumTempo, acceleration);
612 616 deathInvocationCooldown = Math.Clamp((int)Math.Round(
613 MathHelper.Lerp(12f, 3f, acceleration)), 3, 12);
617 12f / Math.Max(1f, invocationTempo)), 4, 12);
614 618 int invocation = deathInvocationCount++;
615 619 float orbitAngle = invocation * 2.3999632f + Projectile.identity * 0.071f;
616 620 Vector2 summonPosition = player.Center + orbitAngle.ToRotationVector2()
617 * (120f + invocation % 3 * 34f);
621 * ((120f + invocation % 3 * 34f) * snapshot.DeathRangeMultiplier);
618 622 Vector2 cursorFocus = player.Center + specialTargetOffset;
619 NPC? target = FindDeathInvocationTarget(cursorFocus);
623 NPC? target = FindDeathInvocationTarget(cursorFocus,
624 snapshot.DeathRangeMultiplier);
620 625 Vector2 strikeFocus = target?.Center ?? cursorFocus;
621 626 ReaperDeathEchoScytheProjectile.Spawn(Projectile.GetSource_FromThis(),
622 627 player.whoAmI, snapshot, invocation % 6, summonPosition, strikeFocus,
@@ -624,13 +629,16 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
624 629 Projectile.netUpdate = true;
625 630 }
626 631
627 private static NPC? FindDeathInvocationTarget(Vector2 cursorFocus)
632 private static NPC? FindDeathInvocationTarget(Vector2 cursorFocus,
633 float rangeMultiplier)
628 634 {
629 635 NPC? nearest = null;
630 636 // Only snap to an enemy genuinely close to the cursor. The right-click
631 637 // destination remains the cursor itself rather than silently selecting a
632 638 // distant target and making the summoned scythes appear stationary.
633 float best = 320f * 320f;
639 float targetingRadius = 320f * MathHelper.Clamp(rangeMultiplier,
640 0.5f, 1f);
641 float best = targetingRadius * targetingRadius;
634 642 foreach (NPC npc in Main.ActiveNPCs)
635 643 {
636 644 if (!npc.CanBeChasedBy())
Modified Projectiles/ReaperDeathDomainRiftProjectile.cs +7 -6
@@ -19,7 +19,6 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
19 19 {
20 20 private const int Lifetime = 300;
21 21 private const int MaximumPoints = 72;
22 private const float TrailWidth = 30f;
23 22 private readonly ulong[] nextHarvestTicks = new ulong[Main.maxNPCs];
24 23 private readonly List<Vector2> points = [];
25 24 private readonly HashSet<int> selectedTargets = [];
@@ -32,6 +31,7 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
32 31 private int volleyCounter;
33 32 private bool configured;
34 33 private bool serverAuthorized;
34 private float TrailWidth => snapshot.DeathRiftWidth;
35 35
36 36 public override string Texture => "Terraria/Images/Projectile_0";
37 37
@@ -163,7 +163,7 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
163 163 volleyCounter = incomingVolley;
164 164 points.Clear();
165 165 points.AddRange(incomingPoints);
166 pathBounds = CalculatePathBounds(points);
166 pathBounds = CalculatePathBounds(points, TrailWidth);
167 167 configured = points.Count >= 2;
168 168 }
169 169
@@ -173,7 +173,7 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
173 173 snapshot = value;
174 174 points.Clear();
175 175 points.AddRange(path);
176 pathBounds = CalculatePathBounds(points);
176 pathBounds = CalculatePathBounds(points, TrailWidth);
177 177 phase = Math.Max(0, sourcePhase);
178 178 actionId = sourceActionId;
179 179 age = 0;
@@ -256,7 +256,8 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
256 256 => target.Right >= pathBounds.X && target.Bottom >= pathBounds.Y
257 257 && target.Left <= pathBounds.Z && target.Top <= pathBounds.W;
258 258
259 private static Vector4 CalculatePathBounds(IReadOnlyList<Vector2> path)
259 private static Vector4 CalculatePathBounds(IReadOnlyList<Vector2> path,
260 float width)
260 261 {
261 262 if (path.Count == 0)
262 263 return Vector4.Zero;
@@ -267,8 +268,8 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
267 268 minimum = Vector2.Min(minimum, path[index]);
268 269 maximum = Vector2.Max(maximum, path[index]);
269 270 }
270 return new Vector4(minimum.X - TrailWidth, minimum.Y - TrailWidth,
271 maximum.X + TrailWidth, maximum.Y + TrailWidth);
271 return new Vector4(minimum.X - width, minimum.Y - width,
272 maximum.X + width, maximum.Y + width);
272 273 }
273 274
274 275 private float GetOpacity()
Modified Projectiles/ReaperDeathEchoScytheProjectile.cs +60 -38
@@ -14,7 +14,10 @@ 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 private const int SummonRiftPointCount = 17;
18 private const int SlashCycleFrames = 20;
19 private const int StrikeFrame = 8;
20 private const int ResidualFrame = 15;
18 21 private static readonly ReaperFormId[] BranchForms =
19 22 [
20 23 ReaperFormId.Bone, ReaperFormId.Blood, ReaperFormId.Infernal,
@@ -35,8 +38,8 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
35 38 private int actionId;
36 39 private int age;
37 40 private int validHistory;
38 private bool struck;
39 private bool residualSpawned;
41 private int lastStrikeCycle = -1;
42 private int lastResidualCycle = -1;
40 43 private bool configured;
41 44 private bool serverAuthorized;
42 45
@@ -70,7 +73,7 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
70 73 Projectile.tileCollide = false;
71 74 Projectile.ignoreWater = true;
72 75 Projectile.penetrate = -1;
73 Projectile.timeLeft = 38;
76 Projectile.timeLeft = 190;
74 77 Projectile.netImportant = true;
75 78 }
76 79
@@ -87,44 +90,53 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
87 90 if (!configured)
88 91 return;
89 92 age++;
90 if (Main.netMode != NetmodeID.Server && age <= 20)
93 int slashCount = snapshot.DeathInvocationSlashCount;
94 int cycle = Math.Min(slashCount - 1, Math.Max(0, (age - 1) / SlashCycleFrames));
95 int cycleAge = Math.Max(1, (age - 1) % SlashCycleFrames + 1);
96 if (Main.netMode != NetmodeID.Server && age <= 18)
91 97 RecordSummonRift();
92 98 float travel = Smooth01(MathHelper.Clamp(age / 11f, 0f, 1f));
93 99 Vector2 arrival = strikeFocus - aim * 42f;
94 100 focus = Vector2.Lerp(summonOrigin, arrival, travel);
95 101 Projectile.Center = focus;
96 float progress = MathHelper.Clamp((age - 5f) / 23f, 0f, 1f);
102 if (cycleAge == 1)
103 validHistory = 0;
104 float progress = MathHelper.Clamp((cycleAge - 2f) / 14f, 0f, 1f);
97 105 float eased = progress * progress * (3f - 2f * progress);
98 106 int facing = aim.X >= 0f ? 1 : -1;
99 weaponAngle = MathHelper.Lerp(aim.ToRotation() - 2.35f * facing,
100 aim.ToRotation() + 2.15f * facing, eased);
107 int swingDirection = (cycle & 1) == 0 ? facing : -facing;
108 weaponAngle = MathHelper.Lerp(aim.ToRotation() - 2.35f * swingDirection,
109 aim.ToRotation() + 2.15f * swingDirection, eased);
101 110 RecordTip(focus + weaponAngle.ToRotationVector2()
102 * ReaperCombatRegistry.GetBladeTipLength(branchForm, ReaperStage.StageIII));
111 * ReaperCombatRegistry.GetBladeTipLength(branchForm, ReaperStage.StageIII)
112 * snapshot.DeathRangeMultiplier);
103 113
104 if (!struck && age >= 14 && Main.netMode != NetmodeID.MultiplayerClient)
114 if (lastStrikeCycle < cycle && cycleAge >= StrikeFrame
115 && Main.netMode != NetmodeID.MultiplayerClient)
105 116 {
106 struck = true;
107 SpawnBranchExecution();
117 lastStrikeCycle = cycle;
118 SpawnBranchExecution(cycle);
108 119 }
109 if (!residualSpawned && age >= 29 && validHistory >= 2
120 if (lastResidualCycle < cycle && cycleAge >= ResidualFrame
121 && validHistory >= 2
110 122 && Main.netMode != NetmodeID.MultiplayerClient
111 123 && branchForm is ReaperFormId.Blood or ReaperFormId.Void)
112 124 {
113 residualSpawned = true;
125 lastResidualCycle = cycle;
114 126 if (branchForm == ReaperFormId.Blood)
115 127 {
116 128 ReaperBloodArcScarProjectile.Spawn(Projectile.GetSource_FromThis(),
117 129 Projectile.owner, snapshot, tipHistory, validHistory, phase,
118 actionId, 0.25f);
130 actionId + cycle, 0.25f);
119 131 }
120 132 else
121 133 {
122 134 ReaperVoidArcRiftProjectile.Spawn(Projectile.GetSource_FromThis(),
123 135 Projectile.owner, snapshot, tipHistory, validHistory, phase,
124 actionId, 0.25f);
136 actionId + cycle, 0.25f);
125 137 }
126 138 }
127 if (age >= 34)
139 if (age >= slashCount * SlashCycleFrames + 2)
128 140 Projectile.Kill();
129 141 }
130 142
@@ -132,8 +144,10 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
132 144 {
133 145 if (!configured || Main.dedServ)
134 146 return false;
135 float opacity = age < 25 ? MathHelper.Clamp(age / 5f, 0f, 1f)
136 : MathHelper.Clamp((34f - age) / 9f, 0f, 1f);
147 int cycleAge = Math.Max(1, (age - 1) % SlashCycleFrames + 1);
148 float opacity = Smooth01(MathHelper.Clamp(cycleAge / 3f, 0f, 1f))
149 * Smooth01(MathHelper.Clamp((SlashCycleFrames + 1f - cycleAge) / 5f,
150 0f, 1f));
137 151 DrawBranchTrail(opacity);
138 152
139 153 Asset<Texture2D> asset = ModContent.Request<Texture2D>(
@@ -150,9 +164,11 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
150 164 branchForm, ReaperStage.StageIII);
151 165 Main.EntitySpriteDraw(texture, focus - Main.screenPosition, null,
152 166 ReaperCombatRegistry.GetPrimaryColor(branchForm) with { A = 0 },
153 weaponAngle + correction, origin, 0.74f, effects);
167 weaponAngle + correction, origin,
168 0.74f * snapshot.DeathRangeMultiplier, effects);
154 169 Main.EntitySpriteDraw(texture, focus - Main.screenPosition, null,
155 Color.White * opacity, weaponAngle + correction, origin, 0.62f,
170 Color.White * opacity, weaponAngle + correction, origin,
171 0.62f * snapshot.DeathRangeMultiplier,
156 172 effects);
157 173 return false;
158 174 }
@@ -170,8 +186,8 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
170 186 writer.WriteVector2(strikeFocus);
171 187 writer.WriteVector2(aim);
172 188 writer.Write((byte)Math.Clamp(age, 0, byte.MaxValue));
173 writer.Write(struck);
174 writer.Write(residualSpawned);
189 writer.Write((sbyte)Math.Clamp(lastStrikeCycle, -1, sbyte.MaxValue));
190 writer.Write((sbyte)Math.Clamp(lastResidualCycle, -1, sbyte.MaxValue));
175 191 }
176 192
177 193 public override void ReceiveExtraAI(BinaryReader reader)
@@ -191,8 +207,8 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
191 207 Vector2 incomingStrikeFocus = reader.ReadVector2();
192 208 Vector2 incomingAim = reader.ReadVector2();
193 209 int incomingAge = reader.ReadByte();
194 bool incomingStruck = reader.ReadBoolean();
195 bool incomingResidualSpawned = reader.ReadBoolean();
210 int incomingStrikeCycle = reader.ReadSByte();
211 int incomingResidualCycle = reader.ReadSByte();
196 212 if (Main.netMode == NetmodeID.Server)
197 213 return;
198 214 snapshot = incomingSnapshot;
@@ -206,8 +222,8 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
206 222 aim = incomingAim;
207 223 BuildSummonRiftPath();
208 224 age = incomingAge;
209 struck = incomingStruck;
210 residualSpawned = incomingResidualSpawned;
225 lastStrikeCycle = incomingStrikeCycle;
226 lastResidualCycle = incomingResidualCycle;
211 227 configured = true;
212 228 }
213 229
@@ -232,13 +248,15 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
232 248 actionId = parentActionId;
233 249 age = 0;
234 250 validHistory = 0;
235 struck = false;
236 residualSpawned = false;
251 lastStrikeCycle = -1;
252 lastResidualCycle = -1;
253 Projectile.timeLeft = snapshot.DeathInvocationSlashCount
254 * SlashCycleFrames + 6;
237 255 configured = true;
238 256 serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
239 257 }
240 258
241 private void SpawnBranchExecution()
259 private void SpawnBranchExecution(int slashCycle)
242 260 {
243 261 Vector2 direction = weaponAngle.ToRotationVector2();
244 262 Vector2 executionFocus = strikeFocus;
@@ -248,15 +266,17 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
248 266 ReaperFormId.Soul => ReaperStrikeShape.Circle,
249 267 _ => ReaperStrikeShape.Line
250 268 };
251 float length = shape == ReaperStrikeShape.Circle ? 94f : 205f;
269 float length = (shape == ReaperStrikeShape.Circle ? 94f : 205f)
270 * snapshot.DeathRangeMultiplier;
252 271 Vector2 start = shape == ReaperStrikeShape.Line
253 272 ? executionFocus - direction * length * 0.5f : executionFocus;
254 273 ReaperStrikeProjectile.Spawn(Projectile.GetSource_FromThis(), Projectile.owner,
255 274 snapshot, ReaperHitKind.Primary, 0, start, direction, shape,
256 length, 25f, 0.75f, actionId: actionId);
275 length, 25f * snapshot.DeathRangeMultiplier, 0.75f,
276 actionId: actionId + slashCycle);
257 277 ReaperSkillCueProjectile.Spawn(Projectile.GetSource_FromThis(), Projectile.owner,
258 278 snapshot, phase % 3, executionFocus, direction, variant: phase,
259 actionId: actionId * 8 + phase);
279 actionId: unchecked(actionId * 8 + phase + slashCycle * 47));
260 280 }
261 281
262 282 private void RecordTip(Vector2 point)
@@ -275,21 +295,23 @@ public sealed class ReaperDeathEchoScytheProjectile : ModProjectile
275 295 {
276 296 float progress = index / (float)(summonRiftPoints.Length - 1);
277 297 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;
298 float arch = (float)Math.Sin(progress * MathHelper.Pi) * 6.5f;
299 float ripple = (float)Math.Sin(progress * MathHelper.Pi * 3f) * 2.2f;
300 summonRiftPoints[index] = summonOrigin + axis * signed * 44f
301 - normal * (arch + ripple);
281 302 }
282 303 }
283 304
284 305 private void RecordSummonRift()
285 306 {
286 307 float appear = Smooth01(age / 3f);
287 float vanish = 1f - Smooth01((age - 11f) / 9f);
308 float vanish = 1f - Smooth01((age - 9f) / 9f);
288 309 float opacity = appear * vanish;
289 310 if (opacity <= 0.001f)
290 311 return;
291 312 DeathDomainTrailVisualSystem.Record(Projectile.owner,
292 Projectile.identity, summonRiftPoints, 39f, opacity);
313 Projectile.identity, summonRiftPoints, 20f, opacity,
314 mergeOverlappingRims: true, fracture: 0.18f);
293 315 }
294 316
295 317 private void DrawBranchTrail(float opacity)
Modified Projectiles/SickleSwingProjectile.cs +14 -10
@@ -54,7 +54,8 @@ public sealed class SickleSwingProjectile : ModProjectile
54 54 internal bool ServerValidated => serverValidated;
55 55 internal float DeathTempo => deathTempo;
56 56 internal bool DeathSpaceBreak => snapshot.Form == ReaperFormId.Death
57 && deathTempo >= ReaperCombatRegistry.DeathPrimaryMaximumTempo - 0.01f;
57 && snapshot.DeathSpaceBreakUnlocked
58 && deathTempo >= snapshot.DeathMaximumTempo - 0.01f;
58 59 private int ItemType => (int)Projectile.ai[2];
59 60 private int SwingDirection => Math.Sign(Projectile.ai[0]) == 0 ? 1 : Math.Sign(Projectile.ai[0]);
60 61 private int VisualStage => snapshot.Form == ReaperFormId.Death ? 3 : snapshot.StageNumber;
@@ -73,7 +74,7 @@ public sealed class SickleSwingProjectile : ModProjectile
73 74 phase = Math.Max(0, attackPhase);
74 75 deathTempo = snapshot.Form == ReaperFormId.Death
75 76 ? MathHelper.Clamp(primaryDeathTempo, 1f,
76 ReaperCombatRegistry.DeathPrimaryMaximumTempo)
77 snapshot.DeathMaximumTempo)
77 78 : 1f;
78 79 deathCrescentVisualInstanceId = 0;
79 80 configured = true;
@@ -277,7 +278,7 @@ public sealed class SickleSwingProjectile : ModProjectile
277 278 float reach;
278 279 if (snapshot.Form == ReaperFormId.Death)
279 280 {
280 reach = ReaperCombatRegistry.DeathPrimaryCollisionReach;
281 reach = ReaperCombatRegistry.GetDeathPrimaryCollisionReach(snapshot);
281 282 width = 74f;
282 283 }
283 284 else if (snapshot.Form == ReaperFormId.Void)
@@ -364,7 +365,7 @@ public sealed class SickleSwingProjectile : ModProjectile
364 365 Texture2D texture = ModContent.Request<Texture2D>(
365 366 ReaperCombatRegistry.GetTexturePath(snapshot.Form, snapshot.Stage)).Value;
366 367 float scale = snapshot.Form == ReaperFormId.Death
367 ? ReaperCombatRegistry.DeathWeaponDrawScale
368 ? ReaperCombatRegistry.GetDeathWeaponDrawScale(snapshot)
368 369 : 0.86f + snapshot.StageNumber * 0.045f;
369 370 SpriteEffects weaponEffects = GetWeaponEffects(0);
370 371 DrawWeaponAfterimages(texture, scale);
@@ -416,7 +417,7 @@ public sealed class SickleSwingProjectile : ModProjectile
416 417 int incomingTimer = reader.ReadInt16();
417 418 float incomingAimRotation = reader.ReadSingle();
418 419 float incomingDeathTempo = MathHelper.Clamp(reader.ReadSingle(), 1f,
419 ReaperCombatRegistry.DeathPrimaryMaximumTempo);
420 incomingSnapshot.DeathMaximumTempo);
420 421 bool incomingInfernalMoveReady = reader.ReadBoolean();
421 422 Vector2 incomingInfernalMoveStart = incomingInfernalMoveReady
422 423 ? reader.ReadVector2()
@@ -615,8 +616,11 @@ public sealed class SickleSwingProjectile : ModProjectile
615 616 int baseDuration = ReaperCombatRegistry.GetUseTime(value.Form, value.Stage, attackPhase);
616 617 int minimumDuration = value.Form == ReaperFormId.Death ? 3 : 12;
617 618 float tempo = value.Form == ReaperFormId.Death ? primaryDeathTempo : 1f;
619 float effectiveAttackSpeed = value.AttackSpeedMultiplier * tempo;
620 if (value.Form == ReaperFormId.Death)
621 effectiveAttackSpeed = Math.Min(effectiveAttackSpeed, value.DeathMaximumTempo);
618 622 return Math.Max(minimumDuration, (int)Math.Ceiling(baseDuration
619 / Math.Max(0.1f, value.AttackSpeedMultiplier * tempo)));
623 / Math.Max(0.1f, effectiveAttackSpeed)));
620 624 }
621 625
622 626 private void UpdateHistory(float progress)
@@ -631,7 +635,7 @@ public sealed class SickleSwingProjectile : ModProjectile
631 635 // front of the blade instead of being carved by its tip.
632 636 Vector2 currentTip = snapshot.Form == ReaperFormId.Death
633 637 ? grip + weaponAngle.ToRotationVector2()
634 * ReaperCombatRegistry.DeathPrimaryBladeReach
638 * ReaperCombatRegistry.GetDeathPrimaryBladeReach(snapshot)
635 639 : GetBladeTip(grip, weaponAngle);
636 640
637 641 // Initialize from the first evaluated animation pose, not aimRotation.
@@ -650,7 +654,7 @@ public sealed class SickleSwingProjectile : ModProjectile
650 654 // per-tick angular motion (including Infernal movement) remains far below
651 655 // this form-scaled limit, so legitimate arcs stay continuous.
652 656 float discontinuityDistance = snapshot.Form == ReaperFormId.Death
653 ? ReaperCombatRegistry.DeathPrimaryBladeReach * 0.96f
657 ? ReaperCombatRegistry.GetDeathPrimaryBladeReach(snapshot) * 0.96f
654 658 : Math.Clamp(ReaperCombatRegistry.GetBladeTipLength(snapshot.Form,
655 659 snapshot.Stage) * 0.62f, 62f, 118f);
656 660 float angularJump = Math.Abs(MathHelper.WrapAngle(weaponAngle - angleHistory[0]));
@@ -1031,7 +1035,7 @@ public sealed class SickleSwingProjectile : ModProjectile
1031 1035 out float rotation, out float reach, out int direction)
1032 1036 {
1033 1037 direction = GetAngularDirection(0);
1034 reach = ReaperCombatRegistry.DeathPrimaryCrescentRadius;
1038 reach = ReaperCombatRegistry.GetDeathPrimaryCrescentRadius(snapshot);
1035 1039 center = grip;
1036 1040 rotation = aimRotation;
1037 1041 }
@@ -1054,7 +1058,7 @@ public sealed class SickleSwingProjectile : ModProjectile
1054 1058 * SwingDirection;
1055 1059 float angle = aimRotation + offset;
1056 1060 bladePath[index] = grip + angle.ToRotationVector2()
1057 * ReaperCombatRegistry.DeathPrimaryBladeReach;
1061 * ReaperCombatRegistry.GetDeathPrimaryBladeReach(snapshot);
1058 1062 }
1059 1063 ReaperDeathDomainRiftProjectile.Spawn(Projectile.GetSource_FromThis(),
1060 1064 Projectile.owner, snapshot, bladePath, phase, Projectile.identity);
Modified UI/ReaperProgressionUI.cs +165 -21