Code/Attack/Events/Patterns/LinePattern.cs
using System;
using Sandbox;

namespace MANIFOLD.BHLib.Events {
    public class LinePattern : PatternEvent, IDrawGizmos {
        [Space]
        public Vector3 StartPoint { get; set; }
        public Vector3 EndPoint { get; set; }
        [Space]
        public int SpawnCount { get; set; }
        public float? SpawnInterval { get; set; }
        public bool FaceNormal { get; set; }
        [ShowIf(nameof(FaceNormal), true)]
        public bool FlipNormal { get; set; }
        [ShowIf(nameof(FaceNormal), false)]
        public Angles SpawnRotation { get; set; }
        [Space]
        public EntityData Data { get; set; }

        public override void OnRemove(AttackData data) {
            foreach (var id in SpawnIDs) {
                var evt = data.SpawnTimeline.GetEvent(id);
                data.SpawnTimeline.RemoveEvent(evt);
            }
        }

        public override void Modify(AttackData data) {
            HandleCountChange(data);
            UpdateEvents(data);
        }

        private void HandleCountChange(AttackData data) {
            int delta = SpawnCount - SpawnIDs.Count;
            if (delta == 0) return;
            if (delta < 0) {
                int target = SpawnIDs.Count + delta;
                for (int i = SpawnIDs.Count - 1; i >= target; i--) {
                    var evt = data.SpawnTimeline.GetEvent(SpawnIDs[i]);
                    data.SpawnTimeline.RemoveEvent(evt);
                    SpawnIDs.RemoveAt(i);
                }
            } else {
                for (int i = 0; i < delta; i++) {
                    SpawnEntity spawnEvt = new SpawnEntity();
                    SpawnIDs.Add(spawnEvt.ID);
                    data.SpawnTimeline.Events.Add(spawnEvt);
                }
            }
        }

        private void UpdateEvents(AttackData data) {
            var distribution = GetDistribution();
            for (int i = 0; i < SpawnCount; i++) {
                var evt = (SpawnEntity)data.SpawnTimeline.GetEvent(SpawnIDs[i]);
                evt.Time = Time + (SpawnInterval.HasValue ? SpawnInterval.Value * i : 0);
                evt.Name = $"PAT // {Name} // {i}";
                evt.Hidden = true;
                evt.Position = distribution[i].pos;
                evt.Rotation = distribution[i].rot;
                evt.Data = null;
                evt.Data = Data;
            }
        }

        public void DrawGizmos() {
            Gizmo.Draw.LineThickness = 2;
            Gizmo.Draw.Line(StartPoint, EndPoint);

            var distribution = GetDistribution();
            Gizmo.Draw.Color = Color.Red;
            for (int i = 0; i < SpawnCount; i++) {
                float factor = (float)i / (SpawnCount - 1);
                Gizmo.Draw.Color = Color.Lerp(Color.Red, Color.Green, factor);
                Gizmo.Draw.SolidSphere(distribution[i].pos, 2);
                
                Vector3 arrowEnd = distribution[i].pos + (distribution[i].rot.ToRotation() * Vector3.Forward * 10);
                Gizmo.Draw.Arrow(distribution[i].pos, arrowEnd, 4, 1.5f);
            }
        }

        private (Vector3 pos, Angles rot)[] GetDistribution() {
            if (SpawnCount == 0) return [];

            Vector3 normal = Vector3.Cross((EndPoint - StartPoint).Normal, Vector3.Down);
            Rotation rotation = FaceNormal ? Rotation.LookAt(normal * (FlipNormal ? -1 : 1)) : SpawnRotation;
            
            if (SpawnCount == 1) return new[] { (StartPoint.LerpTo(EndPoint, 0.5f), (Angles)rotation) };
            
            var arr = new (Vector3 pos, Angles rot)[SpawnCount];
            for (int i = 0; i < SpawnCount; i++) {
                float factor = (float)i / (SpawnCount - 1);
                arr[i].pos = Vector3.Lerp(StartPoint, EndPoint, factor);
                arr[i].rot = rotation;
            }
            return arr;
        }
    }
}