From 0480dc677cbd2bc5e9b9cf57f251d25663bef62d Mon Sep 17 00:00:00 2001 From: MostCromulent <201167372+MostCromulent@users.noreply.github.com> Date: Sun, 10 May 2026 06:47:15 +0930 Subject: [PATCH] Fix lobby start crash for auto-generated deck variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GameLobby.startGame rejected slots with a null deck and then crashed in RegisteredPlayer's constructor when the deck arrived as null anyway, even though MoJhoSto and MomirBasic generate decks themselves and intentionally hide the deck chooser. The desktop lobby's setReady had a hardcoded bypass that named both variants explicitly, but the mobile lobby and the start path itself had no equivalent. Add GameLobby.hasAutoGeneratedVariant() and route all three call sites through it, so any future auto-generated variant is covered without further edits. In startGame, detect the auto-gen variant before the slot validation loop and generate the deck before constructing the RegisteredPlayer — which removes the need for a placeholder deck and drops the now-redundant section-by-section copy block. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../main/java/forge/screens/home/VLobby.java | 2 +- .../screens/constructed/LobbyScreen.java | 2 +- .../java/forge/gamemodes/match/GameLobby.java | 59 +++++++++---------- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index 3ec3b3ef7fd..5367be2338e 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -336,7 +336,7 @@ public void setPlayerChangeListener(final IPlayerChangeListener listener) { } void setReady(final int index, final boolean ready) { - if (ready && decks[index] == null && !vntMomirBasic.isSelected() && !vntMoJhoSto.isSelected()) { + if (ready && decks[index] == null && !lobby.hasAutoGeneratedVariant()) { SOptionPane.showErrorDialog("Select a deck before readying!"); update(false); return; diff --git a/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java b/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java index fe8a9818470..635465f8723 100644 --- a/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java @@ -825,7 +825,7 @@ void setReady(final int index, final boolean ready) { } if (ready) { updateDeck(index); - if (decks[index] == null) { + if (decks[index] == null && !lobby.hasAutoGeneratedVariant()) { FOptionPane.showErrorDialog(Forge.getLocalizer().getMessage("msgSelectAdeckBeforeReadying")); update(false); return; diff --git a/forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java b/forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java index 8778f5e60f4..dbf0ed89caf 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java @@ -86,6 +86,15 @@ public boolean hasVariant(final GameType variant) { return data.appliedVariants.contains(variant); } + public boolean hasAutoGeneratedVariant() { + for (final GameType variant : data.appliedVariants) { + if (variant.isAutoGenerated()) { + return true; + } + } + return false; + } + public int getNumberOfSlots() { return data.slots.size(); } @@ -355,23 +364,6 @@ public Runnable startGame() { return null; } - for (final LobbySlot slot : activeSlots) { - if (!slot.isReady()) { - SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPlayerIsNotReady", slot.getName())); - return null; - } - if (slot.getDeck() == null) { - SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPleaseSpecifyPlayerDeck", slot.getName())); - return null; - } - if (hasVariant(GameType.Commander) || hasVariant(GameType.Oathbreaker) || hasVariant(GameType.TinyLeaders) || hasVariant(GameType.Brawl)) { - if (!slot.getDeck().has(DeckSection.Commander)) { - SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPlayerDoesntHaveCommander", slot.getName())); - return null; - } - } - } - final Set variantTypes = data.appliedVariants; GameType autoGenerateVariant = null; @@ -394,6 +386,23 @@ public Runnable startGame() { } } + for (final LobbySlot slot : activeSlots) { + if (!slot.isReady()) { + SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPlayerIsNotReady", slot.getName())); + return null; + } + if (slot.getDeck() == null && autoGenerateVariant == null) { + SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPleaseSpecifyPlayerDeck", slot.getName())); + return null; + } + if (hasVariant(GameType.Commander) || hasVariant(GameType.Oathbreaker) || hasVariant(GameType.TinyLeaders) || hasVariant(GameType.Brawl)) { + if (!slot.getDeck().has(DeckSection.Commander)) { + SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPlayerDoesntHaveCommander", slot.getName())); + return null; + } + } + } + final boolean checkLegality = FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY); //Auto-generated decks don't need to be checked here @@ -438,6 +447,9 @@ public Runnable startGame() { } Deck deck = slot.getDeck(); + if (autoGenerateVariant != null) { + deck = autoGenerateVariant.autoGenerateDeck(null); + } RegisteredPlayer rp = new RegisteredPlayer(deck); if (!variantTypes.isEmpty()) { @@ -455,19 +467,6 @@ public Runnable startGame() { } } } - else if (autoGenerateVariant != null) { - Deck autoDeck = autoGenerateVariant.autoGenerateDeck(rp); - deck = new Deck(); - for (DeckSection d : DeckSection.values()) { - if (autoDeck.has(d)) { - deck.getOrCreate(d).clear(); - deck.get(d).addAll(autoDeck.get(d)); - } - } - } - - // Initialise variables for other variants - deck = deck == null ? rp.getDeck() : deck; final CardPool avatarPool = deck.get(DeckSection.Avatar);