using System; using System.Collections.Generic; using MinecraftClient.Mapping; using MinecraftClient.Pathing.Execution.Telemetry; using MinecraftClient.Physics; namespace MinecraftClient.Pathing.Execution { public enum PathExecutorState { InProgress, Failed, Complete } /// /// Drives a sequence of PathSegments by instantiating the correct IActionTemplate /// for each segment and ticking it every game tick. /// public sealed class PathExecutor { private readonly List _segments; private int _currentIndex; private IActionTemplate? _currentTemplate; private readonly Action? _debugLog; private readonly IPathExecutionObserver? _observer; private int _segmentTicks; private int _totalTicks; public bool IsComplete => _currentIndex >= _segments.Count && _currentTemplate is null; public int CurrentIndex => _currentIndex; public int TotalSegments => _segments.Count; public int TotalTicks => _totalTicks; public PathSegment? CurrentSegment => _currentIndex < _segments.Count ? _segments[_currentIndex] : null; public PathSegment? LastSegment => _segments.Count > 0 ? _segments[^1] : null; public PathExecutor(List segments, Action? debugLog = null, IPathExecutionObserver? observer = null) { _segments = segments; _currentIndex = 0; _debugLog = debugLog; _observer = observer; _observer?.OnNavigationStarted(segments); AdvanceToNextSegment(); } public PathExecutorState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) { if (_currentTemplate is null) { input.Reset(); return PathExecutorState.Complete; } _totalTicks++; int sameTickAdvanceCount = 0; while (_currentTemplate is not null) { _segmentTicks++; var state = _currentTemplate.Tick(pos, physics, input, world); switch (state) { case TemplateState.Complete: _observer?.OnSegmentCompleted(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos); _debugLog?.Invoke($"[PathExec] Segment {_currentIndex} complete " + $"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2})"); _currentIndex++; _segmentTicks = 0; if (_currentIndex >= _segments.Count) { input.Reset(); _currentTemplate = null; _debugLog?.Invoke("[PathExec] All segments complete!"); return PathExecutorState.Complete; } AdvanceToNextSegment(); // Do not waste the handoff tick when the next segment needs to issue // a jump or braking input immediately. sameTickAdvanceCount++; if (sameTickAdvanceCount > _segments.Count) { input.Reset(); _debugLog?.Invoke("[PathExec] Excessive same-tick segment advances; aborting."); return PathExecutorState.Failed; } continue; case TemplateState.Failed: input.Reset(); _observer?.OnSegmentFailed(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos); _debugLog?.Invoke($"[PathExec] Segment {_currentIndex} FAILED " + $"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2}), " + $"target was ({_currentTemplate.ExpectedEnd.X:F2},{_currentTemplate.ExpectedEnd.Y:F2},{_currentTemplate.ExpectedEnd.Z:F2})"); return PathExecutorState.Failed; default: return PathExecutorState.InProgress; } } input.Reset(); return PathExecutorState.Complete; } private void AdvanceToNextSegment() { if (_currentIndex < _segments.Count) { var seg = _segments[_currentIndex]; PathSegment? next = _currentIndex + 1 < _segments.Count ? _segments[_currentIndex + 1] : null; _currentTemplate = ActionTemplateFactory.Create(seg, next); _observer?.OnSegmentStarted(_currentIndex, _segments.Count, seg); _debugLog?.Invoke($"[PathExec] Starting segment {_currentIndex}/{_segments.Count}: {seg}"); } else { _currentTemplate = null; } } } }