返回提交历史
Modified
Common/ReaperCombatDefinitions.cs
+3
-1
Modified
Common/ReaperCombatService.cs
+6
-5
Modified
Common/ReaperProgressionDefinitions.cs
+4
-0
Modified
Localization/en-US.hjson
+1
-1
Modified
Localization/zh-Hans.hjson
+1
-1
Modified
Projectiles/ReaperActionControllerProjectile.cs
+52
-15
Modified
Projectiles/ReaperStrikeProjectile.cs
+54
-0
Modified
Projectiles/SickleSwingProjectile.cs
+80
-9
XFEstudio/DeathMod
增强基础收魂镰打击反馈并提高死神连斩上限
c26c626
代码差异
8 个文件
+201
-32
@@ -60,6 +60,8 @@ public readonly record struct SickleCombatSnapshot(
60
60
=> ReaperDefinitions.GetDeathInvocationSlashCount(DeathInvocationSlashLevel);
61
61
public float DeathMaximumTempo
62
62
=> ReaperDefinitions.GetDeathTempoCap(DeathTempoCapLevel);
63
public float DeathPrimaryMaximumTempo
64
=> ReaperDefinitions.GetDeathPrimaryTempoCap(DeathTempoCapLevel);
63
65
public float DeathRangeMultiplier
64
66
=> ReaperDefinitions.GetDeathRangeMultiplier(DeathRangeLevel);
65
67
public bool DeathSpaceBreakUnlocked => DeathSpaceBreakLevel > 0;
@@ -162,7 +164,7 @@ public static class ReaperCombatRegistry
162
164
// scales the weapon sprite; it must never be reused as an attack-range
163
165
// multiplier.
164
166
public const float DeathWeaponDrawScale = 2.58f;
165
public const float DeathPrimaryMaximumTempo = 3f;
167
public const float DeathPrimaryMaximumTempo = 5f;
166
168
public const float CrescentOuterRadiusRatio = 0.91f;
167
169
public const float StandardCrescentThicknessRatio = 0.455f;
168
170
public const float DeathPrimaryHalfSweep = 2.43f;
@@ -74,7 +74,8 @@ public static class ReaperCombatService
74
74
int baseDuration = ReaperCombatRegistry.GetUseTime(snapshot.Form, snapshot.Stage, phase);
75
75
float effectiveAttackSpeed = snapshot.AttackSpeedMultiplier * deathTempo;
76
76
if (snapshot.Form == ReaperFormId.Death)
77
effectiveAttackSpeed = Math.Min(effectiveAttackSpeed, snapshot.DeathMaximumTempo);
77
effectiveAttackSpeed = Math.Min(effectiveAttackSpeed,
78
snapshot.DeathPrimaryMaximumTempo);
78
79
int minimumDuration = snapshot.Form == ReaperFormId.Death ? 3 : 12;
79
80
int authoritativeDuration = Math.Max(minimumDuration,
80
81
(int)Math.Ceiling(baseDuration / Math.Max(0.1f,
@@ -112,13 +113,13 @@ public static class ReaperCombatService
112
113
ReaperCombatSystem.DeathPrimaryChain[owner] = (byte)Math.Min(DeathPrimaryMaximumChain, chain);
113
114
ReaperCombatSystem.LastDeathPrimaryTick[owner] = now;
114
115
deathTempo = GetDeathTempo(ReaperCombatSystem.DeathPrimaryChain[owner],
115
snapshot.DeathMaximumTempo);
116
snapshot.DeathPrimaryMaximumTempo);
116
117
}
117
118
return phase;
118
119
}
119
120
120
// Death now takes twice as many uninterrupted attacks to reach full tempo,
121
// but the completed cadence is itself twice as fast as the old cap.
121
// Death takes a sustained uninterrupted chain to reach its separately
122
// upgraded primary cap. The held invocation keeps its own lower cap.
122
123
internal const int DeathPrimaryMaximumChain = 24;
123
124
private const ulong DeathPrimaryChainResetFrames = 46UL;
124
125
@@ -133,7 +134,7 @@ public static class ReaperCombatService
133
134
int capLevel = player.GetModPlayer<MyPlayer>().ReaperProgression
134
135
.GetCommonNodeLevel(ReaperCommonNode.DeathTempoCap);
135
136
return GetDeathTempo(ReaperCombatSystem.DeathPrimaryChain[player.whoAmI],
136
ReaperDefinitions.GetDeathTempoCap(capLevel));
137
ReaperDefinitions.GetDeathPrimaryTempoCap(capLevel));
137
138
}
138
139
139
140
private static float GetDeathTempo(int chain, float maximumTempo)
@@ -341,6 +341,10 @@ public static class ReaperDefinitions
341
341
public static float GetDeathTempoCap(int level)
342
342
=> 1.5f + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathTempoCap)) * 0.3f;
343
343
344
public static float GetDeathPrimaryTempoCap(int level)
345
=> 1.5f + Math.Clamp(level, 0,
346
GetCommonNodeMaxLevel(ReaperCommonNode.DeathTempoCap)) * 0.7f;
347
344
348
public static float GetDeathRangeMultiplier(int level)
345
349
=> 0.5f + Math.Clamp(level, 0,
346
350
GetCommonNodeMaxLevel(ReaperCommonNode.DeathRange)) * 0.10f;
@@ -615,7 +615,7 @@ Mods: {
615
615
616
616
DeathTempoCap: {
617
617
Name: Terminal Acceleration
618
Description: Raises both primary-chain and held-special speed caps from 150% to at most 300%.
618
Description: Raises the primary-chain speed cap from 150% to 500%, and the held-special speed cap from 150% to 300%.
619
619
}
620
620
621
621
DeathRange: {
@@ -615,7 +615,7 @@ Mods: {
615
615
616
616
DeathTempoCap: {
617
617
Name: 终焉加速
618
Description: 同时提高左键连斩与右键召唤的攻速上限;由基础 150% 提高至最高 300%。
618
Description: 提高左键连斩与右键召唤的攻速上限;左键由基础 150% 提高至最高 500%,右键由基础 150% 提高至最高 300%。
619
619
}
620
620
621
621
DeathRange: {
@@ -587,8 +587,8 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
587
587
if (deathInvocationCooldown > 0)
588
588
return;
589
589
590
// Reach the configured cap in roughly 2.5 seconds instead of ten. The
591
// cap itself is character progression and applies to both Death attacks.
590
// Reach the configured held-invocation cap in roughly 2.5 seconds. The
591
// primary chain owns a separate, higher cap from the same upgrade node.
592
592
float acceleration = MathHelper.Clamp(chargeFrames / 150f, 0f, 1f);
593
593
acceleration = acceleration * acceleration * (3f - 2f * acceleration);
594
594
float invocationTempo = MathHelper.Lerp(1f,
@@ -1281,12 +1281,37 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
1281
1281
1282
1282
if (baseDashReady)
1283
1283
{
1284
DrawVisualLine(pixel, baseDashStart, baseDashEnd,
1285
new Color(35, 95, 105, 0) * opacity * 0.34f, 17f);
1286
DrawVisualLine(pixel, baseDashStart, baseDashEnd,
1287
new Color(125, 235, 240, 0) * opacity * 0.65f, 3.2f);
1288
DrawScytheGlyph(pixel, baseDashStart, aim, 70f,
1289
new Color(120, 225, 235, 0) * opacity * 0.22f);
1284
float dashProgress = SmoothStep(MathHelper.Clamp(
1285
(timer - BaseDashStartTick + 1f) / BaseDashTravelFrames,
1286
0f, 1f));
1287
Vector2 dashHead = Vector2.Lerp(baseDashStart, baseDashEnd,
1288
dashProgress);
1289
Vector2 axis = (baseDashEnd - baseDashStart)
1290
.SafeNormalize(aim);
1291
Vector2 normal = axis.RotatedBy(MathHelper.PiOver2);
1292
DrawVisualLine(pixel, baseDashStart, dashHead,
1293
new Color(7, 38, 50, 155) * opacity, 18f);
1294
DrawVisualLine(pixel, baseDashStart, dashHead,
1295
new Color(40, 185, 205, 0) * opacity * 0.62f, 7.5f);
1296
DrawVisualLine(pixel, baseDashStart, dashHead,
1297
new Color(205, 255, 250, 0) * opacity * 0.86f, 1.7f);
1298
for (int lane = -1; lane <= 1; lane += 2)
1299
{
1300
Vector2 offset = normal * lane * 7f;
1301
DrawVisualLine(pixel, baseDashStart + offset,
1302
dashHead + offset,
1303
new Color(75, 220, 230, 0) * opacity * 0.28f,
1304
2.2f);
1305
}
1306
1307
float ringReveal = SmoothStep(MathHelper.Clamp(
1308
(timer - BaseDashStartTick) / 5f, 0f, 1f));
1309
DrawArc(pixel, baseDashStart, 15f + ringReveal * 18f,
1310
aim.ToRotation() + MathHelper.Pi * 0.62f,
1311
MathHelper.Pi * 0.76f,
1312
new Color(105, 235, 240, 0) * opacity
1313
* (1f - ringReveal) * 0.55f,
1314
2.4f, 10);
1290
1315
}
1291
1316
1292
1317
float reveal = SmoothStep(MathHelper.Clamp((timer - 5f) / 8f, 0f, 1f));
@@ -1294,9 +1319,15 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
1294
1319
float startAngle = aim.ToRotation() - 1.42f;
1295
1320
Vector2 center = grip - aim * 24f;
1296
1321
DrawArc(pixel, center, 142f, startAngle, 2.45f * reveal,
1297
new Color(35, 135, 150, 0) * fade * 0.34f, 17f, 16);
1322
new Color(8, 45, 56, 155) * fade * 0.82f, 22f, 22);
1323
DrawArc(pixel, center, 143.5f, startAngle, 2.45f * reveal,
1324
new Color(35, 185, 205, 0) * fade * 0.58f, 9f, 22);
1298
1325
DrawArc(pixel, center, 145f, startAngle, 2.45f * reveal,
1299
new Color(195, 250, 245, 0) * fade * 0.78f, 3.2f, 16);
1326
new Color(215, 255, 250, 0) * fade * 0.88f, 2.1f, 22);
1327
DrawArc(pixel, center, 119f, startAngle + 0.08f,
1328
2.25f * reveal,
1329
new Color(70, 220, 230, 0) * fade * 0.24f,
1330
3.4f, 20);
1300
1331
}
1301
1332
1302
1333
private void DrawBoneSpecial(Texture2D pixel, Vector2 grip)
@@ -1978,11 +2009,17 @@ public sealed class ReaperActionControllerProjectile : ModProjectile
1978
2009
PlayTuned(SoundID.Item8, position, 0.5f, 0.12f, 0.08f);
1979
2010
if (ReachedVisualTick(11) && ConsumeVisualEvent(5))
1980
2011
{
1981
PlayTuned(SoundID.Item71, position, 0.82f, 0.12f, 0.08f);
1982
PlayTuned(SoundID.NPCDeath6, position, 0.28f, 0.3f, 0.06f);
1983
TriggerSpecialImpact(position, aim, 3f, new Color(195, 250, 245), 0.16f, 5);
1984
SpawnVisualBurst(position + aim * 95f, DustID.AncientLight,
1985
new Color(125, 235, 240), 9, 3.2f, 0.85f);
2012
Vector2 impactPosition = baseDashReady
2013
? baseDashEnd + aim * 72f
2014
: position + aim * 95f;
2015
PlayTuned(SoundID.Item71, impactPosition, 0.82f,
2016
0.12f, 0.08f);
2017
PlayTuned(SoundID.NPCDeath6, impactPosition, 0.28f,
2018
0.3f, 0.06f);
2019
TriggerSpecialImpact(impactPosition, aim, 3.6f,
2020
new Color(125, 235, 240), 0f, 0);
2021
SpawnVisualBurst(impactPosition, DustID.AncientLight,
2022
new Color(125, 235, 240), 13, 4.3f, 0.95f);
1986
2023
}
1987
2024
break;
1988
2025
case ReaperFormId.Bone:
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
4
4
using System;
5
5
using System.IO;
6
6
using Terraria;
7
using Terraria.Audio;
7
8
using Terraria.GameContent;
8
9
using Terraria.ID;
9
10
using Terraria.ModLoader;
@@ -118,6 +119,59 @@ public sealed class ReaperStrikeProjectile : ModProjectile
118
119
};
119
120
}
120
121
122
public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone)
123
{
124
if (Main.netMode == NetmodeID.Server || !configured
125
|| snapshot.Form != ReaperFormId.Base
126
|| hitKind != ReaperHitKind.Special)
127
{
128
return;
129
}
130
131
Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX);
132
Vector2 slash = axis.RotatedBy(-0.48f)
133
.SafeNormalize(Vector2.UnitY);
134
ReaperVfxDirector.TriggerGlobalImpact(slash, 3.2f, 4,
135
new Color(90, 220, 230), 0f, 0, 0.08f);
136
SoundEngine.PlaySound(SoundID.Item71 with
137
{
138
Volume = 0.52f,
139
Pitch = 0.3f,
140
PitchVariance = 0.07f
141
}, target.Center);
142
143
for (int index = 0; index < 13; index++)
144
{
145
Vector2 velocity = slash.RotatedBy(
146
Main.rand.NextFloat(-0.68f, 0.68f))
147
* Main.rand.NextFloat(3f, 7.8f);
148
Dust dust = Dust.NewDustPerfect(
149
target.Center + Main.rand.NextVector2Circular(8f, 8f),
150
DustID.AncientLight, velocity, 25,
151
index % 4 == 0 ? new Color(225, 255, 250)
152
: new Color(65, 220, 235),
153
Main.rand.NextFloat(0.78f, 1.16f));
154
dust.noGravity = true;
155
}
156
157
Vector2 towardOwner = Projectile.owner >= 0
158
&& Projectile.owner < Main.maxPlayers
159
? (Main.player[Projectile.owner].Center - target.Center)
160
.SafeNormalize(-axis)
161
: -axis;
162
for (int index = 0; index < 5; index++)
163
{
164
Dust soul = Dust.NewDustPerfect(
165
target.Center + Main.rand.NextVector2Circular(11f, 11f),
166
DustID.AncientLight,
167
towardOwner.RotatedBy(Main.rand.NextFloat(-0.34f, 0.34f))
168
* Main.rand.NextFloat(1.4f, 3.2f),
169
80, new Color(85, 225, 235),
170
Main.rand.NextFloat(0.62f, 0.92f));
171
soul.noGravity = true;
172
}
173
}
174
121
175
public override bool PreDraw(ref Color lightColor)
122
176
{
123
177
if (!configured || age < delay || age > delay + 7)
@@ -55,7 +55,7 @@ public sealed class SickleSwingProjectile : ModProjectile
55
55
internal float DeathTempo => deathTempo;
56
56
internal bool DeathSpaceBreak => snapshot.Form == ReaperFormId.Death
57
57
&& snapshot.DeathSpaceBreakUnlocked
58
&& deathTempo >= snapshot.DeathMaximumTempo - 0.01f;
58
&& deathTempo >= snapshot.DeathPrimaryMaximumTempo - 0.01f;
59
59
private int ItemType => (int)Projectile.ai[2];
60
60
private int SwingDirection => Math.Sign(Projectile.ai[0]) == 0 ? 1 : Math.Sign(Projectile.ai[0]);
61
61
private int VisualStage => snapshot.Form == ReaperFormId.Death ? 3 : snapshot.StageNumber;
@@ -74,7 +74,7 @@ public sealed class SickleSwingProjectile : ModProjectile
74
74
phase = Math.Max(0, attackPhase);
75
75
deathTempo = snapshot.Form == ReaperFormId.Death
76
76
? MathHelper.Clamp(primaryDeathTempo, 1f,
77
snapshot.DeathMaximumTempo)
77
snapshot.DeathPrimaryMaximumTempo)
78
78
: 1f;
79
79
deathCrescentVisualInstanceId = 0;
80
80
configured = true;
@@ -378,6 +378,12 @@ public sealed class SickleSwingProjectile : ModProjectile
378
378
if (Main.netMode == NetmodeID.Server)
379
379
return;
380
380
381
if (snapshot.Form == ReaperFormId.Base)
382
{
383
SpawnBasePrimaryImpact(target.Center);
384
return;
385
}
386
381
387
// Void owns a synchronized, persistent impact rift. Keeping the old
382
388
// shadowflame burst as well made the contact read like an ordinary purple
383
389
// magic hit and doubled the owner's feedback in multiplayer.
@@ -417,7 +423,7 @@ public sealed class SickleSwingProjectile : ModProjectile
417
423
int incomingTimer = reader.ReadInt16();
418
424
float incomingAimRotation = reader.ReadSingle();
419
425
float incomingDeathTempo = MathHelper.Clamp(reader.ReadSingle(), 1f,
420
incomingSnapshot.DeathMaximumTempo);
426
incomingSnapshot.DeathPrimaryMaximumTempo);
421
427
bool incomingInfernalMoveReady = reader.ReadBoolean();
422
428
Vector2 incomingInfernalMoveStart = incomingInfernalMoveReady
423
429
? reader.ReadVector2()
@@ -609,7 +615,8 @@ public sealed class SickleSwingProjectile : ModProjectile
609
615
float tempo = value.Form == ReaperFormId.Death ? primaryDeathTempo : 1f;
610
616
float effectiveAttackSpeed = value.AttackSpeedMultiplier * tempo;
611
617
if (value.Form == ReaperFormId.Death)
612
effectiveAttackSpeed = Math.Min(effectiveAttackSpeed, value.DeathMaximumTempo);
618
effectiveAttackSpeed = Math.Min(effectiveAttackSpeed,
619
value.DeathPrimaryMaximumTempo);
613
620
return Math.Max(minimumDuration, (int)Math.Ceiling(baseDuration
614
621
/ Math.Max(0.1f, effectiveAttackSpeed)));
615
622
}
@@ -960,6 +967,8 @@ public sealed class SickleSwingProjectile : ModProjectile
960
967
return;
961
968
}
962
969
DrawMaterialCrescentTrail();
970
if (VisualForm == ReaperFormId.Base)
971
DrawBaseTrail();
963
972
}
964
973
965
974
private void DrawMaterialCrescentTrail()
@@ -1058,14 +1067,31 @@ public sealed class SickleSwingProjectile : ModProjectile
1058
1067
private void DrawBaseTrail()
1059
1068
{
1060
1069
Texture2D pixel = TextureAssets.MagicPixel.Value;
1061
float fade = GetTerminalFade();
1062
Color spirit = new Color(65, 210, 225, 0);
1063
Color edge = new Color(215, 255, 255, 0);
1070
float fade = GetSwingVisibility();
1071
Color shadow = new(7, 45, 58, 150);
1072
Color spirit = new(45, 205, 225, 0);
1073
Color edge = new(215, 255, 250, 0);
1064
1074
for (int index = validHistorySamples - 1; index > 0; index--)
1065
1075
{
1066
1076
float strength = GetHistoryStrength(index) * fade;
1067
DrawVisualSegment(pixel, tipHistory[index], tipHistory[index - 1], spirit * (strength * 0.48f), 4.2f * strength + 0.8f);
1068
DrawVisualSegment(pixel, tipHistory[index], tipHistory[index - 1], edge * (strength * 0.72f), 1.05f);
1077
Vector2 older = tipHistory[index];
1078
Vector2 newer = tipHistory[index - 1];
1079
float width = 3.2f + strength * 4.8f;
1080
DrawVisualSegment(pixel, older, newer,
1081
shadow * (strength * 0.72f), width * 1.75f);
1082
DrawVisualSegment(pixel, older, newer,
1083
spirit * (strength * 0.62f), width);
1084
DrawVisualSegment(pixel, older, newer,
1085
edge * (strength * 0.88f), 1.25f);
1086
1087
// The inner filament is reconstructed from the same blade-tip
1088
// history, so the starter crescent grows with the real swing rather
1089
// than appearing at full size on its first frame.
1090
Vector2 innerOlder = Vector2.Lerp(grip, older, 0.78f);
1091
Vector2 innerNewer = Vector2.Lerp(grip, newer, 0.78f);
1092
DrawVisualSegment(pixel, innerOlder, innerNewer,
1093
spirit * (strength * 0.24f),
1094
Math.Max(1.4f, width * 0.42f));
1069
1095
}
1070
1096
}
1071
1097
@@ -1504,6 +1530,51 @@ public sealed class SickleSwingProjectile : ModProjectile
1504
1530
}
1505
1531
}
1506
1532
1533
private void SpawnBasePrimaryImpact(Vector2 center)
1534
{
1535
Vector2 blade = weaponAngle.ToRotationVector2();
1536
Vector2 slash = blade.RotatedBy(MathHelper.PiOver2 * SwingDirection)
1537
.SafeNormalize(Vector2.UnitY);
1538
Vector2 towardOwner = (grip - center).SafeNormalize(-blade);
1539
1540
ReaperVfxDirector.TriggerGlobalImpact(slash, 2.6f, 4,
1541
new Color(92, 214, 224), 0f, 0, 0.06f);
1542
SoundEngine.PlaySound(SoundID.Item71 with
1543
{
1544
Volume = 0.46f,
1545
Pitch = 0.24f,
1546
PitchVariance = 0.08f
1547
}, center);
1548
1549
for (int index = 0; index < 9; index++)
1550
{
1551
Vector2 velocity = slash.RotatedBy(
1552
Main.rand.NextFloat(-0.72f, 0.72f))
1553
* Main.rand.NextFloat(2.8f, 6.8f);
1554
Dust spark = Dust.NewDustPerfect(
1555
center + Main.rand.NextVector2Circular(7f, 7f),
1556
DustID.AncientLight, velocity, 30,
1557
index % 3 == 0 ? new Color(225, 255, 250)
1558
: new Color(55, 220, 235),
1559
Main.rand.NextFloat(0.72f, 1.08f));
1560
spark.noGravity = true;
1561
}
1562
1563
// A few harmless motes peel from the target and return toward the
1564
// wielder, preserving the basic weapon's soul-pulling identity.
1565
for (int index = 0; index < 4; index++)
1566
{
1567
Dust mote = Dust.NewDustPerfect(
1568
center + Main.rand.NextVector2Circular(9f, 9f),
1569
DustID.AncientLight,
1570
towardOwner.RotatedBy(Main.rand.NextFloat(-0.28f, 0.28f))
1571
* Main.rand.NextFloat(1.2f, 2.8f),
1572
70, new Color(95, 230, 235),
1573
Main.rand.NextFloat(0.62f, 0.88f));
1574
mote.noGravity = true;
1575
}
1576
}
1577
1507
1578
private void TriggerPrimaryFinisherImpact()
1508
1579
{
1509
1580
Vector2 impactDirection = weaponAngle.ToRotationVector2()