Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions McpMod.Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,10 @@ public static partial class McpMod
if (overlay is not NCardGridSelectionScreen screen)
return Error("No card selection screen is open");

var deckEnchantConfirm = TryConfirmDeckEnchantSelection(screen);
if (deckEnchantConfirm != null)
return deckEnchantConfirm;

// Check all preview containers (upgrade uses UpgradeSinglePreviewContainer / UpgradeMultiPreviewContainer,
// NDeckCardSelectScreen uses PreviewContainer with %PreviewConfirm)
foreach (var containerName in new[] { "%UpgradeSinglePreviewContainer", "%UpgradeMultiPreviewContainer", "%PreviewContainer" })
Expand Down Expand Up @@ -755,6 +759,49 @@ public static partial class McpMod
return Error("No confirm button is currently enabled - select more cards first");
}

private static Dictionary<string, object?>? TryConfirmDeckEnchantSelection(NCardGridSelectionScreen screen)
{
if (screen.GetType().Name != "NDeckEnchantSelectScreen")
return null;

var selectedCards = GetSelectedCardModels(screen);
var minSelect = System.Math.Max(GetCardSelectorInt(screen, "MinSelect") ?? 1, 1);
var maxSelect = GetCardSelectorInt(screen, "MaxSelect") ?? int.MaxValue;

if (selectedCards.Count < minSelect)
return Error($"No confirm button is currently enabled - select {minSelect - selectedCards.Count} more card(s) first");
if (selectedCards.Count > maxSelect)
return Error($"Too many cards selected ({selectedCards.Count}/{maxSelect})");

const System.Reflection.BindingFlags Flags =
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic;

var confirmMethod = screen.GetType().GetMethod("ConfirmSelection", Flags);
if (confirmMethod == null)
return Error("Deck enchant confirm method was not found");

var confirmButton = GetInstanceFieldValue(screen, "_multiPreviewConfirmButton")
?? GetInstanceFieldValue(screen, "_singlePreviewConfirmButton")
?? GetInstanceFieldValue(screen, "_confirmButton");

try
{
var parameters = confirmMethod.GetParameters();
confirmMethod.Invoke(screen, parameters.Length == 0 ? null : new[] { confirmButton });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the ConfirmSelection method signature has more than one parameter (e.g., due to future game updates or optional parameters), passing a single-element array new[] { confirmButton } will throw a TargetParameterCountException. It is safer to dynamically construct the arguments array to match the expected parameter count of the method.

            var parameters = confirmMethod.GetParameters();
            var args = parameters.Length == 0 ? null : new object?[parameters.Length];
            if (args != null)
            {
                args[0] = confirmButton;
            }
            confirmMethod.Invoke(screen, args);

return new Dictionary<string, object?>
{
["status"] = "ok",
["message"] = "Confirming deck enchant selection"
};
}
catch (System.Exception ex)
{
return Error($"Could not confirm deck enchant selection: {ex.GetBaseException().Message}");
}
}

