From 01b05ef0df84d7b05605a1c00d75226a1a4fb39b Mon Sep 17 00:00:00 2001 From: MostCromulent <201167372+MostCromulent@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:50:17 +1030 Subject: [PATCH 1/3] Fix phase stop prefs using isLocalPlayer instead of hardcoded field index The old code assumed field index [0] is always the human player and index [1+] is always AI. This is incorrect in multiplayer and network games. Use isLocalPlayer() to select the correct pref set for each field. Fixes both desktop (CMatchUI) and mobile (MatchController). Co-Authored-By: Claude Opus 4.6 --- .../java/forge/screens/match/CMatchUI.java | 88 ++++--------------- .../forge/screens/match/MatchController.java | 79 +++++------------ .../properties/ForgePreferences.java | 14 +++ 3 files changed, 54 insertions(+), 127 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index 3f36fc31a5b..982e4cbfbed 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -1220,84 +1220,34 @@ public boolean isUiSetToSkipPhase(final PlayerView playerTurn, final PhaseType p return skippedPhase && label != null && !label.getEnabled(); } - /** - * TODO: Needs to be reworked for efficiency with rest of prefs saves in codebase. - */ public void writeMatchPreferences() { final ForgePreferences prefs = FModel.getPreferences(); final List fieldViews = getFieldViews(); - - // AI field is at index [1] - final PhaseIndicator fvAi = fieldViews.get(1).getPhaseIndicator(); - prefs.setPref(FPref.PHASE_AI_UPKEEP, fvAi.getLblUpkeep().getEnabled()); - prefs.setPref(FPref.PHASE_AI_DRAW, fvAi.getLblDraw().getEnabled()); - prefs.setPref(FPref.PHASE_AI_MAIN1, fvAi.getLblMain1().getEnabled()); - prefs.setPref(FPref.PHASE_AI_BEGINCOMBAT, fvAi.getLblBeginCombat().getEnabled()); - prefs.setPref(FPref.PHASE_AI_DECLAREATTACKERS, fvAi.getLblDeclareAttackers().getEnabled()); - prefs.setPref(FPref.PHASE_AI_DECLAREBLOCKERS, fvAi.getLblDeclareBlockers().getEnabled()); - prefs.setPref(FPref.PHASE_AI_FIRSTSTRIKE, fvAi.getLblFirstStrike().getEnabled()); - prefs.setPref(FPref.PHASE_AI_COMBATDAMAGE, fvAi.getLblCombatDamage().getEnabled()); - prefs.setPref(FPref.PHASE_AI_ENDCOMBAT, fvAi.getLblEndCombat().getEnabled()); - prefs.setPref(FPref.PHASE_AI_MAIN2, fvAi.getLblMain2().getEnabled()); - prefs.setPref(FPref.PHASE_AI_EOT, fvAi.getLblEndTurn().getEnabled()); - prefs.setPref(FPref.PHASE_AI_CLEANUP, fvAi.getLblCleanup().getEnabled()); - - // Human field is at index [0] - final PhaseIndicator fvHuman = fieldViews.get(0).getPhaseIndicator(); - prefs.setPref(FPref.PHASE_HUMAN_UPKEEP, fvHuman.getLblUpkeep().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_DRAW, fvHuman.getLblDraw().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_MAIN1, fvHuman.getLblMain1().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_BEGINCOMBAT, fvHuman.getLblBeginCombat().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_DECLAREATTACKERS, fvHuman.getLblDeclareAttackers().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_DECLAREBLOCKERS, fvHuman.getLblDeclareBlockers().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_FIRSTSTRIKE, fvHuman.getLblFirstStrike().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_COMBATDAMAGE, fvHuman.getLblCombatDamage().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_ENDCOMBAT, fvHuman.getLblEndCombat().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_MAIN2, fvHuman.getLblMain2().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_EOT, fvHuman.getLblEndTurn().getEnabled()); - prefs.setPref(FPref.PHASE_HUMAN_CLEANUP, fvHuman.getLblCleanup().getEnabled()); - + final PhaseType[] phases = PhaseType.values(); + + for (int i = 0; i < fieldViews.size(); i++) { + final FPref[] keys = isLocalPlayer(sortedPlayers.get(i)) + ? FPref.PHASES_HUMAN : FPref.PHASES_AI; + final PhaseIndicator pi = fieldViews.get(i).getPhaseIndicator(); + for (int p = 1; p < phases.length; p++) { + prefs.setPref(keys[p - 1], pi.getLabelFor(phases[p]).getEnabled()); + } + } prefs.save(); } - /** - * TODO: Needs to be reworked for efficiency with rest of prefs saves in codebase. - */ private void actuateMatchPreferences() { final ForgePreferences prefs = FModel.getPreferences(); final List fieldViews = getFieldViews(); - - // Human field is at index [0] - //TODO: Rework without that assumption; not true in 4 AI game or hotseat game. - final PhaseIndicator fvHuman = fieldViews.get(0).getPhaseIndicator(); - fvHuman.getLblUpkeep().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_UPKEEP)); - fvHuman.getLblDraw().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DRAW)); - fvHuman.getLblMain1().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_MAIN1)); - fvHuman.getLblBeginCombat().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_BEGINCOMBAT)); - fvHuman.getLblDeclareAttackers().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DECLAREATTACKERS)); - fvHuman.getLblDeclareBlockers().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DECLAREBLOCKERS)); - fvHuman.getLblFirstStrike().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_FIRSTSTRIKE)); - fvHuman.getLblCombatDamage().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_COMBATDAMAGE)); - fvHuman.getLblEndCombat().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_ENDCOMBAT)); - fvHuman.getLblMain2().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_MAIN2)); - fvHuman.getLblEndTurn().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_EOT)); - fvHuman.getLblCleanup().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_CLEANUP)); - - // AI field is at index [1], ... - for (int i = 1; i < fieldViews.size(); i++) { - final PhaseIndicator fvAi = fieldViews.get(i).getPhaseIndicator(); - fvAi.getLblUpkeep().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_UPKEEP)); - fvAi.getLblDraw().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_DRAW)); - fvAi.getLblMain1().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_MAIN1)); - fvAi.getLblBeginCombat().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_BEGINCOMBAT)); - fvAi.getLblDeclareAttackers().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_DECLAREATTACKERS)); - fvAi.getLblDeclareBlockers().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_DECLAREBLOCKERS)); - fvAi.getLblFirstStrike().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_FIRSTSTRIKE)); - fvAi.getLblCombatDamage().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_COMBATDAMAGE)); - fvAi.getLblEndCombat().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_ENDCOMBAT)); - fvAi.getLblMain2().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_MAIN2)); - fvAi.getLblEndTurn().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_EOT)); - fvAi.getLblCleanup().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_CLEANUP)); + final PhaseType[] phases = PhaseType.values(); + + for (int i = 0; i < fieldViews.size(); i++) { + final FPref[] keys = isLocalPlayer(sortedPlayers.get(i)) + ? FPref.PHASES_HUMAN : FPref.PHASES_AI; + final PhaseIndicator pi = fieldViews.get(i).getPhaseIndicator(); + for (int p = 1; p < phases.length; p++) { + pi.getLabelFor(phases[p]).setEnabled(prefs.getPrefBoolean(keys[p - 1])); + } } } diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index ff3f4daea1f..60208c8b934 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -563,69 +563,32 @@ public void resetPlayerPanels() { private static void actuateMatchPreferences() { final ForgePreferences prefs = FModel.getPreferences(); - - for (int i=0; i panels = view.getPlayerPanelsList(); + final PhaseType[] phases = PhaseType.values(); + + for (final VPlayerPanel panel : panels) { + final FPref[] keys = instance.isLocalPlayer(panel.getPlayer()) + ? FPref.PHASES_HUMAN : FPref.PHASES_AI; + final VPhaseIndicator pi = panel.getPhaseIndicator(); + for (int p = 1; p < phases.length; p++) { + pi.getLabel(phases[p]).setStopAtPhase(prefs.getPrefBoolean(keys[p - 1])); + } } - - final VPhaseIndicator fvHuman = view.getBottomPlayerPanel().getPhaseIndicator(); - fvHuman.getLabel(PhaseType.UPKEEP).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_UPKEEP)); - fvHuman.getLabel(PhaseType.DRAW).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DRAW)); - fvHuman.getLabel(PhaseType.MAIN1).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_MAIN1)); - fvHuman.getLabel(PhaseType.COMBAT_BEGIN).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_BEGINCOMBAT)); - fvHuman.getLabel(PhaseType.COMBAT_DECLARE_ATTACKERS).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DECLAREATTACKERS)); - fvHuman.getLabel(PhaseType.COMBAT_DECLARE_BLOCKERS).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DECLAREBLOCKERS)); - fvHuman.getLabel(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_FIRSTSTRIKE)); - fvHuman.getLabel(PhaseType.COMBAT_DAMAGE).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_COMBATDAMAGE)); - fvHuman.getLabel(PhaseType.COMBAT_END).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_ENDCOMBAT)); - fvHuman.getLabel(PhaseType.MAIN2).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_MAIN2)); - fvHuman.getLabel(PhaseType.END_OF_TURN).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_EOT)); - fvHuman.getLabel(PhaseType.CLEANUP).setStopAtPhase(prefs.getPrefBoolean(FPref.PHASE_HUMAN_CLEANUP)); } public static void writeMatchPreferences() { final ForgePreferences prefs = FModel.getPreferences(); - - final VPhaseIndicator fvAi = view.getTopPlayerPanel().getPhaseIndicator(); - prefs.setPref(FPref.PHASE_AI_UPKEEP, String.valueOf(fvAi.getLabel(PhaseType.UPKEEP).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_DRAW, String.valueOf(fvAi.getLabel(PhaseType.DRAW).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_MAIN1, String.valueOf(fvAi.getLabel(PhaseType.MAIN1).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_BEGINCOMBAT, String.valueOf(fvAi.getLabel(PhaseType.COMBAT_BEGIN).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_DECLAREATTACKERS, String.valueOf(fvAi.getLabel(PhaseType.COMBAT_DECLARE_ATTACKERS).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_DECLAREBLOCKERS, String.valueOf(fvAi.getLabel(PhaseType.COMBAT_DECLARE_BLOCKERS).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_FIRSTSTRIKE, String.valueOf(fvAi.getLabel(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_COMBATDAMAGE, String.valueOf(fvAi.getLabel(PhaseType.COMBAT_DAMAGE).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_ENDCOMBAT, String.valueOf(fvAi.getLabel(PhaseType.COMBAT_END).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_MAIN2, String.valueOf(fvAi.getLabel(PhaseType.MAIN2).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_EOT, String.valueOf(fvAi.getLabel(PhaseType.END_OF_TURN).getStopAtPhase())); - prefs.setPref(FPref.PHASE_AI_CLEANUP, String.valueOf(fvAi.getLabel(PhaseType.CLEANUP).getStopAtPhase())); - - final VPhaseIndicator fvHuman = view.getBottomPlayerPanel().getPhaseIndicator(); - prefs.setPref(FPref.PHASE_HUMAN_UPKEEP, String.valueOf(fvHuman.getLabel(PhaseType.UPKEEP).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_DRAW, String.valueOf(fvHuman.getLabel(PhaseType.DRAW).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_MAIN1, String.valueOf(fvHuman.getLabel(PhaseType.MAIN1).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_BEGINCOMBAT, String.valueOf(fvHuman.getLabel(PhaseType.COMBAT_BEGIN).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_DECLAREATTACKERS, String.valueOf(fvHuman.getLabel(PhaseType.COMBAT_DECLARE_ATTACKERS).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_DECLAREBLOCKERS, String.valueOf(fvHuman.getLabel(PhaseType.COMBAT_DECLARE_BLOCKERS).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_FIRSTSTRIKE, String.valueOf(fvHuman.getLabel(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_COMBATDAMAGE, String.valueOf(fvHuman.getLabel(PhaseType.COMBAT_DAMAGE).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_ENDCOMBAT, String.valueOf(fvHuman.getLabel(PhaseType.COMBAT_END).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_MAIN2, String.valueOf(fvHuman.getLabel(PhaseType.MAIN2).getStopAtPhase())); - prefs.setPref(FPref.PHASE_HUMAN_EOT, fvHuman.getLabel(PhaseType.END_OF_TURN).getStopAtPhase()); - prefs.setPref(FPref.PHASE_HUMAN_CLEANUP, fvHuman.getLabel(PhaseType.CLEANUP).getStopAtPhase()); - + final List panels = view.getPlayerPanelsList(); + final PhaseType[] phases = PhaseType.values(); + + for (final VPlayerPanel panel : panels) { + final FPref[] keys = instance.isLocalPlayer(panel.getPlayer()) + ? FPref.PHASES_HUMAN : FPref.PHASES_AI; + final VPhaseIndicator pi = panel.getPhaseIndicator(); + for (int p = 1; p < phases.length; p++) { + prefs.setPref(keys[p - 1], String.valueOf(pi.getLabel(phases[p]).getStopAtPhase())); + } + } prefs.save(); } 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 e9032ffd1fb..97c71f0373b 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java @@ -333,6 +333,20 @@ public String getDefault() { BRAWL_P5_DECK_STATE, BRAWL_P6_DECK_STATE, BRAWL_P7_DECK_STATE, BRAWL_P8_DECK_STATE }; + /** Phase stop prefs in PhaseType order (UPKEEP through CLEANUP, skipping UNTAP). */ + public static FPref[] PHASES_AI = { + PHASE_AI_UPKEEP, PHASE_AI_DRAW, PHASE_AI_MAIN1, + PHASE_AI_BEGINCOMBAT, PHASE_AI_DECLAREATTACKERS, + PHASE_AI_DECLAREBLOCKERS, PHASE_AI_FIRSTSTRIKE, + PHASE_AI_COMBATDAMAGE, PHASE_AI_ENDCOMBAT, + PHASE_AI_MAIN2, PHASE_AI_EOT, PHASE_AI_CLEANUP }; + public static FPref[] PHASES_HUMAN = { + PHASE_HUMAN_UPKEEP, PHASE_HUMAN_DRAW, PHASE_HUMAN_MAIN1, + PHASE_HUMAN_BEGINCOMBAT, PHASE_HUMAN_DECLAREATTACKERS, + PHASE_HUMAN_DECLAREBLOCKERS, PHASE_HUMAN_FIRSTSTRIKE, + PHASE_HUMAN_COMBATDAMAGE, PHASE_HUMAN_ENDCOMBAT, + PHASE_HUMAN_MAIN2, PHASE_HUMAN_EOT, PHASE_HUMAN_CLEANUP }; + } /** Instantiates a ForgePreferences object. */ From 31b5d32a90fadfd001bff40d6781badffd3d30cc Mon Sep 17 00:00:00 2001 From: MostCromulent <201167372+MostCromulent@users.noreply.github.com> Date: Wed, 25 Feb 2026 06:10:43 +1030 Subject: [PATCH 2/3] Save phase stop prefs in finishGame so all network players persist changes Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/forge/screens/match/CMatchUI.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index 982e4cbfbed..7d68304d1af 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -814,6 +814,7 @@ public void enableOverlay() { @Override public void finishGame() { FloatingZone.closeAll(); //ensure floating card areas cleared and closed after the game + writeMatchPreferences(); final GameView gameView = getGameView(); if (hasLocalPlayers() || gameView.isMatchOver()) { new ViewWinLose(gameView, this).show(); From fd66cf19e52ad2ddb8d2028041138e66c85a6ec5 Mon Sep 17 00:00:00 2001 From: MostCromulent <201167372+MostCromulent@users.noreply.github.com> Date: Wed, 25 Feb 2026 06:45:37 +1030 Subject: [PATCH 3/3] Gate finishGame pref save to network play only Local games already save via ControlWinLose.saveOptions(). Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/forge/screens/match/CMatchUI.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index 7d68304d1af..3b171d28121 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -814,7 +814,9 @@ public void enableOverlay() { @Override public void finishGame() { FloatingZone.closeAll(); //ensure floating card areas cleared and closed after the game - writeMatchPreferences(); + if (isNetGame()) { + writeMatchPreferences(); + } final GameView gameView = getGameView(); if (hasLocalPlayers() || gameView.isMatchOver()) { new ViewWinLose(gameView, this).show();