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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

完善领域内外力场与斩击残留

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

代码差异

4 个文件 +168 -62
Modified Common/DeathDomainVisualSystem.cs +78 -17
@@ -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 }
Modified Common/MyPlayer.cs +72 -23
@@ -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 }
Modified Items/DeathNecklace.cs +2 -0
@@ -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;
Modified Projectiles/DeathDomainHarvestSlashProjectile.cs +16 -22
@@ -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 }