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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

重构抗锯齿线条渲染,优化特效表现

重构视觉系统抗锯齿线条渲染,新增 SmoothStripMask 和 SmoothDiscMask 方法,替换 DrawSmoothLine 为 DrawSmoothStrip,提升带状抗锯齿连续性。优化黑洞、血痕、虚空裂隙等特效细节,调整颜色参数和分段数量,减少伪影。细化全屏闪光逻辑,避免多次触发时的过度闪烁,整体提升渲染质量与性能。

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

代码差异

7 个文件 +244 -99
Modified Common/DeathDomainPrimitiveTextureSystem.cs +113 -1
@@ -17,6 +17,8 @@ internal sealed class DeathDomainPrimitiveTextureSystem : ModSystem
17 17 internal static Texture2D? BladeMask { get; private set; }
18 18 internal static Texture2D? RadialGradientMask { get; private set; }
19 19 internal static Texture2D? SmoothLineMask { get; private set; }
20 internal static Texture2D? SmoothStripMask { get; private set; }
21 internal static Texture2D? SmoothDiscMask { get; private set; }
20 22
21 23 private static Texture2D CreateBladeMask()
22 24 {
@@ -70,6 +72,28 @@ internal sealed class DeathDomainPrimitiveTextureSystem : ModSystem
70 72 return texture;
71 73 }
72 74
75 private static Texture2D CreateSmoothDiscMask()
76 {
77 const int size = 512;
78 Texture2D texture = new(Main.instance.GraphicsDevice, size, size, false, SurfaceFormat.Color);
79 Color[] pixels = new Color[size * size];
80 for (int y = 0; y < size; y++)
81 {
82 float normalizedY = (y + 0.5f) / size * 2f - 1f;
83 for (int x = 0; x < size; x++)
84 {
85 float normalizedX = (x + 0.5f) / size * 2f - 1f;
86 float distance = (float)Math.Sqrt(normalizedX * normalizedX + normalizedY * normalizedY);
87 float alpha = 1f - SmoothStep(0.982f, 1.006f, distance);
88 byte value = (byte)Math.Clamp((int)Math.Round(alpha * 255f), 0, 255);
89 pixels[y * size + x] = new Color(value, value, value, value);
90 }
91 }
92
93 texture.SetData(pixels);
94 return texture;
95 }
96
73 97 private static Texture2D CreateSmoothLineMask()
74 98 {
75 99 // The very wide aspect ratio keeps the antialiased end caps compact even
@@ -105,22 +129,54 @@ internal sealed class DeathDomainPrimitiveTextureSystem : ModSystem
105 129 return texture;
106 130 }
107 131
132 private static Texture2D CreateSmoothStripMask()
133 {
134 // Connected curves must not use the rounded end caps from SmoothLineMask:
135 // dozens of overlapping caps make every sampling point visibly brighter.
136 // This mask is fully continuous along X and only antialiases its two long
137 // edges, allowing short rotated spans to read as one uninterrupted ribbon.
138 const int width = 256;
139 const int height = 96;
140 Texture2D texture = new(Main.instance.GraphicsDevice, width, height, false, SurfaceFormat.Color);
141 Color[] pixels = new Color[width * height];
142 float halfHeight = height * 0.5f - 1f;
143 for (int y = 0; y < height; y++)
144 {
145 float distance = Math.Abs(y + 0.5f - height * 0.5f);
146 float alpha = 1f - SmoothStep(halfHeight - 2.25f, halfHeight + 0.75f, distance);
147 byte value = (byte)Math.Clamp((int)Math.Round(alpha * 255f), 0, 255);
148 Color pixel = new(value, value, value, value);
149 for (int x = 0; x < width; x++)
150 pixels[y * width + x] = pixel;
151 }
152
153 texture.SetData(pixels);
154 return texture;
155 }
156
108 157 public override void Unload()
109 158 {
110 159 Texture2D? bladeTexture = BladeMask;
111 160 Texture2D? gradientTexture = RadialGradientMask;
112 161 Texture2D? smoothLineTexture = SmoothLineMask;
162 Texture2D? smoothStripTexture = SmoothStripMask;
163 Texture2D? smoothDiscTexture = SmoothDiscMask;
113 164 BladeMask = null;
114 165 RadialGradientMask = null;
115 166 SmoothLineMask = null;
167 SmoothStripMask = null;
168 SmoothDiscMask = null;
116 169 if (!Main.dedServ && (bladeTexture is not null || gradientTexture is not null
117 || smoothLineTexture is not null))
170 || smoothLineTexture is not null || smoothStripTexture is not null
171 || smoothDiscTexture is not null))
118 172 {
119 173 Main.QueueMainThreadAction(() =>
120 174 {
121 175 bladeTexture?.Dispose();
122 176 gradientTexture?.Dispose();
123 177 smoothLineTexture?.Dispose();
178 smoothStripTexture?.Dispose();
179 smoothDiscTexture?.Dispose();
124 180 });
125 181 }
126 182 }
@@ -192,6 +248,16 @@ internal sealed class DeathDomainPrimitiveTextureSystem : ModSystem
192 248 0f);
193 249 }
194 250
251 internal static void DrawSmoothDisc(SpriteBatch batch, Vector2 center, float radius, Color color)
252 {
253 if (Main.dedServ || radius <= 0.5f)
254 return;
255
256 Texture2D texture = SmoothDiscMask ??= CreateSmoothDiscMask();
257 batch.Draw(texture, center, null, color, 0f, texture.Size() * 0.5f,
258 radius * 2f / texture.Width, SpriteEffects.None, 0f);
259 }
260
195 261 internal static void DrawSmoothLine(
196 262 SpriteBatch batch,
197 263 Vector2 start,
@@ -230,6 +296,52 @@ internal sealed class DeathDomainPrimitiveTextureSystem : ModSystem
230 296 end - Main.screenPosition, color, width);
231 297 }
232 298
299 internal static void DrawSmoothStrip(
300 SpriteBatch batch,
301 Vector2 start,
302 Vector2 end,
303 Color color,
304 float width)
305 {
306 if (Main.dedServ || width <= 0.05f)
307 return;
308
309 Vector2 delta = end - start;
310 float length = delta.Length();
311 if (length <= 0.1f)
312 return;
313
314 Texture2D texture = SmoothStripMask ??= CreateSmoothStripMask();
315 Vector2 direction = delta / length;
316 // A sub-pixel overlap closes rotated joins without reintroducing the
317 // conspicuous circular stamps produced by the capped line mask.
318 float overlap = Math.Min(0.7f, width * 0.08f);
319 start -= direction * overlap;
320 end += direction * overlap;
321 delta = end - start;
322 length = delta.Length();
323 batch.Draw(
324 texture,
325 (start + end) * 0.5f,
326 null,
327 color,
328 delta.ToRotation(),
329 texture.Size() * 0.5f,
330 new Vector2(length / texture.Width, width / texture.Height),
331 SpriteEffects.None,
332 0f);
333 }
334
335 internal static void DrawSmoothWorldStrip(
336 Vector2 start,
337 Vector2 end,
338 Color color,
339 float width)
340 {
341 DrawSmoothStrip(Main.spriteBatch, start - Main.screenPosition,
342 end - Main.screenPosition, color, width);
343 }
344
233 345 private static float SmoothStep(float start, float end, float value)
234 346 {
235 347 if (end <= start)
Modified Common/DeathDomainVisualSystem.cs +51 -60
@@ -94,7 +94,7 @@ internal class DeathDomainVisualSystem : ModSystem
94 94 batch.Begin(
95 95 SpriteSortMode.Deferred,
96 96 BlendState.AlphaBlend,
97 SamplerState.PointClamp,
97 SamplerState.LinearClamp,
98 98 DepthStencilState.None,
99 99 RasterizerState.CullNone,
100 100 null,
@@ -193,13 +193,14 @@ internal class DeathDomainVisualSystem : ModSystem
193 193 mastery,
194 194 reveal);
195 195 }
196 DrawFilledNoiseDisc(
196 // The old scan-band fill stamped hundreds of horizontal rounded strips
197 // over the centre and produced the visible television-line pattern. A
198 // single antialiased disc supplies the same restrained shadow without any
199 // row seams; the animated noisy boundary still defines its organic edge.
200 DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(
197 201 batch,
198 202 center,
199 203 coreRadius,
200 coreNoiseTime,
201 coreNoiseSeed,
202 coreNoiseAmplitude,
203 204 new Color(3, 0, 10) * (MathHelper.Lerp(0.08f, 0.26f, mastery) * reveal));
204 205 DeathDomainPrimitiveTextureSystem.DrawRadialGradient(
205 206 batch,
@@ -649,27 +650,8 @@ internal class DeathDomainVisualSystem : ModSystem
649 650 if (radius <= 1f || opacity <= 0.001f)
650 651 return;
651 652
652 float verticalScale = MathHelper.Lerp(1f, 0.16f, collapse);
653 DeathDomainPrimitiveTextureSystem.DrawRadialGradient(
654 batch, center, radius * 1.62f, new Color(0, 0, 3) * (opacity * 0.86f));
655 DrawFilledEllipse(batch, center, radius * 0.88f,
656 Math.Max(1f, radius * 0.38f * verticalScale), Color.Black * opacity);
657 DrawBlackHoleSuctionStreams(batch, center, radius, verticalScale,
658 rotation, opacity, seed, large: false);
659 DrawEllipseArc(batch, center, radius * 1.34f, radius * 0.70f * verticalScale,
660 rotation, -2.65f, 2.2f, 30, new Color(176, 28, 255) * (opacity * 0.72f), 2.1f);
661 DrawEllipseArc(batch, center, radius * 1.62f, radius * 0.46f * verticalScale,
662 -rotation * 0.7f, 0.18f, 4.45f, 34, new Color(255, 28, 78) * (opacity * 0.82f), 1.35f);
663 for (int particle = 0; particle < 3; particle++)
664 {
665 float angle = rotation * (particle % 2 == 0 ? 1f : -0.8f)
666 + particle * MathHelper.TwoPi / 3f + Hash01(seed + particle * 17) * 0.7f;
667 Vector2 position = center + new Vector2(
668 (float)Math.Cos(angle) * radius * 1.72f,
669 (float)Math.Sin(angle) * radius * 0.58f * verticalScale);
670 DrawCircle(batch, position, Math.Max(0.8f, radius * 0.055f), 8,
671 new Color(255, 104, 145) * opacity, 1.2f, angle);
672 }
653 DrawBackdropDomainBlackHole(batch, center, radius, rotation, opacity,
654 collapse, 0.34f, seed, large: false);
673 655 }
674 656
675 657 private static void DrawLargeBackdropBlackHole(
@@ -685,34 +667,43 @@ internal class DeathDomainVisualSystem : ModSystem
685 667 if (radius <= 2f || opacity <= 0.001f)
686 668 return;
687 669
688 float verticalScale = MathHelper.Lerp(1f, 0.10f, collapse);
689 float coreX = radius * 1.18f;
690 float coreY = radius * 0.52f * verticalScale;
691 DeathDomainPrimitiveTextureSystem.DrawRadialGradient(
692 batch, center, radius * 2.25f, new Color(8, 0, 19) * (opacity * 0.68f));
693 DrawFilledEllipse(batch, center, coreX, Math.Max(1.2f, coreY),
670 DrawBackdropDomainBlackHole(batch, center, radius, rotation, opacity,
671 collapse, mastery, seed, large: true);
672 }
673
674 private static void DrawBackdropDomainBlackHole(
675 SpriteBatch batch,
676 Vector2 center,
677 float radius,
678 float rotation,
679 float opacity,
680 float collapse,
681 float mastery,
682 int seed,
683 bool large)
684 {
685 float time = Main.GlobalTimeWrappedHourly;
686 float coreRadius = radius * (large ? 0.68f : 0.62f);
687 float pulse = opacity * (0.88f + (float)Math.Sin(time * 2.7f + seed) * 0.08f);
688 DeathDomainPrimitiveTextureSystem.DrawRadialGradient(batch, center,
689 radius * (large ? 1.95f : 1.65f), new Color(42, 0, 16) * (opacity * 0.42f));
690 DrawBlackHoleSuctionStreams(batch, center, radius, 1f, rotation,
691 opacity * (1f - collapse * 0.38f), seed, large);
692
693 // Five inward-curving arms deliberately mirror the player's event
694 // horizon instead of the old unrelated crossed-orbit atom symbol.
695 for (int arm = 0; arm < 5; arm++)
696 DrawAccretionSpiral(batch, center, coreRadius * 0.96f, radius * 1.06f,
697 time + rotation * 0.72f, arm, mastery, pulse);
698
699 DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(batch, center, coreRadius,
694 700 new Color(0, 0, 2) * (opacity * 0.98f));
695 DrawBlackHoleSuctionStreams(batch, center, radius, verticalScale,
696 rotation, opacity, seed, large: true);
697
698 Color outer = Color.Lerp(new Color(116, 18, 215), new Color(225, 22, 82), mastery);
699 Color hot = Color.Lerp(new Color(238, 60, 166), new Color(255, 116, 82), mastery);
700 DrawEllipseArc(batch, center, radius * 2.12f, radius * 0.67f * verticalScale,
701 rotation, -2.92f, 2.48f, 54, outer * (opacity * 0.62f), 3.2f);
702 DrawEllipseArc(batch, center, radius * 1.72f, radius * 0.46f * verticalScale,
703 rotation + 0.08f, 0.05f, 3.12f, 44, hot * (opacity * 0.94f), 2.4f);
704 DrawEllipseArc(batch, center, radius * 2.52f, radius * 0.92f * verticalScale,
705 -rotation * 0.35f, -0.72f, 1.48f, 34, new Color(122, 86, 255) * (opacity * 0.42f), 1.3f);
706
707 // Offset lensing crescents make the large variant read as warped space,
708 // not as a scaled copy of the player's five-arm circular domain.
709 Vector2 lensOffset = rotation.ToRotationVector2() * radius * 0.19f;
710 DrawEllipseArc(batch, center + lensOffset, radius * 1.36f,
711 radius * 0.24f * verticalScale, rotation, -2.75f, -0.25f, 28,
712 new Color(255, 172, 196) * (opacity * 0.58f), 1.15f);
713 DrawEllipseArc(batch, center - lensOffset, radius * 1.36f,
714 radius * 0.24f * verticalScale, rotation, 0.36f, 2.75f, 28,
715 outer * (opacity * 0.46f), 1.05f);
701 DrawNoiseBoundary(batch, center, coreRadius + radius * 0.07f,
702 time * 0.76f + rotation, seed * 0.013f, radius * 0.07f,
703 large ? 3.2f : 2.1f, opacity * 0.34f, mastery, gaps: true);
704 DrawNoiseBoundary(batch, center, coreRadius,
705 -time * 0.94f + rotation, seed * 0.021f, radius * 0.045f,
706 large ? 2.4f : 1.55f, opacity * 0.92f, mastery, gaps: false);
716 707 }
717 708
718 709 private static void DrawFilledEllipse(
@@ -835,7 +826,7 @@ internal class DeathDomainVisualSystem : ModSystem
835 826 float ripple = (float)Math.Sin(angle * 9f - time * 2.1f + wave * 1.7f) * 2.6f
836 827 + (float)Math.Sin(angle * 17f + time * 1.3f) * 0.9f;
837 828 Vector2 current = center + angle.ToRotationVector2() * (waveRadius + ripple);
838 DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch, previous, current,
829 DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, previous, current,
839 830 Color.Lerp(new Color(122, 4, 34), new Color(255, 54, 92), mastery)
840 831 * opacity,
841 832 MathHelper.Lerp(1.1f, 2.4f, 1f - travel));
@@ -868,7 +859,7 @@ internal class DeathDomainVisualSystem : ModSystem
868 859 localRadius += (float)Math.Sin(localAngle * 5f + seed * 0.07f)
869 860 * radius * 0.025f * (1f - inward);
870 861 Vector2 local = new((float)Math.Cos(localAngle) * localRadius,
871 (float)Math.Sin(localAngle) * localRadius * verticalScale * 0.52f);
862 (float)Math.Sin(localAngle) * localRadius * verticalScale);
872 863 Vector2 current = center + local.RotatedBy(rotation);
873 864 if (point > 0)
874 865 {
@@ -879,12 +870,12 @@ internal class DeathDomainVisualSystem : ModSystem
879 870 // the far half remains visible behind the black core, giving
880 871 // the accretion flow genuine front/back depth.
881 872 float nearSide = 0.55f + 0.45f * Math.Max(0f, (float)Math.Sin(localAngle));
882 Color color = Color.Lerp(new Color(80, 8, 142),
883 new Color(255, 30, 76), inward * 0.82f + glow * 0.18f);
873 Color color = Color.Lerp(new Color(58, 0, 12),
874 new Color(255, 42, 78), inward * 0.82f + glow * 0.18f);
884 875 float streamOpacity = opacity * body * nearSide * (0.18f + glow * 0.62f);
885 876 float width = MathHelper.Lerp(0.65f, large ? 3.6f : 2.2f, inward)
886 877 * (0.72f + glow * 0.38f);
887 DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch, previous,
878 DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, previous,
888 879 current, color * streamOpacity, width);
889 880 }
890 881 previous = current;
@@ -1128,14 +1119,14 @@ internal class DeathDomainVisualSystem : ModSystem
1128 1119 {
1129 1120 float angle = rotation + MathHelper.TwoPi * index / segments;
1130 1121 Vector2 current = center + angle.ToRotationVector2() * radius;
1131 DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch, previous, current, color, width);
1122 DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, previous, current, color, width);
1132 1123 previous = current;
1133 1124 }
1134 1125 }
1135 1126
1136 1127 private static void DrawSegment(SpriteBatch batch, Vector2 start, Vector2 segment, Color color, float width)
1137 1128 {
1138 DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch,
1129 DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch,
1139 1130 start, start + segment, color, width);
1140 1131 }
1141 1132
Modified Common/ReaperSkillVisualSystem.cs +5 -5
@@ -122,12 +122,12 @@ internal sealed class ReaperSkillVisualSystem : ModSystem
122 122 if (IsNearScreen(center, 620f))
123 123 PlaySkillSound(form, skill, level, variant, center);
124 124
125 // Unique skills keep their restrained camera punctuation, but never tint
126 // the entire viewport. A three-frame pale impact used to present as a
127 // one-frame white screen when several automatic triggers were merged.
125 128 if (level >= 3 && IsNearScreen(center))
126 {
127 Color impact = ReaperCombatRegistry.GetSecondaryColor(form);
128 ReaperVfxDirector.TriggerGlobalImpact(direction, 2.8f, 6, impact,
129 variant > 0 ? 0.14f : 0.09f, 3, 0.06f);
130 }
129 ReaperVfxDirector.TriggerGlobalImpact(direction, 2.8f, 6,
130 Color.Transparent, 0f, 0, 0.06f);
131 131 }
132 132
133 133 public override void OnWorldUnload() => Visuals.Clear();
Modified Common/ReaperVfxDirector.cs +17 -11
@@ -163,18 +163,24 @@ public sealed class ReaperVfxDirector : ModSystem
163 163 shakeDirection = (shakeDirection + direction * 0.45f).SafeNormalize(shakeDirection);
164 164 }
165 165
166 if (opacity >= flashOpacity || flashFrames <= 1)
166 // A camera-only impact must not create, extend or recolor an unrelated
167 // full-screen flash that is already active (for example an overlapping
168 // ultimate). Unique-skill cues intentionally pass zero here.
169 if (opacity > 0.001f && colorFrames > 0)
167 170 {
168 flashColor = color;
169 flashOpacity = opacity;
170 flashDuration = Math.Max(1, colorFrames);
171 flashFrames = colorFrames;
172 }
173 else
174 {
175 flashFrames = Math.Max(flashFrames, colorFrames);
176 flashDuration = Math.Max(flashDuration, flashFrames);
177 flashColor = Color.Lerp(flashColor, color, 0.25f);
171 if (opacity >= flashOpacity || flashFrames <= 1)
172 {
173 flashColor = color;
174 flashOpacity = opacity;
175 flashDuration = Math.Max(1, colorFrames);
176 flashFrames = colorFrames;
177 }
178 else
179 {
180 flashFrames = Math.Max(flashFrames, colorFrames);
181 flashDuration = Math.Max(flashDuration, flashFrames);
182 flashColor = Color.Lerp(flashColor, color, 0.25f);
183 }
178 184 }
179 185
180 186 edgeDarkness = Math.Max(edgeDarkness, MathHelper.Clamp(vignette, 0f, 0.62f));
Modified Common/ReaperVoidBackdropRenderer.cs +45 -16
@@ -33,14 +33,14 @@ internal static class ReaperVoidBackdropRenderer
33 33 {
34 34 Vector2 screenStart = start - Main.screenPosition;
35 35 Vector2 screenEnd = end - Main.screenPosition;
36 DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch, screenStart, screenEnd,
37 new Color(92, 12, 155, 0) * (opacity * 0.34f), width + 5f);
38 DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch, screenStart, screenEnd,
39 new Color(132, 32, 210, 205) * (opacity * 0.82f), width + 2f);
40 DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch, screenStart, screenEnd,
36 DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, screenStart, screenEnd,
37 new Color(58, 4, 92, 0) * (opacity * 0.26f), width + 4f);
38 DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, screenStart, screenEnd,
39 new Color(112, 26, 181, 220) * (opacity * 0.68f), width + 1.7f);
40 DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, screenStart, screenEnd,
41 41 new Color(2, 0, 6, 252) * opacity, width);
42 DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch, screenStart, screenEnd,
43 new Color(10, 2, 18, 205) * (opacity * 0.68f), Math.Max(1f, width * 0.48f));
42 DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, screenStart, screenEnd,
43 new Color(9, 2, 16, 220) * (opacity * 0.72f), Math.Max(1f, width * 0.46f));
44 44 }
45 45
46 46 internal static void DrawHitRift(SpriteBatch batch, Vector2 center, Vector2 axis,
@@ -120,17 +120,18 @@ internal static class ReaperVoidBackdropRenderer
120 120 if (u > visibleEnd)
121 121 continue;
122 122
123 Vector2 root = GetRiftCenter(center, axis, normal, u, width, seed)
124 + axis * (u * length * 0.5f);
125 123 float side = ((branch + seed) & 1) == 0 ? 1f : -1f;
124 float halfWidth = GetRiftHalfWidth(u, width, seed);
125 Vector2 root = GetRiftCenter(center, axis, normal, u, width, seed)
126 + axis * (u * length * 0.5f)
127 + normal * side * halfWidth * 0.82f;
126 128 float branchLength = MathHelper.Clamp(width * (1.45f + Hash01(seed + branch * 31) * 1.3f),
127 129 8f, 72f);
128 130 Vector2 direction = (normal * side + axis * (Hash01(seed + branch * 43) - 0.5f) * 0.85f)
129 131 .SafeNormalize(normal * side);
130 Vector2 middle = root + direction.RotatedBy(-side * 0.18f) * branchLength * 0.58f;
131 Vector2 tip = middle + direction.RotatedBy(side * 0.34f) * branchLength * 0.42f;
132 DrawCoreStrip(batch, root, middle, Math.Max(0.8f, width * 0.19f), opacity * 0.72f);
133 DrawCoreStrip(batch, middle, tip, Math.Max(0.55f, width * 0.10f), opacity * 0.48f);
132 DrawBranchPath(batch, root, direction, branchLength,
133 Math.Max(0.9f, width * 0.17f), opacity * 0.68f,
134 seed + branch * 173);
134 135 }
135 136 }
136 137
@@ -141,13 +142,41 @@ internal static class ReaperVoidBackdropRenderer
141 142 if (delta.LengthSquared() < 9f)
142 143 return;
143 144 Vector2 axis = delta.SafeNormalize(Vector2.UnitX);
145 Vector2 normal = axis.RotatedBy(MathHelper.PiOver2);
144 146 float side = ((seed + index) & 1) == 0 ? 1f : -1f;
145 Vector2 root = Vector2.Lerp(start, end, 0.58f);
147 Vector2 root = Vector2.Lerp(start, end, 0.58f)
148 + normal * side * width * 0.47f;
146 149 Vector2 direction = axis.RotatedBy(side * MathHelper.Lerp(0.55f, 0.92f,
147 150 Hash01(seed + index * 79)));
148 151 float length = MathHelper.Clamp(width * 1.7f, 7f, 29f);
149 Vector2 tip = root + direction * length;
150 DrawCoreStrip(batch, root, tip, Math.Max(0.55f, width * 0.14f), opacity * 0.52f);
152 DrawBranchPath(batch, root, direction, length,
153 Math.Max(0.65f, width * 0.13f), opacity * 0.50f,
154 seed + index * 211);
155 }
156
157 private static void DrawBranchPath(SpriteBatch batch, Vector2 root,
158 Vector2 direction, float length, float width, float opacity, int seed)
159 {
160 direction = direction.SafeNormalize(Vector2.UnitY);
161 Vector2 normal = direction.RotatedBy(MathHelper.PiOver2);
162 int samples = Math.Clamp((int)Math.Ceiling(length / 5f), 5, 15);
163 float wavePhase = Hash01(seed + 19) * MathHelper.TwoPi;
164 Vector2 previous = root;
165 for (int sample = 1; sample <= samples; sample++)
166 {
167 float progress = sample / (float)samples;
168 float envelope = (float)Math.Sin(progress * MathHelper.Pi);
169 float wave = ((float)Math.Sin(progress * 5.4f + wavePhase)
170 + (float)Math.Sin(progress * 11.1f - wavePhase * 0.7f) * 0.26f)
171 * length * 0.055f * envelope;
172 Vector2 current = root + direction * (length * progress) + normal * wave;
173 float localWidth = Math.Max(0.45f,
174 width * (float)Math.Pow(1f - progress * 0.86f, 0.72f));
175 DrawCoreStrip(batch, previous, current, localWidth, opacity * (1f - progress * 0.32f));
176 DrawStarsInStrip(batch, previous, current, localWidth,
177 opacity * (1f - progress * 0.42f));
178 previous = current;
179 }
151 180 }
152 181
153 182 private static float Hash01(int seed)
Modified Projectiles/ReaperBloodScarProjectile.cs +8 -5
@@ -102,7 +102,10 @@ public sealed class ReaperBloodScarProjectile : ModProjectile
102 102 float fade = Smooth01(Projectile.timeLeft / 34f);
103 103 Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX);
104 104 Vector2 normal = axis.RotatedBy(MathHelper.PiOver2);
105 const int segments = 34;
105 // Long ultimate scars used to expose their polygonal sampling (up to
106 // seventy pixels per span). Keep each span short enough that the same
107 // continuous wave noise reads as a smooth torn edge at every length.
108 int segments = Math.Clamp((int)Math.Ceiling(scarLength / 16f), 48, 160);
106 109 float visibleEnd = MathHelper.Lerp(-1f, 1f, reveal);
107 110 Vector2 previous = GetPoint(-1f);
108 111 for (int segment = 1; segment <= segments; segment++)
@@ -187,13 +190,13 @@ public sealed class ReaperBloodScarProjectile : ModProjectile
187 190 private static void DrawScarSegment(Vector2 start, Vector2 end, float width,
188 191 float opacity)
189 192 {
190 DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
193 DeathDomainPrimitiveTextureSystem.DrawSmoothWorldStrip(start, end,
191 194 new Color(112, 0, 20, 0) * (opacity * 0.40f), width + 8f);
192 DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
195 DeathDomainPrimitiveTextureSystem.DrawSmoothWorldStrip(start, end,
193 196 new Color(246, 22, 56, 235) * (opacity * 0.92f), width + 3f);
194 DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
197 DeathDomainPrimitiveTextureSystem.DrawSmoothWorldStrip(start, end,
195 198 new Color(8, 0, 3, 252) * opacity, width);
196 DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
199 DeathDomainPrimitiveTextureSystem.DrawSmoothWorldStrip(start, end,
197 200 new Color(27, 0, 8, 235) * opacity, Math.Max(1f, width * 0.54f));
198 201 }
199 202
Modified Projectiles/SickleSwingProjectile.cs +5 -1
@@ -284,7 +284,11 @@ public sealed class SickleSwingProjectile : ModProjectile
284 284 return false;
285 285 DrawFormBackdrop();
286 286 DrawTrail();
287 if (HasStageVisual(ReaperStage.StageII))
287 // Blood's chevron rune read as an aiming arrow, while Void's parallel
288 // rune bars visibly crossed the torn background. Their trail surfaces are
289 // already the form language, so do not stamp unrelated glyphs over them.
290 if (HasStageVisual(ReaperStage.StageII)
291 && VisualForm is not ReaperFormId.Blood and not ReaperFormId.Void)
288 292 DrawStageRunes();
289 293 Texture2D texture = ModContent.Request<Texture2D>(
290 294 ReaperCombatRegistry.GetTexturePath(snapshot.Form, snapshot.Stage)).Value;