返回提交历史
Modified
Common/ReaperCombatDefinitions.cs
+44
-2
Modified
Common/ReaperCombatService.cs
+13
-6
Modified
Common/ReaperProgressionDefinitions.cs
+74
-8
Modified
Common/ReaperProgressionService.cs
+11
-0
Modified
Common/ReaperProgressionState.cs
+25
-3
Modified
Items/NormalSickle.cs
+2
-0
Modified
Localization/en-US.hjson
+34
-2
Modified
Localization/zh-Hans.hjson
+34
-2
Modified
Projectiles/ReaperActionControllerProjectile.cs
+14
-6
Modified
Projectiles/ReaperDeathDomainRiftProjectile.cs
+7
-6
Modified
Projectiles/ReaperDeathEchoScytheProjectile.cs
+60
-38
Modified
Projectiles/SickleSwingProjectile.cs
+14
-10
Modified
UI/ReaperProgressionUI.cs
+165
-21
XFEstudio/DeathMod
重构死神镰刀专属成长与技能树交互
e2a2e11
代码差异
13 个文件
+497
-104
@@ -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;
@@ -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)
@@ -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
}
@@ -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;
@@ -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));
@@ -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);
@@ -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
@@ -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
@@ -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())
@@ -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()
@@ -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)
@@ -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);