Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
2 changes: 1 addition & 1 deletion docs/User-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion forge-game/src/main/java/forge/game/card/Card.java
Original file line number Diff line number Diff line change
Expand Up @@ -1980,7 +1980,7 @@ public final void putEtbCounters(Map<Optional<Player>, Map<CounterType, Integer>
// used for LKI
for (Map<CounterType, Integer> m : etbCounters.values()) {
for (Map.Entry<CounterType, Integer> e : m.entrySet()) {
CounterType ct = e.getKey();
CounterType ct = e.getKey();
if (canReceiveCounters(ct)) {
setCounters(ct, getCounters(ct) + e.getValue());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
private final FLabel btnRandom = new FLabel.ButtonBuilder().fontSize(14).build();

private boolean isAi;
private int maximumCommanderBracket = 5;

private final ForgePreferences prefs = FModel.getPreferences();
private FPref stateSetting = null;
Expand Down Expand Up @@ -298,9 +299,21 @@ public Deck getDeck() {
if (proxy == null) {
return null;
}
if (isGeneratedCommanderDeckType() && proxy instanceof CommanderDeckGenerator commanderDeckGenerator) {
return commanderDeckGenerator.getDeck(maximumCommanderBracket);
}
return proxy.getDeck();
}

private boolean isGeneratedCommanderDeckType() {
return selectedDeckType == DeckType.RANDOM_COMMANDER_DECK
|| selectedDeckType == DeckType.RANDOM_CARDGEN_COMMANDER_DECK;
}

public void setMaximumCommanderBracket(final int maximumCommanderBracket0) {
maximumCommanderBracket = maximumCommanderBracket0;
}

/** Generates deck from current list selection(s). */
public RegisteredPlayer getPlayer() {
if (lstDecks.getSelectedIndex() < 0) { return null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
97 changes: 83 additions & 14 deletions forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -62,7 +67,11 @@ 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 JPanel pnlStart = new JPanel(new MigLayout("insets 0, gap 0, wrap 3"));
private final JComboBox<String> maximumCommanderBracket = createMaximumCommanderBracketCombo();
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<String> gamesInMatch = new JComboBox<String>(new String[] {"1","3","5"});
private final SwingPrefBinders.ComboBox gamesInMatchBinder =
new SwingPrefBinders.ComboBox(FPref.UI_MATCHES_PER_GAME, gamesInMatch);
Expand Down Expand Up @@ -147,7 +156,12 @@ public class VLobby implements ILobbyView {

// (network draft state lives in CLobby)

// CTR
private static JComboBox<String> createMaximumCommanderBracketCombo() {
final JComboBox<String> comboBox = new JComboBox<>(new String[]{"1", "2", "3", "4", "5"});
comboBox.setSelectedItem(prefs.getPref(FPref.DECKGEN_MAXIMUM_COMMANDER_BRACKET));
return comboBox;
}

public VLobby(final GameLobby lobby) {
this.lobby = lobby;
// Create controller first — VLobby.update() and render methods rely on a non-null
Expand Down Expand Up @@ -260,9 +274,14 @@ public VLobby(final GameLobby lobby) {
// Start Button
if (lobby.hasControl()) {
pnlStart.setOpaque(false);
pnlStart.add(btnStart, "align center");
maximumCommanderBracket.addActionListener(e -> applyMaximumCommanderBracketToDeckChoosers());
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 -> {
updateCommanderGeneratedDeckSelections();
Comment thread
tool4ever marked this conversation as resolved.
Outdated
Runnable startGame = lobby.startGame();
if (startGame != null) {
startGame.run();
Expand All @@ -287,11 +306,28 @@ 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 int getMaximumCommanderBracket() {
return Integer.parseInt((String) maximumCommanderBracket.getSelectedItem());
}

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() {
Expand Down Expand Up @@ -431,7 +467,6 @@ public void update(final boolean fullUpdate) {
public void setController(final CLobby controller) {
this.controller = controller;
}

public CLobby getController() {
return controller;
}
Expand All @@ -447,7 +482,6 @@ String getCurrentModeSelection() {
int getCurrentModeIndex() {
return cboModePanel.getSelectedIndex();
}

void setCurrentModeIndex(int idx) {
cboModePanel.setSelectedIndex(idx);
}
Expand All @@ -473,7 +507,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);
Expand Down Expand Up @@ -587,12 +620,13 @@ private FDeckChooser getDeckChooser(final int iSlot) {
}

private void selectMainDeck(final FDeckChooser mainChooser, final int playerIndex, final boolean isCommanderDeck) {
final DeckType type = mainChooser.getSelectedDeckType();
mainChooser.setMaximumCommanderBracket(getMaximumCommanderBracket());
final Deck deck = mainChooser.getDeck();
// something went wrong, clear selection to prevent error loop
if (deck == null) {
mainChooser.getLstDecks().setSelectedIndex(0);
}
final DeckType type = mainChooser.getSelectedDeckType();
final Collection<DeckProxy> selectedDecks = mainChooser.getLstDecks().getSelectedItems();
if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) {
final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy::getName);
Expand All @@ -606,6 +640,37 @@ private void selectMainDeck(final FDeckChooser mainChooser, final int playerInde
mainChooser.saveState();
}

private void updateCommanderGeneratedDeckSelections() {
final int maximumBracket = getMaximumCommanderBracket();
for (int i = 0; i < activePlayersNum && i < playerPanels.size(); i++) {
if (!lobby.mayEdit(i)) {
continue;
}
final FDeckChooser deckChooser = getDeckChooser(i);
if (deckChooser == null) {
continue;
}
deckChooser.setMaximumCommanderBracket(maximumBracket);
if (isGeneratedCommanderDeckType(deckChooser.getSelectedDeckType())) {
selectMainDeck(deckChooser, i, true);
}
}
}

private void applyMaximumCommanderBracketToDeckChoosers() {
final int maximumBracket = getMaximumCommanderBracket();
for (int i = 0; i < activePlayersNum && i < playerPanels.size(); i++) {
final FDeckChooser deckChooser = getDeckChooser(i);
if (deckChooser != null) {
deckChooser.setMaximumCommanderBracket(maximumBracket);
}
}
}

private static boolean isGeneratedCommanderDeckType(final DeckType type) {
return type == DeckType.RANDOM_COMMANDER_DECK || type == DeckType.RANDOM_CARDGEN_COMMANDER_DECK;
}

private void selectSchemeDeck(final int playerIndex) {
if (playerIndex >= activePlayersNum || !(hasVariant(GameType.Archenemy) || hasVariant(GameType.ArchenemyRumble))) {
return;
Expand Down Expand Up @@ -888,10 +953,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.
Expand Down Expand Up @@ -1061,6 +1123,7 @@ private VariantCheckBox(final GameType variantType) {
lobby.removeVariant(variantType);
}
VLobby.this.update(false);
VLobby.this.updateActionButtons();
});
}
}
Expand Down Expand Up @@ -1099,6 +1162,7 @@ private FDeckChooser createDeckChooser(final GameType type, final int iSlot, fin
return cachedDeckChoosers.computeIfAbsent(prefKey, (key) -> {
final GameType gameType = forCommander ? type : GameType.Constructed;
final FDeckChooser fdc = new FDeckChooser(null, ai, gameType, forCommander);
fdc.setMaximumCommanderBracket(getMaximumCommanderBracket());
fdc.initialize(prefKey, deckType);
fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, forCommander));
return fdc;
Expand Down Expand Up @@ -1159,6 +1223,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)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,19 @@
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;
import forge.gui.framework.DragCell;
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;
Expand Down Expand Up @@ -64,7 +69,10 @@ public class VField implements IVDoc<CField> {
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);
Expand Down Expand Up @@ -100,6 +108,7 @@ public class VField implements IVDoc<CField> {
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())); }
Expand Down Expand Up @@ -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());
Comment thread
tool4ever marked this conversation as resolved.
}

private String getPlayerDetailsHtml() {
final String detailsHtml = player.getDetailsHtml();
final String commanderBracketLine = getCommanderBracketTooltipLine();
if (commanderBracketLine == null) {
return detailsHtml;
}

final String nameSeparator = "<hr/>";
final int insertIndex = detailsHtml.indexOf(nameSeparator);
if (insertIndex < 0) {
return detailsHtml;
}

final int lineIndex = insertIndex + nameSeparator.length();
return detailsHtml.substring(0, lineIndex)
+ commanderBracketLine + "<br/>"
+ 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;
}

}
Loading