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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

增强基础收魂镰打击反馈并提高死神连斩上限

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

代码差异

8 个文件 +201 -32
Modified Common/ReaperCombatDefinitions.cs +3 -1
@@ -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;
Modified Common/ReaperCombatService.cs +6 -5
@@ -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)
Modified Common/ReaperProgressionDefinitions.cs +4 -0
@@ -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;
Modified Localization/en-US.hjson +1 -1
@@ -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: {
Modified Localization/zh-Hans.hjson +1 -1
@@ -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: {
Modified Projectiles/ReaperActionControllerProjectile.cs +52 -15
@@ -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:
Modified Projectiles/ReaperStrikeProjectile.cs +54 -0
@@ -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)
Modified Projectiles/SickleSwingProjectile.cs +80 -9
@@ -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()