返回提交历史
Modified
Common/DeathDomainVisualSystem.cs
+78
-17
Modified
Common/MyPlayer.cs
+72
-23
Modified
Items/DeathNecklace.cs
+2
-0
Modified
Projectiles/DeathDomainHarvestSlashProjectile.cs
+16
-22
XFEstudio/DeathMod
完善领域内外力场与斩击残留
5f78da3
代码差异
4 个文件
+168
-62
@@ -186,6 +186,11 @@ internal class DeathDomainVisualSystem : ModSystem
186
186
float coreNoiseAmplitude = Math.Min(coreRadius * 0.18f, MathHelper.Lerp(5f, 10f, mastery) * MathHelper.Lerp(0.35f, 1f, reveal));
187
187
float blackOpacity = MathHelper.Lerp(0.50f, 0.96f, mastery) * reveal;
188
188
DrawFilledNoiseDisc(batch, pixel, center, coreRadius, coreNoiseTime, coreNoiseSeed, coreNoiseAmplitude, Color.Black * blackOpacity);
189
DrawFilledNoiseDisc(batch, pixel, center, coreRadius * 0.32f, -time * 0.62f, 27.4f,
190
MathHelper.Lerp(2.2f, 4.8f, mastery), new Color(155, 12, 38) * (0.11f * pulse));
191
DrawFilledNoiseDisc(batch, pixel, center, coreRadius * 0.16f, time * 0.9f, 31.8f,
192
MathHelper.Lerp(1.2f, 3.2f, mastery), new Color(255, 74, 96) * (0.17f * pulse));
193
DrawOutwardPressureWaves(batch, pixel, center, coreRadius, time, mastery, pulse);
189
194
DrawNoiseBoundary(batch, pixel, center, coreRadius, coreNoiseTime, coreNoiseSeed, coreNoiseAmplitude, 2.2f, 0.76f * pulse, mastery, gaps: false);
190
195
DrawInwardSuctionWaves(batch, pixel, center, radius, coreRadius, time, mastery, pulse);
191
196
}
@@ -330,46 +335,102 @@ internal class DeathDomainVisualSystem : ModSystem
330
335
float mastery,
331
336
float pulse)
332
337
{
333
// Long accretion streams outside the red boundary restore the visual
334
// language of a black-hole edge: distant, dark matter bends into a spiral
335
// and tightens as it reaches the event horizon.
338
// Short comet-like streams move unambiguously from the outer radius toward
339
// the event horizon. Their tails always remain farther from the centre than
340
// their heads, so the absorption direction cannot appear reversed.
336
341
int streamCount = 7 + (int)Math.Round(mastery * 5f);
337
342
float outerRadius = edgeRadius + MathHelper.Lerp(72f, 126f, mastery);
338
343
for (int stream = 0; stream < streamCount; stream++)
339
344
{
340
345
float seed = stream * 4.731f + 2.4f;
341
float movingHead = PositiveModulo(time * MathHelper.Lerp(0.48f, 0.78f, mastery)
346
float head = PositiveModulo(time * MathHelper.Lerp(0.44f, 0.68f, mastery)
342
347
+ stream / (float)streamCount, 1f);
343
348
float baseAngle = stream * MathHelper.TwoPi / streamCount
344
- time * (0.13f + mastery * 0.08f)
345
+ BoundaryNoise(stream, time * 0.18f, seed) * 0.06f;
349
+ BoundaryNoise(stream, time * 0.12f, seed) * 0.045f;
350
float trailLength = MathHelper.Lerp(0.18f, 0.28f, mastery);
351
float tail = Math.Max(0f, head - trailLength);
352
float spawnFade = MathHelper.SmoothStep(0f, 0.09f, head);
353
float arrivalFade = 1f - MathHelper.SmoothStep(0.84f, 1f, head);
346
354
347
const int points = 46;
355
const int points = 18;
348
356
Vector2 previous = Vector2.Zero;
357
bool hasPrevious = false;
349
358
for (int point = 0; point < points; point++)
350
359
{
351
float inward = point / (points - 1f);
360
float trailProgress = point / (points - 1f);
361
float inward = MathHelper.Lerp(tail, head, trailProgress);
352
362
float curved = inward * inward * (3f - 2f * inward);
353
363
float angle = baseAngle + inward * MathHelper.Lerp(1.08f, 1.54f, mastery);
354
364
float radius = MathHelper.Lerp(outerRadius, edgeRadius + 2f, curved)
355
365
+ BoundaryNoise(angle, -time * 0.82f, seed) * MathHelper.Lerp(5.2f, 1.2f, inward);
356
366
Vector2 current = center + angle.ToRotationVector2() * radius;
357
if (point > 0)
367
if (hasPrevious)
358
368
{
359
float bodyFade = (float)Math.Sin(inward * MathHelper.Pi);
360
float distanceFromHead = Math.Abs(inward - movingHead);
361
distanceFromHead = Math.Min(distanceFromHead, 1f - distanceFromHead);
362
float movingGlow = 1f - MathHelper.SmoothStep(0.02f, 0.25f, distanceFromHead);
363
float opacity = bodyFade * pulse * (0.16f + movingGlow * 0.64f);
369
float headGlow = MathHelper.SmoothStep(0f, 1f, trailProgress);
370
float opacity = pulse * spawnFade * arrivalFade * headGlow * (0.18f + headGlow * 0.72f);
364
371
Color color = Color.Lerp(
365
372
new Color(18, 0, 8),
366
373
Color.Lerp(new Color(174, 3, 40), new Color(255, 34, 76), mastery),
367
inward * (0.45f + movingGlow * 0.55f));
368
float width = MathHelper.Lerp(0.55f, 3.4f + mastery * 1.7f, inward)
369
* (0.68f + movingGlow * 0.48f);
374
headGlow);
375
float width = MathHelper.Lerp(0.45f, 3.8f + mastery * 1.6f, headGlow);
370
376
DrawSegment(batch, pixel, previous, current - previous, color * opacity, width);
371
377
}
372
378
previous = current;
379
hasPrevious = true;
380
}
381
}
382
}
383
384
private static void DrawOutwardPressureWaves(
385
SpriteBatch batch,
386
Texture2D pixel,
387
Vector2 center,
388
float coreRadius,
389
float time,
390
float mastery,
391
float pulse)
392
{
393
int streamCount = 8 + (int)Math.Round(mastery * 6f);
394
float innerRadius = Math.Max(5f, coreRadius * 0.055f);
395
float outerRadius = coreRadius * 0.90f;
396
for (int stream = 0; stream < streamCount; stream++)
397
{
398
float seed = 42.7f + stream * 3.117f;
399
float head = PositiveModulo(time * MathHelper.Lerp(0.38f, 0.62f, mastery)
400
+ stream / (float)streamCount, 1f);
401
float trailLength = MathHelper.Lerp(0.16f, 0.24f, mastery);
402
float tail = Math.Max(0f, head - trailLength);
403
float baseAngle = stream * MathHelper.TwoPi / streamCount
404
- BoundaryNoise(stream, time * 0.15f, seed) * 0.055f;
405
float spawnFade = MathHelper.SmoothStep(0f, 0.10f, head);
406
float arrivalFade = 1f - MathHelper.SmoothStep(0.82f, 1f, head);
407
408
const int points = 15;
409
Vector2 previous = Vector2.Zero;
410
bool hasPrevious = false;
411
for (int point = 0; point < points; point++)
412
{
413
float trailProgress = point / (points - 1f);
414
float outward = MathHelper.Lerp(tail, head, trailProgress);
415
float curved = outward * outward * (3f - 2f * outward);
416
float angle = baseAngle - outward * MathHelper.Lerp(0.72f, 1.12f, mastery);
417
float radius = MathHelper.Lerp(innerRadius, outerRadius, curved)
418
+ BoundaryNoise(angle, time * 0.74f, seed) * MathHelper.Lerp(1.1f, 3.8f, outward);
419
Vector2 current = center + angle.ToRotationVector2() * radius;
420
if (hasPrevious)
421
{
422
float headGlow = MathHelper.SmoothStep(0f, 1f, trailProgress);
423
float opacity = pulse * spawnFade * arrivalFade * headGlow * (0.15f + headGlow * 0.62f);
424
Color color = Color.Lerp(
425
new Color(255, 112, 130),
426
Color.Lerp(new Color(148, 5, 38), new Color(226, 18, 58), mastery),
427
outward);
428
float width = MathHelper.Lerp(3.2f + mastery, 0.7f, outward)
429
* (0.62f + headGlow * 0.55f);
430
DrawSegment(batch, pixel, previous, current - previous, color * opacity, width);
431
}
432
previous = current;
433
hasPrevious = true;
373
434
}
374
435
}
375
436
}
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
4
4
using System;
5
5
using System.Collections.Generic;
6
6
using Terraria;
7
using Terraria.Audio;
7
8
using Terraria.Chat;
8
9
using Terraria.ID;
9
10
using Terraria.GameInput;
@@ -41,6 +42,7 @@ public class MyPlayer : ModPlayer
41
42
private int deathDomainTimer;
42
43
private int deathDomainVolleyCounter;
43
44
private int deathDomainScreenShakeFrames;
45
private int deathDomainSlashSoundCooldown;
44
46
private float deathDomainScreenShakeStrength;
45
47
private Vector2 deathDomainScreenShakeDirection = Vector2.UnitY;
46
48
private readonly Dictionary<int, PendingDeathDomainCut> deathDomainPendingCuts = [];
@@ -63,6 +65,7 @@ public class MyPlayer : ModPlayer
63
65
deathDomainTimer = 0;
64
66
deathDomainVolleyCounter = 0;
65
67
deathDomainScreenShakeFrames = 0;
68
deathDomainSlashSoundCooldown = 0;
66
69
deathDomainScreenShakeStrength = 0f;
67
70
deathDomainScreenShakeDirection = Vector2.UnitY;
68
71
deathDomainPendingCuts.Clear();
@@ -92,6 +95,8 @@ public class MyPlayer : ModPlayer
92
95
lifeStealCooldown--;
93
96
if (SickleAlternateCooldown > 0)
94
97
SickleAlternateCooldown--;
98
if (deathDomainSlashSoundCooldown > 0)
99
deathDomainSlashSoundCooldown--;
95
100
96
101
UpdateDeathDomain();
97
102
@@ -140,6 +145,31 @@ public class MyPlayer : ModPlayer
140
145
deathDomainScreenShakeFrames = 6;
141
146
}
142
147
148
internal void PlayDeathDomainSlashSound(Vector2 worldPosition, float mastery, bool requiem)
149
{
150
if (Main.dedServ
151
|| Player.whoAmI != Main.myPlayer
152
|| deathDomainSlashSoundCooldown > 0
153
|| Vector2.DistanceSquared(Player.Center, worldPosition) > 1600f * 1600f)
154
{
155
return;
156
}
157
158
deathDomainSlashSoundCooldown = 4;
159
SoundEngine.PlaySound(SoundID.DD2_SonicBoomBladeSlash with
160
{
161
Volume = 0.58f + mastery * 0.12f + (requiem ? 0.08f : 0f),
162
Pitch = 0.20f + mastery * 0.08f,
163
PitchVariance = 0.045f
164
}, worldPosition);
165
SoundEngine.PlaySound(SoundID.Item1 with
166
{
167
Volume = 0.30f + mastery * 0.08f,
168
Pitch = 0.52f,
169
PitchVariance = 0.035f
170
}, worldPosition);
171
}
172
143
173
internal void RecordDeathWingTrail(Vector2 upperTip, Vector2 lowerTip, float mastery)
144
174
{
145
175
if (Main.dedServ || deathWingTrailUpdateTick == Main.GameUpdateCount)
@@ -407,7 +437,7 @@ public class MyPlayer : ModPlayer
407
437
return;
408
438
}
409
439
410
ApplyDeathDomainAttraction(necklace);
440
ApplyDeathDomainForces(necklace);
411
441
List<NPC> targets = FindDeathDomainTargets(necklace);
412
442
float visualMastery = MathHelper.Clamp((necklace.MasteryScore - 5f) / 65f, 0f, 1f);
413
443
UpdateDeathDomainTelegraphs(targets, necklace, visualMastery);
@@ -596,15 +626,10 @@ public class MyPlayer : ModPlayer
596
626
return targets;
597
627
}
598
628
599
private void ApplyDeathDomainAttraction(DeathNecklace necklace)
629
private void ApplyDeathDomainForces(DeathNecklace necklace)
600
630
{
601
// A sovereign full-screen domain has no meaningful exterior on the active
602
// battlefield. Its targets are already inside, so no pull is necessary.
603
if (necklace.IsFullScreenDomain)
604
return;
605
606
float innerRadius = necklace.DomainRadius;
607
float outerRadius = necklace.DomainAttractionRadius;
631
float innerRadius = necklace.IsFullScreenDomain ? 3200f : necklace.DomainRadius;
632
float outerRadius = necklace.IsFullScreenDomain ? innerRadius : necklace.DomainAttractionRadius;
608
633
float innerRadiusSquared = innerRadius * innerRadius;
609
634
float outerRadiusSquared = outerRadius * outerRadius;
610
635
float attractionWidth = Math.Max(1f, outerRadius - innerRadius);
@@ -626,29 +651,53 @@ public class MyPlayer : ModPlayer
626
651
627
652
Vector2 toDomain = Player.Center - target.Center;
628
653
float distanceSquared = toDomain.LengthSquared();
629
// Crossing the red boundary terminates the force immediately. This is
630
// deliberately not a vortex that continues dragging enemies at centre.
631
if (distanceSquared <= innerRadiusSquared || distanceSquared > outerRadiusSquared || distanceSquared < 0.001f)
654
if (distanceSquared > outerRadiusSquared)
632
655
continue;
633
656
634
float distance = (float)Math.Sqrt(distanceSquared);
635
float normalizedDistance = MathHelper.Clamp((distance - innerRadius) / attractionWidth, 0f, 1f);
636
float edgeInfluence = 1f - normalizedDistance;
637
edgeInfluence = edgeInfluence * edgeInfluence * (3f - 2f * edgeInfluence);
638
657
bool bossOrBossPart = target.boss
639
658
|| NPCID.Sets.ShouldBeCountedAsBoss[target.type]
640
659
|| target.realLife >= 0 && target.realLife < Main.maxNPCs && Main.npc[target.realLife].boss;
641
660
float bossScale = bossOrBossPart ? 0.18f : 1f;
642
float force = necklace.DomainAttractionStrength * MathHelper.Lerp(0.22f, 1f, edgeInfluence) * bossScale;
643
float maximumInwardSpeed = necklace.DomainAttractionMaximumSpeed * (bossOrBossPart ? 0.35f : 1f);
644
Vector2 inward = toDomain / distance;
645
float currentInwardSpeed = Vector2.Dot(target.velocity, inward);
646
float allowedForce = Math.Max(0f, maximumInwardSpeed - currentInwardSpeed);
647
target.velocity += inward * Math.Min(force, allowedForce);
661
float distance = (float)Math.Sqrt(distanceSquared);
662
bool forceApplied;
663
664
if (distanceSquared <= innerRadiusSquared)
665
{
666
// Inside the event horizon the flow reverses: enemies are expelled
667
// from the pale-crimson core. The force reaches zero at the border
668
// and rises quadratically toward the centre.
669
Vector2 outward = distance > 0.001f
670
? -toDomain / distance
671
: (target.whoAmI * 2.39996323f).ToRotationVector2();
672
float centralInfluence = 1f - MathHelper.Clamp(distance / innerRadius, 0f, 1f);
673
centralInfluence *= centralInfluence;
674
float force = necklace.DomainRepulsionStrength * centralInfluence * bossScale;
675
float maximumOutwardSpeed = necklace.DomainRepulsionMaximumSpeed * (bossOrBossPart ? 0.35f : 1f);
676
float currentOutwardSpeed = Vector2.Dot(target.velocity, outward);
677
float allowedForce = Math.Max(0f, maximumOutwardSpeed - currentOutwardSpeed);
678
float appliedForce = Math.Min(force, allowedForce);
679
target.velocity += outward * appliedForce;
680
forceApplied = appliedForce > 0f;
681
}
682
else
683
{
684
// Outside the border the black-hole streams draw enemies inward.
685
float normalizedDistance = MathHelper.Clamp((distance - innerRadius) / attractionWidth, 0f, 1f);
686
float edgeInfluence = 1f - normalizedDistance;
687
edgeInfluence = edgeInfluence * edgeInfluence * (3f - 2f * edgeInfluence);
688
float force = necklace.DomainAttractionStrength * MathHelper.Lerp(0.22f, 1f, edgeInfluence) * bossScale;
689
float maximumInwardSpeed = necklace.DomainAttractionMaximumSpeed * (bossOrBossPart ? 0.35f : 1f);
690
Vector2 inward = toDomain / distance;
691
float currentInwardSpeed = Vector2.Dot(target.velocity, inward);
692
float allowedForce = Math.Max(0f, maximumInwardSpeed - currentInwardSpeed);
693
float appliedForce = Math.Min(force, allowedForce);
694
target.velocity += inward * appliedForce;
695
forceApplied = appliedForce > 0f;
696
}
648
697
649
698
// Periodic authoritative velocity updates are enough for this gentle
650
699
// force while avoiding an NPC sync packet on every tick.
651
if ((Main.GameUpdateCount + (ulong)target.whoAmI) % 10UL == 0UL)
700
if (forceApplied && (Main.GameUpdateCount + (ulong)target.whoAmI) % 10UL == 0UL)
652
701
target.netUpdate = true;
653
702
}
654
703
}
@@ -36,6 +36,8 @@ public class DeathNecklace : ModItem, IDeathAltarUpgradeable
36
36
public float DomainAttractionRadius => DomainRadius + 320f + (RadiusLevel - 1) * 32f;
37
37
public float DomainAttractionStrength => 0.035f + (RadiusLevel - 1) * 0.0075f;
38
38
public float DomainAttractionMaximumSpeed => 0.75f + (RadiusLevel - 1) * 0.09f;
39
public float DomainRepulsionStrength => 0.055f + (RadiusLevel - 1) * 0.01f;
40
public float DomainRepulsionMaximumSpeed => 1.1f + (RadiusLevel - 1) * 0.12f;
39
41
public int SpawnInterval => Math.Max(34, 100 - (FrequencyLevel - 1) * 7);
40
42
public float NormalSlashLifeRatio => DamageLevel * 0.01f;
41
43
public float BossSlashLifeRatio => DamageLevel * 0.0001f;
@@ -17,7 +17,7 @@ namespace DeathMod.Projectiles;
17
17
/// </summary>
18
18
public class DeathDomainHarvestSlashProjectile : ModProjectile
19
19
{
20
private const int SlashActiveFrames = 16;
20
private const int SlashActiveFrames = 60;
21
21
private const int SlashDelayFrames = 5;
22
22
private const int EndLingerFrames = 7;
23
23
private const int MaximumSlashCount = 5;
@@ -81,9 +81,9 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
81
81
{
82
82
int slashAge = age - slash * SlashDelayFrames;
83
83
int bit = 1 << slash;
84
// The visual blade reaches the target centre after two ticks. Apply
84
// The visible blade reaches the target centre after five ticks. Apply
85
85
// damage at that moment instead of before the slash has visibly landed.
86
if ((emittedMask & bit) == 0 && slashAge >= 2)
86
if ((emittedMask & bit) == 0 && slashAge >= 5)
87
87
{
88
88
emittedMask |= bit;
89
89
ApplyHarvestDamage();
@@ -91,8 +91,8 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
91
91
SpawnExecutionBurst(slash);
92
92
}
93
93
94
if (createClientEffects && slashAge is >= 0 and < 6)
95
SpawnTravellingEdge(slash, slashAge / 5f);
94
if (createClientEffects && slashAge is >= 0 and < 10)
95
SpawnTravellingEdge(slash, GetSlashReveal(slashAge / (float)SlashActiveFrames));
96
96
}
97
97
Projectile.localAI[0] = emittedMask;
98
98
}
@@ -134,14 +134,13 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
134
134
135
135
private void DrawExecutionBlade(float progress, int slash)
136
136
{
137
// A short anticipation is followed by a very fast ease-out traversal. The
138
// blade covers most of its distance in only a few frames, then the wound
139
// hangs briefly and contracts, which reads as a forceful cut instead of a
140
// line being scaled into existence.
141
float reveal = EaseOutCubic((progress - 0.015f) / 0.31f);
142
float disappear = 1f - Smooth01((progress - 0.52f) / 0.42f);
137
// Begin as a single point, then visibly stretch it along the warning line.
138
// SmoothStep preserves acceleration while keeping enough intermediate
139
// lengths on screen for the eye to read the complete cutting motion.
140
float reveal = GetSlashReveal(progress);
141
float disappear = 1f - Smooth01((progress - 0.15f) / 0.81f);
143
142
float strength = Smooth01((progress - 0.008f) / 0.055f) * disappear;
144
float impactPulse = 1f + (float)Math.Exp(-Math.Pow((progress - 0.27f) / 0.105f, 2f)) * 0.24f;
143
float impactPulse = 1f + (float)Math.Exp(-Math.Pow((progress - 0.083f) / 0.034f, 2f)) * 0.24f;
145
144
float rotation = GetSlashRotation(Projectile.rotation, slash);
146
145
float length = GetSlashLength(Mastery, Projectile.scale, slash);
147
146
float width = (21f + Mastery * 13f + (IsRequiem ? 8f : 0f)) * Projectile.scale;
@@ -151,8 +150,8 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
151
150
// over it below, allowing the crimson line to extend beyond both tips while
152
151
// the pure-black core remains completely unbroken.
153
152
Vector2 direction = rotation.ToRotationVector2();
154
float bloodReveal = EaseOutCubic((progress - 0.005f) / 0.27f);
155
float bloodFade = 1f - Smooth01((progress - 0.64f) / 0.28f);
153
float bloodReveal = Smooth01((progress - 0.005f) / 0.12f);
154
float bloodFade = 1f - Smooth01((progress - 0.16f) / 0.80f);
156
155
float bloodStrength = bloodReveal * bloodFade;
157
156
float bloodLength = length * (3f + Mastery * 0.47f + (IsRequiem ? 0.3f : 0f));
158
157
Vector2 bloodStart = center - direction * bloodLength * 0.5f;
@@ -260,11 +259,11 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
260
259
Projectile.Center,
261
260
normal,
262
261
2.2f + Mastery * 1.8f + (IsRequiem ? 1.2f : 0f));
262
Main.LocalPlayer.GetModPlayer<MyPlayer>().PlayDeathDomainSlashSound(Projectile.Center, Mastery, IsRequiem);
263
263
}
264
264
265
private void SpawnTravellingEdge(int slash, float progress)
265
private void SpawnTravellingEdge(int slash, float reveal)
266
266
{
267
float reveal = EaseOutCubic(progress);
268
267
float rotation = GetSlashRotation(Projectile.rotation, slash);
269
268
Vector2 direction = rotation.ToRotationVector2();
270
269
Vector2 normal = direction.RotatedBy(MathHelper.PiOver2);
@@ -306,10 +305,5 @@ public class DeathDomainHarvestSlashProjectile : ModProjectile
306
305
return value * value * (3f - 2f * value);
307
306
}
308
307
309
private static float EaseOutCubic(float value)
310
{
311
value = MathHelper.Clamp(value, 0f, 1f);
312
float inverse = 1f - value;
313
return 1f - inverse * inverse * inverse;
314
}
308
private static float GetSlashReveal(float progress) => Smooth01((progress - 0.008f) / 0.125f);
315
309
}