diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index c9d69ca85f..9752b1cd8a 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -27,16 +27,20 @@ */ package mage.client; +import java.awt.AWTEvent; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.SplashScreen; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; @@ -225,6 +229,26 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { return instance; } + private void handleEvent(AWTEvent event) { + MagePane frame = activeFrame; + + // support multiple mage panes + Object source = event.getSource(); + if(source instanceof Component) { + Component component = (Component)source; + while(component != null) { + if(component instanceof MagePane) { + frame = (MagePane)component; + break; + } + component = component.getParent(); + } + } + + if(frame != null) + frame.handleEvent(event); + } + /** * Creates new form MageFrame */ @@ -240,6 +264,13 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } }); + Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() { + @Override + public void eventDispatched(AWTEvent event) { + handleEvent(event); + } + }, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK); + TConfig config = TConfig.current(); config.setArchiveDetector(new TArchiveDetector("zip")); config.setAccessPreference(FsAccessOption.STORE, true); diff --git a/Mage.Client/src/main/java/mage/client/MagePane.java b/Mage.Client/src/main/java/mage/client/MagePane.java index 8ddadc6695..b4a4d474d6 100644 --- a/Mage.Client/src/main/java/mage/client/MagePane.java +++ b/Mage.Client/src/main/java/mage/client/MagePane.java @@ -33,6 +33,7 @@ */ package mage.client; +import java.awt.AWTEvent; import java.awt.KeyboardFocusManager; import java.beans.PropertyVetoException; import javax.swing.plaf.basic.BasicInternalFrameUI; @@ -91,6 +92,9 @@ public abstract class MagePane extends javax.swing.JInternalFrame { } + public void handleEvent(AWTEvent event) { + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Mage.Client/src/main/java/mage/client/game/GamePane.java b/Mage.Client/src/main/java/mage/client/game/GamePane.java index 06e86103cf..b0c445fdfa 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePane.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePane.java @@ -33,6 +33,7 @@ */ package mage.client.game; +import java.awt.AWTEvent; import java.util.UUID; import javax.swing.SwingUtilities; import mage.client.MagePane; @@ -132,8 +133,12 @@ public class GamePane extends MagePane { gamePanel.activated(); } + @Override + public void handleEvent(AWTEvent event) { + gamePanel.handleEvent(event); + } + private mage.client.game.GamePanel gamePanel; private javax.swing.JScrollPane jScrollPane1; private UUID gameId; - } diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 2b424627a2..a7ab2aad80 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -27,6 +27,7 @@ */ package mage.client.game; +import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -391,6 +392,7 @@ public final class GamePanel extends javax.swing.JPanel { stackObjects.setCardDimension(GUISizeHelper.handCardDimension); txtSpellsCast.setFont(new Font(GUISizeHelper.gameDialogAreaFont.getFontName(), Font.BOLD, GUISizeHelper.gameDialogAreaFont.getSize())); + txtHoldPriority.setFont(new Font(GUISizeHelper.gameDialogAreaFont.getFontName(), Font.BOLD, GUISizeHelper.gameDialogAreaFont.getSize())); GUISizeHelper.changePopupMenuFont(popupMenuTriggerOrder); int newStackWidth = pnlHelperHandButtonsStackArea.getWidth() * GUISizeHelper.stackWidth / 100; @@ -589,7 +591,8 @@ public final class GamePanel extends javax.swing.JPanel { 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") + PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"), + holdingPriority ); updateGame(game); @@ -957,9 +960,9 @@ public final class GamePanel extends javax.swing.JPanel { * @param manaPoolAutomaticRestricted * @param useFirstManaAbility */ - public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, boolean useFirstManaAbility) { + public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, boolean useFirstManaAbility, boolean holdPriority) { for (PlayAreaPanel playAreaPanel : players.values()) { - playAreaPanel.setMenuStates(manaPoolAutomatic, manaPoolAutomaticRestricted, useFirstManaAbility); + playAreaPanel.setMenuStates(manaPoolAutomatic, manaPoolAutomaticRestricted, useFirstManaAbility, holdPriority); } } @@ -1201,6 +1204,14 @@ public final class GamePanel extends javax.swing.JPanel { } public void select(String message, GameView gameView, int messageId, Map options) { + 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); + updateGame(gameView, options); boolean controllingPlayer = false; for (PlayerView playerView : gameView.getPlayers()) { @@ -1340,6 +1351,14 @@ public final class GamePanel extends javax.swing.JPanel { txtSpellsCast.setOpaque(true); txtSpellsCast.setToolTipText("spells cast during the current turn"); + 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); + btnCancelSkip = new javax.swing.JButton(); // F3 btnSkipToNextTurn = new javax.swing.JButton(); // F4 btnSkipToEndTurn = new javax.swing.JButton(); // F5 @@ -1670,7 +1689,8 @@ public final class GamePanel extends javax.swing.JPanel { 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")); + PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"), + holdingPriority); } }); @@ -1713,7 +1733,8 @@ public final class GamePanel extends javax.swing.JPanel { 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")); + PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"), + holdingPriority); } }); @@ -1829,6 +1850,7 @@ public final class GamePanel extends javax.swing.JPanel { .addComponent(btnSkipToEndStepBeforeYourTurn) ) .addGroup(gl_pnlShortCuts.createSequentialGroup() + .addComponent(txtHoldPriority) .addComponent(txtSpellsCast) .addComponent(btnSwitchHands) .addComponent(btnCancelSkip) @@ -1862,6 +1884,7 @@ public final class GamePanel extends javax.swing.JPanel { .addComponent(btnSkipToEndStepBeforeYourTurn) ) .addGroup(gl_pnlShortCuts.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(txtHoldPriority) .addComponent(txtSpellsCast) .addComponent(btnSwitchHands) .addComponent(btnCancelSkip) @@ -2343,6 +2366,48 @@ public final class GamePanel extends javax.swing.JPanel { return feedbackPanel; } + // Use Cmd on OSX since Ctrl+click is already used to simulate right click + private static 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; + 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) + session.sendPlayerAction(PlayerAction.HOLD_PRIORITY, gameId, null); + else + session.sendPlayerAction(PlayerAction.UNHOLD_PRIORITY, gameId, null); + } + } + + private boolean holdingPriority; private mage.client.components.ability.AbilityPicker abilityPicker; private mage.client.cards.BigCard bigCard; @@ -2397,6 +2462,7 @@ public final class GamePanel extends javax.swing.JPanel { private JPanel jPhases; private JPanel phasesContainer; private javax.swing.JLabel txtSpellsCast; + private javax.swing.JLabel txtHoldPriority; private HoverButton currentStep; private Point prevPoint; diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java index e7ad6fff29..9568d51b13 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java @@ -78,6 +78,7 @@ public class PlayAreaPanel extends javax.swing.JPanel { private JCheckBoxMenuItem manaPoolMenuItem2; private JCheckBoxMenuItem useFirstManaAbilityItem; private JCheckBoxMenuItem allowViewHandCardsMenuItem; + private JCheckBoxMenuItem holdPriorityMenuItem; public static final int PANEL_HEIGHT = 242; public static final int PANEL_HEIGHT_SMALL = 190; @@ -210,6 +211,19 @@ public class PlayAreaPanel extends javax.swing.JPanel { popupMenu.add(menuItem); menuItem.addActionListener(skipListener); + holdPriorityMenuItem = new JCheckBoxMenuItem("" + (System.getProperty("os.name").contains("Mac OS X") ? "Cmd" : "Ctrl") + "+click - Hold Priority"); + holdPriorityMenuItem.setMnemonic(KeyEvent.VK_P); + holdPriorityMenuItem.setToolTipText("Hold priority after casting a spell or activating an ability, instead of automatically passing priority."); + popupMenu.add(holdPriorityMenuItem); + holdPriorityMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + boolean holdPriority = ((JCheckBoxMenuItem)e.getSource()).getState(); + gamePanel.setMenuStates(manaPoolMenuItem1.getState(), manaPoolMenuItem2.getState(), useFirstManaAbilityItem.getState(), holdPriority); + gamePanel.holdPriority(holdPriority); + } + }); + JMenu skipMenu = new JMenu("Skip"); skipMenu.setMnemonic(KeyEvent.VK_S); popupMenu.add(skipMenu); @@ -277,7 +291,7 @@ public class PlayAreaPanel extends javax.swing.JPanel { public void actionPerformed(ActionEvent e) { boolean manaPoolAutomatic = ((JCheckBoxMenuItem) e.getSource()).getState(); PreferencesDialog.saveValue(KEY_GAME_MANA_AUTOPAYMENT, manaPoolAutomatic ? "true" : "false"); - gamePanel.setMenuStates(manaPoolAutomatic, manaPoolMenuItem2.getState(), useFirstManaAbilityItem.getState()); + gamePanel.setMenuStates(manaPoolAutomatic, manaPoolMenuItem2.getState(), useFirstManaAbilityItem.getState(), holdPriorityMenuItem.getState()); gamePanel.getSession().sendPlayerAction(manaPoolAutomatic ? PlayerAction.MANA_AUTO_PAYMENT_ON : PlayerAction.MANA_AUTO_PAYMENT_OFF, gameId, null); } }); @@ -295,7 +309,7 @@ public class PlayAreaPanel extends javax.swing.JPanel { public void actionPerformed(ActionEvent e) { boolean manaPoolAutomaticRestricted = ((JCheckBoxMenuItem) e.getSource()).getState(); PreferencesDialog.saveValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, manaPoolAutomaticRestricted ? "true" : "false"); - gamePanel.setMenuStates(manaPoolMenuItem1.getState(), manaPoolAutomaticRestricted, useFirstManaAbilityItem.getState()); + gamePanel.setMenuStates(manaPoolMenuItem1.getState(), manaPoolAutomaticRestricted, useFirstManaAbilityItem.getState(), holdPriorityMenuItem.getState()); gamePanel.getSession().sendPlayerAction(manaPoolAutomaticRestricted ? PlayerAction.MANA_AUTO_PAYMENT_RESTRICTED_ON : PlayerAction.MANA_AUTO_PAYMENT_RESTRICTED_OFF, gameId, null); } }); @@ -313,7 +327,7 @@ public class PlayAreaPanel extends javax.swing.JPanel { public void actionPerformed(ActionEvent e) { boolean useFirstManaAbility = ((JCheckBoxMenuItem) e.getSource()).getState(); PreferencesDialog.saveValue(KEY_USE_FIRST_MANA_ABILITY, useFirstManaAbility ? "true" : "false"); - gamePanel.setMenuStates(manaPoolMenuItem1.getState(), manaPoolMenuItem2.getState(), useFirstManaAbility); + gamePanel.setMenuStates(manaPoolMenuItem1.getState(), manaPoolMenuItem2.getState(), useFirstManaAbility, holdPriorityMenuItem.getState()); gamePanel.getSession().sendPlayerAction(useFirstManaAbility ? PlayerAction.USE_FIRST_MANA_ABILITY_ON: PlayerAction.USE_FIRST_MANA_ABILITY_OFF, gameId, null); } }); @@ -642,7 +656,7 @@ public class PlayAreaPanel extends javax.swing.JPanel { this.playingMode = playingMode; } - public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, boolean useFirstManaAbility) { + public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, boolean useFirstManaAbility, boolean holdPriority) { if (manaPoolMenuItem1 != null) { manaPoolMenuItem1.setSelected(manaPoolAutomatic); } @@ -652,6 +666,9 @@ public class PlayAreaPanel extends javax.swing.JPanel { if (useFirstManaAbilityItem != null) { useFirstManaAbilityItem.setSelected(useFirstManaAbility); } + if (holdPriorityMenuItem != null) { + holdPriorityMenuItem.setSelected(holdPriority); + } } private mage.client.game.BattlefieldPanel battlefieldPanel; diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 326d3a3d59..18bdfa651e 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -395,7 +395,8 @@ public class CallbackClientImpl implements CallbackClient { .append("
F7 - Skip to next main phase but stop on declare attackers/blockers and something on the stack") .append("
F9 - Skip everything until your next turn") .append("
F11 - Skip everything until the end step just prior to your turn") - .append("
F3 - Undo F4/F5/F7/F9/F11").toString(), + .append("
F3 - Undo F4/F5/F7/F9/F11") + .append("
").append(System.getProperty("os.name").contains("Mac OS X") ? "Cmd" : "Ctrl").append(" + click - Hold priority while casting a spell or activating an ability").toString(), null, MessageType.USER_INFO, ChatMessage.MessageColor.BLUE); break; case TOURNAMENT: diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index a2a48fb1d6..203e5d4548 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -123,6 +123,8 @@ public class HumanPlayer extends PlayerImpl { protected Map requestAutoAnswerId = new HashMap<>(); protected Map requestAutoAnswerText = new HashMap<>(); + protected boolean holdingPriority; + public HumanPlayer(String name, RangeOfInfluence range, int skill) { super(name, range); replacementEffectChoice = new ChoiceImpl(true); @@ -565,7 +567,7 @@ public class HumanPlayer extends PlayerImpl { public boolean priority(Game game) { passed = false; if (!abort) { - if (getJustActivatedType() != null) { + if (getJustActivatedType() != null && !holdingPriority) { if (userData.isPassPriorityCast() && getJustActivatedType().equals(AbilityType.SPELL)) { setJustActivatedType(null); pass(game); @@ -661,6 +663,7 @@ public class HumanPlayer extends PlayerImpl { } while (canRespond()) { updateGameStatePriority("priority", game); + holdingPriority = false; game.firePriorityEvent(playerId); waitForResponse(game); if (game.executingRollback()) { @@ -1465,6 +1468,12 @@ public class HumanPlayer extends PlayerImpl { case REQUEST_AUTO_ANSWER_RESET_ALL: setRequestAutoAnswer(playerAction, game, data); break; + case HOLD_PRIORITY: + holdingPriority = true; + break; + case UNHOLD_PRIORITY: + holdingPriority = false; + break; default: super.sendPlayerAction(playerAction, game, data); } diff --git a/Mage/src/main/java/mage/constants/PlayerAction.java b/Mage/src/main/java/mage/constants/PlayerAction.java index c21bf05d45..cbff2a1384 100644 --- a/Mage/src/main/java/mage/constants/PlayerAction.java +++ b/Mage/src/main/java/mage/constants/PlayerAction.java @@ -82,5 +82,6 @@ public enum PlayerAction { CLIENT_DOWNLOAD_CARD_IMAGES, CLIENT_RECONNECT, CLIENT_REPLAY_ACTION, - + HOLD_PRIORITY, + UNHOLD_PRIORITY }