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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

优化死神领域裂缝批处理性能

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

代码差异

2 个文件 +231 -156
Modified Common/DeathDomainTrailVisualSystem.cs +195 -142
@@ -19,10 +19,10 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
19 19 private static readonly Dictionary<long, TrailDrawState> drawStates = [];
20 20 private static readonly VertexPositionColorTexture[] vertices =
21 21 new VertexPositionColorTexture[MaximumPoints * 2];
22 private static readonly VertexPositionColorTexture[] outlineVertices =
23 new VertexPositionColorTexture[(MaximumPoints - 1) * 2 * 6];
24 private static readonly bool[] outlineVisible =
25 new bool[(MaximumPoints - 1) * 2];
22 private static VertexPositionColorTexture[] mergedOutlineVertices =
23 new VertexPositionColorTexture[4096];
24 private static VertexPositionColorTexture[] mergedBackdropVertices =
25 new VertexPositionColorTexture[4096];
26 26 private static readonly short[] indices = CreateIndices();
27 27 private static BasicEffect? effect;
28 28
@@ -44,8 +44,23 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
44 44 return;
45 45 }
46 46 long key = ((long)owner << 32) | (uint)identity;
47 if (drawStates.TryGetValue(key, out TrailDrawState existing)
48 && existing.MergeOverlappingRims == mergeOverlappingRims
49 && ReferenceEquals(existing.Points, points))
50 {
51 // Persistent rifts submit the same immutable path every update. Only
52 // opacity and freshness change, so keep their cached world bounds.
53 drawStates[key] = existing with
54 {
55 Opacity = opacity,
56 UpdateTick = Main.GameUpdateCount
57 };
58 return;
59 }
60
47 61 drawStates[key] = new TrailDrawState(points, width, opacity,
48 mergeOverlappingRims, CalculateBounds(points, width),
62 mergeOverlappingRims,
63 CalculateTrailBounds(points, width * 1.72f),
49 64 Main.GameUpdateCount);
50 65 }
51 66
@@ -67,17 +82,22 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
67 82 drawStates.Remove(key);
68 83 }
69 84
70 public override void OnWorldUnload() => drawStates.Clear();
85 public override void OnWorldUnload() => ClearVisualState();
71 86
72 87 public override void Unload()
73 88 {
74 drawStates.Clear();
89 ClearVisualState();
75 90 BasicEffect? oldEffect = effect;
76 91 effect = null;
77 92 if (oldEffect is not null && !Main.dedServ)
78 93 Main.QueueMainThreadAction(oldEffect.Dispose);
79 94 }
80 95
96 private static void ClearVisualState()
97 {
98 drawStates.Clear();
99 }
100
81 101 public override void PostDrawTiles()
82 102 {
83 103 if (Main.gameMenu || drawStates.Count == 0
@@ -101,38 +121,26 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
101 121 graphicsDevice.DepthStencilState = DepthStencilState.None;
102 122 graphicsDevice.RasterizerState = RasterizerState.CullNone;
103 123 graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
124 DrawMergedTrails(graphicsDevice);
104 125
105 foreach ((long visualId, TrailDrawState state) in drawStates)
126 foreach (TrailDrawState state in drawStates.Values)
106 127 {
107 128 if (Main.GameUpdateCount > state.UpdateTick + 1)
108 129 continue;
130 if (state.MergeOverlappingRims)
131 continue;
132 if (!IsOnScreen(state.Bounds))
133 continue;
109 134 int pointCount = Math.Min(MaximumPoints, state.Points.Count);
110 135 if (pointCount < 2)
111 136 continue;
112 if (state.MergeOverlappingRims)
113 {
114 BuildMergedOutlineVisibility(visualId, state, pointCount);
115 DrawOutlineBand(graphicsDevice, state, pointCount,
116 state.Width * 0.70f, state.Width * 0.86f,
117 new Color(174, 0, 44, 0) * (state.Opacity * 0.24f));
118 DrawOutlineBand(graphicsDevice, state, pointCount,
119 state.Width * 0.585f, state.Width * 0.70f,
120 new Color(244, 12, 66, 235) * state.Opacity);
121 DrawOutlineBand(graphicsDevice, state, pointCount,
122 state.Width * 0.50f, state.Width * 0.585f,
123 new Color(255, 187, 178, 235)
124 * (state.Opacity * 0.88f));
125 }
126 else
127 {
128 DrawSolidRibbon(graphicsDevice, state, state.Width * 1.72f,
129 new Color(174, 0, 44, 0) * (state.Opacity * 0.24f));
130 DrawSolidRibbon(graphicsDevice, state, state.Width * 1.40f,
131 new Color(244, 12, 66, 235) * state.Opacity);
132 DrawSolidRibbon(graphicsDevice, state, state.Width * 1.17f,
133 new Color(255, 187, 178, 235)
134 * (state.Opacity * 0.88f));
135 }
137 DrawSolidRibbon(graphicsDevice, state, state.Width * 1.72f,
138 new Color(174, 0, 44, 0) * (state.Opacity * 0.24f));
139 DrawSolidRibbon(graphicsDevice, state, state.Width * 1.40f,
140 new Color(244, 12, 66, 235) * state.Opacity);
141 DrawSolidRibbon(graphicsDevice, state, state.Width * 1.17f,
142 new Color(255, 187, 178, 235)
143 * (state.Opacity * 0.88f));
136 144 BuildVertices(state, state.Width, Color.White * state.Opacity);
137 145 effect.TextureEnabled = true;
138 146 for (int layer = 0; layer < 3; layer++)
@@ -149,71 +157,96 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
149 157 }
150 158 }
151 159
152 private static void BuildMergedOutlineVisibility(long visualId,
153 TrailDrawState state, int pointCount)
160 private static void DrawMergedTrails(GraphicsDevice graphicsDevice)
154 161 {
155 Array.Clear(outlineVisible, 0, outlineVisible.Length);
156 for (int segment = 0; segment < pointCount - 1; segment++)
162 if (effect is null)
163 return;
164
165 // Render every rim first and the opaque, world-aligned domain ribbons
166 // afterwards. The common backdrop naturally covers rims inside an
167 // overlap, producing one continuous surface without any pairwise trail
168 // intersection tests. Work therefore stays linear as rifts accumulate.
169 DrawMergedOutlineBatch(graphicsDevice, 0.70f, 0.86f,
170 new Color(174, 0, 44, 0), 0.24f);
171 DrawMergedOutlineBatch(graphicsDevice, 0.585f, 0.70f,
172 new Color(244, 12, 66, 235), 1f);
173 DrawMergedOutlineBatch(graphicsDevice, 0.50f, 0.585f,
174 new Color(255, 187, 178, 235), 0.88f);
175
176 int backdropVertexCount = BuildMergedBackdropBatch();
177 if (backdropVertexCount < 3)
178 return;
179 effect.TextureEnabled = true;
180 for (int layer = 0; layer < 3; layer++)
157 181 {
158 Vector2 start = state.Points[segment];
159 Vector2 end = state.Points[segment + 1];
160 Vector2 tangent = (end - start).SafeNormalize(Vector2.UnitX);
161 Vector2 normal = tangent.RotatedBy(MathHelper.PiOver2);
162 Vector2 middle = (start + end) * 0.5f;
163 float middleProgress = (segment + 0.5f)
164 / Math.Max(1f, pointCount - 1f);
165 float boundaryHalfWidth = state.Width
166 * GetTrailTaper(middleProgress) * 0.5f;
167 for (int sideIndex = 0; sideIndex < 2; sideIndex++)
182 effect.Texture = DeathDomainBackdropTextureSystem.GetLayer(layer);
183 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
168 184 {
169 float side = sideIndex == 0 ? -1f : 1f;
170 Vector2 boundaryProbe = middle
171 + normal * side * boundaryHalfWidth;
172 outlineVisible[segment * 2 + sideIndex] =
173 !IsCoveredByAnotherMergedTrail(boundaryProbe, visualId);
185 pass.Apply();
186 graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList,
187 mergedBackdropVertices, 0, backdropVertexCount / 3);
174 188 }
175 189 }
176 190 }
177 191
178 private static void DrawOutlineBand(GraphicsDevice graphicsDevice,
179 TrailDrawState state, int pointCount, float innerHalfWidth,
180 float outerHalfWidth, Color color)
192 private static void DrawMergedOutlineBatch(GraphicsDevice graphicsDevice,
193 float innerWidthRatio, float outerWidthRatio, Color baseColor,
194 float opacityMultiplier)
181 195 {
182 196 if (effect is null)
183 197 return;
184 198
185 199 int vertexIndex = 0;
186 for (int segment = 0; segment < pointCount - 1; segment++)
200 foreach (TrailDrawState state in drawStates.Values)
187 201 {
188 Vector2 start = state.Points[segment];
189 Vector2 end = state.Points[segment + 1];
190 Vector2 startNormal = GetTrailNormal(state.Points, segment,
191 pointCount);
192 Vector2 endNormal = GetTrailNormal(state.Points, segment + 1,
193 pointCount);
194 float startTaper = GetTrailTaper(segment
195 / Math.Max(1f, pointCount - 1f));
196 float endTaper = GetTrailTaper((segment + 1f)
197 / Math.Max(1f, pointCount - 1f));
198 for (int sideIndex = 0; sideIndex < 2; sideIndex++)
202 if (!state.MergeOverlappingRims
203 || Main.GameUpdateCount > state.UpdateTick + 1
204 || !IsOnScreen(state.Bounds))
199 205 {
200 if (!outlineVisible[segment * 2 + sideIndex])
201 continue;
202 float side = sideIndex == 0 ? -1f : 1f;
203 Vector2 innerStart = start
204 + startNormal * side * innerHalfWidth * startTaper;
205 Vector2 outerStart = start
206 + startNormal * side * outerHalfWidth * startTaper;
207 Vector2 innerEnd = end
208 + endNormal * side * innerHalfWidth * endTaper;
209 Vector2 outerEnd = end
210 + endNormal * side * outerHalfWidth * endTaper;
211 WriteOutlineVertex(ref vertexIndex, outerStart, color);
212 WriteOutlineVertex(ref vertexIndex, innerStart, color);
213 WriteOutlineVertex(ref vertexIndex, outerEnd, color);
214 WriteOutlineVertex(ref vertexIndex, outerEnd, color);
215 WriteOutlineVertex(ref vertexIndex, innerStart, color);
216 WriteOutlineVertex(ref vertexIndex, innerEnd, color);
206 continue;
207 }
208 int pointCount = Math.Min(MaximumPoints, state.Points.Count);
209 float innerHalfWidth = state.Width * innerWidthRatio;
210 float outerHalfWidth = state.Width * outerWidthRatio;
211 Color color = baseColor * (state.Opacity * opacityMultiplier);
212 for (int segment = 0; segment < pointCount - 1; segment++)
213 {
214 Vector2 start = state.Points[segment];
215 Vector2 end = state.Points[segment + 1];
216 Vector2 startNormal = GetTrailNormal(state.Points, segment,
217 pointCount);
218 Vector2 endNormal = GetTrailNormal(state.Points, segment + 1,
219 pointCount);
220 float startTaper = GetTrailTaper(segment
221 / Math.Max(1f, pointCount - 1f));
222 float endTaper = GetTrailTaper((segment + 1f)
223 / Math.Max(1f, pointCount - 1f));
224 for (int sideIndex = 0; sideIndex < 2; sideIndex++)
225 {
226 float side = sideIndex == 0 ? -1f : 1f;
227 Vector2 innerStart = start
228 + startNormal * side * innerHalfWidth * startTaper;
229 Vector2 outerStart = start
230 + startNormal * side * outerHalfWidth * startTaper;
231 Vector2 innerEnd = end
232 + endNormal * side * innerHalfWidth * endTaper;
233 Vector2 outerEnd = end
234 + endNormal * side * outerHalfWidth * endTaper;
235 EnsureBatchCapacity(ref mergedOutlineVertices,
236 vertexIndex + 6);
237 WriteBatchVertex(mergedOutlineVertices, ref vertexIndex,
238 outerStart, color, Vector2.Zero);
239 WriteBatchVertex(mergedOutlineVertices, ref vertexIndex,
240 innerStart, color, Vector2.Zero);
241 WriteBatchVertex(mergedOutlineVertices, ref vertexIndex,
242 outerEnd, color, Vector2.Zero);
243 WriteBatchVertex(mergedOutlineVertices, ref vertexIndex,
244 outerEnd, color, Vector2.Zero);
245 WriteBatchVertex(mergedOutlineVertices, ref vertexIndex,
246 innerStart, color, Vector2.Zero);
247 WriteBatchVertex(mergedOutlineVertices, ref vertexIndex,
248 innerEnd, color, Vector2.Zero);
249 }
217 250 }
218 251 }
219 252 if (vertexIndex < 3)
@@ -224,74 +257,87 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
224 257 {
225 258 pass.Apply();
226 259 graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList,
227 outlineVertices, 0, vertexIndex / 3);
260 mergedOutlineVertices, 0, vertexIndex / 3);
228 261 }
229 262 }
230 263
231 private static Vector2 GetTrailNormal(IReadOnlyList<Vector2> points,
232 int index, int pointCount)
264 private static int BuildMergedBackdropBatch()
233 265 {
234 Vector2 previous = points[Math.Max(0, index - 1)];
235 Vector2 next = points[Math.Min(pointCount - 1, index + 1)];
236 return (next - previous).SafeNormalize(Vector2.UnitX)
237 .RotatedBy(MathHelper.PiOver2);
266 int vertexIndex = 0;
267 foreach (TrailDrawState state in drawStates.Values)
268 {
269 if (!state.MergeOverlappingRims
270 || Main.GameUpdateCount > state.UpdateTick + 1
271 || !IsOnScreen(state.Bounds))
272 {
273 continue;
274 }
275 int pointCount = Math.Min(MaximumPoints, state.Points.Count);
276 Color color = Color.White * state.Opacity;
277 for (int segment = 0; segment < pointCount - 1; segment++)
278 {
279 Vector2 start = state.Points[segment];
280 Vector2 end = state.Points[segment + 1];
281 Vector2 startNormal = GetTrailNormal(state.Points, segment,
282 pointCount);
283 Vector2 endNormal = GetTrailNormal(state.Points, segment + 1,
284 pointCount);
285 float startHalfWidth = state.Width
286 * GetTrailTaper(segment / Math.Max(1f, pointCount - 1f))
287 * 0.5f;
288 float endHalfWidth = state.Width
289 * GetTrailTaper((segment + 1f)
290 / Math.Max(1f, pointCount - 1f)) * 0.5f;
291 Vector2 leftStart = start - startNormal * startHalfWidth;
292 Vector2 rightStart = start + startNormal * startHalfWidth;
293 Vector2 leftEnd = end - endNormal * endHalfWidth;
294 Vector2 rightEnd = end + endNormal * endHalfWidth;
295 EnsureBatchCapacity(ref mergedBackdropVertices,
296 vertexIndex + 6);
297 WriteBackdropBatchVertex(ref vertexIndex, leftStart, color);
298 WriteBackdropBatchVertex(ref vertexIndex, rightStart, color);
299 WriteBackdropBatchVertex(ref vertexIndex, leftEnd, color);
300 WriteBackdropBatchVertex(ref vertexIndex, leftEnd, color);
301 WriteBackdropBatchVertex(ref vertexIndex, rightStart, color);
302 WriteBackdropBatchVertex(ref vertexIndex, rightEnd, color);
303 }
304 }
305 return vertexIndex;
238 306 }
239 307
240 private static void WriteOutlineVertex(ref int vertexIndex,
308 private static void WriteBackdropBatchVertex(ref int vertexIndex,
241 309 Vector2 world, Color color)
242 310 {
243 outlineVertices[vertexIndex++] = new VertexPositionColorTexture(
244 new Vector3(world - Main.screenPosition, 0f), color, Vector2.Zero);
311 Vector2 uv = new(
312 world.X / DeathDomainBackdropTextureSystem.LayerWidth,
313 world.Y / DeathDomainBackdropTextureSystem.LayerHeight);
314 WriteBatchVertex(mergedBackdropVertices, ref vertexIndex, world,
315 color, uv);
245 316 }
246 317
247 private static bool IsCoveredByAnotherMergedTrail(Vector2 world,
248 long currentVisualId)
318 private static void WriteBatchVertex(VertexPositionColorTexture[] target,
319 ref int vertexIndex, Vector2 world, Color color, Vector2 uv)
249 320 {
250 const float sharedBoundaryTolerance = 1.5f;
251 foreach ((long otherVisualId, TrailDrawState other) in drawStates)
252 {
253 if (otherVisualId == currentVisualId
254 || !other.MergeOverlappingRims
255 || Main.GameUpdateCount > other.UpdateTick + 1
256 || !Contains(other.Bounds, world))
257 {
258 continue;
259 }
321 target[vertexIndex++] = new VertexPositionColorTexture(
322 new Vector3(world - Main.screenPosition, 0f), color, uv);
323 }
260 324
261 float coverage = GetMaximumTrailCoverage(world, other);
262 if (coverage > sharedBoundaryTolerance)
263 return true;
264 if (coverage >= -sharedBoundaryTolerance
265 && otherVisualId > currentVisualId)
266 {
267 return true;
268 }
269 }
270 return false;
325 private static void EnsureBatchCapacity(
326 ref VertexPositionColorTexture[] target, int required)
327 {
328 if (target.Length >= required)
329 return;
330 int capacity = Math.Max(required, Math.Max(4096, target.Length * 2));
331 Array.Resize(ref target, capacity);
271 332 }
272 333
273 private static float GetMaximumTrailCoverage(Vector2 point,
274 TrailDrawState state)
334 private static Vector2 GetTrailNormal(IReadOnlyList<Vector2> points,
335 int index, int pointCount)
275 336 {
276 float bestCoverage = float.MinValue;
277 int count = Math.Min(MaximumPoints, state.Points.Count);
278 for (int index = 1; index < count; index++)
279 {
280 Vector2 start = state.Points[index - 1];
281 Vector2 delta = state.Points[index] - start;
282 float lengthSquared = delta.LengthSquared();
283 float progress = lengthSquared <= 0.001f ? 0f
284 : MathHelper.Clamp(Vector2.Dot(point - start, delta)
285 / lengthSquared, 0f, 1f);
286 float trailProgress = (index - 1f + progress)
287 / Math.Max(1f, count - 1f);
288 float localHalfWidth = state.Width
289 * GetTrailTaper(trailProgress) * 0.5f;
290 float distance = Vector2.Distance(point,
291 start + delta * progress);
292 bestCoverage = Math.Max(bestCoverage, localHalfWidth - distance);
293 }
294 return bestCoverage;
337 Vector2 previous = points[Math.Max(0, index - 1)];
338 Vector2 next = points[Math.Min(pointCount - 1, index + 1)];
339 return (next - previous).SafeNormalize(Vector2.UnitX)
340 .RotatedBy(MathHelper.PiOver2);
295 341 }
296 342
297 343 private static float GetTrailTaper(float progress)
@@ -299,8 +345,8 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
299 345 Math.Sin(MathHelper.Clamp(progress, 0f, 1f) * MathHelper.Pi)),
300 346 0.34f);
301 347
302 private static Vector4 CalculateBounds(IReadOnlyList<Vector2> points,
303 float width)
348 private static Vector4 CalculateTrailBounds(
349 IReadOnlyList<Vector2> points, float width)
304 350 {
305 351 Vector2 minimum = points[0];
306 352 Vector2 maximum = points[0];
@@ -310,14 +356,21 @@ internal sealed class DeathDomainTrailVisualSystem : ModSystem
310 356 minimum = Vector2.Min(minimum, points[index]);
311 357 maximum = Vector2.Max(maximum, points[index]);
312 358 }
313 float padding = width * 0.5f + 2f;
359 float padding = width * 0.5f + 4f;
314 360 return new Vector4(minimum.X - padding, minimum.Y - padding,
315 361 maximum.X + padding, maximum.Y + padding);
316 362 }
317 363
318 private static bool Contains(Vector4 bounds, Vector2 point)
319 => point.X >= bounds.X && point.Y >= bounds.Y
320 && point.X <= bounds.Z && point.Y <= bounds.W;
364 private static bool IsOnScreen(Vector4 bounds)
365 {
366 const float padding = 96f;
367 float left = Main.screenPosition.X - padding;
368 float top = Main.screenPosition.Y - padding;
369 float right = Main.screenPosition.X + Main.screenWidth + padding;
370 float bottom = Main.screenPosition.Y + Main.screenHeight + padding;
371 return bounds.Z >= left && bounds.W >= top
372 && bounds.X <= right && bounds.Y <= bottom;
373 }
321 374
322 375 private static void DrawSolidRibbon(GraphicsDevice graphicsDevice,
323 376 TrailDrawState state, float width, Color color)
Modified Projectiles/ReaperDeathDomainRiftProjectile.cs +36 -14
@@ -20,8 +20,11 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
20 20 private const int Lifetime = 300;
21 21 private const int MaximumPoints = 72;
22 22 private const float TrailWidth = 30f;
23 private readonly int[] harvestCooldowns = new int[Main.maxNPCs];
23 private readonly ulong[] nextHarvestTicks = new ulong[Main.maxNPCs];
24 24 private readonly List<Vector2> points = [];
25 private readonly HashSet<int> selectedTargets = [];
26 private readonly List<NPC> harvestTargets = [];
27 private Vector4 pathBounds;
25 28 private SickleCombatSnapshot snapshot;
26 29 private int phase;
27 30 private int actionId;
@@ -158,6 +161,7 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
158 161 volleyCounter = incomingVolley;
159 162 points.Clear();
160 163 points.AddRange(incomingPoints);
164 pathBounds = CalculatePathBounds(points);
161 165 configured = points.Count >= 2;
162 166 }
163 167
@@ -167,6 +171,7 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
167 171 snapshot = value;
168 172 points.Clear();
169 173 points.AddRange(path);
174 pathBounds = CalculatePathBounds(points);
170 175 phase = Math.Max(0, sourcePhase);
171 176 actionId = sourceActionId;
172 177 age = 0;
@@ -191,15 +196,13 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
191 196 if (necklace is null)
192 197 return;
193 198
194 for (int index = 0; index < harvestCooldowns.Length; index++)
195 if (harvestCooldowns[index] > 0)
196 harvestCooldowns[index]--;
197
198 HashSet<int> selected = [];
199 List<NPC> targets = [];
199 selectedTargets.Clear();
200 harvestTargets.Clear();
201 ulong currentTick = Main.GameUpdateCount;
200 202 foreach (NPC npc in Main.ActiveNPCs)
201 203 {
202 204 if (!ReaperTargeting.IsValidWeaponTarget(npc)
205 || !IntersectsPathBounds(npc.Hitbox)
203 206 || !IntersectsPath(npc.Hitbox))
204 207 {
205 208 continue;
@@ -207,20 +210,21 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
207 210 NPC target = npc.realLife >= 0 && npc.realLife < Main.maxNPCs
208 211 && Main.npc[npc.realLife].active ? Main.npc[npc.realLife] : npc;
209 212 if (!ReaperTargeting.IsValidWeaponTarget(target)
210 || !selected.Add(target.whoAmI)
211 || harvestCooldowns[target.whoAmI] > 0)
213 || !selectedTargets.Add(target.whoAmI)
214 || nextHarvestTicks[target.whoAmI] > currentTick)
212 215 {
213 216 continue;
214 217 }
215 harvestCooldowns[target.whoAmI] = necklace.SpawnInterval;
216 targets.Add(target);
218 nextHarvestTicks[target.whoAmI] = currentTick
219 + (ulong)Math.Max(1, necklace.SpawnInterval);
220 harvestTargets.Add(target);
217 221 }
218 if (targets.Count == 0)
222 if (harvestTargets.Count == 0)
219 223 return;
220 224
221 225 volleyCounter++;
222 226 bool requiem = necklace.DeathRequiemUnlocked && volleyCounter % 4 == 0;
223 foreach (NPC target in targets)
227 foreach (NPC target in harvestTargets)
224 228 {
225 229 uint hash = unchecked((uint)(actionId * 16777619
226 230 + target.whoAmI * 486187739 + volleyCounter * 97));
@@ -230,7 +234,6 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
230 234 modPlayer.SpawnDeathDomainHarvestFromReaperRift(target, necklace,
231 235 requiem, rotation);
232 236 }
233 Projectile.netUpdate = true;
234 237 }
235 238
236 239 private bool IntersectsPath(Rectangle target)
@@ -247,6 +250,25 @@ public sealed class ReaperDeathDomainRiftProjectile : ModProjectile
247 250 return false;
248 251 }
249 252
253 private bool IntersectsPathBounds(Rectangle target)
254 => target.Right >= pathBounds.X && target.Bottom >= pathBounds.Y
255 && target.Left <= pathBounds.Z && target.Top <= pathBounds.W;
256
257 private static Vector4 CalculatePathBounds(IReadOnlyList<Vector2> path)
258 {
259 if (path.Count == 0)
260 return Vector4.Zero;
261 Vector2 minimum = path[0];
262 Vector2 maximum = path[0];
263 for (int index = 1; index < path.Count; index++)
264 {
265 minimum = Vector2.Min(minimum, path[index]);
266 maximum = Vector2.Max(maximum, path[index]);
267 }
268 return new Vector4(minimum.X - TrailWidth, minimum.Y - TrailWidth,
269 maximum.X + TrailWidth, maximum.Y + TrailWidth);
270 }
271
250 272 private float GetOpacity()
251 273 => Smooth01(age / 8f) * Smooth01(Projectile.timeLeft / 44f);
252 274