From 1d80860b9d903ba84199a29246f18b462edfa925 Mon Sep 17 00:00:00 2001 From: Rafael Oliveira Date: Sun, 26 Apr 2026 13:14:56 -0300 Subject: [PATCH] feat: auto-call coin flips for win/lose outcomes Adds UI_AUTO_CALL_COIN_FLIP preference (default true) that skips the Heads/Tails prompt for coin flips where only the win/lose result matters (e.g. Krark's Thumb, Frenetic Efreet). The mathematical outcome is unaffected: any call has 50/50 odds, so auto-calling Heads removes a redundant click without changing game state. Behavior: - chooseBinary(HeadsOrTails): returns true (Heads) when pref is enabled. - chooseFlipResult: when call=true (Krark replacement effect), returns true to keep the matching result and guarantee the called side. NoCall flips still prompt the player. Players who prefer to choose each call can disable the pref via Settings -> Gameplay Preferences -> Auto-Call Coin Flips. --- .../forge/screens/home/settings/CSubmenuPreferences.java | 1 + .../forge/screens/home/settings/VSubmenuPreferences.java | 8 ++++++++ forge-gui/res/languages/en-US.properties | 2 ++ .../forge/localinstance/properties/ForgePreferences.java | 1 + .../main/java/forge/player/PlayerControllerHuman.java | 9 +++++++++ 5 files changed, 21 insertions(+) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java index 5fbec6209ce1..0084a7044eb1 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java @@ -158,6 +158,7 @@ public void initialize() { lstControls.add(Pair.of(view.getCbTokensInSeparateRow(), FPref.UI_TOKENS_IN_SEPARATE_ROW)); lstControls.add(Pair.of(view.getCbStackCreatures(), FPref.UI_STACK_CREATURES)); lstControls.add(Pair.of(view.getCbManaLostPrompt(), FPref.UI_MANA_LOST_PROMPT)); + lstControls.add(Pair.of(view.getCbAutoCallCoinFlip(), FPref.UI_AUTO_CALL_COIN_FLIP)); lstControls.add(Pair.of(view.getCbEscapeEndsTurn(), FPref.UI_ALLOW_ESC_TO_END_TURN)); lstControls.add(Pair.of(view.getCbDetailedPaymentDesc(), FPref.UI_DETAILED_SPELLDESC_IN_PROMPT)); lstControls.add(Pair.of(view.getCbGrayText(), FPref.UI_GRAY_INACTIVE_TEXT)); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java index 85fe017e1081..9d1ee83c8c09 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java @@ -76,6 +76,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final JCheckBox cbManaBurn = new OptionsCheckBox(localizer.getMessage("cbManaBurn")); private final JCheckBox cbOrderCombatants = new OptionsCheckBox(localizer.getMessage("cbOrderCombatants")); private final JCheckBox cbManaLostPrompt = new OptionsCheckBox(localizer.getMessage("cbManaLostPrompt")); + private final JCheckBox cbAutoCallCoinFlip = new OptionsCheckBox(localizer.getMessage("cbAutoCallCoinFlip")); private final JCheckBox cbDevMode = new OptionsCheckBox(localizer.getMessage("cbDevMode")); private final JCheckBox cbLoadCardsLazily = new OptionsCheckBox(localizer.getMessage("cbLoadCardsLazily")); private final JCheckBox cbLoadArchivedFormats = new OptionsCheckBox(localizer.getMessage("cbLoadArchivedFormats")); @@ -245,6 +246,9 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbManaLostPrompt, titleConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlManaLostPrompt")), descriptionConstraints); + pnlPrefs.add(cbAutoCallCoinFlip, titleConstraints); + pnlPrefs.add(new NoteLabel(localizer.getMessage("nlAutoCallCoinFlip")), descriptionConstraints); + pnlPrefs.add(cbpStackAdditions, comboBoxConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpStackAdditions")), descriptionConstraints); @@ -1017,6 +1021,10 @@ public final JCheckBox getCbManaLostPrompt() { return cbManaLostPrompt; } + public final JCheckBox getCbAutoCallCoinFlip() { + return cbAutoCallCoinFlip; + } + public final JCheckBox getCbDetailedPaymentDesc() { return cbDetailedPaymentDesc; } diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index d7660d94a7e3..170c9370641d 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -96,6 +96,7 @@ cbEnableAICheats=Allow AI Cheating cbManaBurn=Mana Burn cbOrderCombatants=Order Combatants cbManaLostPrompt=Prompt Mana Pool Emptying +cbAutoCallCoinFlip=Auto-Call Coin Flips cbDevMode=Developer Mode cbLoadCardsLazily=Load Card Scripts Lazily cbLoadArchivedFormats=Load Archived Formats @@ -204,6 +205,7 @@ nlEnableAICheats=Allow the AI to cheat to gain advantage (for personalities that nlManaBurn=Play with mana burn (from pre-Magic 2010 rules). nlOrderCombatants=Play with legacy ordering combatants (rule from Magic 2010 to pre-Foundations). nlManaLostPrompt=When enabled, you get a warning if passing priority would cause you to lose mana in your mana pool. +nlAutoCallCoinFlip=Automatically call Heads on coin flips where only the win/lose outcome matters (e.g. Krark's Thumb). Disable to manually choose each call. nlEnforceDeckLegality=Enforces deck legality relevant to each environment (minimum deck sizes, max card count etc). nlpAiSideboardingMode=Choose the way the AI sideboards: Off (the AI doesn't sideboard), AI (the AI sideboards for itself, currently mostly random), Human For AI (the human player sideboards for the AI in Constructed formats). nlExperimentalRestore=EXPERIMENTAL - Stores a snapshot to be used for undoing spells or abilities diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java index 163433a33867..fe1c545691ca 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java @@ -142,6 +142,7 @@ public enum FPref implements PreferencesStore.IPref { UI_ALT_PLAYERZONETABS ("false"), UI_PRESELECT_PREVIOUS_ABILITY_ORDER ("false"), UI_AUTO_YIELD_MODE (ForgeConstants.AUTO_YIELD_PER_ABILITY), + UI_AUTO_CALL_COIN_FLIP ("true"), UI_SHOW_STORM_COUNT_IN_PROMPT ("false"), UI_REMIND_ON_PRIORITY ("false"), UI_CARD_COUNTER_DISPLAY_TYPE(ForgeConstants.CounterDisplayType.TEXT.getName()), diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 4a0cc3be21b3..8ad37d7ce473 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1653,6 +1653,9 @@ public boolean chooseBinary(final SpellAbility sa, final String question, final final List labels; switch (kindOfChoice) { case HeadsOrTails: + if (FModel.getPreferences().getPrefBoolean(FPref.UI_AUTO_CALL_COIN_FLIP)) { + return true; // Auto-call Heads — for win/lose flips the call is irrelevant (50/50) + } labels = ImmutableList.of(localizer.getMessage("lblHeads"), localizer.getMessage("lblTails")); break; case TapOrUntap: @@ -1686,6 +1689,12 @@ public boolean chooseBinary(final SpellAbility sa, final String question, final @Override public boolean chooseFlipResult(final SpellAbility sa, final Player flipper, final boolean call) { + // Krark's Thumb / Frenetic Efreet replacement effect: choose which of two flips to keep. + // For called flips (heads/tails was called), we already auto-called Heads above, so picking + // the matching result guarantees a "win". For NoCall flips, the player still chooses. + if (call && FModel.getPreferences().getPrefBoolean(FPref.UI_AUTO_CALL_COIN_FLIP)) { + return true; + } final List labelsSrc = call ? List.of(localizer.getMessage("lblHeads"), localizer.getMessage("lblTails")) : List.of(localizer.getMessage("lblWinTheFlip"), localizer.getMessage("lblLoseTheFlip")); return getGui().one(sa.getHostCard().getDisplayName() + " - " + localizer.getMessage("lblChooseAResult"), labelsSrc).equals(labelsSrc.get(0));