diff --git a/McpMod.Actions.cs b/McpMod.Actions.cs index ec3a0376..b722dac3 100644 --- a/McpMod.Actions.cs +++ b/McpMod.Actions.cs @@ -85,7 +85,7 @@ public static partial class McpMod { if (!CombatManager.Instance.IsInProgress) return Error("Not in combat"); - if (!CombatManager.Instance.IsPlayPhase) + if (!IsCombatPlayPhase(player)) return Error("Not in play phase - cannot act during enemy turn"); if (CombatManager.Instance.PlayerActionsDisabled) return Error("Player actions are currently disabled"); @@ -140,7 +140,7 @@ public static partial class McpMod { if (!CombatManager.Instance.IsInProgress) return Error("Not in combat"); - if (!CombatManager.Instance.IsPlayPhase) + if (!IsCombatPlayPhase(player)) return Error("Not in play phase - cannot act during enemy turn"); if (CombatManager.Instance.PlayerActionsDisabled) return Error("Player actions are currently disabled (turn may already be ending)"); @@ -183,7 +183,7 @@ public static partial class McpMod { if (!inCombat) return Error($"Potion '{SafeGetText(() => potion.Title)}' can only be used in combat"); - if (!CombatManager.Instance.IsPlayPhase) + if (!IsCombatPlayPhase(player)) return Error("Cannot use potions outside of play phase"); } else if (potion.Usage == PotionUsage.Automatic) @@ -1030,7 +1030,7 @@ public static partial class McpMod }; } - private static Creature? ResolveTarget(CombatState combatState, string entityId) + private static Creature? ResolveTarget(ICombatState combatState, string entityId) { // Try to match by entity_id pattern: "model_entry_N" // First try matching by combat_id if it's a pure number diff --git a/McpMod.Helpers.cs b/McpMod.Helpers.cs index 55d1aa53..32bb1c0f 100644 --- a/McpMod.Helpers.cs +++ b/McpMod.Helpers.cs @@ -4,7 +4,9 @@ using System.Text; using System.Text.Json; using Godot; +using MegaCrit.Sts2.Core.Combat; using MegaCrit.Sts2.Core.Entities.Cards; +using MegaCrit.Sts2.Core.Entities.Players; using MegaCrit.Sts2.Core.HoverTips; using MegaCrit.Sts2.Core.Models; @@ -12,6 +14,29 @@ namespace STS2_MCP; public static partial class McpMod { + // STS2 v0.104.0 removed CombatManager.IsPlayPhase. Reconstruct the same + // action-safe window from the current combat side and turn-ending flags. + private static bool IsCombatPlayPhase(Player? player = null) + { + try + { + var combatState = CombatManager.Instance.DebugOnlyGetState(); + bool playerTurn = player != null + ? CombatManager.Instance.IsPartOfPlayerTurn(player) + : combatState?.CurrentSide.ToString().Equals("Player", StringComparison.OrdinalIgnoreCase) == true; + + return CombatManager.Instance.IsInProgress + && playerTurn + && !CombatManager.Instance.EndingPlayerTurnPhaseOne + && !CombatManager.Instance.EndingPlayerTurnPhaseTwo + && !CombatManager.Instance.PlayerActionsDisabled; + } + catch + { + return false; + } + } + private static string? SafeGetCardDescription(CardModel card, PileType pile = PileType.Hand) { try { return StripRichTextTags(card.GetDescriptionForPile(pile)).Replace("\n", " "); } diff --git a/McpMod.MultiplayerActions.cs b/McpMod.MultiplayerActions.cs index 8124ebf5..cd182450 100644 --- a/McpMod.MultiplayerActions.cs +++ b/McpMod.MultiplayerActions.cs @@ -68,7 +68,7 @@ public static partial class McpMod { if (!CombatManager.Instance.IsInProgress) return Error("Not in combat"); - if (!CombatManager.Instance.IsPlayPhase) + if (!IsCombatPlayPhase(player)) return Error("Not in play phase - cannot act during enemy turn"); if (CombatManager.Instance.PlayerActionsDisabled) return Error("Player actions are currently disabled"); @@ -101,7 +101,7 @@ public static partial class McpMod { if (!CombatManager.Instance.IsInProgress) return Error("Not in combat"); - if (!CombatManager.Instance.IsPlayPhase) + if (!IsCombatPlayPhase(player)) return Error("Not in play phase - cannot act during enemy turn"); if (CombatManager.Instance.PlayerActionsDisabled) return Error("Player actions are currently disabled"); diff --git a/McpMod.MultiplayerState.cs b/McpMod.MultiplayerState.cs index 3dfab55d..1049b88f 100644 --- a/McpMod.MultiplayerState.cs +++ b/McpMod.MultiplayerState.cs @@ -256,7 +256,7 @@ public static partial class McpMod battle["round"] = combatState.RoundNumber; battle["turn"] = combatState.CurrentSide.ToString().ToLower(); - battle["is_play_phase"] = CombatManager.Instance.IsPlayPhase; + battle["is_play_phase"] = IsCombatPlayPhase(); battle["all_players_ready"] = CombatManager.Instance.AllPlayersReadyToEndTurn(); // Enemies diff --git a/McpMod.StateBuilder.cs b/McpMod.StateBuilder.cs index fccb3992..1b92506b 100644 --- a/McpMod.StateBuilder.cs +++ b/McpMod.StateBuilder.cs @@ -254,7 +254,7 @@ public static partial class McpMod battle["round"] = combatState.RoundNumber; battle["turn"] = combatState.CurrentSide.ToString().ToLower(); - battle["is_play_phase"] = CombatManager.Instance.IsPlayPhase; + battle["is_play_phase"] = IsCombatPlayPhase(); // Enemies var enemies = new List>(); diff --git a/README.md b/README.md index 2ec77c30..56a862b0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A mod for [**Slay the Spire 2**](https://store.steampowered.com/app/2868840/Slay_the_Spire_2/) that lets AI agents play the game. Exposes game state and actions via a localhost REST API, with an optional MCP server for Claude Desktop / Claude Code integration. -Singleplayer and multiplayer (co-op) supported. Tested against STS2 `v0.103.2`. +Singleplayer and multiplayer (co-op) supported. Tested against STS2 `v0.103.2` and `v0.104.0`. > [!warning] > This mod allows external programs to read and control your game via a localhost API. Use at your own risk with runs you care less about.