foul-magics/Mage.Client/src/main/java/mage/client/game/GamePanel.java
Oleg Agafonov b40e7222b3 Dungeon improves:
* Dungeons: added dungeon name hint to room's game log and choices (part of #12274);
* GUI, game: added card popup hints support in feedback panel (yes/no choices);
* Images: fixed miss images for dungeons in command zone, game logs and choice dialogs;
2024-09-19 13:42:23 +04:00

3254 lines
140 KiB
Java

package mage.client.game;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import mage.cards.Card;
import mage.cards.MageCard;
import mage.cards.action.ActionCallback;
import mage.choices.Choice;
import mage.client.MageFrame;
import mage.client.SessionHandler;
import mage.client.cards.BigCard;
import mage.client.chat.ChatPanelBasic;
import mage.client.combat.CombatManager;
import mage.client.components.HoverButton;
import mage.client.components.KeyboundButton;
import mage.client.components.MageComponents;
import mage.client.components.ext.dlg.DialogManager;
import mage.client.components.tray.MageTray;
import mage.client.dialog.*;
import mage.client.dialog.CardInfoWindowDialog.ShowType;
import mage.client.game.FeedbackPanel.FeedbackMode;
import mage.client.plugins.adapters.MageActionCallback;
import mage.client.plugins.impl.Plugins;
import mage.client.util.Event;
import mage.client.util.*;
import mage.client.util.audio.AudioManager;
import mage.client.util.gui.ArrowBuilder;
import mage.client.util.gui.MageDialogState;
import mage.constants.*;
import mage.game.events.PlayerQueryEvent;
import mage.players.PlayableObjectStats;
import mage.players.PlayableObjectsList;
import mage.util.DebugUtil;
import mage.util.MultiAmountMessage;
import mage.view.*;
import org.apache.log4j.Logger;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import javax.swing.Timer;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyVetoException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.List;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import static mage.client.dialog.PreferencesDialog.*;
import static mage.constants.PlayerAction.*;
/**
* Game GUI: main game panel with all controls
*
* @author BetaSteward_at_googlemail.com, nantuko8, JayDi85
*/
public final class GamePanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(GamePanel.class);
private static final String YOUR_HAND = "Your hand";
private static final int SKIP_BUTTONS_SPACE_H = 3;
private static final int SKIP_BUTTONS_SPACE_V = 3;
private static final int PHASE_BUTTONS_SPACE_H = 3;
private static final int PHASE_BUTTONS_SPACE_V = 3;
private static final String CMD_AUTO_ORDER_FIRST = "cmdAutoOrderFirst";
private static final String CMD_AUTO_ORDER_LAST = "cmdAutoOrderLast";
private static final String CMD_AUTO_ORDER_NAME_FIRST = "cmdAutoOrderNameFirst";
private static final String CMD_AUTO_ORDER_NAME_LAST = "cmdAutoOrderNameLast";
private static final String CMD_AUTO_ORDER_RESET_ALL = "cmdAutoOrderResetAll";
// on window resize: 0.0 keep left size (hand), 1.0 keep right size (stack), 0.5 keep proportion
private static final double DIVIDER_KEEP_LEFT_COMPONENT = 0.0;
private static final double DIVIDER_KEEP_RIGHT_COMPONENT = 1.0;
private static final double DIVIDER_KEEP_PROPORTION = 0.5;
private static final int DIVIDER_POSITION_DEFAULT = -1;
private static final int DIVIDER_POSITION_HIDDEN_LEFT_OR_TOP = -2;
private static final int DIVIDER_POSITION_HIDDEN_RIGHT_OR_BOTTOM = -3;
private final Map<UUID, PlayAreaPanel> players = new LinkedHashMap<>();
private final Map<UUID, Boolean> playersWhoLeft = new LinkedHashMap<>();
// non modal frames
private final Map<UUID, CardInfoWindowDialog> exiles = new HashMap<>();
private final Map<String, CardInfoWindowDialog> revealed = new HashMap<>();
private final Map<String, CardInfoWindowDialog> lookedAt = new HashMap<>();
private final Map<String, CardsView> graveyards = new HashMap<>(); // need to sync selection
private final Map<String, CardInfoWindowDialog> graveyardWindows = new HashMap<>();
private final Map<String, CardInfoWindowDialog> companion = new HashMap<>();
private final Map<String, CardsView> sideboards = new HashMap<>(); // need to sync selection
private final Map<String, CardInfoWindowDialog> sideboardWindows = new HashMap<>();
private final ArrayList<ShowCardsDialog> pickTarget = new ArrayList<>();
private final ArrayList<PickPileDialog> pickPile = new ArrayList<>();
private final Map<String, CardHintsHelperDialog> cardHintsWindows = new LinkedHashMap<>();
private UUID currentTableId;
private UUID parentTableId;
private UUID gameId;
private UUID playerId; // playerId of the player
GamePane gamePane;
private ReplayTask replayTask;
private final PickNumberDialog pickNumber;
private final PickMultiNumberDialog pickMultiNumber;
private JLayeredPane jLayeredPane;
private String chosenHandKey = "You";
private final skipButtonsList skipButtons = new skipButtonsList();
private boolean menuNameSet = false;
private boolean handCardsOfOpponentAvailable = false;
private final Map<String, Card> loadedCards = new HashMap<>();
private int storedHeight;
private final Map<String, HoverButton> phaseButtons = new LinkedHashMap<>(); // phase name, phase button
// splitters with save and restore feature
// TODO: use same logic for draft and deck editor splitters like SplitterManager
private final Map<String, MageSplitter> splitters = new LinkedHashMap<>(); // settings key, splitter
// do not save splitters in intermediate state, e.g. connection to new server with active game
private boolean isSplittersFullyRestored = false;
public static class MageSplitter {
JSplitPane splitPane;
double defaultProportion;
MageSplitter(JSplitPane splitPane, double defaultProportion) {
this.splitPane = splitPane;
this.defaultProportion = defaultProportion;
}
}
private MageDialogState choiceWindowState;
private boolean initComponents;
private Timer resizeTimer; // can't be final
private enum PopUpMenuType {
TRIGGER_ORDER
}
// CardView popupMenu was invoked last
private CardView cardViewPopupMenu;
// popup menu for triggered abilities order
private JPopupMenu popupMenuTriggerOrder;
// keep game data for updates/re-draws
public static class LastGameData {
int messageId;
GameView game;
boolean showPlayable;
Map<String, Serializable> options;
Set<UUID> targets;
Map<UUID, CardView> allCardsIndex = new HashMap<>(); // fast access to all game objects (for cards hints, etc)
private void setNewGame(GameView game) {
this.game = game;
prepareAllCardsIndex();
}
private void prepareAllCardsIndex() {
this.allCardsIndex.clear();
if (this.game == null) {
return;
}
this.game.getMyHand().values().forEach(c -> this.allCardsIndex.put(c.getId(), c));
this.game.getMyHelperEmblems().values().forEach(c -> this.allCardsIndex.put(c.getId(), c));
this.game.getStack().values().forEach(c -> this.allCardsIndex.put(c.getId(), c));
this.game.getExile()
.stream()
.flatMap(s -> s.values().stream())
.forEach(c -> this.allCardsIndex.put(c.getId(), c));
this.game.getLookedAt()
.stream()
.flatMap(s -> s.getCards().values().stream())
.filter(c -> c instanceof CardView)
.map(c -> (CardView) c)
.forEach(c -> this.allCardsIndex.put(c.getId(), c));
this.game.getRevealed().stream()
.flatMap(s -> s.getCards().values().stream())
.forEach(c -> this.allCardsIndex.put(c.getId(), c));
this.game.getPlayers().forEach(player -> {
player.getBattlefield().values().forEach(c -> this.allCardsIndex.put(c.getId(), c));
player.getGraveyard().values().forEach(c -> this.allCardsIndex.put(c.getId(), c));
Optional.ofNullable(player.getTopCard()).ifPresent(c -> this.allCardsIndex.put(c.getId(), c));
// TODO: add support of dungeon, emblem all another non-card objects
// commanders and custom emblems
player.getCommandObjectList()
.forEach(object -> {
if (object instanceof CardView) {
// commanders and custom emblems
this.allCardsIndex.put(object.getId(), (CardView) object);
} else if (object instanceof DungeonView) {
// dungeons
this.allCardsIndex.put(object.getId(), new CardView((DungeonView) object));
} else {
// TODO: enable after all view types added here?
//throw new IllegalArgumentException("Unsupported object type: " + object.getName() + " - " + object.getClass().getSimpleName());
}
});
});
}
public CardView findCard(UUID id) {
return this.allCardsIndex.getOrDefault(id, null);
}
}
private final LastGameData lastGameData = new LastGameData();
public LastGameData getLastGameData() {
return this.lastGameData;
}
public GamePanel() {
initComponents = true;
initComponents();
// prepare command panels (feedback, hand, skip buttons and stack)
if (DebugUtil.GUI_GAME_DRAW_COMMANDS_PANEL_BORDER) {
pnlHelperHandButtonsStackArea.setBorder(BorderFactory.createLineBorder(Color.MAGENTA));
}
// all game panels
pnlHelperHandButtonsStackArea.removeAll();
pnlHelperHandButtonsStackArea.setLayout(new BorderLayout());
// battlefields + phases
JPanel pnlBattlefieldAndPhases = new JPanel(new BorderLayout());
pnlBattlefieldAndPhases.setOpaque(false);
pnlBattlefieldAndPhases.add(pnlBattlefield, BorderLayout.CENTER);
pnlBattlefieldAndPhases.add(phasesContainer, BorderLayout.EAST);
pnlHelperHandButtonsStackArea.add(pnlBattlefieldAndPhases, BorderLayout.CENTER);
// commands (feedback + hand + skip + stack)
JPanel pnlCommandsRoot = new JPanel(new BorderLayout());
pnlCommandsRoot.setOpaque(false);
// ... feedback + hand
JPanel pnlCommandsFeedbackAndHand = new JPanel(new BorderLayout());
pnlCommandsFeedbackAndHand.setOpaque(false);
pnlCommandsFeedbackAndHand.add(feedbackPanel, BorderLayout.NORTH);
pnlCommandsFeedbackAndHand.add(handContainer, BorderLayout.CENTER);
// ... skip + stack
JPanel pnlCommandsSkipAndStack = new JPanel(new BorderLayout());
pnlCommandsSkipAndStack.setOpaque(false);
pnlCommandsSkipAndStack.add(pnlShortCuts, BorderLayout.NORTH);
pnlCommandsSkipAndStack.add(stackObjects, BorderLayout.CENTER);
// ... split: feedback + hand <|> skip + stack
splitHandAndStack.setLeftComponent(pnlCommandsFeedbackAndHand);
splitHandAndStack.setRightComponent(pnlCommandsSkipAndStack);
splitHandAndStack.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT);
pnlCommandsFeedbackAndHand.setMinimumSize(new Dimension(0, 0)); // allow any sizes for hand
pnlCommandsSkipAndStack.setMinimumSize(new Dimension(0, 0)); // allow any sizes for stack
// ... all
pnlCommandsRoot.add(splitHandAndStack, BorderLayout.CENTER);
pnlHelperHandButtonsStackArea.add(pnlCommandsRoot, BorderLayout.SOUTH);
// prepare commands buttons panel with flow layout (instead custom from IDE)
// size changes in helper method at the end
if (DebugUtil.GUI_GAME_DRAW_SKIP_BUTTONS_PANEL_BORDER) {
pnlShortCuts.setBorder(BorderFactory.createLineBorder(Color.red));
}
pnlShortCuts.removeAll();
pnlShortCuts.setLayout(null); // real layout on size settings
pnlShortCuts.add(btnSkipToNextTurn);
pnlShortCuts.add(btnSkipToEndTurn);
pnlShortCuts.add(btnSkipToNextMain);
pnlShortCuts.add(btnSkipToYourTurn);
pnlShortCuts.add(btnSkipStack);
pnlShortCuts.add(btnSkipToEndStepBeforeYourTurn);
pnlShortCuts.add(txtHoldPriority);
//pnlShortCuts.add(btnToggleMacro);
pnlShortCuts.add(btnSwitchHands);
pnlShortCuts.add(btnCancelSkip);
pnlShortCuts.add(btnConcede);
pnlShortCuts.add(btnStopWatching);
pickNumber = new PickNumberDialog();
MageFrame.getDesktop().add(pickNumber, pickNumber.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
pickMultiNumber = new PickMultiNumberDialog();
MageFrame.getDesktop().add(pickMultiNumber, pickMultiNumber.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
this.feedbackPanel.setConnectedChatPanel(this.userChatPanel);
// Override layout (I can't edit generated code)
// TODO: research - why it used all that panels on the root
this.setLayout(new BorderLayout());
final JLayeredPane jLayeredBackgroundPane = new JLayeredPane();
jLayeredBackgroundPane.setSize(1024, 768);
this.add(jLayeredBackgroundPane);
jLayeredBackgroundPane.add(splitGameAndBigCard, JLayeredPane.DEFAULT_LAYER);
Map<String, JComponent> myUi = getUIComponents(jLayeredBackgroundPane);
Plugins.instance.updateGamePanel(myUi);
// Enlarge jlayeredpane on resize of game panel
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
int width = ((JComponent) e.getSource()).getWidth();
int height = ((JComponent) e.getSource()).getHeight();
jLayeredBackgroundPane.setSize(width, height);
splitGameAndBigCard.setSize(width, height);
if (height < storedHeight) {
// TODO: wtf, is it needs? Research and delete that code with storedHeight
pnlBattlefield.setSize(0, 200);
}
storedHeight = height;
sizeToScreen();
}
});
bigCardPanel.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
// TODO: on panel resize card don't want to redraw until new mouse move over card
sizeBigCard();
}
});
// Resize the width of the stack area if the size of the play area is changed
ComponentAdapter componentAdapterPlayField = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (initComponents) {
return;
}
if (resizeTimer.isRunning()) {
resizeTimer.restart();
} else {
resizeTimer.start();
}
}
};
resizeTimer = new Timer(1000, evt -> SwingUtilities.invokeLater(() -> {
if (initComponents) {
return;
}
resizeTimer.stop();
setGUISize(false);
feedbackPanel.changeGUISize();
}));
pnlHelperHandButtonsStackArea.addComponentListener(componentAdapterPlayField);
initComponents = false;
setGUISize(true);
}
private Map<String, JComponent> getUIComponents(JLayeredPane jLayeredPane) {
Map<String, JComponent> components = new HashMap<>();
components.put("splitChatAndLogs", splitChatAndLogs);
components.put("splitHandAndStack", splitHandAndStack);
components.put("splitBattlefieldAndChats", splitBattlefieldAndChats);
components.put("splitGameAndBigCard", splitGameAndBigCard);
components.put("pnlBattlefield", pnlBattlefield);
components.put("pnlHelperHandButtonsStackArea", pnlHelperHandButtonsStackArea);
components.put("hand", handContainer);
components.put("gameChatPanel", gameChatPanel);
components.put("userChatPanel", userChatPanel);
components.put("jLayeredPane", jLayeredPane);
components.put("gamePanel", this);
return components;
}
public void cleanUp() {
MageFrame.removeGame(gameId);
this.gameChatPanel.cleanUp();
this.userChatPanel.cleanUp();
this.removeListener();
this.handContainer.cleanUp();
this.stackObjects.cleanUp();
for (Map.Entry<UUID, PlayAreaPanel> playAreaPanelEntry : players.entrySet()) {
playAreaPanelEntry.getValue().CleanUp();
}
this.players.clear();
this.playersWhoLeft.clear();
uninstallComponents();
if (pickNumber != null) {
pickNumber.removeDialog();
}
if (pickMultiNumber != null) {
pickMultiNumber.removeDialog();
}
for (CardInfoWindowDialog windowDialog : exiles.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog windowDialog : revealed.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog windowDialog : companion.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
clearPickDialogs();
Plugins.instance.getActionCallback().hideOpenComponents();
try {
Component popupContainer = MageFrame.getUI().getComponent(MageComponents.POPUP_CONTAINER);
popupContainer.setVisible(false);
} catch (InterruptedException ex) {
logger.fatal("popupContainer error:", ex);
}
}
private void hidePickDialogs() {
// temporary hide opened dialog on redraw/update
for (ShowCardsDialog dialog : this.pickTarget) {
dialog.setVisible(false);
}
for (PickPileDialog dialog : this.pickPile) {
dialog.setVisible(false);
}
}
private void clearPickDialogs() {
// remove dialogs forever on clean or full update
clearPickTargetDialogs();
clearPickPileDialogs();
}
private void clearPickTargetDialogs() {
for (ShowCardsDialog dialog : this.pickTarget) {
dialog.cleanUp();
dialog.removeDialog();
}
this.pickTarget.clear();
}
private void clearPickPileDialogs() {
for (PickPileDialog dialog : this.pickPile) {
dialog.cleanUp();
dialog.removeDialog();
}
this.pickPile.clear();
}
public void changeGUISize() {
initComponents = true;
setGUISize(true);
stackObjects.changeGUISize();
feedbackPanel.changeGUISize();
handContainer.changeGUISize();
for (PlayAreaPanel playAreaPanel : players.values()) {
playAreaPanel.changeGUISize();
}
for (CardInfoWindowDialog windowDialog : exiles.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog windowDialog : revealed.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog windowDialog : companion.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
windowDialog.changeGUISize();
}
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
windowDialog.changeGUISize();
}
for (ShowCardsDialog windowDialog : pickTarget) {
windowDialog.changeGUISize();
}
for (PickPileDialog windowDialog : pickPile) {
windowDialog.changeGUISize();
}
this.revalidate();
this.repaint();
initComponents = false;
}
private void setGUISize(boolean themeReload) {
splitters.values().forEach(splitter -> {
splitter.splitPane.setDividerSize(GUISizeHelper.dividerBarSize);
});
txtHoldPriority.setFont(new Font(GUISizeHelper.gameFeedbackPanelFont.getFontName(), Font.BOLD, GUISizeHelper.gameFeedbackPanelFont.getSize()));
GUISizeHelper.changePopupMenuFont(popupMenuTriggerOrder);
// commands panel
// hand <|> stack
int upperPanelsHeight = getSkipButtonsPanelDefaultHeight();
feedbackPanel.setPreferredSize(new Dimension(Short.MAX_VALUE, upperPanelsHeight));
feedbackPanel.setMaximumSize(new Dimension(Short.MAX_VALUE, upperPanelsHeight));
pnlShortCuts.setPreferredSize(new Dimension(500, upperPanelsHeight));
pnlShortCuts.setMaximumSize(new Dimension(500, upperPanelsHeight));
// stack
stackObjects.setCardDimension(GUISizeHelper.handCardDimension);
stackObjects.changeGUISize(); // must call to cards fit
// game logs and chat
userChatPanel.changeGUISize(GUISizeHelper.chatFont);
gameChatPanel.changeGUISize(GUISizeHelper.chatFont);
// skip buttons - sizes
// must be able to put controls in 2 rows
float guiScale = GUISizeHelper.dialogGuiScale;
int hGap = GUISizeHelper.guiSizeScale(SKIP_BUTTONS_SPACE_H, guiScale);
int vGap = GUISizeHelper.guiSizeScale(SKIP_BUTTONS_SPACE_V, guiScale);
pnlShortCuts.setLayout(new FlowLayout(FlowLayout.RIGHT, hGap, vGap));
// skip buttons - sizes
Dimension strictSize = new Dimension(2 * GUISizeHelper.gameCommandButtonHeight, GUISizeHelper.gameCommandButtonHeight);
setSkipButtonSize(btnCancelSkip, guiScale, strictSize);
setSkipButtonSize(btnSkipToNextTurn, guiScale, strictSize);
setSkipButtonSize(btnSkipToEndTurn, guiScale, strictSize);
setSkipButtonSize(btnSkipToEndStepBeforeYourTurn, guiScale, strictSize);
setSkipButtonSize(btnSkipToYourTurn, guiScale, strictSize);
setSkipButtonSize(btnSkipToNextMain, guiScale, strictSize);
setSkipButtonSize(btnSkipStack, guiScale, strictSize);
setSkipButtonSize(btnConcede, guiScale, strictSize);
setSkipButtonSize(btnToggleMacro, guiScale, strictSize);
setSkipButtonSize(btnSwitchHands, guiScale, strictSize);
setSkipButtonSize(btnStopWatching, guiScale, strictSize);
pnlShortCuts.invalidate();
// phase buttons - sizes
int buttonSize = GUISizeHelper.gamePhaseButtonSize;
guiScale = GUISizeHelper.dialogGuiScale;
hGap = GUISizeHelper.guiSizeScale(PHASE_BUTTONS_SPACE_H, guiScale);
vGap = GUISizeHelper.guiSizeScale(PHASE_BUTTONS_SPACE_V, guiScale);
BoxLayout layout = new BoxLayout(jPhases, BoxLayout.Y_AXIS);
jPhases.setLayout(layout);
int fullPhaseWidth = Math.round(1.5f * GUISizeHelper.gamePhaseButtonSize);
jPhases.setPreferredSize(new Dimension(fullPhaseWidth, (vGap * phaseButtons.size()) + (buttonSize * phaseButtons.size())));
jPhases.setMaximumSize(new Dimension(fullPhaseWidth, Short.MAX_VALUE));
phaseButtons.forEach((phaseName, phaseButton) -> {
phaseButton.setPreferredSize(new Dimension(buttonSize, buttonSize));
});
// phase buttons - active size
if (lastGameData.game != null) {
updateActivePhase(lastGameData.game.getStep());
}
if (themeReload) {
reloadThemeRelatedGraphic();
}
}
private int getSkipButtonsPanelDefaultHeight() {
// make sure it will get two rows of buttons
float guiScale = GUISizeHelper.dialogGuiScale;
int vGap = GUISizeHelper.guiSizeScale(SKIP_BUTTONS_SPACE_V, guiScale);
//int extraSpace = GUISizeHelper.guiSizeScale(30, guiScale); // extra space for messages in feedback
int extraSpace = 0; // no needs in extra space for 3+ lines
int lines = 3;
return extraSpace + ((lines * 2 - 1) * vGap) + (lines * GUISizeHelper.gameCommandButtonHeight);
}
private void reloadThemeRelatedGraphic() {
// skip buttons - images
int buttonHeight = GUISizeHelper.gameCommandButtonHeight;
setSkipButtonImage(btnCancelSkip, ImageManagerImpl.instance.getCancelSkipButtonImage(buttonHeight));
setSkipButtonImage(btnSkipToNextTurn, ImageManagerImpl.instance.getSkipNextTurnButtonImage(buttonHeight));
setSkipButtonImage(btnSkipToEndTurn, ImageManagerImpl.instance.getSkipEndTurnButtonImage(buttonHeight));
setSkipButtonImage(btnSkipToEndStepBeforeYourTurn, ImageManagerImpl.instance.getSkipEndStepBeforeYourTurnButtonImage(buttonHeight));
setSkipButtonImage(btnSkipToYourTurn, ImageManagerImpl.instance.getSkipYourNextTurnButtonImage(buttonHeight));
setSkipButtonImage(btnSkipToNextMain, ImageManagerImpl.instance.getSkipMainButtonImage(buttonHeight));
setSkipButtonImage(btnSkipStack, ImageManagerImpl.instance.getSkipStackButtonImage(buttonHeight));
setSkipButtonImage(btnConcede, ImageManagerImpl.instance.getConcedeButtonImage(buttonHeight));
setSkipButtonImage(btnToggleMacro, ImageManagerImpl.instance.getToggleRecordMacroButtonImage(buttonHeight));
setSkipButtonImage(btnSwitchHands, ImageManagerImpl.instance.getSwitchHandsButtonImage(buttonHeight));
setSkipButtonImage(btnStopWatching, ImageManagerImpl.instance.getStopWatchButtonImage(buttonHeight));
// hotkeys for skip buttons
boolean displayButtonText = PreferencesDialog.getCurrentTheme().isShortcutsVisibleForSkipButtons();
btnCancelSkip.setShowKey(displayButtonText);
btnSkipToNextTurn.setShowKey(displayButtonText);
btnSkipToEndTurn.setShowKey(displayButtonText);
btnSkipToEndStepBeforeYourTurn.setShowKey(displayButtonText);
btnSkipToYourTurn.setShowKey(displayButtonText);
btnSkipToNextMain.setShowKey(displayButtonText);
btnSkipStack.setShowKey(displayButtonText);
btnToggleMacro.setShowKey(displayButtonText);
// phase buttons
phaseButtons.forEach((phaseName, phaseButton) -> {
Image buttonImage = ImageManagerImpl.instance.getPhaseImage(phaseName, GUISizeHelper.gamePhaseButtonSize);
Rectangle buttonRect = new Rectangle(buttonImage.getWidth(null), buttonImage.getHeight(null));
phaseButton.update(phaseButton.getText(), buttonImage, buttonImage, buttonImage, buttonImage, buttonRect);
});
// player panels
if (lastGameData.game != null) {
lastGameData.game.getPlayers().forEach(player -> {
PlayAreaPanel playPanel = this.players.getOrDefault(player.getPlayerId(), null);
if (playPanel != null) {
// see test render dialog for refresh commands order
playPanel.getPlayerPanel().fullRefresh(GUISizeHelper.playerPanelGuiScale);
playPanel.init(player, bigCard, gameId, player.getPriorityTimeLeftSecs());
playPanel.update(lastGameData.game, player, lastGameData.targets);
playPanel.getPlayerPanel().sizePlayerPanel(false);
}
});
}
// as workaround: can change size for closed ability picker only
if (this.abilityPicker != null && !this.abilityPicker.isVisible()) {
this.abilityPicker.fullRefresh(GUISizeHelper.dialogGuiScale);
this.abilityPicker.init(gameId, bigCard);
}
}
private void setSkipButtonImage(JButton button, Image image) {
button.setIcon(new ImageIcon(image));
}
private void setSkipButtonSize(JComponent button, float guiScale, Dimension size) {
if (button instanceof KeyboundButton) {
((KeyboundButton) button).updateGuiScale(guiScale);
}
}
private Map<String, Integer> loadSplitterLocationsFromSettings(String settingsKey) {
Map<String, Integer> res;
Type type = new TypeToken<Map<String, Integer>>() {
}.getType();
try {
String savedData = PreferencesDialog.getCachedValue(settingsKey, "");
res = new Gson().fromJson(savedData, type);
} catch (Exception e) {
res = null;
logger.error("Found broken data for divider locations " + settingsKey, e);
}
if (res == null) {
res = new HashMap<>();
}
return res;
}
private void saveSplitterLocationsToSettings(Map<String, Integer> newValues, String settingsKey) {
PreferencesDialog.saveValue(settingsKey, new Gson().toJson(newValues));
}
private void saveSplitters() {
if (!isSplittersFullyRestored) {
logger.warn("splitters do not fully restored yet");
return;
}
splitters.forEach((settingsKey, splitter) -> {
saveSplitter(splitter.splitPane, settingsKey);
});
}
private void saveSplitter(JSplitPane splitPane, String settingsKey) {
Map<String, Integer> allLocations = loadSplitterLocationsFromSettings(settingsKey);
Rectangle screenRec = MageFrame.getDesktop().getBounds();
String screenKey = String.format("%d_x_%d", screenRec.width, screenRec.height);
// store location information (position or hidden state)
// splits with hidden panels will give location < min/max divider
int newLocation = splitPane.getDividerLocation();
if (newLocation == 0 || newLocation < splitPane.getMinimumDividerLocation()) {
newLocation = DIVIDER_POSITION_HIDDEN_LEFT_OR_TOP;
} else if (newLocation > splitPane.getMaximumDividerLocation()) {
newLocation = DIVIDER_POSITION_HIDDEN_RIGHT_OR_BOTTOM;
}
allLocations.put(screenKey, newLocation);
saveSplitterLocationsToSettings(allLocations, settingsKey);
}
/**
* Restore split position from last time used
*
* @param splitPane
* @param settingsKey
* @param defaultProportion 0.25 means 25% for left component and 75% for right
*/
private void restoreSplitter(JSplitPane splitPane, String settingsKey, double defaultProportion) {
Map<String, Integer> allLocations = loadSplitterLocationsFromSettings(settingsKey);
Rectangle screenRec = MageFrame.getDesktop().getBounds();
String screenKey = String.format("%d_x_%d", screenRec.width, screenRec.height);
// on first run it has nothing in saved values, so make sure to use default location (depends on inner components preferred sizes)
// WARNING, new divider location must be restored independently in swing threads one by one
int newLocation = allLocations.getOrDefault(screenKey, DIVIDER_POSITION_DEFAULT);
if (newLocation == DIVIDER_POSITION_DEFAULT) {
// use default location
SwingUtilities.invokeLater(() -> {
splitPane.resetToPreferredSizes();
splitPane.setDividerLocation(defaultProportion);
});
} else if (newLocation == DIVIDER_POSITION_HIDDEN_LEFT_OR_TOP) {
// use hidden (hide left)
SwingUtilities.invokeLater(() -> {
splitPane.resetToPreferredSizes();
splitPane.setDividerLocation(defaultProportion);
splitPane.getLeftComponent().setMinimumSize(new Dimension());
splitPane.setDividerLocation(0.0d);
});
} else if (newLocation == DIVIDER_POSITION_HIDDEN_RIGHT_OR_BOTTOM) {
// use hidden (hide right)
SwingUtilities.invokeLater(() -> {
splitPane.resetToPreferredSizes();
splitPane.setDividerLocation(defaultProportion);
splitPane.getRightComponent().setMinimumSize(new Dimension());
splitPane.setDividerLocation(1.0d);
});
} else {
// use saved location
SwingUtilities.invokeLater(() -> {
splitPane.resetToPreferredSizes();
splitPane.setDividerLocation(defaultProportion);
splitPane.setDividerLocation(newLocation);
});
}
}
private void restoreSplitters() {
// split/divider locations must be restored after game panel will be visible, e.g. on frame activated
isSplittersFullyRestored = false;
SwingUtilities.invokeLater(() -> {
restoreSplittersByQueue(new LinkedHashMap<>(this.splitters));
});
}
private void restoreSplittersByQueue(Map<String, MageSplitter> splittersQueue) {
if (splittersQueue.isEmpty()) {
isSplittersFullyRestored = true;
return;
}
// current
String currentKey = splittersQueue.keySet().stream().findFirst().get();
MageSplitter currentSplitter = splittersQueue.remove(currentKey);
restoreSplitter(currentSplitter.splitPane, currentKey, currentSplitter.defaultProportion);
// next in queue
SwingUtilities.invokeLater(() -> {
restoreSplittersByQueue(splittersQueue);
});
}
private void sizeBigCard() {
int width = bigCard.getParent().getWidth();
int height = Math.round(width * GUISizeHelper.CARD_WIDTH_TO_HEIGHT_COEF);
bigCard.setPreferredSize(new Dimension(width, height));
bigCard.setMaximumSize(new Dimension(Short.MAX_VALUE, height));
}
private void sizeToScreen() {
// on resize frame
Rectangle rect = this.getBounds();
pnlShortCuts.revalidate();
for (PlayAreaPanel p : players.values()) {
p.getPlayerPanel().sizePlayerPanel(false);
}
ArrowBuilder.getBuilder().setSize(rect.width, rect.height);
DialogManager.getManager(gameId).setScreenWidth(rect.width);
DialogManager.getManager(gameId).setScreenHeight(rect.height);
DialogManager.getManager(gameId).setBounds(0, 0, rect.width, rect.height);
}
public synchronized void showGame(UUID currentTableId, UUID parentTableId, UUID gameId, UUID playerId, GamePane gamePane) {
this.currentTableId = currentTableId;
this.parentTableId = parentTableId;
this.gameId = gameId;
this.gamePane = gamePane;
this.playerId = playerId;
MageFrame.addGame(gameId, this);
this.feedbackPanel.init(gameId, bigCard);
this.feedbackPanel.clear();
this.abilityPicker.init(gameId, bigCard);
this.btnConcede.setVisible(true);
this.btnStopWatching.setVisible(false);
this.btnSwitchHands.setVisible(false);
this.btnCancelSkip.setVisible(true);
this.btnToggleMacro.setVisible(true);
// cards popup info in chats
this.gameChatPanel.setGameData(gameId, bigCard);
this.userChatPanel.setGameData(gameId, bigCard);
this.btnSkipToNextTurn.setVisible(true);
this.btnSkipToEndTurn.setVisible(true);
this.btnSkipToNextMain.setVisible(true);
this.btnSkipStack.setVisible(true);
this.btnSkipToYourTurn.setVisible(true);
this.btnSkipToEndStepBeforeYourTurn.setVisible(true);
this.pnlReplay.setVisible(false);
this.gameChatPanel.clear();
SessionHandler.getGameChatId(gameId).ifPresent(uuid -> this.gameChatPanel.connect(uuid));
if (!SessionHandler.joinGame(gameId)) {
removeGame();
} else {
// play start sound
AudioManager.playYourGameStarted();
if (!AppUtil.isAppActive()) {
MageTray.instance.displayMessage("Your game has started!");
MageTray.instance.blink();
}
}
}
public synchronized void watchGame(UUID currentTableId, UUID parentTableId, UUID gameId, GamePane gamePane) {
this.currentTableId = currentTableId;
this.parentTableId = parentTableId;
this.gameId = gameId;
this.gamePane = gamePane;
this.playerId = null;
MageFrame.addGame(gameId, this);
this.feedbackPanel.init(gameId, bigCard);
this.feedbackPanel.clear();
this.btnConcede.setVisible(false);
this.btnStopWatching.setVisible(true);
this.btnSwitchHands.setVisible(false);
this.chosenHandKey = "";
this.btnCancelSkip.setVisible(false);
this.btnToggleMacro.setVisible(false);
this.btnSkipToNextTurn.setVisible(false);
this.btnSkipToEndTurn.setVisible(false);
this.btnSkipToNextMain.setVisible(false);
this.btnSkipStack.setVisible(false);
this.btnSkipToYourTurn.setVisible(false);
this.btnSkipToEndStepBeforeYourTurn.setVisible(false);
this.pnlReplay.setVisible(false);
this.gameChatPanel.clear();
SessionHandler.getGameChatId(gameId).ifPresent(uuid
-> this.gameChatPanel.connect(uuid));
if (!SessionHandler.watchGame(gameId)) {
removeGame();
}
for (PlayAreaPanel panel : players.values()) {
panel.setPlayingMode(false);
}
}
public synchronized void replayGame(UUID gameId) {
this.currentTableId = null;
this.parentTableId = null;
this.gameId = gameId;
this.playerId = null;
MageFrame.addGame(gameId, this);
this.feedbackPanel.init(gameId, bigCard);
this.feedbackPanel.clear();
this.btnConcede.setVisible(false);
this.btnSkipToNextTurn.setVisible(false);
this.btnSwitchHands.setVisible(false);
this.btnStopWatching.setVisible(false);
this.pnlReplay.setVisible(true);
this.gameChatPanel.clear();
if (!SessionHandler.startReplay(gameId)) {
removeGame();
}
for (PlayAreaPanel panel : players.values()) {
panel.setPlayingMode(false);
}
}
/**
* Closes the game and it's resources
*/
public void removeGame() {
Component c = this.getParent();
while (c != null && !(c instanceof GamePane)) {
c = c.getParent();
}
if (c != null) {
((GamePane) c).removeGame();
}
}
public synchronized void init(int messageId, GameView game, boolean callGameUpdateAfterInit) {
addPlayers(game);
// default menu states
setMenuStates(
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"),
holdingPriority
);
if (callGameUpdateAfterInit) {
updateGame(messageId, game);
}
}
private void addPlayers(GameView game) {
this.players.clear();
this.playersWhoLeft.clear();
this.pnlBattlefield.removeAll();
//arrange players in a circle with the session player at the bottom left
int numSeats = game.getPlayers().size();
int numColumns = (numSeats + 1) / 2;
boolean oddNumber = (numColumns > 1 && numSeats % 2 == 1);
int col = 0;
int row = 1;
int playerSeat = 0;
if (playerId != null) {
for (PlayerView player : game.getPlayers()) {
if (playerId.equals(player.getPlayerId())) {
break;
}
playerSeat++;
}
}
PlayerView player = game.getPlayers().get(playerSeat);
PlayAreaPanel playAreaPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this,
new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), game.isPlayer(),
game.isRollbackTurnsAllowed(), row == 0));
players.put(player.getPlayerId(), playAreaPanel);
playersWhoLeft.put(player.getPlayerId(), false);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 0.5;
c.weighty = 0.5;
if (oddNumber) {
c.gridwidth = 2;
}
c.gridx = col;
c.gridy = 0;
// Top panel (row=0)
JPanel topPanel = new JPanel();
topPanel.setOpaque(false);
// Bottom panel (row=1)
JPanel bottomPanel = new JPanel();
bottomPanel.setOpaque(false);
topPanel.setLayout(new GridBagLayout());
bottomPanel.setLayout(new GridBagLayout());
bottomPanel.add(playAreaPanel, c);
playAreaPanel.setVisible(true);
if (oddNumber) {
col++;
}
int playerNum = playerSeat + 1;
if (playerNum >= numSeats) {
playerNum = 0;
}
while (true) {
if (row == 1) {
col++;
} else {
col--;
}
if (col >= numColumns) {
row = 0;
col = numColumns - 1;
}
player = game.getPlayers().get(playerNum);
PlayAreaPanel playerPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this,
new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), false, game.isRollbackTurnsAllowed(),
row == 0));
players.put(player.getPlayerId(), playerPanel);
playersWhoLeft.put(player.getPlayerId(), false);
c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 0.5;
c.weighty = 0.5;
c.gridx = col;
c.gridy = 0;
if (row == 0) {
topPanel.add(playerPanel, c);
} else {
bottomPanel.add(playerPanel, c);
}
playerPanel.setVisible(true);
playerNum++;
if (playerNum >= numSeats) {
playerNum = 0;
}
if (playerNum == playerSeat) {
break;
}
}
// set init sizes
for (PlayAreaPanel p : players.values()) {
p.getPlayerPanel().sizePlayerPanel(false);
}
GridBagConstraints panelC = new GridBagConstraints();
panelC.fill = GridBagConstraints.BOTH;
panelC.weightx = 0.5;
panelC.weighty = 0.5;
panelC.gridwidth = 1;
panelC.gridy = 0;
this.pnlBattlefield.add(topPanel, panelC);
panelC.gridy = 1;
this.pnlBattlefield.add(bottomPanel, panelC);
}
public synchronized void updateGame(int messageId, GameView game) {
updateGame(messageId, game, false, null, null);
}
public synchronized void updateGame(int messageId, GameView game, boolean showPlayable, Map<String, Serializable> options, Set<UUID> targets) {
keepLastGameData(messageId, game, showPlayable, options, targets);
if (this.players.isEmpty() && !game.getPlayers().isEmpty()) {
logger.warn("Found empty players list, trying to init game again (possible reason: reconnection)");
init(messageId, game, false);
}
prepareSelectableView();
updateGame();
}
public synchronized void updateGame() {
if (playerId == null && lastGameData.game.getWatchedHands().isEmpty()) {
this.handContainer.setVisible(false);
} else {
this.handContainer.setVisible(true);
handCards.clear();
if (!lastGameData.game.getWatchedHands().isEmpty()) {
for (Map.Entry<String, SimpleCardsView> hand : lastGameData.game.getWatchedHands().entrySet()) {
handCards.put(hand.getKey(), CardsViewUtil.convertSimple(hand.getValue(), loadedCards));
}
}
if (playerId != null) {
handCards.put(YOUR_HAND, lastGameData.game.getMyHand());
// Get opponents hand cards if available (only possible for players)
if (!lastGameData.game.getOpponentHands().isEmpty()) {
for (Map.Entry<String, SimpleCardsView> hand : lastGameData.game.getOpponentHands().entrySet()) {
handCards.put(hand.getKey(), CardsViewUtil.convertSimple(hand.getValue(), loadedCards));
}
}
if (!handCards.containsKey(chosenHandKey)) {
chosenHandKey = YOUR_HAND;
}
} else if (chosenHandKey.isEmpty() && !handCards.isEmpty()) {
chosenHandKey = handCards.keySet().iterator().next();
}
if (chosenHandKey != null && handCards.containsKey(chosenHandKey)) {
handContainer.loadCards(handCards.get(chosenHandKey), bigCard, gameId);
}
hideAll();
if (playerId != null) {
// set visible only if we have any other hand visible than ours
btnSwitchHands.setVisible(handCards.size() > 1);
boolean change = (handCardsOfOpponentAvailable == lastGameData.game.getOpponentHands().isEmpty());
if (change) {
handCardsOfOpponentAvailable = !handCardsOfOpponentAvailable;
if (handCardsOfOpponentAvailable) {
MageFrame.getInstance().showMessage("You control other player's turn. \nUse \"Switch Hand\" button to switch between cards in different hands.");
} else {
MageFrame.getInstance().showMessage("You lost control on other player's turn.");
}
}
} else {
btnSwitchHands.setVisible(!handCards.isEmpty());
}
}
if (lastGameData.game.getPhase() != null) {
this.txtPhase.setText(lastGameData.game.getPhase().toString());
} else {
this.txtPhase.setText("");
}
if (lastGameData.game.getStep() != null) {
updateActivePhase(lastGameData.game.getStep());
this.txtStep.setText(lastGameData.game.getStep().toString());
} else {
logger.debug("Step is empty");
this.txtStep.setText("");
}
this.txtActivePlayer.setText(lastGameData.game.getActivePlayerName());
this.txtPriority.setText(lastGameData.game.getPriorityPlayerName());
this.txtTurn.setText(Integer.toString(lastGameData.game.getTurn()));
List<UUID> possibleAttackers = new ArrayList<>();
if (lastGameData.options != null && lastGameData.options.containsKey(Constants.Option.POSSIBLE_ATTACKERS)) {
if (lastGameData.options.get(Constants.Option.POSSIBLE_ATTACKERS) instanceof List) {
possibleAttackers.addAll((List) lastGameData.options.get(Constants.Option.POSSIBLE_ATTACKERS));
}
}
List<UUID> possibleBlockers = new ArrayList<>();
if (lastGameData.options != null && lastGameData.options.containsKey(Constants.Option.POSSIBLE_BLOCKERS)) {
if (lastGameData.options.get(Constants.Option.POSSIBLE_BLOCKERS) instanceof List) {
possibleBlockers.addAll((List) lastGameData.options.get(Constants.Option.POSSIBLE_BLOCKERS));
}
}
for (PlayerView player : lastGameData.game.getPlayers()) {
if (players.containsKey(player.getPlayerId())) {
if (!possibleAttackers.isEmpty()) {
for (UUID permanentId : possibleAttackers) {
if (player.getBattlefield().containsKey(permanentId)) {
player.getBattlefield().get(permanentId).setCanAttack(true);
}
}
}
if (!possibleBlockers.isEmpty()) {
for (UUID permanentId : possibleBlockers) {
if (player.getBattlefield().containsKey(permanentId)) {
player.getBattlefield().get(permanentId).setCanBlock(true);
}
}
}
players.get(player.getPlayerId()).update(lastGameData.game, player, lastGameData.targets);
if (player.getPlayerId().equals(playerId)) {
skipButtons.updateFromPlayer(player);
}
// update open or remove closed graveyard windows
graveyards.put(player.getName(), player.getGraveyard());
if (graveyardWindows.containsKey(player.getName())) {
CardInfoWindowDialog windowDialog = graveyardWindows.get(player.getName());
if (windowDialog.isClosed()) {
graveyardWindows.remove(player.getName());
} else {
windowDialog.loadCardsAndShow(player.getGraveyard(), bigCard, gameId, false);
}
}
// update open or remove closed sideboard windows
sideboards.put(player.getName(), player.getSideboard());
if (sideboardWindows.containsKey(player.getName())) {
CardInfoWindowDialog windowDialog = sideboardWindows.get(player.getName());
if (windowDialog.isClosed()) {
sideboardWindows.remove(player.getName());
} else {
windowDialog.loadCardsAndShow(player.getSideboard(), bigCard, gameId, false);
}
}
// show top card window
if (player.getTopCard() != null) {
CardsView cardsView = new CardsView();
cardsView.put(player.getTopCard().getId(), player.getTopCard());
handleGameInfoWindow(revealed, ShowType.REVEAL_TOP_LIBRARY, player.getName() + "'s top library card", cardsView);
}
} else if (!players.isEmpty()) {
logger.warn("Couldn't find player.");
logger.warn(" uuid:" + player.getPlayerId());
logger.warn(" players:");
for (PlayAreaPanel p : players.values()) {
logger.warn(String.valueOf(p));
}
} else {
// can happen at the game start before player list is initiated
}
}
updateSkipButtons();
if (!menuNameSet) {
StringBuilder sb = new StringBuilder();
if (playerId == null) {
sb.append("Watching: ");
} else {
sb.append("Playing: ");
}
boolean first = true;
for (PlayerView player : lastGameData.game.getPlayers()) {
if (first) {
first = false;
} else {
sb.append(" - ");
}
sb.append(player.getName());
}
menuNameSet = true;
gamePane.setTitle(sb.toString());
}
displayStack(lastGameData.game, bigCard, feedbackPanel, gameId);
// auto-show exile views
for (ExileView exile : lastGameData.game.getExile()) {
CardInfoWindowDialog exileWindow = exiles.getOrDefault(exile.getId(), null);
if (exileWindow == null) {
exileWindow = new CardInfoWindowDialog(ShowType.EXILE, exile.getName());
exiles.put(exile.getId(), exileWindow);
MageFrame.getDesktop().add(exileWindow, exileWindow.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
exileWindow.show();
}
exileWindow.loadCardsAndShow(exile, bigCard, gameId);
}
// update open or remove closed card hints windows
clearClosedCardHintsWindows();
cardHintsWindows.forEach((s, windowDialog) -> {
// TODO: optimize for multiple windows (prepare data here and send it for filters/groups)
windowDialog.loadHints(lastGameData.game);
});
// reveal and look at dialogs can unattached, so windows opened by game doesn't have it
showRevealed(lastGameData.game);
showLookedAt(lastGameData.game);
// sideboard dialogs is unattached all the time -- user opens it by command
showCompanion(lastGameData.game);
if (!lastGameData.game.getCombat().isEmpty()) {
CombatManager.instance.showCombat(lastGameData.game.getCombat(), gameId);
} else {
CombatManager.instance.hideCombat(gameId);
}
for (PlayerView player : lastGameData.game.getPlayers()) {
if (player.hasLeft() && !playersWhoLeft.get(player.getPlayerId())) {
PlayAreaPanel playerLeftPanel = players.get(player.getPlayerId());
playersWhoLeft.put(player.getPlayerId(), true);
Container parent = playerLeftPanel.getParent();
GridBagLayout layout = (GridBagLayout) parent.getLayout();
for (Component otherPanel : parent.getComponents()) {
if (otherPanel instanceof PlayAreaPanel) {
GridBagConstraints gbc = layout.getConstraints(otherPanel);
if (gbc.weightx > 0.1) {
gbc.weightx = 0.99;
}
gbc.fill = GridBagConstraints.BOTH;
gbc.anchor = GridBagConstraints.WEST;
if (gbc.gridx > 0) {
gbc.anchor = GridBagConstraints.EAST;
}
if (otherPanel == playerLeftPanel) {
gbc.weightx = 0.01;
Dimension d = playerLeftPanel.getPreferredSize();
d.width = 95;
otherPanel.setPreferredSize(d);
}
parent.remove(otherPanel);
parent.add(otherPanel, gbc);
}
}
parent.validate();
parent.repaint();
}
}
//logger.info("game update, message = " + lastGameData.messageId + ", options = " + lastGameData.options + ", priority = " + lastGameData.game.getPriorityPlayerName());
feedbackPanel.disableUndo();
feedbackPanel.updateOptions(lastGameData.options);
this.revalidate();
this.repaint();
}
// skip buttons border
private static final int BORDER_SIZE = 2;
private static final Border BORDER_ACTIVE = new LineBorder(Color.orange, BORDER_SIZE);
private static final Border BORDER_NON_ACTIVE = new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE);
// skip buttons info
private class skipButton {
private final String text;
private final String extraFalse;
private final String extraTrue;
private final String hotkeyName;
private boolean extraMode = false; // extra option enabled from preferences
private boolean pressState = false; // activated by user or not
skipButton(String text, String extraFalse, String extraTrue, String hotkeyName) {
this.text = text;
this.extraFalse = extraFalse;
this.extraTrue = extraTrue;
this.hotkeyName = hotkeyName;
}
public void setExtraMode(boolean enable) {
this.extraMode = enable;
}
public void setPressState(boolean enable) {
this.pressState = enable;
}
public String getTooltip() {
// show hotkey and selects current button mode
// text
String res = "<html>"
+ "<b>" + getCachedKeyText(this.hotkeyName) + "</b>"
+ " - " + text;
// mode
String mesTrue = this.extraTrue;
String mesFalse = this.extraFalse;
if (!this.extraTrue.isEmpty() || !this.extraFalse.isEmpty()) {
if (this.extraMode) {
mesTrue = "<b>" + mesTrue + "</b>";
} else {
mesFalse = "<b>" + mesFalse + "</b>";
}
res = res.replace("EXTRA_FALSE", mesFalse);
res = res.replace("EXTRA_TRUE", mesTrue);
res = res + " - adjust using preferences";
}
return res;
}
public Border getBorder() {
return this.pressState ? BORDER_ACTIVE : BORDER_NON_ACTIVE;
}
}
private class skipButtonsList {
private final skipButton turn;
private final skipButton untilEndOfTurn;
private final skipButton untilNextMain;
private final skipButton allTurns;
private final skipButton untilStackResolved;
private final skipButton untilUntilEndStepBeforeMyTurn;
skipButtonsList() {
this.turn = new skipButton("Skip to next turn", "", "", KEY_CONTROL_NEXT_TURN);
this.untilEndOfTurn = new skipButton("Skip to [EXTRA_TRUE / EXTRA_FALSE] END OF TURN step", "opponent", "next", KEY_CONTROL_END_STEP);
this.untilNextMain = new skipButton("Skip to [EXTRA_TRUE / EXTRA_FALSE] MAIN step", "opponent", "next", KEY_CONTROL_MAIN_STEP);
this.allTurns = new skipButton("Skip to YOUR turn", "", "", KEY_CONTROL_YOUR_TURN);
this.untilStackResolved = new skipButton("Skip until stack is resolved [EXTRA_TRUE]", "", "or stop on new objects added", KEY_CONTROL_SKIP_STACK);
this.untilUntilEndStepBeforeMyTurn = new skipButton("Skip to END OF TURN before YOUR", "", "", KEY_CONTROL_PRIOR_END);
}
private void updateExtraMode(PlayerView player) {
this.turn.setExtraMode(false); // not used
this.untilEndOfTurn.setExtraMode(player.getUserData().getUserSkipPrioritySteps().isStopOnAllEndPhases());
this.untilNextMain.setExtraMode(player.getUserData().getUserSkipPrioritySteps().isStopOnAllMainPhases());
this.allTurns.setExtraMode(false); // not used
this.untilStackResolved.setExtraMode(player.getUserData().getUserSkipPrioritySteps().isStopOnStackNewObjects());
this.untilUntilEndStepBeforeMyTurn.setExtraMode(false); // not used
}
private void updatePressState(PlayerView player) {
this.turn.setPressState(player.isPassedTurn());
this.untilEndOfTurn.setPressState(player.isPassedUntilEndOfTurn());
this.untilNextMain.setPressState(player.isPassedUntilNextMain());
this.allTurns.setPressState(player.isPassedAllTurns());
this.untilStackResolved.setPressState(player.isPassedUntilStackResolved());
this.untilUntilEndStepBeforeMyTurn.setPressState(player.isPassedUntilEndStepBeforeMyTurn());
}
public void updateFromPlayer(PlayerView player) {
updateExtraMode(player);
updatePressState(player);
}
private skipButton findButton(String hotkey) {
switch (hotkey) {
case KEY_CONTROL_NEXT_TURN:
return this.turn;
case KEY_CONTROL_END_STEP:
return this.untilEndOfTurn;
case KEY_CONTROL_MAIN_STEP:
return this.untilNextMain;
case KEY_CONTROL_YOUR_TURN:
return this.allTurns;
case KEY_CONTROL_SKIP_STACK:
return this.untilStackResolved;
case KEY_CONTROL_PRIOR_END:
return this.untilUntilEndStepBeforeMyTurn;
default:
logger.error("Unknown hotkey name " + hotkey);
return null;
}
}
public String getTooltip(String hotkey) {
skipButton butt = findButton(hotkey);
return butt != null ? butt.getTooltip() : "";
}
public Border getBorder(String hotkey) {
skipButton butt = findButton(hotkey);
return butt != null ? butt.getBorder() : BORDER_NON_ACTIVE;
}
public void activateSkipButton(String hotkey) {
// enable ONE button and disable all other (no needs to wait server feedback)
this.turn.setPressState(false);
this.untilEndOfTurn.setPressState(false);
this.untilNextMain.setPressState(false);
this.allTurns.setPressState(false);
this.untilStackResolved.setPressState(false);
this.untilUntilEndStepBeforeMyTurn.setPressState(false);
if (!hotkey.isEmpty()) {
skipButton butt = findButton(hotkey);
if (butt != null) butt.setPressState(true);
}
}
}
private void updateSkipButtons() {
// hints
btnSkipToNextTurn.setToolTipText(skipButtons.turn.getTooltip());
btnSkipToEndTurn.setToolTipText(skipButtons.untilEndOfTurn.getTooltip());
btnSkipToNextMain.setToolTipText(skipButtons.untilNextMain.getTooltip());
btnSkipStack.setToolTipText(skipButtons.untilStackResolved.getTooltip());
btnSkipToYourTurn.setToolTipText(skipButtons.allTurns.getTooltip());
btnSkipToEndStepBeforeYourTurn.setToolTipText(skipButtons.untilUntilEndStepBeforeMyTurn.getTooltip());
// border
btnSkipToNextTurn.setBorder(skipButtons.turn.getBorder());
btnSkipToEndTurn.setBorder(skipButtons.untilEndOfTurn.getBorder());
btnSkipToNextMain.setBorder(skipButtons.untilNextMain.getBorder());
btnSkipStack.setBorder(skipButtons.untilStackResolved.getBorder());
btnSkipToYourTurn.setBorder(skipButtons.allTurns.getBorder());
btnSkipToEndStepBeforeYourTurn.setBorder(skipButtons.untilUntilEndStepBeforeMyTurn.getBorder());
}
/**
* Set the same state for menu selections to all player areas.
*
* @param manaPoolAutomatic
* @param manaPoolAutomaticRestricted
* @param useFirstManaAbility
*/
public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, boolean useFirstManaAbility, boolean holdPriority) {
for (PlayAreaPanel playAreaPanel : players.values()) {
playAreaPanel.setMenuStates(manaPoolAutomatic, manaPoolAutomaticRestricted, useFirstManaAbility, holdPriority);
}
}
private void displayStack(GameView game, BigCard bigCard, FeedbackPanel feedbackPanel, UUID gameId) {
this.stackObjects.loadCards(game.getStack(), bigCard, gameId, true);
}
private void updateActivePhase(PhaseStep currentStep) {
if (currentStep == null) {
return;
}
switch (currentStep) {
case UNTAP:
updatePhaseButtons("Untap");
break;
case UPKEEP:
updatePhaseButtons("Upkeep");
break;
case DRAW:
updatePhaseButtons("Draw");
break;
case PRECOMBAT_MAIN:
updatePhaseButtons("Main1");
break;
case BEGIN_COMBAT:
updatePhaseButtons("Combat_Start");
break;
case DECLARE_ATTACKERS:
updatePhaseButtons("Combat_Attack");
break;
case DECLARE_BLOCKERS:
updatePhaseButtons("Combat_Block");
break;
case FIRST_COMBAT_DAMAGE:
case COMBAT_DAMAGE:
updatePhaseButtons("Combat_Damage");
break;
case END_COMBAT:
updatePhaseButtons("Combat_End");
break;
case POSTCOMBAT_MAIN:
updatePhaseButtons("Main2");
break;
case END_TURN:
case CLEANUP:
updatePhaseButtons("Cleanup");
break;
default:
break;
}
}
private void updatePhaseButtons(String currentPhaseName) {
phaseButtons.forEach((phaseName, phaseButton) -> {
if (phaseName.equals(currentPhaseName)) {
//phaseButton.setBorder(this.phaseButtonBorderActive);
phaseButton.setAlignmentX(Component.CENTER_ALIGNMENT);
} else {
//phaseButton.setBorder(this.phaseButtonBorderInactive);
phaseButton.setAlignmentX(Component.LEFT_ALIGNMENT);
}
});
jPhases.invalidate();
}
public void onDeactivated() {
// save current dividers location
saveSplitters();
// hide the non modal windows (because otherwise they are shown on top of the new active pane)
// TODO: is it need to hide other dialogs like graveyards (CardsView)?
for (CardInfoWindowDialog windowDialog : exiles.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog windowDialog : revealed.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog windowDialog : companion.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
windowDialog.hideDialog();
}
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
windowDialog.hideDialog();
}
}
public void onActivated() {
// restore divider positions
// must be called by swing after all pack and paint done (possible bug: zero size in restored divider)
SwingUtilities.invokeLater(this::restoreSplitters);
// hide the non modal windows (because otherwise they are shown on top of the new active pane)
for (CardInfoWindowDialog windowDialog : exiles.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog windowDialog : revealed.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog windowDialog : companion.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
windowDialog.show();
}
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
windowDialog.show();
}
}
public void openGraveyardWindow(String playerName) {
if (graveyardWindows.containsKey(playerName)) {
CardInfoWindowDialog cardInfoWindowDialog = graveyardWindows.get(playerName);
if (cardInfoWindowDialog.isVisible()) {
cardInfoWindowDialog.hideDialog();
} else {
cardInfoWindowDialog.show();
}
return;
}
CardInfoWindowDialog newGraveyard = new CardInfoWindowDialog(ShowType.GRAVEYARD, playerName);
graveyardWindows.put(playerName, newGraveyard);
MageFrame.getDesktop().add(newGraveyard, newGraveyard.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
// use graveyards to sync selection (don't use player data here)
newGraveyard.loadCardsAndShow(graveyards.get(playerName), bigCard, gameId, false);
}
private void clearClosedCardHintsWindows() {
cardHintsWindows.entrySet().removeIf(entry -> entry.getValue().isClosed());
}
public void openCardHintsWindow(String code) {
// clear closed
clearClosedCardHintsWindows();
// too many dialogs can cause bad GUI performance, so limit it
if (cardHintsWindows.size() >= CardHintsHelperDialog.GUI_MAX_CARD_HINTS_DIALOGS_PER_GAME) {
// show last one instead
cardHintsWindows.values().stream().reduce((a, b) -> b).ifPresent(CardHintsHelperDialog::show);
return;
}
// open new
CardHintsHelperDialog newDialog = new CardHintsHelperDialog();
newDialog.setSize(GUISizeHelper.dialogGuiScaleSize(newDialog.getSize()));
newDialog.setGameData(this.lastGameData.game, this.gameId, this.bigCard);
cardHintsWindows.put(code + UUID.randomUUID(), newDialog);
MageFrame.getDesktop().add(newDialog, newDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
newDialog.loadHints(lastGameData.game);
}
public void openSideboardWindow(UUID playerId) {
if (lastGameData == null) {
return;
}
PlayerView playerView = lastGameData.game.getPlayers().stream()
.filter(p -> p.getPlayerId().equals(playerId))
.findFirst()
.orElse(null);
if (playerView == null) {
return;
}
if (sideboardWindows.containsKey(playerView.getName())) {
CardInfoWindowDialog windowDialog = sideboardWindows.get(playerView.getName());
if (windowDialog.isVisible()) {
windowDialog.hideDialog();
} else {
windowDialog.show();
}
return;
}
CardInfoWindowDialog windowDialog = new CardInfoWindowDialog(ShowType.SIDEBOARD, playerView.getName());
sideboardWindows.put(playerView.getName(), windowDialog);
MageFrame.getDesktop().add(windowDialog, windowDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
// use sideboards to sync selection (don't use player data here)
windowDialog.loadCardsAndShow(sideboards.get(playerView.getName()), bigCard, gameId, false);
}
public void openTopLibraryWindow(String playerName) {
String title = playerName + "'s top library card";
if (revealed.containsKey(title)) {
CardInfoWindowDialog cardInfoWindowDialog = revealed.get(title);
if (cardInfoWindowDialog.isVisible()) {
cardInfoWindowDialog.hideDialog();
} else {
cardInfoWindowDialog.show();
}
}
}
private void showRevealed(GameView game) {
for (RevealedView revealView : game.getRevealed()) {
handleGameInfoWindow(revealed, ShowType.REVEAL, revealView.getName(), revealView.getCards());
}
removeClosedCardInfoWindows(revealed);
}
private void showLookedAt(GameView game) {
for (LookedAtView lookedAtView : game.getLookedAt()) {
handleGameInfoWindow(lookedAt, ShowType.LOOKED_AT, lookedAtView.getName(), lookedAtView.getCards());
}
removeClosedCardInfoWindows(lookedAt);
}
private void showCompanion(GameView game) {
for (RevealedView revealView : game.getCompanion()) {
handleGameInfoWindow(companion, ShowType.COMPANION, revealView.getName(), revealView.getCards());
}
// Close the companion view if not in the game view
companion.forEach((name, companionDialog) -> {
if (game.getCompanion().stream().noneMatch(revealedView -> revealedView.getName().equals(name))) {
try {
companionDialog.setClosed(true);
} catch (PropertyVetoException e) {
logger.error("Couldn't close companion dialog", e);
}
}
});
removeClosedCardInfoWindows(companion);
}
private void handleGameInfoWindow(Map<String, CardInfoWindowDialog> windowMap, ShowType showType, String name, LinkedHashMap cardsView) {
CardInfoWindowDialog cardInfoWindowDialog;
if (!windowMap.containsKey(name)) {
cardInfoWindowDialog = new CardInfoWindowDialog(showType, name);
windowMap.put(name, cardInfoWindowDialog);
MageFrame.getDesktop().add(cardInfoWindowDialog, cardInfoWindowDialog.isModal() ? JLayeredPane.MODAL_LAYER : JLayeredPane.PALETTE_LAYER);
} else {
cardInfoWindowDialog = windowMap.get(name);
}
if (cardInfoWindowDialog != null && !cardInfoWindowDialog.isClosed()) {
switch (showType) {
case REVEAL:
case REVEAL_TOP_LIBRARY:
case COMPANION:
cardInfoWindowDialog.loadCardsAndShow((CardsView) cardsView, bigCard, gameId, false);
break;
case LOOKED_AT:
cardInfoWindowDialog.loadCardsAndShow(CardsViewUtil.convertSimple((SimpleCardsView) cardsView), bigCard, gameId, false);
break;
default:
break;
}
}
}
private void removeClosedCardInfoWindows(Map<String, CardInfoWindowDialog> windowMap) {
// Remove closed window objects from the maps
windowMap.entrySet().removeIf(entry -> entry.getValue().isClosed());
}
public void ask(int messageId, GameView gameView, String question, Map<String, Serializable> options) {
updateGame(messageId, gameView, false, options, null);
this.feedbackPanel.prepareFeedback(FeedbackMode.QUESTION, question, "", false, options, true, gameView.getPhase());
}
public boolean isMissGameData() {
return lastGameData.game == null || lastGameData.game.getPlayers().isEmpty();
}
private void keepLastGameData(int messageId, GameView game, boolean showPlayable, Map<String, Serializable> options, Set<UUID> targets) {
lastGameData.messageId = messageId;
lastGameData.setNewGame(game);
lastGameData.showPlayable = showPlayable;
lastGameData.options = options;
lastGameData.targets = targets;
}
private void prepareSelectableView() {
// make cards/perm selectable/chooseable/playable update game data updates
if (lastGameData.game == null) {
return;
}
Zone needZone = Zone.ALL;
if (lastGameData.options != null && lastGameData.options.containsKey("targetZone")) {
needZone = (Zone) lastGameData.options.get("targetZone");
}
List<UUID> needChosen;
if (lastGameData.options != null && lastGameData.options.containsKey("chosenTargets")) {
needChosen = (List<UUID>) lastGameData.options.get("chosenTargets");
} else {
needChosen = new ArrayList<>();
}
Set<UUID> needSelectable;
if (lastGameData.targets != null) {
needSelectable = lastGameData.targets;
} else {
needSelectable = new HashSet<>();
}
PlayableObjectsList needPlayable;
if (lastGameData.showPlayable && lastGameData.game.getCanPlayObjects() != null) {
needPlayable = lastGameData.game.getCanPlayObjects();
} else {
needPlayable = new PlayableObjectsList();
}
if (needChosen.isEmpty() && needSelectable.isEmpty() && needPlayable.isEmpty()) {
return;
}
// hand
if (needZone == Zone.HAND || needZone == Zone.ALL) {
for (CardView card : lastGameData.game.getMyHand().values()) {
if (needSelectable.contains(card.getId())) {
card.setChoosable(true);
}
if (needChosen.contains(card.getId())) {
card.setSelected(true);
}
if (needPlayable.containsObject(card.getId())) {
card.setPlayableStats(needPlayable.getStats(card.getId()));
}
}
}
// stack
if (needZone == Zone.STACK || needZone == Zone.ALL) {
for (Map.Entry<UUID, CardView> card : lastGameData.game.getStack().entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
// users can activate abilities of the spell on the stack (example: Lightning Storm);
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
// battlefield
if (needZone == Zone.BATTLEFIELD || needZone == Zone.ALL) {
for (PlayerView player : lastGameData.game.getPlayers()) {
for (Map.Entry<UUID, PermanentView> perm : player.getBattlefield().entrySet()) {
if (needSelectable.contains(perm.getKey())) {
perm.getValue().setChoosable(true);
}
if (needChosen.contains(perm.getKey())) {
perm.getValue().setSelected(true);
}
if (needPlayable.containsObject(perm.getKey())) {
perm.getValue().setPlayableStats(needPlayable.getStats(perm.getKey()));
}
}
}
}
// graveyard
if (needZone == Zone.GRAVEYARD || needZone == Zone.ALL) {
for (PlayerView player : lastGameData.game.getPlayers()) {
for (Map.Entry<UUID, CardView> card : player.getGraveyard().entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
}
// sideboard
if (needZone == Zone.OUTSIDE || needZone == Zone.ALL) {
for (PlayerView player : lastGameData.game.getPlayers()) {
for (Map.Entry<UUID, CardView> card : player.getSideboard().entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
}
// sideboards (old windows all the time, e.g. unattached from game data)
prepareSelectableWindows(sideboardWindows.values(), needSelectable, needChosen, needPlayable);
// exile
if (needZone == Zone.EXILED || needZone == Zone.ALL) {
// exile from player panel
for (PlayerView player : lastGameData.game.getPlayers()) {
for (CardView card : player.getExile().values()) {
if (needSelectable.contains(card.getId())) {
card.setChoosable(true);
}
if (needChosen.contains(card.getId())) {
card.setSelected(true);
}
if (needPlayable.containsObject(card.getId())) {
card.setPlayableStats(needPlayable.getStats(card.getId()));
}
}
}
// exile from windows
for (ExileView exile : lastGameData.game.getExile()) {
for (Map.Entry<UUID, CardView> card : exile.entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
}
// command
if (needZone == Zone.COMMAND || needZone == Zone.ALL) {
for (PlayerView player : lastGameData.game.getPlayers()) {
for (CommandObjectView com : player.getCommandObjectList()) {
if (needSelectable.contains(com.getId())) {
com.setChoosable(true);
}
if (needChosen.contains(com.getId())) {
com.setSelected(true);
}
if (needPlayable.containsObject(com.getId())) {
com.setPlayableStats(needPlayable.getStats(com.getId()));
}
}
}
}
// companion
for (RevealedView rev : lastGameData.game.getCompanion()) {
for (Map.Entry<UUID, CardView> card : rev.getCards().entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
// revealed (current cards)
for (RevealedView rev : lastGameData.game.getRevealed()) {
for (Map.Entry<UUID, CardView> card : rev.getCards().entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
// revealed (old windows)
prepareSelectableWindows(revealed.values(), needSelectable, needChosen, needPlayable);
// looked at (current cards)
for (LookedAtView look : lastGameData.game.getLookedAt()) {
for (Map.Entry<UUID, SimpleCardView> card : look.getCards().entrySet()) {
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
// looked at (old windows)
prepareSelectableWindows(lookedAt.values(), needSelectable, needChosen, needPlayable);
}
private void prepareSelectableWindows(
Collection<CardInfoWindowDialog> windows,
Set<UUID> needSelectable,
List<UUID> needChosen,
PlayableObjectsList needPlayable
) {
// lookAt or reveals windows clean up on next priority, so users can see dialogs, but xmage can't restore it
// so it must be updated manually (it's ok to keep outdated cards in dialog, but not ok to show wrong selections)
for (CardInfoWindowDialog window : windows) {
for (MageCard mageCard : window.getMageCardsForUpdate().values()) {
CardView cardView = mageCard.getOriginal();
cardView.setChoosable(needSelectable.contains(cardView.getId()));
cardView.setSelected(needChosen.contains(cardView.getId()));
if (needPlayable.containsObject(cardView.getId())) {
cardView.setPlayableStats(needPlayable.getStats(cardView.getId()));
} else {
cardView.setPlayableStats(new PlayableObjectStats());
}
// TODO: little bug with toggled night card after update/clicks, but that's ok (can't click on second side)
mageCard.update(cardView);
}
}
}
/**
* Shows a pick target dialog and allows the player to pick a target (e.g.
* the pick triggered ability)
*
* @param message
* @param cardsView
* @param gameView
* @param targets
* @param required
* @param options
* @param messageId
*/
public void pickTarget(int messageId, GameView gameView, Map<String, Serializable> options, String message, CardsView cardsView, Set<UUID> targets, boolean required) {
updateGame(messageId, gameView, false, options, targets);
hideAll();
DialogManager.getManager(gameId).fadeOut();
clearPickTargetDialogs();
PopUpMenuType popupMenuType = null;
if (lastGameData.options != null) {
if (options.containsKey("queryType")) {
PlayerQueryEvent.QueryType needType = (PlayerQueryEvent.QueryType) lastGameData.options.get("queryType");
switch (needType) {
case PICK_ABILITY:
popupMenuType = PopUpMenuType.TRIGGER_ORDER;
break;
case PICK_TARGET:
break;
default:
logger.warn("Unknown query type in pick target: " + needType + " in " + message);
break;
}
}
}
Map<String, Serializable> options0 = lastGameData.options == null ? new HashMap<>() : lastGameData.options;
ShowCardsDialog dialog = null;
if (cardsView != null && !cardsView.isEmpty()) {
dialog = prepareCardsDialog(message, cardsView, required, options0, popupMenuType);
options0.put("dialog", dialog);
}
this.feedbackPanel.prepareFeedback(required ? FeedbackMode.INFORM : FeedbackMode.CANCEL, message, "", gameView.getSpecial(), options0, true, gameView.getPhase());
if (dialog != null) {
this.pickTarget.add(dialog);
}
}
public void inform(int messageId, GameView gameView, String information) {
updateGame(messageId, gameView);
this.feedbackPanel.prepareFeedback(FeedbackMode.INFORM, information, "", gameView.getSpecial(), null, false, gameView.getPhase());
}
public void endMessage(int messageId, GameView gameView, Map<String, Serializable> options, String message) {
updateGame(messageId, gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.feedbackPanel.prepareFeedback(FeedbackMode.END, message, "", false, null, true, null);
ArrowBuilder.getBuilder().removeAllArrows(gameId);
}
public void select(int messageId, GameView gameView, Map<String, Serializable> options, String message) {
updateGame(messageId, gameView, true, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.abilityPicker.setVisible(false);
holdingPriority = false;
txtHoldPriority.setVisible(false);
setMenuStates(
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"),
false);
boolean controllingPlayer = false;
for (PlayerView playerView : gameView.getPlayers()) {
if (playerView.getPlayerId().equals(playerId)) {
// magenoxx: because of uncaught bug with saving state, rolling back and stack
// undo is allowed only for empty stack
controllingPlayer = !gameView.getPriorityPlayerName().equals(playerView.getName());
if (playerView.getStatesSavedSize() > 0 && gameView.getStack().isEmpty()) {
feedbackPanel.allowUndo(playerView.getStatesSavedSize());
}
break;
}
}
Map<String, Serializable> panelOptions = new HashMap<>();
if (lastGameData.options != null) {
panelOptions.putAll(lastGameData.options);
}
panelOptions.put("your_turn", true);
String activePlayerText;
if (gameView.getActivePlayerId().equals(playerId)) {
activePlayerText = "Your turn";
} else {
activePlayerText = gameView.getActivePlayerName() + "'s turn";
}
String priorityPlayerText = "";
if (controllingPlayer) {
priorityPlayerText = " / priority " + gameView.getPriorityPlayerName();
}
String additionalMessage = activePlayerText + " / " + gameView.getStep().toString() + priorityPlayerText;
this.feedbackPanel.prepareFeedback(FeedbackMode.SELECT, message, additionalMessage, gameView.getSpecial(), panelOptions, true, gameView.getPhase());
}
public void playMana(int messageId, GameView gameView, Map<String, Serializable> options, String message) {
updateGame(messageId, gameView, true, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.feedbackPanel.prepareFeedback(FeedbackMode.CANCEL, message, "", gameView.getSpecial(), options, true, gameView.getPhase());
}
public void playXMana(int messageId, GameView gameView, Map<String, Serializable> options, String message) {
updateGame(messageId, gameView, true, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.feedbackPanel.prepareFeedback(FeedbackMode.CONFIRM, message, "", gameView.getSpecial(), null, true, gameView.getPhase());
}
public void replayMessage(String message) {
//TODO: implement this
}
public void pickAbility(int messageId, GameView gameView, Map<String, Serializable> options, AbilityPickerView choices) {
updateGame(messageId, gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.abilityPicker.show(choices, MageFrame.getDesktop().getMousePosition());
}
private void hideAll() {
hidePickDialogs();
this.abilityPicker.setVisible(false);
ActionCallback callback = Plugins.instance.getActionCallback();
((MageActionCallback) callback).hideGameUpdate(gameId);
}
private ShowCardsDialog prepareCardsDialog(String title, CardsView cards, boolean required, Map<String, Serializable> options, PopUpMenuType popupMenuType) {
ShowCardsDialog showCards = new ShowCardsDialog();
JPopupMenu popupMenu = null;
if (PopUpMenuType.TRIGGER_ORDER == popupMenuType) {
popupMenu = popupMenuTriggerOrder;
}
showCards.loadCards(title, cards, bigCard, gameId, required, options, popupMenu, getShowCardsEventListener(showCards));
return showCards;
}
public void getAmount(int messageId, GameView gameView, Map<String, Serializable> options, int min, int max, String message) {
updateGame(messageId, gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
pickNumber.showDialog(min, max, message, () -> {
if (pickNumber.isCancel()) {
SessionHandler.sendPlayerBoolean(gameId, false);
} else {
SessionHandler.sendPlayerInteger(gameId, pickNumber.getAmount());
}
});
}
public void getMultiAmount(int messageId, GameView gameView, List<MultiAmountMessage> messages, Map<String, Serializable> options,
int min, int max) {
updateGame(messageId, gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
pickMultiNumber.showDialog(messages, min, max, lastGameData.options, () -> {
if (pickMultiNumber.isCancel()) {
SessionHandler.sendPlayerBoolean(gameId, false);
} else {
SessionHandler.sendPlayerString(gameId, pickMultiNumber.getMultiAmount());
}
});
}
public void getChoice(int messageId, GameView gameView, Map<String, Serializable> options, Choice choice, UUID objectId) {
updateGame(messageId, gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
// TODO: remember last choices and search incremental for same events?
PickChoiceDialog pickChoice = new PickChoiceDialog();
pickChoice.showDialog(choice, null, objectId, choiceWindowState, bigCard, () -> {
// special mode adds # to the answer (server side code must process that prefix, see replacementEffectChoice)
String specialPrefix = choice.isChosenSpecial() ? "#" : "";
String valueToSend;
if (choice.isKeyChoice()) {
valueToSend = choice.getChoiceKey();
} else {
valueToSend = choice.getChoice();
}
SessionHandler.sendPlayerString(gameId, valueToSend == null ? null : specialPrefix + valueToSend);
// keep dialog position
choiceWindowState = new MageDialogState(pickChoice);
pickChoice.removeDialog();
});
}
public void pickPile(int messageId, GameView gameView, Map<String, Serializable> options, String message, CardsView pile1, CardsView pile2) {
updateGame(messageId, gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
// remove old dialogs before the new
clearPickPileDialogs();
PickPileDialog pickPileDialog = new PickPileDialog();
this.pickPile.add(pickPileDialog);
pickPileDialog.showDialog(message, pile1, pile2, bigCard, gameId, () -> {
if (pickPileDialog.isPickedOK()) {
SessionHandler.sendPlayerBoolean(gameId, pickPileDialog.isPickedPile1());
}
});
}
public Map<UUID, PlayAreaPanel> getPlayers() {
return players;
}
@SuppressWarnings("unchecked")
private void initComponents() {
abilityPicker = new mage.client.components.ability.AbilityPicker(GUISizeHelper.dialogGuiScale);
pnlHelperHandButtonsStackArea = new javax.swing.JPanel();
pnlShortCuts = new javax.swing.JPanel();
lblPhase = new javax.swing.JLabel();
txtPhase = new javax.swing.JLabel();
lblStep = new javax.swing.JLabel();
txtStep = new javax.swing.JLabel();
lblTurn = new javax.swing.JLabel();
txtTurn = new javax.swing.JLabel();
txtActivePlayer = new javax.swing.JLabel();
lblActivePlayer = new javax.swing.JLabel();
txtPriority = new javax.swing.JLabel();
lblPriority = new javax.swing.JLabel();
feedbackPanel = new mage.client.game.FeedbackPanel();
helper = new HelperPanel();
feedbackPanel.setHelperPanel(helper);
feedbackPanel.setLayout(new BorderLayout());
feedbackPanel.add(helper, BorderLayout.CENTER);
Border paddingBorder = BorderFactory.createEmptyBorder(4, 4, 4, 4);
Border border = BorderFactory.createLineBorder(Color.DARK_GRAY, 2);
txtHoldPriority = new javax.swing.JLabel();
txtHoldPriority.setText("Hold");
txtHoldPriority.setBorder(BorderFactory.createCompoundBorder(border, paddingBorder));
txtHoldPriority.setBackground(Color.LIGHT_GRAY);
txtHoldPriority.setOpaque(true);
txtHoldPriority.setToolTipText("Holding priority after the next spell cast or ability activation");
txtHoldPriority.setVisible(false);
boolean displayButtonText = PreferencesDialog.getCurrentTheme().isShortcutsVisibleForSkipButtons();
btnToggleMacro = new KeyboundButton(KEY_CONTROL_TOGGLE_MACRO, displayButtonText);
btnCancelSkip = new KeyboundButton(KEY_CONTROL_CANCEL_SKIP, displayButtonText); // F3
btnSkipToNextTurn = new KeyboundButton(KEY_CONTROL_NEXT_TURN, displayButtonText); // F4
btnSkipToEndTurn = new KeyboundButton(KEY_CONTROL_END_STEP, displayButtonText); // F5
btnSkipToNextMain = new KeyboundButton(KEY_CONTROL_MAIN_STEP, displayButtonText); // F7
btnSkipStack = new KeyboundButton(KEY_CONTROL_SKIP_STACK, displayButtonText); // F10
btnSkipToYourTurn = new KeyboundButton(KEY_CONTROL_YOUR_TURN, displayButtonText); // F9
btnSkipToEndStepBeforeYourTurn = new KeyboundButton(KEY_CONTROL_PRIOR_END, displayButtonText); // F11
btnConcede = new javax.swing.JButton();
btnSwitchHands = new javax.swing.JButton();
btnStopWatching = new javax.swing.JButton();
bigCard = new mage.client.cards.BigCard();
pnlReplay = new javax.swing.JPanel();
btnStopReplay = new javax.swing.JButton();
btnNextPlay = new javax.swing.JButton();
btnPlay = new javax.swing.JButton();
btnSkipForward = new javax.swing.JButton();
btnPreviousPlay = new javax.swing.JButton();
pnlBattlefield = new javax.swing.JPanel();
gameChatPanel = new mage.client.chat.ChatPanelBasic();
gameChatPanel.useExtendedView(ChatPanelBasic.VIEW_MODE.GAME);
userChatPanel = new mage.client.chat.ChatPanelBasic();
userChatPanel.setParentChat(gameChatPanel);
userChatPanel.useExtendedView(ChatPanelBasic.VIEW_MODE.CHAT);
userChatPanel.setChatType(ChatPanelBasic.ChatType.GAME);
gameChatPanel.setConnectedChat(userChatPanel);
gameChatPanel.disableInput();
gameChatPanel.setMinimumSize(new java.awt.Dimension(100, 48));
handContainer = new HandPanel();
handCards = new HashMap<>();
pnlShortCuts.setOpaque(false);
//pnlShortCuts.setPreferredSize(new Dimension(410, 72));
stackObjects = new mage.client.cards.Cards();
// split: [hand] <|> [stack]
splitHandAndStack = new javax.swing.JSplitPane();
splitHandAndStack.setBorder(null);
splitHandAndStack.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT);
splitHandAndStack.setOneTouchExpandable(true);
// split: [game + chats] <|> [big card]
splitGameAndBigCard = new javax.swing.JSplitPane();
splitGameAndBigCard.setBorder(null);
splitGameAndBigCard.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT);
splitGameAndBigCard.setOneTouchExpandable(true);
// split: chat <|> game logs
splitChatAndLogs = new javax.swing.JSplitPane();
splitChatAndLogs.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
splitChatAndLogs.setResizeWeight(DIVIDER_KEEP_LEFT_COMPONENT);
splitChatAndLogs.setTopComponent(userChatPanel);
splitChatAndLogs.setBottomComponent(gameChatPanel);
// split: [battlefield + hand + stack] <|> [chats]
splitBattlefieldAndChats = new javax.swing.JSplitPane();
splitBattlefieldAndChats.setBorder(null);
splitBattlefieldAndChats.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT);
splitBattlefieldAndChats.setOneTouchExpandable(true);
splitBattlefieldAndChats.setLeftComponent(pnlHelperHandButtonsStackArea);
splitBattlefieldAndChats.setRightComponent(splitChatAndLogs);
// warning, it's important to store/restore splitters in same order as real life GUI
// from outer to inner (otherwise panels will be hidden or weird)
// also it must be restored by thread queue
this.splitters.put(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATIONS_GAME_AND_BIG_CARD, new MageSplitter(splitGameAndBigCard, 0.85));
this.splitters.put(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATIONS_BATTLEFIELD_AND_CHATS, new MageSplitter(splitBattlefieldAndChats, 0.80));
this.splitters.put(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATIONS_HAND_STACK, new MageSplitter(splitHandAndStack, 0.70));
this.splitters.put(PreferencesDialog.KEY_GAMEPANEL_DIVIDER_LOCATIONS_CHAT_AND_LOGS, new MageSplitter(splitChatAndLogs, 0.40));
lblPhase.setLabelFor(txtPhase);
lblPhase.setText("Phase:");
txtPhase.setText("Phase");
txtPhase.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(153, 153, 153), 1, true));
txtPhase.setMinimumSize(new java.awt.Dimension(0, 16));
lblStep.setLabelFor(txtStep);
lblStep.setText("Step:");
txtStep.setText("Step");
txtStep.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(153, 153, 153), 1, true));
txtStep.setMinimumSize(new java.awt.Dimension(0, 16));
lblTurn.setLabelFor(txtTurn);
lblTurn.setText("Turn:");
txtTurn.setText("Turn");
txtTurn.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(153, 153, 153), 1, true));
txtTurn.setMinimumSize(new java.awt.Dimension(0, 16));
txtActivePlayer.setText("Active Player");
txtActivePlayer.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(153, 153, 153), 1, true));
txtActivePlayer.setMinimumSize(new java.awt.Dimension(0, 16));
lblActivePlayer.setLabelFor(txtActivePlayer);
lblActivePlayer.setText("Active Player:");
txtPriority.setText("Priority Player");
txtPriority.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(153, 153, 153), 1, true));
txtPriority.setMinimumSize(new java.awt.Dimension(0, 16));
lblPriority.setLabelFor(txtPriority);
lblPriority.setText("Priority Player:");
// CHATS and HINTS support
// HOTKEYS
int c = JComponent.WHEN_IN_FOCUSED_WINDOW;
// special hotkeys for custom rendered dialogs without focus
// call it before any user defined hotkeys
this.abilityPicker.injectHotkeys(this, "ABILITY_PICKER");
btnToggleMacro.setContentAreaFilled(false);
btnToggleMacro.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnToggleMacro.setToolTipText("Toggle Record Macro ("
+ getCachedKeyText(KEY_CONTROL_TOGGLE_MACRO) + ").");
btnToggleMacro.setFocusable(false);
btnToggleMacro.addMouseListener(new FirstButtonMousePressedAction(e ->
btnToggleMacroActionPerformed(null)));
KeyStroke kst = getCachedKeystroke(KEY_CONTROL_TOGGLE_MACRO);
this.getInputMap(c).put(kst, "F8_PRESS");
this.getActionMap().put("F8_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
btnToggleMacroActionPerformed(actionEvent);
}
});
KeyStroke ks3 = getCachedKeystroke(KEY_CONTROL_CANCEL_SKIP);
this.getInputMap(c).put(ks3, "F3_PRESS");
this.getActionMap().put("F3_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
restorePriorityActionPerformed(actionEvent);
}
});
// SKIP BUTTONS (button's hint/state is dynamic)
// button icons setup in setGUISize
btnCancelSkip.setContentAreaFilled(false);
btnCancelSkip.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnCancelSkip.setToolTipText("CANCEL all skips");
btnCancelSkip.setFocusable(false);
btnCancelSkip.addMouseListener(new FirstButtonMousePressedAction(e ->
restorePriorityActionPerformed(null)));
btnSkipToNextTurn.setContentAreaFilled(false);
btnSkipToNextTurn.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnSkipToNextTurn.setToolTipText("dynamic");
btnSkipToNextTurn.setFocusable(false);
btnSkipToNextTurn.addMouseListener(new FirstButtonMousePressedAction(e ->
btnEndTurnActionPerformed(null)));
KeyStroke ks = getCachedKeystroke(KEY_CONTROL_NEXT_TURN);
this.getInputMap(c).put(ks, "F4_PRESS");
this.getActionMap().put("F4_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
btnEndTurnActionPerformed(actionEvent);
}
});
btnSkipToEndTurn.setContentAreaFilled(false);
btnSkipToEndTurn.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnSkipToEndTurn.setToolTipText("dynamic");
btnSkipToEndTurn.setFocusable(false);
btnSkipToEndTurn.addMouseListener(new FirstButtonMousePressedAction(e ->
btnUntilEndOfTurnActionPerformed(null)));
ks = getCachedKeystroke(KEY_CONTROL_END_STEP);
this.getInputMap(c).put(ks, "F5_PRESS");
this.getActionMap().put("F5_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
btnUntilEndOfTurnActionPerformed(actionEvent);
}
});
ks = getCachedKeystroke(KEY_CONTROL_SKIP_STEP);
this.getInputMap(c).put(ks, "F6_PRESS");
this.getActionMap().put("F6_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
btnEndTurnSkipStackActionPerformed(actionEvent);
}
});
btnSkipToNextMain.setContentAreaFilled(false);
btnSkipToNextMain.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnSkipToNextMain.setToolTipText("dynamic");
btnSkipToNextMain.setFocusable(false);
btnSkipToNextMain.addMouseListener(new FirstButtonMousePressedAction(e ->
btnUntilNextMainPhaseActionPerformed(null)));
ks = getCachedKeystroke(KEY_CONTROL_MAIN_STEP);
this.getInputMap(c).put(ks, "F7_PRESS");
this.getActionMap().put("F7_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
btnUntilNextMainPhaseActionPerformed(actionEvent);
}
});
btnSkipToYourTurn.setContentAreaFilled(false);
btnSkipToYourTurn.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnSkipToYourTurn.setToolTipText("dynamic");
btnSkipToYourTurn.setFocusable(false);
btnSkipToYourTurn.addMouseListener(new FirstButtonMousePressedAction(e ->
btnPassPriorityUntilNextYourTurnActionPerformed(null)));
KeyStroke ks9 = getCachedKeystroke(KEY_CONTROL_YOUR_TURN);
this.getInputMap(c).put(ks9, "F9_PRESS");
this.getActionMap().put("F9_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
btnPassPriorityUntilNextYourTurnActionPerformed(actionEvent);
}
});
btnSkipToEndStepBeforeYourTurn.setContentAreaFilled(false);
btnSkipToEndStepBeforeYourTurn.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnSkipToEndStepBeforeYourTurn.setToolTipText("dynamic");
btnSkipToEndStepBeforeYourTurn.setFocusable(false);
btnSkipToEndStepBeforeYourTurn.addMouseListener(new FirstButtonMousePressedAction(e ->
btnSkipToEndStepBeforeYourTurnActionPerformed(null)));
KeyStroke ks11 = getCachedKeystroke(KEY_CONTROL_PRIOR_END);
this.getInputMap(c).put(ks11, "F11_PRESS");
this.getActionMap().put("F11_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
btnSkipToEndStepBeforeYourTurnActionPerformed(actionEvent);
}
});
btnSkipStack.setContentAreaFilled(false);
btnSkipStack.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnSkipStack.setToolTipText("dynamic");
btnSkipStack.setFocusable(false);
btnSkipStack.addMouseListener(new FirstButtonMousePressedAction(e ->
btnPassPriorityUntilStackResolvedActionPerformed(null)));
ks = getCachedKeystroke(KEY_CONTROL_SKIP_STACK);
this.getInputMap(c).put(ks, "F10_PRESS");
this.getActionMap().put("F10_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
btnPassPriorityUntilStackResolvedActionPerformed(actionEvent);
}
});
btnConcede.setContentAreaFilled(false);
btnConcede.setBorder(new EmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
btnConcede.setToolTipText("CONCEDE current game");
btnConcede.setFocusable(false);
btnConcede.addMouseListener(new FirstButtonMousePressedAction(e ->
btnConcedeActionPerformed(null)));
// update button hint/states to default values
updateSkipButtons();
// HOTKEYS
KeyStroke ks2 = getCachedKeystroke(KEY_CONTROL_CONFIRM);
this.getInputMap(c).put(ks2, "F2_PRESS");
this.getActionMap().put("F2_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
if (feedbackPanel != null) {
feedbackPanel.pressOKYesOrDone();
}
}
});
KeyStroke ks12 = getCachedKeystroke(KEY_CONTROL_SWITCH_CHAT);
this.getInputMap(c).put(ks12, "F12_PRESS");
this.getActionMap().put("F12_PRESS", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// switch in/out to chat, must triggers in chat input too
//if (isUserImputActive()) return;
if (isChatInputActive()) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearFocusOwner();
} else if (!isUserImputActive()) {
userChatPanel.getTxtMessageInputComponent().requestFocusInWindow();
}
}
});
KeyStroke ksAltE = KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.ALT_MASK);
this.getInputMap(c).put(ksAltE, "ENLARGE");
this.getActionMap().put("ENLARGE", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
ActionCallback callback = Plugins.instance.getActionCallback();
((MageActionCallback) callback).enlargeCard(EnlargeMode.NORMAL);
}
});
KeyStroke ksAltS = KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.ALT_MASK);
this.getInputMap(c).put(ksAltS, "ENLARGE_SOURCE");
this.getActionMap().put("ENLARGE_SOURCE", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// TODO: doesn't work? 26.11.2023, delete as useless
if (isUserImputActive()) return;
ActionCallback callback = Plugins.instance.getActionCallback();
((MageActionCallback) callback).enlargeCard(EnlargeMode.ALTERNATE);
}
});
KeyStroke ksAlt1 = KeyStroke.getKeyStroke(KeyEvent.VK_1, InputEvent.ALT_MASK);
this.getInputMap(c).put(ksAlt1, "USEFIRSTMANAABILITY");
this.getActionMap().put("USEFIRSTMANAABILITY", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
SessionHandler.sendPlayerAction(PlayerAction.USE_FIRST_MANA_ABILITY_ON, gameId, null);
setMenuStates(
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"),
holdingPriority);
}
});
// TODO: delete as useless
KeyStroke ksAltEReleased = KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.ALT_MASK, true);
this.getInputMap(c).put(ksAltEReleased, "ENLARGE_RELEASE");
KeyStroke ksAltSReleased = KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.ALT_MASK, true);
this.getInputMap(c).put(ksAltSReleased, "ENLARGE_RELEASE");
this.getActionMap().put("ENLARGE_RELEASE", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
ActionCallback callback = Plugins.instance.getActionCallback();
((MageActionCallback) callback).hideEnlargedCard();
}
});
KeyStroke ksAlt1Released = KeyStroke.getKeyStroke(KeyEvent.VK_1, InputEvent.ALT_MASK, true);
this.getInputMap(c).put(ksAlt1Released, "USEFIRSTMANAABILITY_RELEASE");
this.getActionMap().put("USEFIRSTMANAABILITY_RELEASE", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (isUserImputActive()) return;
SessionHandler.sendPlayerAction(PlayerAction.USE_FIRST_MANA_ABILITY_OFF, gameId, null);
setMenuStates(
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"),
holdingPriority);
}
});
btnSwitchHands.setContentAreaFilled(false);
btnSwitchHands.setBorder(new EmptyBorder(0, 0, 0, 0));
btnSwitchHands.setFocusable(false);
btnSwitchHands.setToolTipText("Switch between your hand cards and hand cards of controlled players.");
btnSwitchHands.addMouseListener(new FirstButtonMousePressedAction(e ->
btnSwitchHandActionPerformed(null)));
btnStopWatching.setContentAreaFilled(false);
btnStopWatching.setBorder(new EmptyBorder(0, 0, 0, 0));
btnStopWatching.setFocusable(false);
btnStopWatching.setToolTipText("Stop watching this game.");
btnStopWatching.addMouseListener(new FirstButtonMousePressedAction(e ->
btnStopWatchingActionPerformed(null)));
stackObjects.setBackgroundColor(new Color(0, 0, 0, 40));
btnStopReplay.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/control_stop.png")));
btnStopReplay.addActionListener(evt -> btnStopReplayActionPerformed(evt));
btnNextPlay.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/control_stop_right.png")));
btnNextPlay.addActionListener(evt -> btnNextPlayActionPerformed(evt));
btnPlay.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/control_right.png")));
btnPlay.addActionListener(evt -> btnPlayActionPerformed(evt));
btnSkipForward.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/control_double_stop_right.png")));
btnSkipForward.addActionListener(evt -> btnSkipForwardActionPerformed(evt));
btnPreviousPlay.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/control_stop_left.png")));
btnPreviousPlay.addActionListener(evt -> btnPreviousPlayActionPerformed(evt));
initPopupMenuTriggerOrder();
pnlBattlefield.setLayout(new java.awt.GridBagLayout());
jPhases = new JPanel();
jPhases.setBackground(new Color(0, 0, 0, 0));
// layout on gui size
MouseAdapter phasesMouseAdapter = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent evt) {
mouseClickPhaseBar(evt);
}
};
/// phase buttons
if (DebugUtil.GUI_GAME_DRAW_PHASE_BUTTONS_PANEL_BORDER) {
jPhases.setBorder(BorderFactory.createLineBorder(Color.red));
}
String[] phases = {"Untap", "Upkeep", "Draw", "Main1",
"Combat_Start", "Combat_Attack", "Combat_Block", "Combat_Damage", "Combat_End",
"Main2", "Cleanup", "Next_Turn"};
for (String name : phases) {
createPhaseButton(name, phasesMouseAdapter);
}
pnlReplay.setOpaque(false);
// phases buttons
phasesContainer = new JPanel(new FlowLayout(FlowLayout.CENTER));
phasesContainer.setOpaque(false);
phasesContainer.add(jPhases);
// card hint panel
bigCardPanel = new javax.swing.JPanel();
bigCardPanel.setOpaque(false);
bigCardPanel.setLayout(new BorderLayout());
bigCardPanel.add(bigCard, BorderLayout.NORTH);
// split: game <|> chat/log
splitGameAndBigCard.setLeftComponent(splitBattlefieldAndChats);
splitGameAndBigCard.setRightComponent(bigCardPanel);
}
private void removeListener() {
for (MouseListener ml : this.getMouseListeners()) {
this.removeMouseListener(ml);
}
for (MouseListener ml : this.btnToggleMacro.getMouseListeners()) {
this.btnToggleMacro.removeMouseListener(ml);
}
for (MouseListener ml : this.btnCancelSkip.getMouseListeners()) {
this.btnCancelSkip.removeMouseListener(ml);
}
for (MouseListener ml : this.btnConcede.getMouseListeners()) {
this.btnConcede.removeMouseListener(ml);
}
for (MouseListener ml : this.btnSkipToYourTurn.getMouseListeners()) {
this.btnSkipToYourTurn.removeMouseListener(ml);
}
for (MouseListener ml : this.btnSkipStack.getMouseListeners()) {
this.btnSkipStack.removeMouseListener(ml);
}
for (MouseListener ml : this.btnSkipToEndStepBeforeYourTurn.getMouseListeners()) {
this.btnSkipToEndStepBeforeYourTurn.removeMouseListener(ml);
}
for (MouseListener ml : this.btnSkipToEndTurn.getMouseListeners()) {
this.btnSkipToEndTurn.removeMouseListener(ml);
}
for (MouseListener ml : this.btnSkipToNextMain.getMouseListeners()) {
this.btnSkipToNextMain.removeMouseListener(ml);
}
for (MouseListener ml : this.btnSkipToNextTurn.getMouseListeners()) {
this.btnSkipToNextTurn.removeMouseListener(ml);
}
for (MouseListener ml : this.btnSwitchHands.getMouseListeners()) {
this.btnSwitchHands.removeMouseListener(ml);
}
for (MouseListener ml : this.btnStopWatching.getMouseListeners()) {
this.btnStopWatching.removeMouseListener(ml);
}
for (MouseListener ml : this.jPhases.getMouseListeners()) {
this.jPhases.removeMouseListener(ml);
}
for (String name : phaseButtons.keySet()) {
HoverButton hoverButton = phaseButtons.get(name);
for (MouseListener ml : hoverButton.getMouseListeners()) {
hoverButton.removeMouseListener(ml);
}
}
for (ActionListener al : this.btnPlay.getActionListeners()) {
this.btnPlay.removeActionListener(al);
}
for (ActionListener al : this.btnStopReplay.getActionListeners()) {
this.btnStopReplay.removeActionListener(al);
}
for (ActionListener al : this.btnNextPlay.getActionListeners()) {
this.btnNextPlay.removeActionListener(al);
}
for (ActionListener al : this.btnNextPlay.getActionListeners()) {
this.btnNextPlay.removeActionListener(al);
}
for (ActionListener al : this.btnPreviousPlay.getActionListeners()) {
this.btnPreviousPlay.removeActionListener(al);
}
for (ActionListener al : this.btnSkipForward.getActionListeners()) {
this.btnSkipForward.removeActionListener(al);
}
final BasicSplitPaneUI myUi = (BasicSplitPaneUI) splitGameAndBigCard.getUI();
final BasicSplitPaneDivider divider = myUi.getDivider();
final JButton upArrowButton = (JButton) divider.getComponent(0);
for (ActionListener al : upArrowButton.getActionListeners()) {
upArrowButton.removeActionListener(al);
}
final JButton downArrowButton = (JButton) divider.getComponent(1);
for (ActionListener al : downArrowButton.getActionListeners()) {
downArrowButton.removeActionListener(al);
}
for (ComponentListener cl : this.getComponentListeners()) {
this.removeComponentListener(cl);
}
for (KeyListener kl : this.getKeyListeners()) {
this.removeKeyListener(kl);
}
}
private void btnConcedeActionPerformed(java.awt.event.ActionEvent evt) {
UserRequestMessage message = new UserRequestMessage("Confirm concede", "Are you sure you want to concede?");
message.setButton1("No", null);
message.setButton2("Yes", PlayerAction.CLIENT_CONCEDE_GAME);
message.setGameId(gameId);
MageFrame.getInstance().showUserRequestDialog(message);
}
private void btnToggleMacroActionPerformed(java.awt.event.ActionEvent evt) {
SessionHandler.sendPlayerAction(PlayerAction.TOGGLE_RECORD_MACRO, gameId, null);
skipButtons.activateSkipButton("");
AudioManager.playOnSkipButton();
if (btnToggleMacro.getBorder().equals(BORDER_ACTIVE)) {
btnToggleMacro.setBorder(BORDER_NON_ACTIVE);
} else {
btnToggleMacro.setBorder(BORDER_ACTIVE);
}
}
private void btnEndTurnActionPerformed(java.awt.event.ActionEvent evt) {
SessionHandler.sendPlayerAction(PlayerAction.PASS_PRIORITY_UNTIL_NEXT_TURN, gameId, null);
skipButtons.activateSkipButton(KEY_CONTROL_NEXT_TURN);
AudioManager.playOnSkipButton();
updateSkipButtons();
}
private boolean isChatInputUnderCursor(Point p) {
Component c = this.getComponentAt(p);
return gameChatPanel.getTxtMessageInputComponent().equals(c) || userChatPanel.getTxtMessageInputComponent().equals(c);
}
private boolean isChatInputActive() {
Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
return gameChatPanel.getTxtMessageInputComponent().equals(c) || userChatPanel.getTxtMessageInputComponent().equals(c);
}
private boolean isUserImputActive() {
// any imput or choose dialog active (need to disable skip buttons in dialogs and chat)
return MageDialog.isModalDialogActivated() || isChatInputActive();
}
private void btnUntilEndOfTurnActionPerformed(java.awt.event.ActionEvent evt) {
SessionHandler.sendPlayerAction(PlayerAction.PASS_PRIORITY_UNTIL_TURN_END_STEP, gameId, null);
skipButtons.activateSkipButton(KEY_CONTROL_END_STEP);
AudioManager.playOnSkipButton();
updateSkipButtons();
}
private void btnEndTurnSkipStackActionPerformed(java.awt.event.ActionEvent evt) {
logger.error("Skip action don't used", new Throwable());
/*
SessionHandler.sendPlayerAction(PlayerAction.PASS_PRIORITY_UNTIL_NEXT_TURN_SKIP_STACK, gameId, null);
AudioManager.playOnSkipButton();
updateSkipButtons(true, false, false, false, true, false);
*/
}
private void btnUntilNextMainPhaseActionPerformed(java.awt.event.ActionEvent evt) {
SessionHandler.sendPlayerAction(PlayerAction.PASS_PRIORITY_UNTIL_NEXT_MAIN_PHASE, gameId, null);
skipButtons.activateSkipButton(KEY_CONTROL_MAIN_STEP);
AudioManager.playOnSkipButton();
updateSkipButtons();
}
private void btnPassPriorityUntilNextYourTurnActionPerformed(java.awt.event.ActionEvent evt) {
SessionHandler.sendPlayerAction(PlayerAction.PASS_PRIORITY_UNTIL_MY_NEXT_TURN, gameId, null);
skipButtons.activateSkipButton(KEY_CONTROL_YOUR_TURN);
AudioManager.playOnSkipButton();
updateSkipButtons();
}
private void btnPassPriorityUntilStackResolvedActionPerformed(java.awt.event.ActionEvent evt) {
SessionHandler.sendPlayerAction(PlayerAction.PASS_PRIORITY_UNTIL_STACK_RESOLVED, gameId, null);
skipButtons.activateSkipButton(KEY_CONTROL_SKIP_STACK);
AudioManager.playOnSkipButton();
updateSkipButtons();
}
private void btnSkipToEndStepBeforeYourTurnActionPerformed(java.awt.event.ActionEvent evt) {
SessionHandler.sendPlayerAction(PlayerAction.PASS_PRIORITY_UNTIL_END_STEP_BEFORE_MY_NEXT_TURN, gameId, null);
skipButtons.activateSkipButton(KEY_CONTROL_PRIOR_END);
AudioManager.playOnSkipButton();
updateSkipButtons();
}
private void restorePriorityActionPerformed(java.awt.event.ActionEvent evt) {
SessionHandler.sendPlayerAction(PlayerAction.PASS_PRIORITY_CANCEL_ALL_ACTIONS, gameId, null);
skipButtons.activateSkipButton("");
AudioManager.playOnSkipButtonCancel();
updateSkipButtons();
}
private void mouseClickPhaseBar(MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt)) {
PreferencesDialog.main(new String[]{PreferencesDialog.OPEN_PHASES_TAB});
// TODO: add event handler on preferences closed and refresh game data from server
}
}
private void btnSwitchHandActionPerformed(java.awt.event.ActionEvent evt) {
String[] choices = handCards.keySet().toArray(new String[0]);
String newChosenHandKey = (String) JOptionPane.showInputDialog(
this,
"Choose hand to display:", "Switch between hands",
JOptionPane.PLAIN_MESSAGE,
null,
choices,
this.chosenHandKey);
if (newChosenHandKey != null && !newChosenHandKey.isEmpty()) {
this.chosenHandKey = newChosenHandKey;
CardsView cards = handCards.get(chosenHandKey);
handContainer.loadCards(cards, bigCard, gameId);
}
}
private void btnStopWatchingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnStopWatchingActionPerformed
UserRequestMessage message = new UserRequestMessage("Stop watching", "Are you sure you want to stop watching?");
message.setButton1("No", null);
message.setButton2("Yes", PlayerAction.CLIENT_STOP_WATCHING);
message.setGameId(gameId);
MageFrame.getInstance().showUserRequestDialog(message);
}//GEN-LAST:event_btnStopWatchingActionPerformed
private void btnStopReplayActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnStopReplayActionPerformed
if (replayTask != null && !replayTask.isDone()) {
replayTask.cancel(true);
} else {
UserRequestMessage message = new UserRequestMessage("Stop replay", "Are you sure you want to stop replay?");
message.setButton1("No", null);
message.setButton2("Yes", PlayerAction.CLIENT_REPLAY_ACTION);
message.setGameId(gameId);
MageFrame.getInstance().showUserRequestDialog(message);
}
}//GEN-LAST:event_btnStopReplayActionPerformed
private void btnNextPlayActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnNextPlayActionPerformed
SessionHandler.nextPlay(gameId);
}//GEN-LAST:event_btnNextPlayActionPerformed
private void btnPreviousPlayActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPreviousPlayActionPerformed
SessionHandler.previousPlay(gameId);
}//GEN-LAST:event_btnPreviousPlayActionPerformed
private void btnPlayActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPlayActionPerformed
if (replayTask == null || replayTask.isDone()) {
replayTask = new ReplayTask(gameId);
replayTask.execute();
}
}//GEN-LAST:event_btnPlayActionPerformed
private void btnSkipForwardActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSkipForwardActionPerformed
SessionHandler.skipForward(gameId, 10);
}//GEN-LAST:event_btnSkipForwardActionPerformed
public void setJLayeredPane(JLayeredPane jLayeredPane) {
this.jLayeredPane = jLayeredPane;
}
public void installComponents() {
jLayeredPane.setOpaque(false);
jLayeredPane.add(DialogManager.getManager(gameId), JLayeredPane.MODAL_LAYER, 0);
installAbilityPicker();
}
private void installAbilityPicker() {
jLayeredPane.add(abilityPicker, JLayeredPane.MODAL_LAYER);
abilityPicker.setVisible(false);
}
private void uninstallComponents() {
if (jLayeredPane != null) {
jLayeredPane.remove(DialogManager.getManager(gameId));
}
DialogManager.removeGame(gameId);
uninstallAbilityPicker();
}
private void uninstallAbilityPicker() {
abilityPicker.setVisible(false);
if (jLayeredPane != null) {
jLayeredPane.remove(abilityPicker);
}
this.abilityPicker.cleanUp();
}
private void createPhaseButton(String name, MouseAdapter mouseAdapter) {
int buttonSize = GUISizeHelper.gamePhaseButtonSize;
Rectangle rect = new Rectangle(buttonSize, buttonSize);
HoverButton button = new HoverButton("", ImageManagerImpl.instance.getPhaseImage(name, buttonSize), rect);
button.setToolTipText(name.replaceAll("_", " "));
button.setPreferredSize(new Dimension(buttonSize, buttonSize));
button.addMouseListener(mouseAdapter);
phaseButtons.put(name, button);
jPhases.add(button);
}
// Event listener for the ShowCardsDialog
private Listener<Event> getShowCardsEventListener(final ShowCardsDialog dialog) {
return event -> {
if (event.getEventType() == ClientEventType.CARD_POPUP_MENU) {
if (event.getComponent() != null && event.getComponent() instanceof MageCard) {
JPopupMenu menu = ((MageCard) event.getComponent()).getPopupMenu();
if (menu != null) {
cardViewPopupMenu = ((CardView) event.getSource());
menu.show(event.getComponent(), event.getxPos(), event.getyPos());
}
}
}
};
}
public void handleTriggerOrderPopupMenuEvent(ActionEvent e) {
UUID abilityId = null;
String abilityRuleText = null;
if (cardViewPopupMenu instanceof CardView && cardViewPopupMenu.getAbility() != null) {
abilityId = cardViewPopupMenu.getAbility().getId();
if (!cardViewPopupMenu.getAbility().getRules().isEmpty()
&& !cardViewPopupMenu.getAbility().getRules().get(0).isEmpty()) {
abilityRuleText = cardViewPopupMenu.getAbility().getRules().get(0);
abilityRuleText = abilityRuleText.replace("{this}", cardViewPopupMenu.getName());
}
}
switch (e.getActionCommand()) {
case CMD_AUTO_ORDER_FIRST:
SessionHandler.sendPlayerAction(TRIGGER_AUTO_ORDER_ABILITY_FIRST, gameId, abilityId);
SessionHandler.sendPlayerUUID(gameId, abilityId);
break;
case CMD_AUTO_ORDER_LAST:
SessionHandler.sendPlayerAction(TRIGGER_AUTO_ORDER_ABILITY_LAST, gameId, abilityId);
SessionHandler.sendPlayerUUID(gameId, null); // Don't use this but refresh the displayed abilities
break;
case CMD_AUTO_ORDER_NAME_FIRST:
if (abilityRuleText != null) {
SessionHandler.sendPlayerAction(TRIGGER_AUTO_ORDER_NAME_FIRST, gameId, abilityRuleText);
SessionHandler.sendPlayerUUID(gameId, abilityId);
}
break;
case CMD_AUTO_ORDER_NAME_LAST:
if (abilityRuleText != null) {
SessionHandler.sendPlayerAction(TRIGGER_AUTO_ORDER_NAME_LAST, gameId, abilityRuleText);
SessionHandler.sendPlayerUUID(gameId, null); // Don't use this but refresh the displayed abilities
}
break;
case CMD_AUTO_ORDER_RESET_ALL:
SessionHandler.sendPlayerAction(TRIGGER_AUTO_ORDER_RESET_ALL, gameId, null);
break;
default:
break;
}
// TODO: 2021-01-23 why it here? Can be removed?
for (ShowCardsDialog dialog : pickTarget) {
dialog.removeDialog();
}
for (PickPileDialog dialog : pickPile) {
dialog.removeDialog();
}
}
private void initPopupMenuTriggerOrder() {
ActionListener actionListener = e -> handleTriggerOrderPopupMenuEvent(e);
popupMenuTriggerOrder = new JPopupMenu();
// String tooltipText = "";
JMenuItem menuItem;
menuItem = new JMenuItem("Put this ability always first on the stack");
menuItem.setActionCommand(CMD_AUTO_ORDER_FIRST);
menuItem.addActionListener(actionListener);
popupMenuTriggerOrder.add(menuItem);
menuItem = new JMenuItem("Put this ability always last on the stack");
menuItem.setActionCommand(CMD_AUTO_ORDER_LAST);
menuItem.addActionListener(actionListener);
popupMenuTriggerOrder.add(menuItem);
menuItem = new JMenuItem("Put all abilities with that rule text always first on the stack");
menuItem.setActionCommand(CMD_AUTO_ORDER_NAME_FIRST);
menuItem.addActionListener(actionListener);
popupMenuTriggerOrder.add(menuItem);
menuItem = new JMenuItem("Put all abilities with that rule text always last on the stack");
menuItem.setActionCommand(CMD_AUTO_ORDER_NAME_LAST);
menuItem.addActionListener(actionListener);
popupMenuTriggerOrder.add(menuItem);
menuItem = new JMenuItem("Reset all order settings for triggered abilities");
menuItem.setActionCommand(CMD_AUTO_ORDER_RESET_ALL);
menuItem.addActionListener(actionListener);
popupMenuTriggerOrder.add(menuItem);
}
public String getGameLog() {
return gameChatPanel.getText();
}
public FeedbackPanel getFeedbackPanel() {
return feedbackPanel;
}
// Use Cmd on OSX since Ctrl+click is already used to simulate right click
private static final int holdPriorityMask = System.getProperty("os.name").contains("Mac OS X") ? InputEvent.META_DOWN_MASK : InputEvent.CTRL_DOWN_MASK;
public void handleEvent(AWTEvent event) {
if (event instanceof InputEvent) {
int id = event.getID();
boolean isActionEvent = false;
if (id == MouseEvent.MOUSE_PRESSED) {
isActionEvent = true;
// clear chat focus on click
if (event instanceof MouseEvent) {
MouseEvent me = (MouseEvent) event;
if (isChatInputActive() && !isChatInputUnderCursor(me.getPoint())) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearFocusOwner();
}
}
} else if (id == KeyEvent.KEY_PRESSED) {
KeyEvent key = (KeyEvent) event;
int keyCode = key.getKeyCode();
if (keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_SPACE) {
isActionEvent = true;
}
}
if (isActionEvent) {
InputEvent input = (InputEvent) event;
if ((input.getModifiersEx() & holdPriorityMask) != 0) {
setMenuStates(
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"),
PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"),
true);
holdPriority(true);
}
}
}
}
public void holdPriority(boolean holdPriority) {
if (holdingPriority != holdPriority) {
holdingPriority = holdPriority;
txtHoldPriority.setVisible(holdPriority);
if (holdPriority) {
SessionHandler.sendPlayerAction(PlayerAction.HOLD_PRIORITY, gameId, null);
} else {
SessionHandler.sendPlayerAction(PlayerAction.UNHOLD_PRIORITY, gameId, null);
}
}
}
private boolean holdingPriority;
private mage.client.components.ability.AbilityPicker abilityPicker;
private mage.client.cards.BigCard bigCard;
private KeyboundButton btnToggleMacro;
private KeyboundButton btnCancelSkip;
private KeyboundButton btnSkipToNextTurn; // F4
private KeyboundButton btnSkipToEndTurn; // F5
private KeyboundButton btnSkipToNextMain; // F7
private KeyboundButton btnSkipStack; // F8
private KeyboundButton btnSkipToYourTurn; // F9
private KeyboundButton btnSkipToEndStepBeforeYourTurn; // F11
private javax.swing.JButton btnConcede;
private javax.swing.JButton btnSwitchHands;
private javax.swing.JButton btnNextPlay;
private javax.swing.JButton btnPlay;
private javax.swing.JButton btnPreviousPlay;
private javax.swing.JButton btnSkipForward;
private javax.swing.JButton btnStopReplay;
private javax.swing.JButton btnStopWatching;
private mage.client.chat.ChatPanelBasic gameChatPanel;
private mage.client.game.FeedbackPanel feedbackPanel;
private HelperPanel helper;
private mage.client.chat.ChatPanelBasic userChatPanel;
private javax.swing.JPanel bigCardPanel;
private javax.swing.JPanel pnlHelperHandButtonsStackArea;
private javax.swing.JSplitPane splitGameAndBigCard;
private javax.swing.JSplitPane splitBattlefieldAndChats;
private javax.swing.JSplitPane splitChatAndLogs;
private javax.swing.JSplitPane splitHandAndStack;
private javax.swing.JLabel lblActivePlayer;
private javax.swing.JLabel lblPhase;
private javax.swing.JLabel lblPriority;
private javax.swing.JLabel lblStep;
private javax.swing.JLabel lblTurn;
private javax.swing.JPanel pnlBattlefield;
private javax.swing.JPanel pnlShortCuts;
private javax.swing.JPanel pnlReplay;
private javax.swing.JLabel txtActivePlayer;
private javax.swing.JLabel txtPhase;
private javax.swing.JLabel txtPriority;
private javax.swing.JLabel txtStep;
private javax.swing.JLabel txtTurn;
private Map<String, CardsView> handCards;
private mage.client.cards.Cards stackObjects;
private HandPanel handContainer;
private JPanel jPhases;
private JPanel phasesContainer;
private javax.swing.JLabel txtHoldPriority;
private boolean imagePanelState;
}
class ReplayTask extends SwingWorker<Void, Collection<MatchView>> {
// replay without table - just single game
private final UUID gameId;
private static final Logger logger = Logger.getLogger(ReplayTask.class);
ReplayTask(UUID gameId) {
this.gameId = gameId;
}
@Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
SessionHandler.nextPlay(gameId);
TimeUnit.SECONDS.sleep(1);
}
return null;
}
@Override
protected void done() {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
logger.fatal("Replay Match Task error", ex);
} catch (CancellationException ex) {
}
}
}