namespace BardFrontline.Core; public sealed class ManualControl { private bool initialized; private ulong lastTarget; private ushort lastSequence; private double until; public bool Forced { get; set; } public double TargetChangedAt { get; private set; } public bool Active(double now) => Forced || now < until; public void Observe(double now, ulong target, ushort sequence, bool pluginPending, Settings c) { if (initialized && (target != lastTarget || sequence != lastSequence && !pluginPending) && c.DetectManualControl) until = now + c.ManualControlSeconds; if (!initialized || target != lastTarget) TargetChangedAt = now; initialized = true; lastTarget = target; lastSequence = sequence; if (!c.DetectManualControl) until = 0; } public void OwnTarget(ulong id, double now) { if (id != lastTarget) TargetChangedAt = now; lastTarget = id; } public void OwnAction(ushort sequence) => lastSequence = sequence; public void Resume() { Forced = false; until = 0; initialized = false; } public void ResetObservations() { initialized = false; until = 0; TargetChangedAt = 0; } } public sealed class WeaveTiming { private readonly Queue samples = new(); private (double Start, double? Ack, double Unlock)? pending; public int SampleCount => samples.Count; public float Budget => samples.Count < 3 ? .65f : Math.Clamp(samples.Order().ElementAt((int)Math.Ceiling(samples.Count * .9) - 1) + .05f, .5f, 1.25f); public void Submitted(double now, bool cast) { pending = cast ? null : (now, null, now); } public void Observe(double now, bool acknowledged, float animationLock, bool queued, bool casting) { if (pending is not { } p) return; if (now - p.Start > 2 || casting) { pending = null; return; } if (acknowledged && p.Ack == null) p.Ack = now; p.Unlock = Math.Max(p.Unlock, now + Math.Max(0, animationLock)); pending = p; if (p.Ack == null || animationLock > 0 || queued) return; var duration = (float)(Math.Max(p.Ack.Value, p.Unlock) - p.Start); pending = null; if (duration is < .05f or > 1.5f) return; samples.Enqueue(duration); while (samples.Count > 20) samples.Dequeue(); } public bool Allows(float remaining, Skill skill, bool adaptive, BurstStrategy strategy = BurstStrategy.Normal, bool casting = false) => skill == Skill.EncoreOfLight || remaining <= 0 || remaining >= (adaptive ? Budget : .65f) // This only waives our extra GCD reservation; native locks/recasts still gate dispatch. || strategy == BurstStrategy.Pressure && !casting && skill is (Skill.HarmonicArrow or Skill.PitchPerfect or Skill.BlastArrow or Skill.ApexArrow or Skill.SilentNocturne or Skill.EagleEyeShot or Skill.Bravery or Skill.Dervish or Skill.FinalFantasia); public void Reset() { samples.Clear(); pending = null; } }