diff --git a/RunReplays/Commands/SelectHandCardsCommand.cs b/RunReplays/Commands/SelectHandCardsCommand.cs index e4ab93d..841d987 100644 --- a/RunReplays/Commands/SelectHandCardsCommand.cs +++ b/RunReplays/Commands/SelectHandCardsCommand.cs @@ -79,7 +79,16 @@ public override ExecuteResult Execute() HandSelectionCapture.PressHolder(nHand, holder); } - HandSelectionCapture.ConfirmSelection(nHand); + try + { + HandSelectionCapture.ConfirmSelection(nHand); + } + catch (System.Reflection.TargetInvocationException) + { + // Single-card selections auto-complete when PressHolder toggles the + // card, so the TaskCompletionSource is already resolved. Swallow the + // "task already completed" exception — the selection succeeded. + } HandSelectionCapture.ActiveHand = null; return ExecuteResult.Ok(); } diff --git a/RunReplays/Commands/TreasureCommands.cs b/RunReplays/Commands/TreasureCommands.cs index ebedc25..b048819 100644 --- a/RunReplays/Commands/TreasureCommands.cs +++ b/RunReplays/Commands/TreasureCommands.cs @@ -70,9 +70,20 @@ public TakeChestRelicCommand() : base("") { } public override ExecuteResult Execute() { var sync = RunManager.Instance.TreasureRoomRelicSynchronizer; - PlayerActionBuffer.LogDispatcher("[TakeChestRelic] PickRelicLocally(0)"); - Callable.From(() => sync.PickRelicLocally(0)).CallDeferred(); - return ExecuteResult.Ok(); + + var relics = sync.CurrentRelics; + if (relics == null || relics.Count == 0) + return ExecuteResult.Retry(200); + + try + { + sync.PickRelicLocally(0); + return ExecuteResult.Ok(); + } + catch (System.InvalidOperationException) + { + return ExecuteResult.Retry(200); + } } public static TakeChestRelicCommand? TryParse(string raw) diff --git a/RunReplays/ReplayDispatcher.cs b/RunReplays/ReplayDispatcher.cs index 42b8a29..0dbc495 100644 --- a/RunReplays/ReplayDispatcher.cs +++ b/RunReplays/ReplayDispatcher.cs @@ -612,9 +612,21 @@ public static bool Paused /// public static void Step() { + if (!ReplayEngine.IsActive) + return; + if (!_paused) Paused = true; + + if (_dispatchInProgress + || ReplayState.ActionInFlight + || ReplayState.CardPlayInFlight + || ReplayState.PotionInFlight + || CardPlayReplayPatch.IsAwaitingEndTurnCompletion + || MapMoveInFlight) + return; + _stepping = true; - DispatchNow(); + Callable.From(ExecuteNext).CallDeferred(); } private static float _delayBetweenCommands = 1.0f; /// @@ -878,14 +890,18 @@ private static void WatchdogTick() && ReplayEngine.PeekNext(out ReplayCommand? cmd) && cmd != null && cmd is MapMoveCommand) { - // Force-clear blockers so dispatch can proceed. - ReplayState.ClearActionInFlight(); - MapMoveInFlight = false; - _dispatchInProgress = false; - _lastDispatchedCmd = null; - NMapScreen.Instance?.Open(); - NMapScreen.Instance?.SetTravelEnabled(true); - DispatchNow(); + var room = GetCurrentRoom(); + if (room == null) + { + // Force-clear blockers so dispatch can proceed. + ReplayState.ClearActionInFlight(); + MapMoveInFlight = false; + _dispatchInProgress = false; + _lastDispatchedCmd = null; + NMapScreen.Instance?.Open(); + NMapScreen.Instance?.SetTravelEnabled(true); + DispatchNow(); + } } ScheduleWatchdogTick(); @@ -918,6 +934,16 @@ internal static void TryDispatch([System.Runtime.CompilerServices.CallerMemberNa GD.Print($"[RunReplays] TryDispatch miss — cmd={cmd.GetType().Name} dispatchable=[{dispatchableNames}]"); DiagnosticLog.Write("Dispatch", $"miss — cmd={cmd.GetType().Name}({cmd}) dispatchable=[{dispatchableNames}]"); + if (cmd is MapMoveCommand && GetCurrentRoom() is TreasureRoom) + { + var sync = RunManager.Instance.TreasureRoomRelicSynchronizer; + var relics = sync.CurrentRelics; + if (relics != null && relics.Count > 0) + { + try { sync.PickRelicLocally(0); } + catch (InvalidOperationException) { } + } + } return; }