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.CardUtil; import mage.util.DebugUtil; import mage.util.MultiAmountMessage; import mage.util.StreamUtils; 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 java.util.stream.Collectors; 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 players = new LinkedHashMap<>(); private final Map playersWhoLeft = new LinkedHashMap<>(); // non modal frames private final Map exiles = new HashMap<>(); private final Map revealed = new HashMap<>(); private final Map lookedAt = new HashMap<>(); private final Map graveyards = new HashMap<>(); // need to sync selection private final Map graveyardWindows = new HashMap<>(); private final Map companion = new HashMap<>(); private final Map sideboards = new HashMap<>(); // need to sync selection private final Map sideboardWindows = new HashMap<>(); private final ArrayList pickTarget = new ArrayList<>(); private final ArrayList pickPile = new ArrayList<>(); private final Map 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 loadedCards = new HashMap<>(); private int storedHeight; private final Map 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 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; private boolean vertical = 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 options; Set targets; Map 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 List getPossibleTargets() { if (options != null && options.containsKey("possibleTargets")) { return (List) options.get("possibleTargets"); } else { return Collections.emptyList(); } } public Set getChosenTargets() { if (options != null && options.containsKey("chosenTargets")) { return new HashSet<>((List) options.get("chosenTargets")); } else { return Collections.emptySet(); } } 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() { this.vertical = PreferencesDialog.getCachedValue(KEY_GUI_VERTICAL_LAYOUT, "false").equals("true"); 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 if (vertical) { splitHandAndStack.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); splitHandAndStack.setBottomComponent(pnlCommandsFeedbackAndHand); splitHandAndStack.setTopComponent(pnlCommandsSkipAndStack); splitHandAndStack.setResizeWeight(DIVIDER_KEEP_RIGHT_COMPONENT); } else { 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); var basePane = this.vertical ? splitBattlefieldAndChats : splitGameAndBigCard; jLayeredBackgroundPane.add(basePane, JLayeredPane.DEFAULT_LAYER); Map 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); basePane.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 getUIComponents(JLayeredPane jLayeredPane) { Map 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 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); if (vertical) { pnlShortCuts.setLayout(new FlowLayout(FlowLayout.CENTER, 1, vGap)); } else { 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, lastGameData.getChosenTargets()); 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); } if (this.pickMultiNumber != null && !this.pickMultiNumber.isVisible()) { // TODO: add pick number dialogs support here //this.pickMultiNumber.fullRefresh(GUISizeHelper.dialogGuiScale); this.pickMultiNumber.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 loadSplitterLocationsFromSettings(String settingsKey) { Map res; Type type = new TypeToken>() { }.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 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 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 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 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.pickMultiNumber.init(gameId, bigCard); 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 options, Set 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 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 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 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 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, lastGameData.getChosenTargets()); 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 = "" + "" + getCachedKeyText(this.hotkeyName) + "" + " - " + text; // mode String mesTrue = this.extraTrue; String mesFalse = this.extraFalse; if (!this.extraTrue.isEmpty() || !this.extraFalse.isEmpty()) { if (this.extraMode) { mesTrue = "" + mesTrue + ""; } else { mesFalse = "" + mesFalse + ""; } 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 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 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 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 options, Set 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"); } Set needChosen = lastGameData.getChosenTargets(); Set 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) { // my hand 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())); } } // opponent hands (switching by GUI's button with my hand) List list = lastGameData.game.getOpponentHands().values().stream().flatMap(s -> s.values().stream()).collect(Collectors.toList()); for (SimpleCardView card : list) { 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())); } } // watched hands (switching by GUI's button with my hand) list = lastGameData.game.getWatchedHands().values().stream().flatMap(s -> s.values().stream()).collect(Collectors.toList()); for (SimpleCardView card : list) { 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 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 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 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 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 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 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 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 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 windows, Set needSelectable, Set 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 options, String message, CardsView cardsView, Set 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 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 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 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 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 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 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 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 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 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 messages, Map options, int min, int max) { updateGame(messageId, gameView, false, options, null); hideAll(); DialogManager.getManager(gameId).fadeOut(); pickMultiNumber.init(gameId, bigCard); 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 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 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 getPlayers() { return players; } @SuppressWarnings("unchecked") private boolean 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(); if (vertical) { splitChatAndLogs.setOrientation(javax.swing.JSplitPane.HORIZONTAL_SPLIT); } else { 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); if (vertical) { splitBattlefieldAndChats.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); splitBattlefieldAndChats.setBottomComponent(pnlHelperHandButtonsStackArea); splitBattlefieldAndChats.setTopComponent(splitChatAndLogs); } else { 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); if (!vertical) { splitGameAndBigCard.setRightComponent(bigCardPanel); } return vertical; } 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 = vertical ? (BasicSplitPaneUI) splitBattlefieldAndChats.getUI() : (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 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 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> { // 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) { } } }