diff --git a/docs/User-Guide.md b/docs/User-Guide.md index 4d729e0591f..d46acc5e21c 100644 --- a/docs/User-Guide.md +++ b/docs/User-Guide.md @@ -256,7 +256,7 @@ The match screen is divided into resizable panels. The numbers below identify ea 3. **Player field tabs** — switch between opponents' fields in multiplayer; the (N new) indicator highlights cards that have changed since you last viewed that field. See [Sort Player Fields in Turn Order](#sort-player-fields-in-turn-order) for ordering options. 4. **Card Detail** — selected card's text and metadata. 5. **Opponent's field** — same layout as your own field (items 6-10 describe its components). -6. **Avatar and life total** — left-click to target the player, hover to see more info including Commander damage. +6. **Avatar and life total** — left-click to target the player, hover to see more info including Commander damage and, when a Commander bracket cap is active, estimated Commander bracket. 7. **Zone buttons** — hand, library, graveyard, exile, command, and sideboard with live counts; click to view, right-click for display options. See [Viewing cards in different zones](#viewing-cards-in-different-zones) for details. 8. **Turn Phases** — click any pip to toggle a priority stop in that phase; right-click to yield to that phase. 9. **Mana pool** — floating mana; click a symbol to spend it during cost payment. diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 95a5d019b20..38fdb861402 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1980,7 +1980,7 @@ public final void putEtbCounters(Map, Map // used for LKI for (Map m : etbCounters.values()) { for (Map.Entry e : m.entrySet()) { - CounterType ct = e.getKey(); + CounterType ct = e.getKey(); if (canReceiveCounters(ct)) { setCounters(ct, getCounters(ct) + e.getValue()); } diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java index 65fe16c7825..891bb2ce5bc 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java @@ -342,7 +342,6 @@ public void populate() { public final boolean isAi() { return isAi; } - public void setIsAi(final boolean isAiDeck) { isAi = isAiDeck; } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java index e455820ac7b..4115a241e8e 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java @@ -177,6 +177,7 @@ public void update() { view.getBtnStart().requestFocusInWindow(); }); view.getGamesInMatchBinder().load(); + view.getMaximumCommanderBracketBinder().load(); } /** React to a lobby-data change: detect event-state transitions and refresh the panel. */ @@ -537,11 +538,17 @@ public void initialize() { view.getCbSingletons().addActionListener(arg0 -> { prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected())); prefs.save(); + view.markDirty(); }); view.getCbArtifacts().addActionListener(arg0 -> { prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected())); prefs.save(); + view.markDirty(); + }); + + view.getMaximumCommanderBracketBinder().getComponent().addActionListener(arg0 -> { + view.markDirty(); }); // Pre-select checkboxes 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 9e5aa946a39..8f27566e3ef 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 @@ -50,6 +50,11 @@ public class VLobby implements ILobbyView { static final int MAX_PLAYERS = 8; private static final int EVENT_BTN_WIDTH = 200; private static final int EVENT_BTN_HEIGHT = 50; + private static final int START_ROW_LABEL_WIDTH = 150; + private static final int START_ROW_COMBO_WIDTH = 50; + private static final int START_ROW_GAMES_WIDTH = START_ROW_LABEL_WIDTH + START_ROW_COMBO_WIDTH; + private static final int COMMANDER_BRACKET_SIDE_WIDTH = START_ROW_LABEL_WIDTH * 2 + START_ROW_COMBO_WIDTH; + private static final int COMMANDER_GAMES_SIDE_WIDTH = COMMANDER_BRACKET_SIDE_WIDTH + START_ROW_GAMES_WIDTH; final Localizer localizer = Localizer.getInstance(); private static final ForgePreferences prefs = FModel.getPreferences(); @@ -62,10 +67,12 @@ public class VLobby implements ILobbyView { private int playerWithFocus = 0; // index of the player that currently has focus private final StartButton btnStart = new StartButton(); - private final JPanel pnlStart = new JPanel(new MigLayout("insets 0, gap 0, wrap 2")); - private final JComboBox gamesInMatch = new JComboBox(new String[] {"1","3","5"}); - private final SwingPrefBinders.ComboBox gamesInMatchBinder = - new SwingPrefBinders.ComboBox(FPref.UI_MATCHES_PER_GAME, gamesInMatch); + private final JPanel pnlStart = new JPanel(new MigLayout("insets 0, gap 0, wrap 3")); + private final JComboBox maximumCommanderBracket = new JComboBox<>(new String[]{"1", "2", "3", "4", "5"}); + private final SwingPrefBinders.ComboBox maximumCommanderBracketBinder = new SwingPrefBinders.ComboBox(FPref.DECKGEN_MAXIMUM_COMMANDER_BRACKET, maximumCommanderBracket); + private final JPanel maximumCommanderBracketFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap 2")); + private final JComboBox gamesInMatch = new JComboBox<>(new String[]{"1", "3", "5"}); + private final SwingPrefBinders.ComboBox gamesInMatchBinder = new SwingPrefBinders.ComboBox(FPref.UI_MATCHES_PER_GAME, gamesInMatch); private final JPanel gamesInMatchFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap 2")); private final JPanel constructedFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap 2, hidemode 3")); // Main content frame @@ -145,9 +152,10 @@ public class VLobby implements ILobbyView { private final FButton btnStartEvent = new FButton(Localizer.getInstance().getMessage("lblNetworkStartDraft")); private final FButton btnStartMatch = new FButton(Localizer.getInstance().getMessage("lblNetworkStartMatch")); + private boolean refreshGeneratedDecks = false; + // (network draft state lives in CLobby) - // CTR public VLobby(final GameLobby lobby) { this.lobby = lobby; // Create controller first — VLobby.update() and render methods rely on a non-null @@ -260,9 +268,16 @@ public VLobby(final GameLobby lobby) { // Start Button if (lobby.hasControl()) { pnlStart.setOpaque(false); - pnlStart.add(btnStart, "align center"); + maximumCommanderBracketFrame.add(newLabel("Maximum Bracket:"), "w " + START_ROW_LABEL_WIDTH + "px!, h 30px!"); + maximumCommanderBracketFrame.add(maximumCommanderBracket, "w " + START_ROW_COMBO_WIDTH + "px!, h 30px!"); + maximumCommanderBracketFrame.setOpaque(false); + addConstructedStartControls(); // Start button event handling btnStart.addActionListener(arg0 -> { + if (refreshGeneratedDecks) { + refreshGeneratedDecks = false; + update(true); + } Runnable startGame = lobby.startGame(); if (startGame != null) { startGame.run(); @@ -287,11 +302,24 @@ public VLobby(final GameLobby lobby) { defaultGamesInMatch = "3"; } - gamesInMatchFrame.add(newLabel(localizer.getMessage("lblGamesInMatch")), "w 150px!, h 30px!"); - gamesInMatchFrame.add(gamesInMatch, "w 50px!, h 30px!"); + gamesInMatchFrame.add(newLabel(localizer.getMessage("lblGamesInMatch")), "w " + START_ROW_LABEL_WIDTH + "px!, h 30px!"); + gamesInMatchFrame.add(gamesInMatch, "w " + START_ROW_COMBO_WIDTH + "px!, h 30px!"); gamesInMatchFrame.setOpaque(false); + } - pnlStart.add(gamesInMatchFrame); + private void addConstructedStartControls() { + if (lobby.getGameType() == GameType.Commander || hasVariant(GameType.Commander)) { + maximumCommanderBracketFrame.setVisible(true); + pnlStart.setLayout(new MigLayout("insets 0, gap 0, wrap 3")); + pnlStart.add(maximumCommanderBracketFrame, "w " + COMMANDER_BRACKET_SIDE_WIDTH + "px!, align left"); + pnlStart.add(btnStart, "align center"); + pnlStart.add(gamesInMatchFrame, "w " + COMMANDER_GAMES_SIDE_WIDTH + "px!, align left"); + } else { + maximumCommanderBracketFrame.setVisible(false); + pnlStart.setLayout(new MigLayout("insets 0, gap 0, wrap 2")); + pnlStart.add(btnStart, "align center"); + pnlStart.add(gamesInMatchFrame, "align center"); + } } public void updateDeckPanel() { @@ -431,7 +459,6 @@ public void update(final boolean fullUpdate) { public void setController(final CLobby controller) { this.controller = controller; } - public CLobby getController() { return controller; } @@ -447,7 +474,6 @@ String getCurrentModeSelection() { int getCurrentModeIndex() { return cboModePanel.getSelectedIndex(); } - void setCurrentModeIndex(int idx) { cboModePanel.setSelectedIndex(idx); } @@ -460,7 +486,6 @@ void refreshConstructedFrame() { boolean getConformanceSelected() { return cbDeckConformance.isSelected(); } - void setConformanceSelected(boolean selected) { cbDeckConformance.setSelected(selected); } @@ -473,7 +498,6 @@ void setReady(final int index, final boolean ready) { // Limited mode: deck is produced by the draft/sealed flow (no pre-selection // required when starting a new event) or is selected from the filtered event // deck list when running a match from a past event. Skip the generic check. - boolean deckRequired = !controller.isLimitedMode(); if (ready && decks[index] == null && !lobby.hasAutoGeneratedVariant() && !controller.isLimitedMode()) { SOptionPane.showErrorDialog(localizer.getMessage("msgSelectAdeckBeforeReadying")); update(false); @@ -527,6 +551,7 @@ private void fireDeckSectionChangeListener(final int index, final DeckSection se void removePlayer(final int index) { lobby.removeSlot(index); } + boolean hasVariant(final GameType variant) { return lobby.hasVariant(variant); } @@ -587,6 +612,7 @@ private FDeckChooser getDeckChooser(final int iSlot) { } private void selectMainDeck(final FDeckChooser mainChooser, final int playerIndex, final boolean isCommanderDeck) { + refreshGeneratedDecks = false; final DeckType type = mainChooser.getSelectedDeckType(); final Deck deck = mainChooser.getDeck(); // something went wrong, clear selection to prevent error loop @@ -888,10 +914,7 @@ void updateActionButtons() { pnlStart.add(btnStartMatch, "cell 2 0, " + eventBtn); pnlStart.add(gamesInMatchFrame, "cell 2 1, align center"); } else { - // Constructed mode: Start button centered with games-in-match below - pnlStart.setLayout(new MigLayout("insets 0, gap 0, wrap 2")); - pnlStart.add(btnStart, "align center, spanx 2, wrap"); - pnlStart.add(gamesInMatchFrame, "spanx 2, align center"); + addConstructedStartControls(); } } // Non-host: nothing to show here — match controls are host-only. @@ -1044,6 +1067,10 @@ List getPlayerNames() { return names; } + public void markDirty() { + refreshGeneratedDecks = true; + } + ///////////////////////////////////////////// //========== Various listeners in build order @@ -1061,6 +1088,7 @@ private VariantCheckBox(final GameType variantType) { lobby.removeVariant(variantType); } VLobby.this.update(false); + VLobby.this.updateActionButtons(); }); } } @@ -1159,6 +1187,11 @@ public SwingPrefBinders.ComboBox getGamesInMatchBinder() { return gamesInMatchBinder; } + /** Return the maximumCommanderBracketBinder. */ + public SwingPrefBinders.ComboBox getMaximumCommanderBracketBinder() { + return maximumCommanderBracketBinder; + } + /** Populate vanguard lists. */ private void populateVanguardLists() { humanListData.add("Use deck's default avatar (random if unavailable)"); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java index c56528156ac..01c6b3524f1 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java @@ -27,6 +27,9 @@ import javax.swing.border.Border; import javax.swing.border.LineBorder; +import forge.deck.CommanderBracketCalculator; +import forge.deck.Deck; +import forge.game.GameType; import forge.game.card.CounterEnumType; import forge.game.player.PlayerView; import forge.game.zone.ZoneType; @@ -34,7 +37,9 @@ import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; import forge.gui.framework.IVDoc; +import forge.localinstance.properties.ForgePreferences.FPref; import forge.localinstance.skin.FSkinProp; +import forge.model.FModel; import forge.screens.match.CMatchUI; import forge.screens.match.controllers.CField; import forge.toolbox.FLabel; @@ -64,7 +69,10 @@ public class VField implements IVDoc { private final DragTab tab = new DragTab(Localizer.getInstance().getMessage("lblField")); // Other fields + private final CMatchUI matchUI; private final PlayerView player; + private boolean commanderBracketTooltipCalculated = false; + private String commanderBracketTooltipLine; // Top-level containers private final FScrollPane scroller = new FScrollPane(false); @@ -100,6 +108,7 @@ public class VField implements IVDoc { public VField(final CMatchUI matchUI, final EDocID id0, final PlayerView p, final boolean mirror) { this.docID = id0; + this.matchUI = matchUI; this.player = p; if (p != null) { tab.setText(Localizer.getInstance().getMessage("lblPlayField", p.getName())); } else { tab.setText(Localizer.getInstance().getMessage("lblNoPlayerForEDocID", docID.toString())); } @@ -404,8 +413,55 @@ public void updateDetails() { } final boolean highlighted = isHighlighted(); - this.avatarArea.setBorder(highlighted ? borderAvatarHighlighted : borderAvatarSimple ); + this.avatarArea.setBorder(highlighted ? borderAvatarHighlighted : borderAvatarSimple); this.avatarArea.setOpaque(highlighted); - this.avatarArea.setToolTipText(player.getDetailsHtml()); + this.avatarArea.setToolTipText(getPlayerDetailsHtml()); } + + private String getPlayerDetailsHtml() { + final String detailsHtml = player.getDetailsHtml(); + final String commanderBracketLine = getCommanderBracketTooltipLine(); + if (commanderBracketLine == null) { + return detailsHtml; + } + + final String nameSeparator = "
"; + final int insertIndex = detailsHtml.indexOf(nameSeparator); + if (insertIndex < 0) { + return detailsHtml; + } + + final int lineIndex = insertIndex + nameSeparator.length(); + return detailsHtml.substring(0, lineIndex) + + commanderBracketLine + "
" + + detailsHtml.substring(lineIndex); + } + + private String getCommanderBracketTooltipLine() { + if (commanderBracketTooltipCalculated) { + return commanderBracketTooltipLine; + } + + commanderBracketTooltipCalculated = true; + if (matchUI == null || matchUI.getGameView() == null || !matchUI.getGameView().isCommander()) { + return null; + } + final GameType gameType = matchUI.getGameView().getGameType(); + if (gameType == GameType.Adventure || gameType == GameType.AdventureEvent) { + return null; + } + final int maximumBracket = FModel.getPreferences().getPrefInt(FPref.DECKGEN_MAXIMUM_COMMANDER_BRACKET); + if (maximumBracket < 1 || maximumBracket > 4) { + return null; + } + final Deck deck = matchUI.getGameView().getDeck(player); + if (deck == null) { + return null; + } + + commanderBracketTooltipLine = Localizer.getInstance().getMessage("lblBracket") + + ": " + CommanderBracketCalculator.getBracket(deck); + return commanderBracketTooltipLine; + } + } diff --git a/forge-gui/src/main/java/forge/deck/CommanderBracketCalculator.java b/forge-gui/src/main/java/forge/deck/CommanderBracketCalculator.java index 52a23af7d7e..6d885553705 100644 --- a/forge-gui/src/main/java/forge/deck/CommanderBracketCalculator.java +++ b/forge-gui/src/main/java/forge/deck/CommanderBracketCalculator.java @@ -20,6 +20,7 @@ public final class CommanderBracketCalculator { private static final String EARLY_GAME = "early_game"; private static final Data DATA = new Data(); + private static final Localizer localizer = Localizer.getInstance(); private CommanderBracketCalculator() { } @@ -29,7 +30,12 @@ public static Result calculate(final Deck deck) { return Result.empty(); } - final Set deckCards = getDeckCardNames(deck); + return calculate(getDeckCardNames(deck)); + } + + public static Result calculate(final Set cardNames) { + final Set deckCards = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + deckCards.addAll(cardNames); final List gamechangers = DATA.findCards(deckCards, DATA.gamechangers); final List massLandDenial = DATA.findCards(deckCards, DATA.massLandDenial); final List extraTurns = DATA.findCards(deckCards, DATA.extraTurns); @@ -73,10 +79,6 @@ public static int getBracket(final Deck deck) { return calculate(deck).getBracket(); } - public static String getDisplayBracket(final Deck deck) { - return String.valueOf(getBracket(deck)); - } - public static String getExplanation(final Deck deck) { return calculate(deck).toExplanation(); } @@ -89,6 +91,14 @@ private static Set getDeckCardNames(final Deck deck) { return result; } + public static Set getCardNames(final PaperCard card) { + final Set result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + if (card != null) { + result.addAll(card.getAllSearchableNames()); + } + return result; + } + private static String normalize(final String text) { return text.trim().toLowerCase(Locale.ROOT); } @@ -217,28 +227,26 @@ public int getBracket() { } public String toExplanation() { - final Localizer localizer = Localizer.getInstance(); final StringBuilder sb = new StringBuilder(); sb.append(localizer.getMessage("lblCommanderBracketMinimum", bracket)).append("\n\n"); - appendCards(sb, localizer, localizer.getMessage("lblCommanderBracketGameChangers"), gamechangers, + appendCards(sb, localizer.getMessage("lblCommanderBracketGameChangers"), gamechangers, gamechangers.size() >= 4 ? localizer.getMessage("lblCommanderBracketReasonGameChangersFour") : !gamechangers.isEmpty() ? localizer.getMessage("lblCommanderBracketReasonGameChangersOne") : null); - appendCards(sb, localizer, localizer.getMessage("lblCommanderBracketMassLandDenial"), massLandDenial, + appendCards(sb, localizer.getMessage("lblCommanderBracketMassLandDenial"), massLandDenial, massLandDenial.isEmpty() ? null : localizer.getMessage("lblCommanderBracketReasonMassLandDenial")); - appendCards(sb, localizer, localizer.getMessage("lblCommanderBracketExtraTurns"), extraTurns, + appendCards(sb, localizer.getMessage("lblCommanderBracketExtraTurns"), extraTurns, extraTurns.size() >= 4 ? localizer.getMessage("lblCommanderBracketReasonExtraTurnsFour") : extraTurns.size() >= 3 ? localizer.getMessage("lblCommanderBracketReasonExtraTurnsThree") : extraTurns.size() >= 2 ? localizer.getMessage("lblCommanderBracketReasonExtraTurnsTwo") : extraTurns.isEmpty() ? null : localizer.getMessage("lblCommanderBracketReasonExtraTurnsFew")); - appendCards(sb, localizer, localizer.getMessage("lblCommanderBracketChainedExtraTurns"), chainedExtraTurns, + appendCards(sb, localizer.getMessage("lblCommanderBracketChainedExtraTurns"), chainedExtraTurns, chainedExtraTurns.isEmpty() ? null : localizer.getMessage("lblCommanderBracketReasonChainedExtraTurn")); - appendCombos(sb, localizer, lateGameCombos, earlyGameCombos); + appendCombos(sb, lateGameCombos, earlyGameCombos); return sb.toString(); } - private static void appendCards(final StringBuilder sb, final Localizer localizer, - final String title, final List cards, final String reason) { + private static void appendCards(final StringBuilder sb, final String title, final List cards, final String reason) { sb.append(title).append("\n"); if (cards.isEmpty()) { sb.append(" ").append(localizer.getMessage("lblNone")).append("\n"); @@ -254,8 +262,7 @@ private static void appendCards(final StringBuilder sb, final Localizer localize sb.append("\n"); } - private static void appendCombos(final StringBuilder sb, final Localizer localizer, - final List lateGameCombos, final List earlyGameCombos) { + private static void appendCombos(final StringBuilder sb, final List lateGameCombos, final List earlyGameCombos) { sb.append(localizer.getMessage("lblCommanderBracketTwoCardCombos")).append("\n"); if (lateGameCombos.isEmpty() && earlyGameCombos.isEmpty()) { sb.append(" ").append(localizer.getMessage("lblNone")).append("\n"); diff --git a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java index e565259440c..7f0b4bf7b4d 100644 --- a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java +++ b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java @@ -24,6 +24,7 @@ import forge.item.PaperCard; import forge.item.PaperCardPredicates; import forge.itemmanager.IItemManager; +import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences.FPref; import forge.model.FModel; import forge.util.*; @@ -323,7 +324,7 @@ public static Deck buildColorDeck(List selection, Predicate f CardDb cardDb = FModel.getMagicDb().getCommonCards(); if (formatFilter == null){ if (selection.size() == 1) { - gen = new DeckGeneratorMonoColor(cardDb, DeckFormat.Constructed,selection.get(0)); + gen = new DeckGeneratorMonoColor(cardDb, DeckFormat.Constructed, selection.get(0)); } else if (selection.size() == 2) { gen = new DeckGenerator2Color(cardDb, DeckFormat.Constructed,selection.get(0), selection.get(1)); @@ -648,10 +649,13 @@ public static Deck generateCommanderDeck(boolean forAi, GameType gameType) { return generateRandomCommanderDeck(commander, format, forAi, false); } - /** Generate a ramdom Commander deck. */ public static Deck generateRandomCommanderDeck(PaperCard commander, DeckFormat format, boolean forAi, boolean isCardGen) { + return generateRandomCommanderDeck(commander, format, forAi, isCardGen, FModel.getPreferences().getPrefInt(ForgePreferences.FPref.DECKGEN_MAXIMUM_COMMANDER_BRACKET)); + } + + /** Generate a random Commander deck with an optional maximum bracket. */ + public static Deck generateRandomCommanderDeck(PaperCard commander, DeckFormat format, boolean forAi, boolean isCardGen, int maxBracket) { final Deck deck; - IDeckGenPool cardDb; DeckGeneratorBase gen = null; PaperCard selectedPartner = null; List preSelectedCards = new ArrayList<>(); @@ -682,6 +686,7 @@ public static Deck generateRandomCommanderDeck(PaperCard commander, DeckFormat f } } + preSelectedCards = limitCardsToCommanderBracket(preSelectedCards, commander, null, maxBracket); if (format.equals(DeckFormat.Oathbreaker)) { //pass signature spell as partner for simplicity selectedPartner = getRandomSignatureSpell(preSelectedCards); @@ -714,39 +719,43 @@ else if (commander.getRules().canBePartnerCommander()) { } preSelectedCards.removeAll(toRemove); preSelectedCards.removeAll(StaticData.instance().getCommonCards().getAllCards(commander)); + preSelectedCards = limitCardsToCommanderBracket(preSelectedCards, commander, selectedPartner, maxBracket); gen = new CardThemedCommanderDeckBuilder(commander, selectedPartner, preSelectedCards, forAi, format); }else{ - cardDb = FModel.getMagicDb().getCommonCards(); - //shuffle first 400 random cards + IDeckGenPool cardDb = FModel.getMagicDb().getCommonCards(); Iterable colorList = IterableUtil.filter(format.getCardPool(cardDb).getAllCards(), format.isLegalCardPredicate().and(PaperCardPredicates.fromRules( new CardThemedDeckBuilder.MatchColorIdentity(commander.getRules().getColorIdentity()) .or(DeckGeneratorBase.COLORLESS_CARDS)))); - switch (format) { - case Brawl: //for Brawl - add additional filterprinted rule to remove old reprints for a consistent look + if (format == DeckFormat.Brawl) { + // add additional filterprinted rule to remove old reprints for a consistent look colorList = IterableUtil.filter(colorList,FModel.getFormats().getStandard().getFilterPrinted()); - break; + } + //shuffle first 400 random cards + List cardList = Lists.newArrayList(colorList); + Collections.shuffle(cardList, MyRandom.getRandom()); + int shortlistlength=400; + if(cardList.size() shortList = new ArrayList<>(cardList.subList(0, shortlistlength)); + shortList = limitCardsToCommanderBracket(shortList, commander, null, maxBracket); + switch (format) { case Oathbreaker: //pass signature spell as partner for simplicity - selectedPartner = getRandomSignatureSpell(colorList); + selectedPartner = getRandomSignatureSpell(shortList); break; default: if (commander.getRules().canBePartnerCommander()) { - selectedPartner = getRandomPartnerCommander(colorList, commander); + selectedPartner = getRandomPartnerCommander(shortList, commander); } break; } - List cardList = Lists.newArrayList(colorList); - Collections.shuffle(cardList, MyRandom.getRandom()); - int shortlistlength=400; - if(cardList.size() shortList = cardList.subList(0, shortlistlength); - shortList.removeAll(StaticData.instance().getCommonCards().getAllCards(commander)); if (selectedPartner != null) { shortList.removeAll(StaticData.instance().getCommonCards().getAllCards(selectedPartner)); + shortList = limitCardsToCommanderBracket(shortList, commander, selectedPartner, maxBracket); } + shortList.removeAll(StaticData.instance().getCommonCards().getAllCards(commander)); gen = new CardThemedCommanderDeckBuilder(commander, selectedPartner, shortList, forAi, format); } @@ -771,6 +780,29 @@ else if (commander.getRules().canBePartnerCommander()) { return deck; } + private static List limitCardsToCommanderBracket(final List cards, + final PaperCard commander, final PaperCard selectedPartner, final int maxBracket) { + if (maxBracket < 1 || maxBracket >= 4) { + return cards; + } + + final List result = new ArrayList<>(); + Set prospectiveDeckNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + prospectiveDeckNames.addAll(CommanderBracketCalculator.getCardNames(commander)); + prospectiveDeckNames.addAll(CommanderBracketCalculator.getCardNames(selectedPartner)); + for (final PaperCard card : cards) { + final Set cardNames = CommanderBracketCalculator.getCardNames(card); + final Set candidateDeckNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + candidateDeckNames.addAll(prospectiveDeckNames); + candidateDeckNames.addAll(cardNames); + if (CommanderBracketCalculator.calculate(candidateDeckNames).getBracket() <= maxBracket) { + result.add(card); + prospectiveDeckNames = candidateDeckNames; + } + } + return result; + } + private static PaperCard getRandomSignatureSpell(final Iterable cards) { final List signatureSpells = new ArrayList<>(); for (final PaperCard card : cards) { diff --git a/forge-gui/src/main/java/forge/gamemodes/net/server/RemoteClientGuiGame.java b/forge-gui/src/main/java/forge/gamemodes/net/server/RemoteClientGuiGame.java index 6a8dadd6c6b..bced310b7de 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/server/RemoteClientGuiGame.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/server/RemoteClientGuiGame.java @@ -88,11 +88,9 @@ public boolean isLibgdxPort() { public void pause() { paused = true; } - public void resume() { paused = false; } - public boolean isPaused() { return paused; } diff --git a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java index aa7631da309..81e9c752e97 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java +++ b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java @@ -331,7 +331,7 @@ public enum ColumnDef { }, from -> { DeckProxy deck = toDeck(from.getKey()); - return deck == null ? "" : CommanderBracketCalculator.getDisplayBracket(deck.getDeck()); + return deck == null ? "" : CommanderBracketCalculator.getBracket(deck.getDeck()); }), /** * The main library size column. 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 05722e2893e..ab4ef929bda 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java @@ -254,10 +254,11 @@ public enum FPref implements PreferencesStore.IPref { PRELOAD_CUSTOM_DRAFTS ("false"), DECK_DEFAULT_CARD_LIMIT ("4"), + DECKGEN_CARDBASED ("true"), DECKGEN_SINGLETONS ("false"), DECKGEN_ARTIFACTS ("false"), DECKGEN_NOSMALL ("false"), - DECKGEN_CARDBASED ("true"), + DECKGEN_MAXIMUM_COMMANDER_BRACKET("5"), PHASE_AI_UPKEEP ("false"), PHASE_AI_DRAW ("false"), diff --git a/forge-gui/src/main/java/forge/model/FPrefsBinder.java b/forge-gui/src/main/java/forge/model/FPrefsBinder.java index 2aa23be6afb..8ef6415fdf5 100644 --- a/forge-gui/src/main/java/forge/model/FPrefsBinder.java +++ b/forge-gui/src/main/java/forge/model/FPrefsBinder.java @@ -37,6 +37,10 @@ public FPrefsBinder(ForgePreferences.FPref prefKey, this.fromString = fromString; } + public C getComponent() { + return component; + } + public void load() { ForgePreferences prefs = FModel.getPreferences(); String prefValue = prefs.getPref(prefKey);