using System.Collections.Generic; using System.Linq; namespace LLib.Movement.Unstuck; public sealed class RecoveryLadder { private readonly IRecoveryRung[] _rungs; private readonly long[] _lastRunMs; private readonly bool[] _hasRun; private readonly List _rungsFired; private bool _hasRunAny; private long _lastRunAnyMs; private long _firstStallMs; private long _lastStallMs; public int Attempts { get; private set; } public bool IsExhausted { get; private set; } private int Cap => _003Coptions_003EP.Mode switch { RecoveryMode.Aggressive => _003Coptions_003EP.AggressiveCap, RecoveryMode.Passive => _003Coptions_003EP.PassiveCap, _ => _003Coptions_003EP.ConservativeCap, }; public RecoveryLadder(RecoveryLadderOptions options, IReadOnlyList rungs) { _003Coptions_003EP = options; _rungs = rungs.ToArray(); _lastRunMs = new long[rungs.Count]; _hasRun = new bool[rungs.Count]; _rungsFired = new List(); base._002Ector(); } public RecoveryDecision OnStall(long nowMs) { if (IsExhausted) { return new RecoveryDecision(RecoveryDecisionKind.None, null, null); } if (Attempts >= Cap) { IsExhausted = true; return new RecoveryDecision(RecoveryDecisionKind.Exhausted, null, new RecoveryExhaustion(Attempts, _rungsFired.ToArray(), _firstStallMs, _lastStallMs)); } if (_hasRunAny && nowMs - _lastRunAnyMs < _003Coptions_003EP.GlobalCooldownMs) { return new RecoveryDecision(RecoveryDecisionKind.None, null, null); } if (_003Coptions_003EP.Mode == RecoveryMode.Passive) { Consume(nowMs); return new RecoveryDecision(RecoveryDecisionKind.Observe, null, null); } for (int i = 0; i < _rungs.Length; i++) { IRecoveryRung recoveryRung = _rungs[i]; if ((!_hasRun[i] || nowMs - _lastRunMs[i] >= recoveryRung.CooldownMs) && recoveryRung.CanRun(Attempts + 1, nowMs)) { Consume(nowMs); _rungsFired.Add(recoveryRung.Name); _hasRun[i] = true; _lastRunMs[i] = nowMs; _hasRunAny = true; _lastRunAnyMs = nowMs; return new RecoveryDecision(RecoveryDecisionKind.Run, recoveryRung, null); } } return new RecoveryDecision(RecoveryDecisionKind.None, null, null); } public void Reset() { Attempts = 0; IsExhausted = false; _rungsFired.Clear(); _hasRunAny = false; for (int i = 0; i < _hasRun.Length; i++) { _hasRun[i] = false; } } private void Consume(long nowMs) { if (Attempts == 0) { _firstStallMs = nowMs; } Attempts++; _lastStallMs = nowMs; } }