Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -342,7 +342,6 @@ public void populate() {
public final boolean isAi() {
return isAi;
}

public void setIsAi(final boolean isAiDeck) {
isAi = isAiDeck;
}
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 Expand Up @@ -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
Expand Down
67 changes: 50 additions & 17 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,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<String> gamesInMatch = new JComboBox<String>(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<String> 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<String> 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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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() {
Expand Down Expand Up @@ -431,7 +459,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 +474,6 @@ String getCurrentModeSelection() {
int getCurrentModeIndex() {
return cboModePanel.getSelectedIndex();
}

void setCurrentModeIndex(int idx) {
cboModePanel.setSelectedIndex(idx);
}
Expand All @@ -460,7 +486,6 @@ void refreshConstructedFrame() {
boolean getConformanceSelected() {
return cbDeckConformance.isSelected();
}

void setConformanceSelected(boolean selected) {
cbDeckConformance.setSelected(selected);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1044,6 +1067,10 @@ List<String> getPlayerNames() {
return names;
}

public void markDirty() {
refreshGeneratedDecks = true;
}

/////////////////////////////////////////////
//========== Various listeners in build order

Expand All @@ -1061,6 +1088,7 @@ private VariantCheckBox(final GameType variantType) {
lobby.removeVariant(variantType);
}
VLobby.this.update(false);
VLobby.this.updateActionButtons();
});
}
}
Expand Down Expand Up @@ -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)");
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
Loading