From a7409f3d0824b18ae15a74842c515bc01bf19032 Mon Sep 17 00:00:00 2001 From: draxdyn Date: Fri, 24 Jun 2016 17:46:46 +0200 Subject: [PATCH] Hold priority on Ctrl+click In most cases, it is not useful to hold priority after doing something, since the spell or ability added to the stack can just be performed beforehand. Hence, automatically passing priority is the only comfortable setting. However, in some cases it is useful (chiefly for Infernal Tutor + Lion's Eye Diamond and similar interaction), and currently players have to open Preferences, change the option and then change it back whenever they need to hold priority, which is very annoying. This change allows to temporarily hold priority by simply holding Control while performing an action, which solves the issue in a manner similar to other clients. A "Hold" indicator next to the spells cast indicator is displayed so that the user knows his Control key holding was registered. The code works by adding a new HOLD_PRIORITY player action that causes the automatic priority pass options to be ignored until the player is given priority again. The client sends the message whenever it's not already holding priority and Ctrl+click/space/enter happens anywhere. This is somewhat "loose" as it means that Ctrl+click on the background also holds priority, but this might actually be desirable and it greatly simplifies the code, since only a global AWT event listener is required, and there is no need to change every place in the code that could add something to the stack. It is also possible to hold priority and stop holding priority using the context menu. --- .../src/main/java/mage/client/MageFrame.java | 31 ++++++++ .../src/main/java/mage/client/MagePane.java | 4 + .../main/java/mage/client/game/GamePane.java | 7 +- .../main/java/mage/client/game/GamePanel.java | 76 +++++++++++++++++-- .../java/mage/client/game/PlayAreaPanel.java | 25 +++++- .../client/remote/CallbackClientImpl.java | 3 +- .../src/mage/player/human/HumanPlayer.java | 11 ++- .../java/mage/constants/PlayerAction.java | 3 +- 8 files changed, 147 insertions(+), 13 deletions(-) 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 }