using SoulHarvest.Items; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using Terraria; using Terraria.GameContent; using Terraria.ModLoader; namespace SoulHarvest.Common; [Autoload(Side = ModSide.Client)] internal sealed class SickleTrailPrimitiveSystem : ModSystem { private const int CurveSubdivisions = 4; internal static void DrawTrail(IReadOnlyList controlPoints, NormalSickle sickle, bool alternateAttack) { if (Main.dedServ || controlPoints.Count < 2) return; List curve = BuildSmoothCurve(controlPoints); if (curve.Count < 2) return; // Projectile.PreDraw runs inside Terraria's shared SpriteBatch. Ending that // batch and applying a BasicEffect here leaves FNA's vertex shader state in // a format that the following player draw cannot consume. Draw the ribbon // with the already active batch so this effect owns no shared render state. DrawCurveLayer(curve, sickle, alternateAttack, core: false); DrawCurveLayer(curve, sickle, alternateAttack, core: true); } private static List BuildSmoothCurve(IReadOnlyList controlPoints) { List result = new((controlPoints.Count - 1) * CurveSubdivisions + 1) { controlPoints[0] }; for (int segment = 0; segment < controlPoints.Count - 1; segment++) { Vector2 p0 = controlPoints[Math.Max(0, segment - 1)]; Vector2 p1 = controlPoints[segment]; Vector2 p2 = controlPoints[segment + 1]; Vector2 p3 = controlPoints[Math.Min(controlPoints.Count - 1, segment + 2)]; for (int subdivision = 1; subdivision <= CurveSubdivisions; subdivision++) { float amount = subdivision / (float)CurveSubdivisions; Vector2 point = Vector2.CatmullRom(p0, p1, p2, p3, amount); if (Vector2.DistanceSquared(result[^1], point) > 0.04f) result.Add(point); } } return result; } private static void DrawCurveLayer( IReadOnlyList points, NormalSickle sickle, bool alternateAttack, bool core) { float attackScale = alternateAttack ? 1.25f : 1f; for (int index = 0; index < points.Count - 1; index++) { Vector2 start = points[index]; Vector2 end = points[index + 1]; Vector2 difference = end - start; float length = difference.Length(); if (length <= 0.2f) continue; float progress = (index + 0.5f) / (points.Count - 1f); float strength = progress * progress * (3f - 2f * progress); float width = sickle.TrailWidth * attackScale * MathHelper.Lerp(0.08f, 1f, strength); if (sickle.SwingStyle == SickleSwingStyle.Rising) width *= 0.8f + strength * 0.65f; else if (sickle.SwingStyle == SickleSwingStyle.VoidOrbit) width *= 1f + (float)Math.Sin(progress * MathHelper.Pi * 5f + Main.GlobalTimeWrappedHourly * 8f) * 0.08f; if (core) width *= 0.42f; else width += 4f; float opacity = MathHelper.Lerp(0f, core ? 0.68f : 0.38f, (float)Math.Pow(strength, 0.72f)); Color color = core ? Color.Lerp(sickle.SickleSecondaryColor, Color.White, strength * 0.18f) : Color.Lerp(sickle.SickleColor, sickle.SickleSecondaryColor, strength * 0.22f); DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine( start, end, color * opacity, Math.Max(1f, width)); } } }