namespace BardFrontline.Core; // One outstanding native request. Cooldown changes alone are insufficient for a cleanse: wait for acknowledgement. public sealed class RequestGate { public ActionDecision? Pending { get; private set; } private double sentAt; private ushort beforeSequence; private bool sequenceObserved; private ushort requestSequence; private readonly Dictionary<(Skill, ulong), double> failures = []; public string Reason { get; private set; } = ""; public void Submitted(ActionDecision decision, double now, ushort before, ushort after) { Pending = decision; sentAt = now; beforeSequence = before; sequenceObserved = after != before; requestSequence = after; Reason = "等待动作确认"; } public bool Update(double now, ushort used, ushort handled, bool guardActive, bool cleansingStateChanged, bool ownedCastInProgress = false) { if (Pending == null) return false; if (!sequenceObserved && used != beforeSequence) { sequenceObserved = true; requestSequence = used; } var acknowledged = sequenceObserved && unchecked((short)(handled - requestSequence)) >= 0; var elapsed = now - sentAt; var release = Pending.Skill switch { Skill.Guard => guardActive || acknowledged && elapsed >= .5, Skill.Purify or Skill.WardensPaean => cleansingStateChanged || acknowledged && elapsed >= .5, _ => acknowledged && elapsed >= .1, }; if (release) { Pending = null; Reason = ""; return false; } if (ownedCastInProgress) return true; if (elapsed < 2) return true; Fail(Pending, now, "动作确认超时,短暂退避"); Pending = null; return false; } public bool IsBlocked(Skill s, ulong id, double now) => failures.TryGetValue((s, id), out var until) && until > now; public void Fail(ActionDecision d, double now, string reason) { failures[(d.Skill, d.TargetId)] = now + .5; Reason = reason; foreach (var key in failures.Where(x => x.Value <= now).Select(x => x.Key).ToArray()) failures.Remove(key); } public void Reset() { Pending = null; failures.Clear(); Reason = ""; } }