private static Dictionary<string, object?> ExecuteCancelSelection()
{
var overlay = NOverlayStack.Instance?.Peek();
Expand Down
71 changes: 71 additions & 0 deletions McpMod.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using MegaCrit.Sts2.Core.Nodes.CommonUi;
using MegaCrit.Sts2.Core.Nodes.GodotExtensions;
using MegaCrit.Sts2.Core.Nodes.Screens.Map;
using MegaCrit.Sts2.Core.Nodes.Screens.CardSelection;

namespace STS2_MCP;

Expand Down Expand Up @@ -196,6 +197,76 @@ internal static void SendReadResultJson(HttpListenerResponse response, object re
return null;
}

private static object? GetInstanceMemberValue(object source, string memberName)
{
const System.Reflection.BindingFlags Flags =
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.DeclaredOnly;

for (var type = source.GetType(); type != null; type = type.BaseType)
{
try
{
var property = type.GetProperty(memberName, Flags);
if (property != null && property.GetIndexParameters().Length == 0)
return property.GetValue(source);
}
catch { }

try
{
var field = type.GetField(memberName, Flags);
if (field != null)
return field.GetValue(source);
}
catch { }
Comment on lines +210 to +224
}

return null;
}

private static object? GetCardSelectorValue(NCardGridSelectionScreen screen, string memberName)
{
var value = GetInstanceMemberValue(screen, memberName);
if (value != null)
return value;

var prefs = GetInstanceFieldValue(screen, "_prefs");
return prefs != null ? GetInstanceMemberValue(prefs, memberName) : null;
}

private static int? GetCardSelectorInt(NCardGridSelectionScreen screen, string memberName)
=> TryConvertToInt(GetCardSelectorValue(screen, memberName));

private static int? TryConvertToInt(object? value)
{
return value switch
{
int intValue => intValue,
long longValue when longValue >= int.MinValue && longValue <= int.MaxValue => (int)longValue,
short shortValue => shortValue,
byte byteValue => byteValue,
_ => null
};
}

private static HashSet<CardModel> GetSelectedCardModels(NCardGridSelectionScreen screen)
{
var selected = new HashSet<CardModel>();
if (GetInstanceFieldValue(screen, "_selectedCards") is not System.Collections.IEnumerable selectedCards)
return selected;

foreach (var item in selectedCards)
{
if (item is CardModel card)
selected.Add(card);
}

return selected;
}

private static void AddMenuOptionIfVisible(
List<Dictionary<string, object?>> options,
object owner,
Expand Down
27 changes: 22 additions & 5 deletions McpMod.StateBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2157,6 +2157,7 @@ private static bool IsCardHolderSelectable(Control holder)

// Cards in the grid (sorted by visual position - MoveToFront can reorder children)
var cardHolders = FindAllSortedByPosition<NGridCardHolder>(screen);
var selectedModels = GetSelectedCardModels(screen);
var cards = new List<Dictionary<string, object?>>();
var selectedCards = new List<Dictionary<string, object?>>();
int index = 0;
Expand All @@ -2168,6 +2169,8 @@ private static bool IsCardHolderSelectable(Control holder)
var cardInfo = BuildCardInfo(card);
cardInfo["index"] = index;
AddCardHolderState(cardInfo, holder);
if (selectedModels.Contains(card))
cardInfo["is_selected"] = true;
cards.Add(cardInfo);
if (cardInfo["is_selected"] is true)
selectedCards.Add(new Dictionary<string, object?>(cardInfo));
Expand All @@ -2176,17 +2179,25 @@ private static bool IsCardHolderSelectable(Control holder)
state["cards"] = cards;
state["selected_cards"] = selectedCards;
state["selected_count"] = selectedCards.Count;
state["min_select"] = GetMemberValue(screen, "MinSelect");
state["max_select"] = GetMemberValue(screen, "MaxSelect");
var minSelectValue = GetCardSelectorValue(screen, "MinSelect");
var maxSelectValue = GetCardSelectorValue(screen, "MaxSelect");
state["min_select"] = minSelectValue;
state["max_select"] = maxSelectValue;

// Preview container showing? (selection complete, awaiting confirm)
// Upgrade screens use UpgradeSinglePreviewContainer / UpgradeMultiPreviewContainer
var previewSingle = screen.GetNodeOrNull<Godot.Control>("%UpgradeSinglePreviewContainer");
var previewMulti = screen.GetNodeOrNull<Godot.Control>("%UpgradeMultiPreviewContainer");
var previewGeneric = screen.GetNodeOrNull<Godot.Control>("%PreviewContainer");
var previewEnchantSingle = screen.GetNodeOrNull<Godot.Control>("%EnchantSinglePreviewContainer")
?? screen.GetNodeOrNull<Godot.Control>("EnchantSinglePreviewContainer");
var previewEnchantMulti = screen.GetNodeOrNull<Godot.Control>("%EnchantMultiPreviewContainer")
?? screen.GetNodeOrNull<Godot.Control>("EnchantMultiPreviewContainer");
bool previewShowing = (previewSingle?.Visible ?? false)
|| (previewMulti?.Visible ?? false)
|| (previewGeneric?.Visible ?? false);
|| (previewGeneric?.Visible ?? false)
|| (previewEnchantSingle?.Visible ?? false)
|| (previewEnchantMulti?.Visible ?? false);
state["preview_showing"] = previewShowing;

var previewCards = new List<Dictionary<string, object?>>();
Expand All @@ -2213,7 +2224,7 @@ private static bool IsCardHolderSelectable(Control holder)
bool canCancel = false;
if (previewShowing)
{
foreach (var container in new[] { previewSingle, previewMulti, previewGeneric })
foreach (var container in new[] { previewSingle, previewMulti, previewGeneric, previewEnchantSingle, previewEnchantMulti })
{
if (container?.Visible == true)
{
Expand All @@ -2232,7 +2243,7 @@ private static bool IsCardHolderSelectable(Control holder)

// Confirm button - search all preview containers and main screen
bool canConfirm = false;
foreach (var container in new[] { previewSingle, previewMulti, previewGeneric })
foreach (var container in new[] { previewSingle, previewMulti, previewGeneric, previewEnchantSingle, previewEnchantMulti })
{
if (container?.Visible == true)
{
Expand All @@ -2253,6 +2264,12 @@ private static bool IsCardHolderSelectable(Control holder)
{
canConfirm = FindAll<NConfirmButton>(screen).Any(IsControlVisibleOrActionable);
}
if (screen.GetType().Name == "NDeckEnchantSelectScreen")
{
var minSelect = Math.Max(TryConvertToInt(minSelectValue) ?? 1, 1);
var maxSelect = TryConvertToInt(maxSelectValue) ?? int.MaxValue;
canConfirm = selectedCards.Count >= minSelect && selectedCards.Count <= maxSelect;
}
state["can_confirm"] = canConfirm;

return state;
Expand Down