From addcece58222026735709e7dfb670b90a6193302 Mon Sep 17 00:00:00 2001 From: elcnesh Date: Sun, 26 Apr 2015 10:54:55 +0000 Subject: [PATCH] - Huge update to network play - Updated protocol: now used everywhere for type checking - No more locks between server and client: client no longer expects any replies - More code reuse - Fixed issue with location of multiple abilities selection popup menu - Improved stability; actual games should now be possible - Other updates - Mobile version player panel now uses same code as desktop - Remove automatic "view all cards" cheat in hotseat (no longer necessary) - Code cleanup --- .gitattributes | 3 + .../java/forge/ai/PlayerControllerAi.java | 2 +- .../main/java/forge/util/ITriggerEvent.java | 2 + .../main/java/forge/util/ReflectionUtil.java | 9 + .../src/main/java/forge/game/GameView.java | 4 - .../ability/effects/ChangeZoneEffect.java | 2 +- .../game/ability/effects/PlayEffect.java | 18 +- .../java/forge/game/player/DelayedReveal.java | 5 +- .../forge/game/player/PlayerController.java | 4 +- .../java/forge/game/player/PlayerView.java | 2 + .../java/forge/screens/match/CMatchUI.java | 165 ++++++++------ .../java/forge/screens/match/ZoneAction.java | 2 +- .../screens/match/controllers/CDock.java | 2 +- .../screens/match/controllers/CField.java | 8 +- .../screens/match/controllers/CPrompt.java | 29 +-- .../forge/screens/match/menus/GameMenu.java | 2 +- .../forge/screens/match/views/VStack.java | 4 +- .../src/main/java/forge/toolbox/FButton.java | 23 +- .../java/forge/toolbox/MouseTriggerEvent.java | 22 +- .../forge/view/arcane/FloatingCardArea.java | 37 ++- .../main/java/forge/view/arcane/HandArea.java | 4 +- .../main/java/forge/view/arcane/PlayArea.java | 2 +- .../util/PlayerControllerForTests.java | 5 +- .../forge/screens/match/MatchController.java | 30 +-- .../src/forge/screens/match/MatchScreen.java | 2 +- .../forge/screens/match/views/VManaPool.java | 8 +- .../forge/screens/match/views/VPlayers.java | 31 +-- .../control/FControlGameEventHandler.java | 3 +- .../java/forge/control/WatchLocalGame.java | 12 +- .../forge/interfaces/IGameController.java | 8 +- .../main/java/forge/interfaces/IGuiGame.java | 18 +- .../java/forge/match/AbstractGuiGame.java | 16 +- .../main/java/forge/match/HostedMatch.java | 5 - .../forge/match/input/InputPassPriority.java | 2 +- .../java/forge/match/input/InputPayMana.java | 4 +- .../src/main/java/forge/net/GameProtocol.java | 191 ++++++++++------ .../java/forge/net/GameProtocolHandler.java | 96 ++++++++ .../java/forge/net/GameProtocolSender.java | 32 +++ .../forge/net/client/GameClientHandler.java | 126 +++-------- .../forge/net/client/NetGameController.java | 76 +++---- .../java/forge/net/event/GuiGameEvent.java | 7 +- .../java/forge/net/server/FServerManager.java | 214 +----------------- .../forge/net/server/GameServerHandler.java | 41 ++++ .../java/forge/net/server/NetGuiGame.java | 213 ++++------------- .../java/forge/net/server/RemoteClient.java | 4 +- .../src/main/java/forge/player/HumanPlay.java | 2 +- .../forge/player/PlayerControllerHuman.java | 40 ++-- .../java/forge/player/PlayerZoneUpdates.java | 13 +- 48 files changed, 694 insertions(+), 856 deletions(-) create mode 100644 forge-gui/src/main/java/forge/net/GameProtocolHandler.java create mode 100644 forge-gui/src/main/java/forge/net/GameProtocolSender.java create mode 100644 forge-gui/src/main/java/forge/net/server/GameServerHandler.java diff --git a/.gitattributes b/.gitattributes index c5835e66905..e44abcdecea 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17715,6 +17715,8 @@ forge-gui/src/main/java/forge/model/MultipleForgeJarsFoundError.java -text forge-gui/src/main/java/forge/model/UnOpenedMeta.java -text forge-gui/src/main/java/forge/model/package-info.java svneol=native#text/plain forge-gui/src/main/java/forge/net/GameProtocol.java -text +forge-gui/src/main/java/forge/net/GameProtocolHandler.java -text +forge-gui/src/main/java/forge/net/GameProtocolSender.java -text forge-gui/src/main/java/forge/net/IRemote.java -text forge-gui/src/main/java/forge/net/ReplyPool.java -text forge-gui/src/main/java/forge/net/client/ClientGameLobby.java -text @@ -17735,6 +17737,7 @@ forge-gui/src/main/java/forge/net/event/UpdateLobbyPlayerEvent.java -text forge-gui/src/main/java/forge/net/event/package-info.java -text forge-gui/src/main/java/forge/net/package-info.java -text forge-gui/src/main/java/forge/net/server/FServerManager.java -text +forge-gui/src/main/java/forge/net/server/GameServerHandler.java -text forge-gui/src/main/java/forge/net/server/IToClient.java -text forge-gui/src/main/java/forge/net/server/NetGuiGame.java -text forge-gui/src/main/java/forge/net/server/RemoteClient.java -text diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 885fdff5c4f..7cde8cb93a1 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -79,7 +79,7 @@ public class PlayerControllerAi extends PlayerController { brains.setUseSimulation(value); } - public SpellAbility getAbilityToPlay(List abilities, ITriggerEvent triggerEvent) { + public SpellAbility getAbilityToPlay(Card hostCard, List abilities, ITriggerEvent triggerEvent) { if (abilities.size() == 0) { return null; } diff --git a/forge-core/src/main/java/forge/util/ITriggerEvent.java b/forge-core/src/main/java/forge/util/ITriggerEvent.java index 7a9bbd154c6..c3fac78fca1 100644 --- a/forge-core/src/main/java/forge/util/ITriggerEvent.java +++ b/forge-core/src/main/java/forge/util/ITriggerEvent.java @@ -2,4 +2,6 @@ package forge.util; public interface ITriggerEvent { int getButton(); + int getX(); + int getY(); } diff --git a/forge-core/src/main/java/forge/util/ReflectionUtil.java b/forge-core/src/main/java/forge/util/ReflectionUtil.java index 6ca51669ca3..a713011670e 100644 --- a/forge-core/src/main/java/forge/util/ReflectionUtil.java +++ b/forge-core/src/main/java/forge/util/ReflectionUtil.java @@ -1,8 +1,11 @@ package forge.util; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import org.apache.commons.lang3.reflect.TypeUtils; + /** * Static utilities related to reflection. * @@ -62,4 +65,10 @@ public final class ReflectionUtil { return null; } + public static boolean isInstance(final Object obj, final Class type) { + if (Array.class.equals(type)) { + return obj.getClass().isArray(); + } + return TypeUtils.isInstance(obj, type); + } } diff --git a/forge-game/src/main/java/forge/game/GameView.java b/forge-game/src/main/java/forge/game/GameView.java index 59c530a34d6..b40915dd48f 100644 --- a/forge-game/src/main/java/forge/game/GameView.java +++ b/forge-game/src/main/java/forge/game/GameView.java @@ -26,10 +26,6 @@ import forge.util.FCollectionView; public class GameView extends TrackableObject { private static final long serialVersionUID = 8522884512960961528L; - /*private final TrackableIndex cards = new TrackableIndex(); - private final TrackableIndex players = new TrackableIndex(); - private final TrackableIndex spellAbilities = new TrackableIndex(); - private final TrackableIndex stackItems = new TrackableIndex();*/ private final TrackableCollection players; private CombatView combatView; private final transient Game game; //TODO: Remove this when possible before network support added diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index a18a42f6acb..0d48fc903fc 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -720,7 +720,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (sas.isEmpty()) { continue; } - SpellAbility tgtSA = decider.getController().getAbilityToPlay(sas); + SpellAbility tgtSA = decider.getController().getAbilityToPlay(tgtCard, sas); if (!decider.getController().confirmAction(tgtSA, null, "Do you want to play " + tgtCard + "?")) { continue; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 359ad9b7ea4..8a60f5dc8c2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -1,13 +1,18 @@ package forge.game.ability.effects; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.StaticData; -import forge.card.CardStateName; import forge.card.CardRulesPredicates; +import forge.card.CardStateName; import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; @@ -23,11 +28,6 @@ import forge.item.PaperCard; import forge.util.Aggregates; import forge.util.Lang; -import org.apache.commons.lang3.StringUtils; - -import java.util.ArrayList; -import java.util.List; - public class PlayEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { @@ -160,13 +160,13 @@ public class PlayEffect extends SpellAbilityEffect { } Card original = tgtCard; if (sa.hasParam("CopyCard")) { - Zone zone = tgtCard.getZone(); + final Zone zone = tgtCard.getZone(); tgtCard = Card.fromPaperCard(tgtCard.getPaperCard(), sa.getActivatingPlayer()); tgtCard.setToken(true); tgtCard.setZone(zone); if (zone != null) { - zone.add(tgtCard); + zone.add(tgtCard); } if (useEncoded) { @@ -197,7 +197,7 @@ public class PlayEffect extends SpellAbilityEffect { } // only one mode can be used - SpellAbility tgtSA = sa.getActivatingPlayer().getController().getAbilityToPlay(sas); + SpellAbility tgtSA = sa.getActivatingPlayer().getController().getAbilityToPlay(tgtCard, sas); boolean noManaCost = sa.hasParam("WithoutManaCost"); if (noManaCost) { tgtSA = tgtSA.copyWithNoManaCost(); diff --git a/forge-game/src/main/java/forge/game/player/DelayedReveal.java b/forge-game/src/main/java/forge/game/player/DelayedReveal.java index fe9bd2ea430..c5008d48eca 100644 --- a/forge-game/src/main/java/forge/game/player/DelayedReveal.java +++ b/forge-game/src/main/java/forge/game/player/DelayedReveal.java @@ -1,5 +1,6 @@ package forge.game.player; +import java.io.Serializable; import java.util.Collection; import forge.game.card.Card; @@ -10,7 +11,9 @@ import forge.game.zone.ZoneType; * Stores information to reveal cards after a delay unless those cards can be * revealed in the same dialog as cards being selected */ -public class DelayedReveal { +public class DelayedReveal implements Serializable { + private static final long serialVersionUID = 5516713460440436615L; + private final Collection cards; private final ZoneType zone; private final PlayerView owner; diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index 79e409c61ea..b76a8547c0f 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -90,8 +90,8 @@ public abstract class PlayerController { public Player getPlayer() { return player; } public LobbyPlayer getLobbyPlayer() { return lobbyPlayer; } - public final SpellAbility getAbilityToPlay(List abilities) { return getAbilityToPlay(abilities, null); } - public abstract SpellAbility getAbilityToPlay(List abilities, ITriggerEvent triggerEvent); + public final SpellAbility getAbilityToPlay(final Card hostCard, final List abilities) { return getAbilityToPlay(hostCard, abilities, null); } + public abstract SpellAbility getAbilityToPlay(Card hostCard, List abilities, ITriggerEvent triggerEvent); //public abstract void playFromSuspend(Card c); public abstract void playSpellAbilityForFree(SpellAbility copySA, boolean mayChoseNewTargets); diff --git a/forge-game/src/main/java/forge/game/player/PlayerView.java b/forge-game/src/main/java/forge/game/player/PlayerView.java index cfb35b1c72a..cf233192674 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerView.java +++ b/forge-game/src/main/java/forge/game/player/PlayerView.java @@ -375,6 +375,8 @@ public class PlayerView extends GameEntityView { } public String getDetails() { final StringBuilder builder = new StringBuilder(); + builder.append(getName()); + builder.append('\n'); for (final String detailsPart : getDetailsList()) { builder.append(detailsPart); builder.append('\n'); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index e16fcd5149e..0f96d37d334 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -17,8 +17,8 @@ */ package forge.screens.match; +import java.awt.Component; import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -32,11 +32,10 @@ import java.util.concurrent.atomic.AtomicReference; import javax.swing.JMenu; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; -import javax.swing.MenuElement; -import javax.swing.MenuSelectionManager; import javax.swing.SwingUtilities; import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import forge.FThreads; @@ -56,7 +55,6 @@ import forge.game.combat.CombatView; import forge.game.phase.PhaseType; import forge.game.player.DelayedReveal; import forge.game.player.IHasIcon; -import forge.game.player.Player; import forge.game.player.PlayerView; import forge.game.spellability.SpellAbilityView; import forge.game.zone.ZoneType; @@ -72,11 +70,9 @@ import forge.gui.framework.IVDoc; import forge.gui.framework.SDisplayUtil; import forge.gui.framework.SLayoutIO; import forge.gui.framework.VEmptyDoc; -import forge.interfaces.IButton; import forge.item.InventoryItem; import forge.item.PaperCard; import forge.match.AbstractGuiGame; -import forge.match.MatchButtonType; import forge.menus.IMenuProvider; import forge.model.FModel; import forge.player.PlayerZoneUpdate; @@ -97,7 +93,6 @@ import forge.toolbox.FButton; import forge.toolbox.FOptionPane; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinImage; -import forge.toolbox.MouseTriggerEvent; import forge.toolbox.special.PhaseIndicator; import forge.toolbox.special.PhaseLabel; import forge.trackable.TrackableCollection; @@ -149,13 +144,13 @@ public final class CMatchUI Singletons.getView().getLpnDocument().add(targetingOverlay.getPanel(), FView.TARGETING_LAYER); targetingOverlay.getPanel().setSize(Singletons.getControl().getDisplaySize()); this.myDocs = new EnumMap>(EDocID.class); - this.myDocs.put(EDocID.CARD_PICTURE, getCDetailPicture().getCPicture().getView()); - this.myDocs.put(EDocID.CARD_DETAIL, getCDetailPicture().getCDetail().getView()); - this.myDocs.put(EDocID.CARD_ANTES, getCAntes().getView()); + this.myDocs.put(EDocID.CARD_PICTURE, cDetailPicture.getCPicture().getView()); + this.myDocs.put(EDocID.CARD_DETAIL, cDetailPicture.getCDetail().getView()); + this.myDocs.put(EDocID.CARD_ANTES, cAntes.getView()); this.myDocs.put(EDocID.REPORT_MESSAGE, getCPrompt().getView()); this.myDocs.put(EDocID.REPORT_STACK, getCStack().getView()); - this.myDocs.put(EDocID.REPORT_COMBAT, getCCombat().getView()); - this.myDocs.put(EDocID.REPORT_LOG, getCLog().getView()); + this.myDocs.put(EDocID.REPORT_COMBAT, cCombat.getView()); + this.myDocs.put(EDocID.REPORT_LOG, cLog.getView()); this.myDocs.put(EDocID.DEV_MODE, getCDev().getView()); this.myDocs.put(EDocID.BUTTON_DOCK, getCDock().getView());; } @@ -212,25 +207,13 @@ public final class CMatchUI getCDev().update(); } - public CAntes getCAntes() { - return cAntes; - } - public CCombat getCCombat() { - return cCombat; - } - public CDetailPicture getCDetailPicture() { - return cDetailPicture; - } public CDev getCDev() { return cDev; } public CDock getCDock() { return cDock; } - public CLog getCLog() { - return cLog; - } - public CPrompt getCPrompt() { + CPrompt getCPrompt() { return cPrompt; } public CStack getCStack() { @@ -338,24 +321,6 @@ public final class CMatchUI return idx < 0 || idx >= allHands.size() ? null : allHands.get(idx); } - /** - * Checks if game control should stop at a phase, for either a forced - * programmatic stop, or a user-induced phase toggle. - * - * @param turn - * the {@link Player} at whose phase might be stopped. - * @param phase - * the {@link PhaseType} at which might be stopped. - * @return boolean whether the current GUI calls for a stop at the specified - * phase of the specified player. - */ - @Override - public final boolean stopAtPhase(final PlayerView turn, final PhaseType phase) { - final VField vf = getFieldViewFor(turn); - final PhaseLabel label = vf.getPhaseIndicator().getLabelFor(phase); - return label == null || label.getEnabled(); - } - @Override public void setCard(final CardView c) { this.setCard(c, false); @@ -395,8 +360,8 @@ public final class CMatchUI SDisplayUtil.showTab(selectedDocBeforeCombat); selectedDocBeforeCombat = null; } - getCCombat().setModel(combat); - getCCombat().update(); + cCombat.setModel(combat); + cCombat.update(); } // showCombat(CombatView) @Override @@ -548,26 +513,60 @@ public final class CMatchUI return panels; } - @Override - public IButton getBtnOK(final PlayerView playerView) { - return view.getBtnOK(); + /** + * Find the card panel belonging to a card, bringing up the corresponding + * window or tab if necessary. + * + * @param card + * the {@link CardView} to find a panel for. + * @return a {@link CardPanel}, or {@code null} if no corresponding panel is + * found. + */ + private CardPanel findCardPanel(final CardView card) { + final int id = card.getId(); + switch (card.getZone()) { + case Battlefield: + for (final VField f : view.getFieldViews()) { + final CardPanel panel = f.getTabletop().getCardPanel(id); + if (panel != null) { + SDisplayUtil.showTab(f); + return panel; + } + } + break; + case Hand: + for (final VHand h : view.getHands()) { + final CardPanel panel = h.getHandArea().getCardPanel(id); + if (panel != null) { + SDisplayUtil.showTab(h); + return panel; + } + } + break; + case Command: + case Exile: + case Graveyard: + case Library: + return FloatingCardArea.getCardPanel(this, card); + default: + break; + } + return null; } - @Override - public IButton getBtnCancel(final PlayerView playerView) { - return view.getBtnCancel(); - } + public void updateButtons(final PlayerView owner, final String label1, final String label2, final boolean enable1, final boolean enable2, final boolean focus1) { + final FButton btn1 = view.getBtnOK(), btn2 = view.getBtnCancel(); + btn1.setText(label1); + btn2.setText(label2); + btn1.setEnabled(enable1); + btn2.setEnabled(enable2); - @Override - public void focusButton(final MatchButtonType button) { + final FButton toFocus = enable1 && focus1 ? btn1 : (enable2 ? btn2 : null); // ensure we don't steal focus from an overlay - if (!SOverlayUtils.overlayHasFocus()) { + if (toFocus != null && !SOverlayUtils.overlayHasFocus()) { FThreads.invokeInEdtLater(new Runnable() { @Override public final void run() { - final FButton btn = button == MatchButtonType.OK - ? getCPrompt().getView().getBtnOK() - : getCPrompt().getView().getBtnCancel(); - btn.requestFocusInWindow(); + toFocus.requestFocusInWindow(); } }); } @@ -671,7 +670,7 @@ public final class CMatchUI } @Override - public SpellAbilityView getAbilityToPlay(final List abilities, final ITriggerEvent triggerEvent) { + public SpellAbilityView getAbilityToPlay(final CardView hostCard, final List abilities, final ITriggerEvent triggerEvent) { if (triggerEvent == null) { if (abilities.isEmpty()) { return null; @@ -696,19 +695,20 @@ public final class CMatchUI final JPopupMenu menu = new JPopupMenu("Abilities"); boolean enabled; - boolean hasEnabled = false; + int firstEnabled = -1; int shortcut = KeyEvent.VK_1; //use number keys as shortcuts for abilities 1-9 + int index = 0; for (final SpellAbilityView ab : abilities) { enabled = ab.canPlay(); - if (enabled) { - hasEnabled = true; + if (enabled && firstEnabled < 0) { + firstEnabled = index; } GuiUtils.addMenuItem(menu, FSkin.encodeSymbols(ab.toString(), true), shortcut > 0 ? KeyStroke.getKeyStroke(shortcut, 0) : null, new Runnable() { @Override public void run() { - cPrompt.selectAbility(ab); + getGameController().selectAbility(ab); } }, enabled); if (shortcut > 0) { @@ -717,15 +717,38 @@ public final class CMatchUI shortcut = 0; //stop adding shortcuts after 9 } } + index++; } - if (hasEnabled) { //only show menu if at least one ability can be played - SwingUtilities.invokeLater(new Runnable() { //use invoke later to ensure first ability selected by default - public void run() { - MenuSelectionManager.defaultManager().setSelectedPath(new MenuElement[]{menu, menu.getSubElements()[0]}); + + if (firstEnabled >= 0) { //only show menu if at least one ability can be played + final CardPanel panel = findCardPanel(hostCard); + final Component menuParent; + final int x, y; + if (panel == null) { + // Fall back to showing in VPrompt if no panel can be found + menuParent = getCPrompt().getView().getTarMessage(); + x = 0; + y = 0; + SDisplayUtil.showTab(getCPrompt().getView()); + } else { + final ZoneType zone = hostCard.getZone(); + if (ImmutableList.of(ZoneType.Command, ZoneType.Exile, ZoneType.Graveyard, ZoneType.Library).contains(zone)) { + FloatingCardArea.show(this, hostCard.getController(), zone); + } + menuParent = panel.getParent(); + x = triggerEvent.getX(); + y = triggerEvent.getY(); + } + menu.show(menuParent, x, y); + + final int _firstEnabled = firstEnabled; + SwingUtilities.invokeLater(new Runnable() { //use invoke later to ensure first enabled ability selected by default + @Override public final void run() { + for (int i = 0; i <= _firstEnabled; i++) { + menu.dispatchEvent(new KeyEvent(menu, KeyEvent.KEY_PRESSED, 0, 0, KeyEvent.VK_DOWN, KeyEvent.CHAR_UNDEFINED)); + } } }); - MouseEvent mouseEvent = ((MouseTriggerEvent)triggerEvent).getMouseEvent(); - menu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY()); } return null; //delay ability until choice made @@ -772,7 +795,7 @@ public final class CMatchUI @Override public void openView(final TrackableCollection myPlayers) { final GameView gameView = getGameView(); - gameView.getGameLog().addObserver(getCLog()); + gameView.getGameLog().addObserver(cLog); // Sort players FCollectionView players = gameView.getPlayers(); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/ZoneAction.java b/forge-gui-desktop/src/main/java/forge/screens/match/ZoneAction.java index f9c5a690fbc..effe55bdc87 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/ZoneAction.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/ZoneAction.java @@ -32,6 +32,6 @@ public class ZoneAction extends ForgeAction { @Override public void actionPerformed(final ActionEvent e) { - FloatingCardArea.show(matchUI, player, zone); + FloatingCardArea.showOrHide(matchUI, player, zone); } } \ No newline at end of file diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java index 218736b867a..6941ddd708e 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java @@ -61,7 +61,7 @@ public class CDock implements ICDoc { * End turn. */ public void endTurn() { - matchUI.getCPrompt().passPriorityUntilEndOfTurn(); + matchUI.getGameController().passPriorityUntilEndOfTurn(); } /** diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CField.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CField.java index 98763c0c626..1580e50441c 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CField.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CField.java @@ -47,7 +47,7 @@ public class CField implements ICDoc { private final MouseListener madAvatar = new MouseAdapter() { @Override public void mousePressed(final MouseEvent e) { - matchUI.getCPrompt().selectPlayer(player, new MouseTriggerEvent(e)); + matchUI.getGameController().selectPlayer(player, new MouseTriggerEvent(e)); } }; @@ -71,9 +71,11 @@ public class CField implements ICDoc { final ZoneAction commandAction = new ZoneAction(matchUI, player, ZoneType.Command, MatchConstants.HUMANCOMMAND); final Function manaAction = new Function() { - public Boolean apply(Byte colorCode) { + public Boolean apply(final Byte colorCode) { if (CField.this.player.isLobbyPlayer(Singletons.getControl().getGuiPlayer())) { - return matchUI.getGameController().useMana(colorCode.byteValue()); + final int oldMana = player.getMana(colorCode); + matchUI.getGameController().useMana(colorCode.byteValue()); + return oldMana != player.getMana(colorCode); } return false; } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java index 7f63b45b273..afc85924628 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java @@ -23,22 +23,17 @@ import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; -import java.util.List; import javax.swing.JButton; import forge.FThreads; import forge.UiCommand; import forge.game.GameView; -import forge.game.card.CardView; -import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbilityView; import forge.gui.framework.ICDoc; import forge.gui.framework.SDisplayUtil; import forge.screens.match.CMatchUI; import forge.screens.match.views.VPrompt; import forge.toolbox.FSkin; -import forge.util.ITriggerEvent; /** * Controls the prompt panel in the match UI. @@ -96,34 +91,14 @@ public class CPrompt implements ICDoc { _initButton(view.getBtnOK(), actOK); } - public void selectButtonOk() { + private void selectButtonOk() { matchUI.getGameController().selectButtonOk(); } - public void selectButtonCancel() { + private void selectButtonCancel() { matchUI.getGameController().selectButtonCancel(); } - public boolean passPriority() { - return matchUI.getGameController().passPriority(); - } - - public boolean passPriorityUntilEndOfTurn() { - return matchUI.getGameController().passPriorityUntilEndOfTurn(); - } - - public void selectPlayer(final PlayerView playerView, final ITriggerEvent triggerEvent) { - matchUI.getGameController().selectPlayer(playerView, triggerEvent); - } - - public boolean selectCard(final CardView cardView, final List otherCardViewsToSelect, final ITriggerEvent triggerEvent) { - return matchUI.getGameController().selectCard(cardView, otherCardViewsToSelect, triggerEvent); - } - - public void selectAbility(final SpellAbilityView sa) { - matchUI.getGameController().selectAbility(sa); - } - public void setMessage(String s0) { view.getTarMessage().setText(FSkin.encodeSymbols(s0, false)); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java b/forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java index df822f6ff5c..de0452b2378 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/menus/GameMenu.java @@ -85,7 +85,7 @@ public final class GameMenu { return new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { - matchUI.getGameController().tryUndoLastAction(); + matchUI.getGameController().undoLastAction(); } }; } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java index 56144f73cef..de2606fc2bd 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java @@ -69,7 +69,7 @@ public class VStack implements IVDoc { // Other fields private final AbilityMenu abilityMenu = new AbilityMenu(); - private static StackInstanceTextArea hoveredItem; + private StackInstanceTextArea hoveredItem; public StackInstanceTextArea getHoveredItem() { return hoveredItem; @@ -267,7 +267,7 @@ public class VStack implements IVDoc { controller.getMatchUI().setShouldAutoYield(key, !autoYield); if (!autoYield && controller.getMatchUI().getGameView().peekStack() == item) { //auto-pass priority if ability is on top of stack - controller.getMatchUI().getCPrompt().passPriority(); + controller.getMatchUI().getGameController().passPriority(); } } }); diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FButton.java b/forge-gui-desktop/src/main/java/forge/toolbox/FButton.java index 3ca3c8e7bb0..7ce9f8a91c3 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FButton.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FButton.java @@ -17,6 +17,24 @@ */ package forge.toolbox; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; + +import javax.swing.BorderFactory; + import forge.UiCommand; import forge.assets.FSkinProp; import forge.gui.framework.ILocalRepaint; @@ -25,11 +43,6 @@ import forge.toolbox.FSkin.Colors; import forge.toolbox.FSkin.SkinImage; import forge.toolbox.FSkin.SkinnedButton; -import javax.swing.*; - -import java.awt.*; -import java.awt.event.*; - /** * The core JButton used throughout the Forge project. Follows skin font and * theme button styling. diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/MouseTriggerEvent.java b/forge-gui-desktop/src/main/java/forge/toolbox/MouseTriggerEvent.java index e414b547954..00da7c4b069 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/MouseTriggerEvent.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/MouseTriggerEvent.java @@ -7,20 +7,28 @@ import forge.util.ITriggerEvent; //MouseEvent wrapper used for passing trigger to input classes public class MouseTriggerEvent implements ITriggerEvent, Serializable { - private static final long serialVersionUID = -4746127117012991732L; + private static final long serialVersionUID = -5440485066050000298L; - private final MouseEvent event; + private final int button, x, y; - public MouseTriggerEvent(MouseEvent event0) { - event = event0; + public MouseTriggerEvent(final MouseEvent event) { + this.button = event.getButton(); + this.x = event.getX(); + this.y = event.getY(); } @Override public int getButton() { - return event.getButton(); + return button; } - public MouseEvent getMouseEvent() { - return event; + @Override + public int getX() { + return x; + } + + @Override + public int getY() { + return y; } } diff --git a/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingCardArea.java b/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingCardArea.java index f67e3f2af22..ec7b334e3b2 100644 --- a/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingCardArea.java +++ b/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingCardArea.java @@ -26,6 +26,7 @@ import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.swing.ScrollPaneConstants; import javax.swing.Timer; @@ -55,12 +56,20 @@ public class FloatingCardArea extends CardArea { private static final String COORD_DELIM = ","; private static final ForgePreferences prefs = FModel.getPreferences(); - private static final HashMap floatingAreas = new HashMap(); + private static final Map floatingAreas = new HashMap(); private static int getKey(final PlayerView player, final ZoneType zone) { return 40 * player.getId() + zone.hashCode(); } + public static void showOrHide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) { + final FloatingCardArea cardArea = _init(matchUI, player, zone); + cardArea.showOrHideWindow(); + } public static void show(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) { + final FloatingCardArea cardArea = _init(matchUI, player, zone); + cardArea.showWindow(); + } + private static FloatingCardArea _init(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) { final int key = getKey(player, zone); FloatingCardArea cardArea = floatingAreas.get(key); if (cardArea == null || cardArea.getMatchUI() != matchUI) { @@ -69,7 +78,11 @@ public class FloatingCardArea extends CardArea { } else { cardArea.setPlayer(player); //ensure player is updated if needed } - cardArea.showWindow(); + return cardArea; + } + public static CardPanel getCardPanel(final CMatchUI matchUI, final CardView card) { + final FloatingCardArea window = _init(matchUI, card.getController(), card.getZone()); + return window.getCardPanel(card.getId()); } public static void refresh(final PlayerView player, final ZoneType zone) { FloatingCardArea cardArea = floatingAreas.get(getKey(player, zone)); @@ -188,16 +201,22 @@ public class FloatingCardArea extends CardArea { } private void showWindow() { + onShow(); + window.setVisible(true); + } + private void showOrHideWindow() { + onShow(); + window.setVisible(!window.isVisible()); + } + private void onShow() { if (!hasBeenShown) { loadLocation(); window.getTitleBar().addMouseListener(new FMouseAdapter() { - @Override - public void onLeftDoubleClick(MouseEvent e) { + @Override public final void onLeftDoubleClick(final MouseEvent e) { window.setVisible(false); //hide window if titlebar double-clicked } }); } - window.setVisible(!window.isVisible()); } private void loadLocation() { @@ -251,7 +270,7 @@ public class FloatingCardArea extends CardArea { window.setSize(mainFrame.getWidth() / 5, mainFrame.getHeight() / 2); } - public void refresh() { + private void refresh() { if (!window.isVisible()) { return; } //don't refresh while window hidden List cardPanels = new ArrayList(); @@ -292,7 +311,7 @@ public class FloatingCardArea extends CardArea { } } - final Timer layoutTimer = new Timer(250, new ActionListener() { + private final Timer layoutTimer = new Timer(250, new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { layoutTimer.stop(); @@ -311,12 +330,12 @@ public class FloatingCardArea extends CardArea { } @Override public final void mouseLeftClicked(final CardPanel panel, final MouseEvent evt) { - getMatchUI().getCPrompt().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt)); + getMatchUI().getGameController().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt)); super.mouseLeftClicked(panel, evt); } @Override public final void mouseRightClicked(final CardPanel panel, final MouseEvent evt) { - getMatchUI().getCPrompt().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt)); + getMatchUI().getGameController().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt)); super.mouseRightClicked(panel, evt); } } diff --git a/forge-gui-desktop/src/main/java/forge/view/arcane/HandArea.java b/forge-gui-desktop/src/main/java/forge/view/arcane/HandArea.java index 416575452b7..31505afbb95 100644 --- a/forge-gui-desktop/src/main/java/forge/view/arcane/HandArea.java +++ b/forge-gui-desktop/src/main/java/forge/view/arcane/HandArea.java @@ -60,14 +60,14 @@ public class HandArea extends CardArea { /** {@inheritDoc} */ @Override public final void mouseLeftClicked(final CardPanel panel, final MouseEvent evt) { - getMatchUI().getCPrompt().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt)); + getMatchUI().getGameController().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt)); super.mouseLeftClicked(panel, evt); } /** {@inheritDoc} */ @Override public final void mouseRightClicked(final CardPanel panel, final MouseEvent evt) { - getMatchUI().getCPrompt().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt)); + getMatchUI().getGameController().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt)); super.mouseRightClicked(panel, evt); } } diff --git a/forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java b/forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java index 821ef95a9eb..8460b148b19 100644 --- a/forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java +++ b/forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java @@ -543,7 +543,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen } } } - if (getMatchUI().getCPrompt().selectCard(panel.getCard(), otherCardViewsToSelect, triggerEvent)) { + if (getMatchUI().getGameController().selectCard(panel.getCard(), otherCardViewsToSelect, triggerEvent)) { return true; } //if panel can't do anything with card selection, try selecting previous panel in stack diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index 83869ca7bfc..53753a20aab 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -473,8 +473,9 @@ public class PlayerControllerForTests extends PlayerController { } @Override - public SpellAbility getAbilityToPlay(List abilities, ITriggerEvent triggerEvent) { - return getAbilityToPlay(abilities); + public SpellAbility getAbilityToPlay(Card hostCard, List abilities, ITriggerEvent triggerEvent) { + // Isn't this a method invocation loop? --elcnesh + return getAbilityToPlay(hostCard, abilities); } @Override diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index b7ad9be6e69..e02d4a97b21 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -34,11 +34,9 @@ import forge.game.player.IHasIcon; import forge.game.player.PlayerView; import forge.game.spellability.SpellAbilityView; import forge.game.zone.ZoneType; -import forge.interfaces.IButton; import forge.item.PaperCard; import forge.match.AbstractGuiGame; import forge.match.HostedMatch; -import forge.match.MatchButtonType; import forge.model.FModel; import forge.player.PlayerZoneUpdate; import forge.properties.ForgePreferences; @@ -50,6 +48,7 @@ import forge.screens.match.views.VPlayerPanel; import forge.screens.match.views.VPlayerPanel.InfoTab; import forge.screens.match.views.VPrompt; import forge.screens.match.winlose.ViewWinLose; +import forge.toolbox.FButton; import forge.toolbox.FDisplayObject; import forge.toolbox.FOptionPane; import forge.trackable.TrackableCollection; @@ -142,24 +141,18 @@ public class MatchController extends AbstractGuiGame { Forge.openScreen(view); } - @Override - public IButton getBtnOK(PlayerView player) { - return view.getPrompt(player).getBtnOk(); - } - - @Override - public IButton getBtnCancel(PlayerView player) { - return view.getPrompt(player).getBtnCancel(); - } - @Override public void showPromptMessage(final PlayerView player, final String message) { view.getPrompt(player).setMessage(message); } - @Override - public void focusButton(final MatchButtonType button) { - //not needed for mobile game + public void updateButtons(final PlayerView owner, final String label1, final String label2, final boolean enable1, final boolean enable2, final boolean focus1) { + final VPrompt prompt = view.getPrompt(owner); + final FButton btn1 = prompt.getBtnOk(), btn2 = prompt.getBtnCancel(); + btn1.setText(label1); + btn2.setText(label2); + btn1.setEnabled(enable1); + btn2.setEnabled(enable2); } @Override @@ -224,7 +217,7 @@ public class MatchController extends AbstractGuiGame { } @Override - public SpellAbilityView getAbilityToPlay(List abilities, ITriggerEvent triggerEvent) { + public SpellAbilityView getAbilityToPlay(final CardView hostCard, final List abilities, final ITriggerEvent triggerEvent) { if (abilities.isEmpty()) { return null; } @@ -320,11 +313,6 @@ public class MatchController extends AbstractGuiGame { } } - @Override - public boolean stopAtPhase(PlayerView turn, PhaseType phase) { - return view.stopAtPhase(turn, phase); - } - @Override public void updateZones(Iterable zonesToUpdate) { view.updateZones(zonesToUpdate); diff --git a/forge-gui-mobile/src/forge/screens/match/MatchScreen.java b/forge-gui-mobile/src/forge/screens/match/MatchScreen.java index d792fdd84b4..d11a132d625 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchScreen.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchScreen.java @@ -384,7 +384,7 @@ public class MatchScreen extends FScreen { break; case Keys.Z: //undo on Ctrl+Z if (KeyInputAdapter.isCtrlKeyDown()) { - getGameController().tryUndoLastAction(); + getGameController().undoLastAction(); return true; } break; diff --git a/forge-gui-mobile/src/forge/screens/match/views/VManaPool.java b/forge-gui-mobile/src/forge/screens/match/views/VManaPool.java index 67edc0c3a86..6652ec96490 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VManaPool.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VManaPool.java @@ -91,7 +91,13 @@ public class VManaPool extends VDisplayArea { public boolean flick(float x, float y) { if (player.isLobbyPlayer(GamePlayerUtil.getGuiPlayer())) { //on two finger tap, keep using mana until it runs out or no longer can be put towards the cost - while (MatchController.instance.getGameController().useMana(colorCode)) {} + int oldMana, newMana = player.getMana(colorCode); + do { + oldMana = newMana; + MatchController.instance.getGameController().useMana(colorCode); + newMana = player.getMana(colorCode); + } + while (oldMana != newMana); } return true; } diff --git a/forge-gui-mobile/src/forge/screens/match/views/VPlayers.java b/forge-gui-mobile/src/forge/screens/match/views/VPlayers.java index 15ef1d4a779..0237f2a47e8 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VPlayers.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VPlayers.java @@ -5,11 +5,8 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment; import forge.Graphics; import forge.assets.FImage; import forge.assets.FSkinFont; -import forge.game.card.CardView; import forge.game.player.PlayerView; import forge.menu.FDropDown; -import forge.model.FModel; -import forge.properties.ForgePreferences.FPref; import forge.screens.match.MatchController; import forge.toolbox.FContainer; import forge.toolbox.FDisplayObject; @@ -65,33 +62,7 @@ public class VPlayers extends FDropDown { g.drawImage(avatarImage, x, y, h, h); x += h + PADDING; - StringBuilder builder = new StringBuilder(); - builder.append(player.getName()); - builder.append("\nLife: " + String.valueOf(player.getLife())); - builder.append(" | Poison counters: " + String.valueOf(player.getPoisonCounters())); - builder.append(" | Maximum hand size: " + String.valueOf(player.getMaxHandSize())); - builder.append(" | Cards drawn this turn: " + String.valueOf(player.getNumDrawnThisTurn())); - builder.append(" | Damage Prevention: " + String.valueOf(player.getPreventNextDamage())); - if (!player.getKeywords().isEmpty()) { - builder.append(" | " + player.getKeywords().toString()); - } - if (FModel.getPreferences().getPrefBoolean(FPref.UI_ANTE)) { - Iterable list = player.getAnte(); - builder.append(" | Ante'd: "); - boolean needDelim = false; - for (CardView cv : list) { - if (needDelim) { - builder.append(", "); - } - else { needDelim = true; } - builder.append(cv); - } - } - if (MatchController.instance.getGameView().isCommander()) { - builder.append(" | " + player.getCommanderInfo()); - } - - g.drawText(builder.toString(), FONT, FList.FORE_COLOR, x, y, getWidth() - PADDING - x, h, true, HAlignment.LEFT, true); + g.drawText(player.getDetails(), FONT, FList.FORE_COLOR, x, y, getWidth() - PADDING - x, h, true, HAlignment.LEFT, true); } @Override diff --git a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java index de17abf74e1..0266ce405b1 100644 --- a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java @@ -126,7 +126,8 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { } synchronized (zonesUpdate) { if (!zonesUpdate.isEmpty()) { - matchController.updateZones(zonesUpdate); + // Copy to prevent concurrency issues + matchController.updateZones(new PlayerZoneUpdates(zonesUpdate)); zonesUpdate.clear(); } } diff --git a/forge-gui/src/main/java/forge/control/WatchLocalGame.java b/forge-gui/src/main/java/forge/control/WatchLocalGame.java index 41488eeb0ae..e82bc081c9d 100644 --- a/forge-gui/src/main/java/forge/control/WatchLocalGame.java +++ b/forge-gui/src/main/java/forge/control/WatchLocalGame.java @@ -32,8 +32,7 @@ public class WatchLocalGame extends PlayerControllerHuman { } @Override - public boolean tryUndoLastAction() { - return false; + public void undoLastAction() { } @Override @@ -63,18 +62,15 @@ public class WatchLocalGame extends PlayerControllerHuman { } @Override - public boolean passPriority() { - return false; + public void passPriority() { } @Override - public boolean passPriorityUntilEndOfTurn() { - return false; + public void passPriorityUntilEndOfTurn() { } @Override - public boolean useMana(final byte mana) { - return false; + public void useMana(final byte mana) { } @Override diff --git a/forge-gui/src/main/java/forge/interfaces/IGameController.java b/forge-gui/src/main/java/forge/interfaces/IGameController.java index e2f035df6e9..eda65957b98 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGameController.java +++ b/forge-gui/src/main/java/forge/interfaces/IGameController.java @@ -18,15 +18,15 @@ public interface IGameController { void alphaStrike(); - boolean useMana(byte color); + void useMana(byte color); void selectButtonOk(); void selectButtonCancel(); - boolean passPriority(); + void passPriority(); - boolean passPriorityUntilEndOfTurn(); + void passPriorityUntilEndOfTurn(); void selectPlayer(PlayerView playerView, ITriggerEvent triggerEvent); @@ -35,7 +35,7 @@ public interface IGameController { void selectAbility(SpellAbilityView sa); - boolean tryUndoLastAction(); + void undoLastAction(); IDevModeCheats cheat(); diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/interfaces/IGuiGame.java index db717b7630f..1e4af7f7cfe 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiGame.java @@ -19,7 +19,6 @@ import forge.game.player.PlayerView; import forge.game.spellability.SpellAbilityView; import forge.game.zone.ZoneType; import forge.item.PaperCard; -import forge.match.MatchButtonType; import forge.player.PlayerZoneUpdate; import forge.trackable.TrackableCollection; import forge.util.ITriggerEvent; @@ -31,10 +30,8 @@ public interface IGuiGame { void afterGameEnd(); void showCombat(); void showPromptMessage(PlayerView playerView, String message); - boolean stopAtPhase(PlayerView playerTurn, PhaseType phase); - IButton getBtnOK(PlayerView playerView); - IButton getBtnCancel(PlayerView playerView); - void focusButton(MatchButtonType button); + void updateButtons(PlayerView owner, boolean okEnabled, boolean cancelEnabled, boolean focusOk); + void updateButtons(PlayerView owner, String label1, String label2, boolean enable1, boolean enable2, boolean focus1); void flashIncorrectAction(); void updatePhase(); void updateTurn(PlayerView player); @@ -52,11 +49,8 @@ public interface IGuiGame { void updateManaPool(Iterable manaPoolUpdate); void updateLives(Iterable livesUpdate); void setPanelSelection(CardView hostCard); - SpellAbilityView getAbilityToPlay(List abilities, - ITriggerEvent triggerEvent); - Map assignDamage(CardView attacker, - List blockers, int damage, GameEntityView defender, - boolean overrideOrder); + SpellAbilityView getAbilityToPlay(CardView hostCard, List abilities, ITriggerEvent triggerEvent); + Map assignDamage(CardView attacker, List blockers, int damage, GameEntityView defender, boolean overrideOrder); void message(String message); void message(String message, String title); @@ -163,10 +157,6 @@ public interface IGuiGame { void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi); boolean openZones(Collection zones, Map players); void restoreOldZones(Map playersToRestoreZonesFor); - void updateButtons(PlayerView owner, boolean okEnabled, - boolean cancelEnabled, boolean focusOk); - void updateButtons(PlayerView owner, String okLabel, String cancelLabel, - boolean okEnabled, boolean cancelEnabled, boolean focusOk); void setHighlighted(PlayerView pv, boolean b); void setUsedToPay(CardView card, boolean value); diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index 8e8db25faed..e044a412f6f 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -24,7 +24,6 @@ import forge.game.GameView; import forge.game.card.CardView; import forge.game.card.CardView.CardStateView; import forge.game.player.PlayerView; -import forge.interfaces.IButton; import forge.interfaces.IGameController; import forge.interfaces.IGuiGame; import forge.interfaces.IMayViewCards; @@ -195,20 +194,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { } @Override - public void updateButtons(final PlayerView owner, final String okLabel, final String cancelLabel, final boolean okEnabled, final boolean cancelEnabled, final boolean focusOk) { - final IButton btnOk = getBtnOK(owner); - final IButton btnCancel = getBtnCancel(owner); - - btnOk.setText(okLabel); - btnCancel.setText(cancelLabel); - btnOk.setEnabled(okEnabled); - btnCancel.setEnabled(cancelEnabled); - if (okEnabled && focusOk) { - focusButton(MatchButtonType.OK); - } else if (cancelEnabled) { - focusButton(MatchButtonType.CANCEL); - } - } + public abstract void updateButtons(PlayerView owner, String label1, String label2, boolean enable1, boolean enable2, boolean focus1); // Auto-yield and other input-related code diff --git a/forge-gui/src/main/java/forge/match/HostedMatch.java b/forge-gui/src/main/java/forge/match/HostedMatch.java index 70826a7184b..fe046542f3b 100644 --- a/forge-gui/src/main/java/forge/match/HostedMatch.java +++ b/forge-gui/src/main/java/forge/match/HostedMatch.java @@ -193,11 +193,6 @@ public class HostedMatch { gui.setGameController(null, humanController); gui.openView(null); - } else if (humanCount == players.size()) { - //if there are no AI's, allow all players to see all cards (hotseat mode). - for (final PlayerControllerHuman humanController : humanControllers) { - humanController.setMayLookAtAllCards(true); - } } //prompt user for player one name if needed diff --git a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java index ac6e87af596..44828b970ed 100644 --- a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java +++ b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java @@ -129,7 +129,7 @@ public class InputPassPriority extends InputSyncronizedBase { return false; } - final SpellAbility ability = getController().getAbilityToPlay(abilities, triggerEvent); + final SpellAbility ability = getController().getAbilityToPlay(card, abilities, triggerEvent); if (ability != null) { chosenSa = new ArrayList(); chosenSa.add(ability); diff --git a/forge-gui/src/main/java/forge/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/match/input/InputPayMana.java index 1e1a74103ba..c39d5af2884 100644 --- a/forge-gui/src/main/java/forge/match/input/InputPayMana.java +++ b/forge-gui/src/main/java/forge/match/input/InputPayMana.java @@ -153,14 +153,12 @@ public abstract class InputPayMana extends InputSyncronizedBase { return abilities; } - public boolean useManaFromPool(byte colorCode) { + public void useManaFromPool(byte colorCode) { // find the matching mana in pool. if (player.getManaPool().tryPayCostWithColor(colorCode, saPaidFor, manaCost)) { onManaAbilityPaid(); showMessage(); - return true; } - return false; } protected boolean activateManaAbility(final Card card, ManaCostBeingPaid manaCost) { diff --git a/forge-gui/src/main/java/forge/net/GameProtocol.java b/forge-gui/src/main/java/forge/net/GameProtocol.java index a9acca2c091..2e268f1e8d3 100644 --- a/forge-gui/src/main/java/forge/net/GameProtocol.java +++ b/forge-gui/src/main/java/forge/net/GameProtocol.java @@ -8,7 +8,6 @@ import java.util.Map; import com.google.common.base.Function; -import forge.UiCommand; import forge.assets.FSkinProp; import forge.deck.CardPool; import forge.game.GameEntityView; @@ -18,11 +17,12 @@ import forge.game.phase.PhaseType; import forge.game.player.DelayedReveal; import forge.game.player.PlayerView; import forge.game.spellability.SpellAbilityView; -import forge.interfaces.IButton; +import forge.interfaces.IGameController; import forge.interfaces.IGuiGame; -import forge.match.MatchButtonType; +import forge.match.NextGameDecision; import forge.trackable.TrackableCollection; import forge.util.ITriggerEvent; +import forge.util.ReflectionUtil; public final class GameProtocol { @@ -36,101 +36,146 @@ public final class GameProtocol { return ProtocolMethod.valueOf(name); } + private enum Mode { + SERVER(IGuiGame.class), + CLIENT(IGameController.class); + + private final Class toInvoke; + private Mode(final Class toInvoke) { + this.toInvoke = toInvoke; + } + }; + /** * The methods that can be sent through this protocol. */ public enum ProtocolMethod { - setGameView(Void.TYPE, GameView.class), - openView(Void.TYPE, TrackableCollection.class), - afterGameEnd(), - showCombat(), - showPromptMessage(Void.TYPE, PlayerView.class, String.class), - stopAtPhase(Boolean.TYPE, PlayerView.class, PhaseType.class), - focusButton(Void.TYPE, MatchButtonType.class), - flashIncorrectAction(), - updatePhase(), - updateTurn(Void.TYPE, PlayerView.class), - updatePlayerControl(), - enableOverlay(), - disableOverlay(), - finishGame(), - showManaPool(Void.TYPE, PlayerView.class), - hideManaPool(Void.TYPE, PlayerView.class), - updateStack(), - updateZones(Void.TYPE, Iterable.class), - updateCards(Void.TYPE, Iterable.class), - updateManaPool(Void.TYPE, Iterable.class), - updateLives(Void.TYPE, Iterable.class), - setPanelSelection(Void.TYPE, CardView.class), - getAbilityToPlay(SpellAbilityView.class, List.class, ITriggerEvent.class), - assignDamage(Map.class, CardView.class, List.class, Integer.TYPE, GameEntityView.class, Boolean.TYPE), - message(Void.TYPE, String.class, String.class), - showErrorDialog(Void.TYPE, String.class, String.class), - showConfirmDialog(Boolean.TYPE, String.class, String.class, String.class, String.class, Boolean.TYPE), - showOptionDialog(Integer.TYPE, String.class, String.class, FSkinProp.class, Array.class, Integer.TYPE), - showCardOptionDialog(Integer.TYPE, CardView.class, String.class, String.class, FSkinProp.class, String.class, Array.class), - showInputDialog(String.class, String.class, String.class, FSkinProp.class, String.class, Array.class), - confirm(Boolean.TYPE, CardView.class, String.class, Boolean.TYPE, Array.class), - getChoices(List.class, String.class, Integer.TYPE, Integer.TYPE, Collection.class, Object.class, Function.class), - order(List.class, String.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, CardView.class, Boolean.TYPE), - sideboard(List.class, CardPool.class, CardPool.class), - chooseSingleEntityForEffect(GameEntityView.class, String.class, TrackableCollection.class, DelayedReveal.class, Boolean.TYPE), - setCard(Void.TYPE, CardView.class), + // Server -> Client + setGameView (Mode.SERVER, Void.TYPE, GameView.class), + openView (Mode.SERVER, Void.TYPE, TrackableCollection/*PlayerView*/.class), + afterGameEnd (Mode.SERVER), + showCombat (Mode.SERVER), + showPromptMessage (Mode.SERVER, Void.TYPE, PlayerView.class, String.class), + updateButtons (Mode.SERVER, Void.TYPE, PlayerView.class, String.class, String.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE), + flashIncorrectAction(Mode.SERVER), + updatePhase (Mode.SERVER), + updateTurn (Mode.SERVER, Void.TYPE, PlayerView.class), + updatePlayerControl (Mode.SERVER), + enableOverlay (Mode.SERVER), + disableOverlay (Mode.SERVER), + finishGame (Mode.SERVER), + showManaPool (Mode.SERVER, Object.class, PlayerView.class), + hideManaPool (Mode.SERVER, Void.TYPE, PlayerView.class), + updateStack (Mode.SERVER), + updateZones (Mode.SERVER, Void.TYPE, Iterable/*PlayerZoneUpdate*/.class), + updateCards (Mode.SERVER, Void.TYPE, Iterable/*CardView*/.class), + updateManaPool (Mode.SERVER, Void.TYPE, Iterable/*PlayerView*/.class), + updateLives (Mode.SERVER, Void.TYPE, Iterable/*PlayerView*/.class), + setPanelSelection (Mode.SERVER, Void.TYPE, CardView.class), + getAbilityToPlay (Mode.SERVER, SpellAbilityView.class, CardView.class, List/*SpellAbilityView*/.class, ITriggerEvent.class), + assignDamage (Mode.SERVER, Map.class, CardView.class, List/*CardView*/.class, Integer.TYPE, GameEntityView.class, Boolean.TYPE), + message (Mode.SERVER, Void.TYPE, String.class, String.class), + showErrorDialog (Mode.SERVER, Void.TYPE, String.class, String.class), + showConfirmDialog (Mode.SERVER, Boolean.TYPE, String.class, String.class, String.class, String.class, Boolean.TYPE), + showOptionDialog (Mode.SERVER, Integer.TYPE, String.class, String.class, FSkinProp.class, Array/*String*/.class, Integer.TYPE), + showCardOptionDialog(Mode.SERVER, Integer.TYPE, CardView.class, String.class, String.class, FSkinProp.class, String.class, Array/*String*/.class), + showInputDialog (Mode.SERVER, String.class, String.class, String.class, FSkinProp.class, String.class, Array/*String*/.class), + confirm (Mode.SERVER, Boolean.TYPE, CardView.class, String.class, Boolean.TYPE, Array/*String*/.class), + getChoices (Mode.SERVER, List.class, String.class, Integer.TYPE, Integer.TYPE, Collection.class, Object.class, Function.class), + order (Mode.SERVER, List.class, String.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, CardView.class, Boolean.TYPE), + sideboard (Mode.SERVER, List.class, CardPool.class, CardPool.class), + chooseSingleEntityForEffect(Mode.SERVER, GameEntityView.class, String.class, TrackableCollection.class, DelayedReveal.class, Boolean.TYPE), + setCard (Mode.SERVER, Void.TYPE, CardView.class), // TODO case "setPlayerAvatar": - openZones(Boolean.TYPE, Collection.class, Map.class), - restoreOldZones(Void.TYPE, Map.class), - isUiSetToSkipPhase(Boolean.TYPE, PlayerView.class, PhaseType.class), - // BUTTONS - btn_setEnabled(Void.TYPE, Boolean.TYPE), - btn_setVisible(Void.TYPE, Boolean.TYPE), - btn_setText(Void.TYPE, String.class), - btn_isSelected(Boolean.TYPE), - btn_setSelected(Void.TYPE, Boolean.TYPE), - btn_requestFocusInWindows(Boolean.TYPE), - btn_setCommand(Void.TYPE, UiCommand.class), - btn_setImage(Void.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE), - btn_setTextImage(Void.TYPE, FSkinProp.class); + openZones (Mode.SERVER, Boolean.TYPE, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class), + restoreOldZones (Mode.SERVER, Void.TYPE, Map/*PlayerView,Object*/.class), + isUiSetToSkipPhase (Mode.SERVER, Boolean.TYPE, PlayerView.class, PhaseType.class), + // Client -> Server + // Note: these should all return void, to avoid awkward situations in + // which client and server wait for one another's response and block + // the threads that're supposed to give that response + useMana (Mode.CLIENT, Void.TYPE, Byte.TYPE), + undoLastAction (Mode.CLIENT, Void.TYPE, Boolean.TYPE), + selectPlayer (Mode.CLIENT, Void.TYPE, PlayerView.class, ITriggerEvent.class), + selectCard (Mode.CLIENT, Void.TYPE, CardView.class, List.class, ITriggerEvent.class), + selectButtonOk (Mode.CLIENT), + selectButtonCancel (Mode.CLIENT), + selectAbility (Mode.CLIENT, Void.TYPE, SpellAbilityView.class), + passPriorityUntilEndOfTurn(Mode.CLIENT), + passPriority (Mode.CLIENT), + nextGameDecision (Mode.CLIENT, Void.TYPE, NextGameDecision.class), + getActivateDescription (Mode.CLIENT, Void.TYPE, String.class, CardView.class), + concede (Mode.CLIENT), + alphaStrike (Mode.CLIENT), + reorderHand (Mode.CLIENT, Void.TYPE, CardView.class, Integer.TYPE); + + private final Mode mode; private final Class returnType; private final Class[] args; - private ProtocolMethod() { - this(Void.TYPE); + private ProtocolMethod(final Mode mode) { + this(mode, Void.TYPE); } - private ProtocolMethod(final Class returnType) { - this(returnType, (Class[]) null); + private ProtocolMethod(final Mode mode, final Class returnType) { + this(mode, returnType, (Class[]) null); } @SafeVarargs - private ProtocolMethod(final Class returnType, final Class ... args) { + private ProtocolMethod(final Mode mode, final Class returnType, final Class ... args) { + this.mode = mode; this.returnType = returnType; this.args = args == null ? new Class[] {} : args; } public Method getMethod() { try { - final String name; - final Class toCall; - if (name().startsWith("btn_")) { - name = name().substring("btn_".length()); - toCall = IButton.class; - } else { - name = name(); - toCall = IGuiGame.class; - } - - final Method candidate = toCall.getMethod(name, args); - if (!candidate.getReturnType().equals(returnType)) { + final Class toCall = mode.toInvoke; + final Method candidate = toCall.getMethod(name(), args); + // Don't check Client return values for now as some use void + // and a default return value, to improve performance + if (mode == Mode.SERVER && !candidate.getReturnType().equals(returnType)) { throw new NoSuchMethodException(String.format("Wrong return type for method %s", name())); } return candidate; } catch (final NoSuchMethodException | SecurityException e) { - System.err.println(String.format("Warning: class contains no method named %s", name())); - return null; + System.err.println(String.format("Warning: class contains no accessible method named %s", name())); + return getMethodNoArgs(); } } - public boolean invokeOnButton() { - return name().startsWith("btn_"); + private Method getMethodNoArgs() { + try { + return mode.toInvoke.getMethod(name(), (Class[]) null); + } catch (final NoSuchMethodException | SecurityException e) { + System.err.println(String.format("Warning: class contains no accessible arg-less method named %s", name())); + return null; + } + } + public Class getReturnType() { + return returnType; + } + public Class[] getArgTypes() { + return args; + } + + public void checkArgs(final Object[] args) { + for (int iArg = 0; iArg < args.length; iArg++) { + final Object arg = args[iArg]; + final Class type = this.args[iArg]; + if (!ReflectionUtil.isInstance(arg, type)) { + throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName())); + } + } + } + + public void checkReturnValue(final Object value) { + if (returnType.equals(Void.TYPE)) { + // If void is expected, any return value is fine + return; + } + if (!ReflectionUtil.isInstance(value, returnType)) { + throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName())); + } } } } diff --git a/forge-gui/src/main/java/forge/net/GameProtocolHandler.java b/forge-gui/src/main/java/forge/net/GameProtocolHandler.java new file mode 100644 index 00000000000..b10fa09fff6 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/GameProtocolHandler.java @@ -0,0 +1,96 @@ +package forge.net; + +import forge.FThreads; +import forge.net.GameProtocol.ProtocolMethod; +import forge.net.event.GuiGameEvent; +import forge.net.event.ReplyEvent; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapter { + + private final boolean runInEdt; + protected GameProtocolHandler(final boolean runInEdt) { + this.runInEdt = runInEdt; + } + + protected abstract ReplyPool getReplyPool(ChannelHandlerContext ctx); + protected abstract IRemote getRemote(ChannelHandlerContext ctx); + + protected abstract T getToInvoke(ChannelHandlerContext ctx); + protected abstract void beforeCall(ProtocolMethod protocolMethod, Object[] args); + + @Override + public final void channelRead(final ChannelHandlerContext ctx, final Object msg) { + System.out.println("Received: " + msg); + if (msg instanceof ReplyEvent) { + final ReplyEvent event = (ReplyEvent) msg; + getReplyPool(ctx).complete(event.getIndex(), event.getReply()); + } else if (msg instanceof GuiGameEvent) { + final GuiGameEvent event = (GuiGameEvent) msg; + final ProtocolMethod protocolMethod = event.getMethod(); + final String methodName = protocolMethod.name(); + + final Method method = protocolMethod.getMethod(); + if (method == null) { + throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name())); + } + + final Object[] args = event.getObjects(); + protocolMethod.checkArgs(args); + + final Object toInvoke = getToInvoke(ctx); + + // Pre-call actions + beforeCall(protocolMethod, args); + + final Class returnType = protocolMethod.getReturnType(); + final Runnable toRun = new Runnable() { + @Override public final void run() { + if (returnType.equals(Void.TYPE)) { + try { + method.invoke(toInvoke, args); + } catch (final IllegalAccessException | IllegalArgumentException e) { + System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length)); + } catch (final InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + Serializable reply = null; + try { + final Object theReply = method.invoke(toInvoke, args); + if (theReply instanceof Serializable) { + protocolMethod.checkReturnValue(theReply); + reply = (Serializable) theReply; + } else if (theReply != null) { + System.err.println(String.format("Non-serializable return type %s for method %s, returning null", returnType.getName(), methodName)); + } + } catch (final IllegalAccessException | IllegalArgumentException e) { + System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length)); + } catch (final InvocationTargetException e) { + throw new RuntimeException(e); + } + getRemote(ctx).send(new ReplyEvent(event.getId(), reply)); + } + } + }; + + if (runInEdt) { + FThreads.invokeInEdtNowOrLater(toRun); + } else { + FThreads.invokeInBackgroundThread(toRun); + } + } + } + + @Override + public final void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { + cause.printStackTrace(); + ctx.close(); + } + +} diff --git a/forge-gui/src/main/java/forge/net/GameProtocolSender.java b/forge-gui/src/main/java/forge/net/GameProtocolSender.java new file mode 100644 index 00000000000..ba6eb4f56a0 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/GameProtocolSender.java @@ -0,0 +1,32 @@ +package forge.net; + +import java.util.concurrent.TimeoutException; + +import forge.net.GameProtocol.ProtocolMethod; +import forge.net.event.GuiGameEvent; + +public final class GameProtocolSender { + + private final IRemote remote; + public GameProtocolSender(final IRemote remote) { + this.remote = remote; + } + + public void send(final ProtocolMethod method, final Object... args) { + method.checkArgs(args); + remote.send(new GuiGameEvent(method, args)); + } + + @SuppressWarnings("unchecked") + public T sendAndWait(final ProtocolMethod method, final Object... args) { + method.checkArgs(args); + try { + final Object returned = remote.sendAndWait(new GuiGameEvent(method, args)); + method.checkReturnValue(returned); + return (T) returned; + } catch (final TimeoutException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/forge-gui/src/main/java/forge/net/client/GameClientHandler.java b/forge-gui/src/main/java/forge/net/client/GameClientHandler.java index 6e421976ac2..7f21432091b 100644 --- a/forge-gui/src/main/java/forge/net/client/GameClientHandler.java +++ b/forge-gui/src/main/java/forge/net/client/GameClientHandler.java @@ -1,26 +1,18 @@ package forge.net.client; -import forge.FThreads; +import io.netty.channel.ChannelHandlerContext; import forge.game.player.PlayerView; import forge.interfaces.IGuiGame; -import forge.match.MatchButtonType; import forge.model.FModel; -import forge.net.GameProtocol; import forge.net.GameProtocol.ProtocolMethod; -import forge.net.event.GuiGameEvent; +import forge.net.GameProtocolHandler; +import forge.net.IRemote; +import forge.net.ReplyPool; import forge.net.event.LoginEvent; -import forge.net.event.ReplyEvent; import forge.properties.ForgePreferences.FPref; import forge.trackable.TrackableCollection; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; - -class GameClientHandler extends ChannelInboundHandlerAdapter { +final class GameClientHandler extends GameProtocolHandler { private final FGameClient client; private final IGuiGame gui; @@ -28,93 +20,43 @@ class GameClientHandler extends ChannelInboundHandlerAdapter { * Creates a client-side game handler. */ public GameClientHandler(final FGameClient client) { + super(true); this.client = client; this.gui = client.getGui(); } + @Override + protected ReplyPool getReplyPool(final ChannelHandlerContext ctx) { + return client.getReplyPool(); + } + + @Override + protected IRemote getRemote(final ChannelHandlerContext ctx) { + return client; + } + + @Override + protected IGuiGame getToInvoke(final ChannelHandlerContext ctx) { + return gui; + } + + @SuppressWarnings("unchecked") + @Override + protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) { + switch (protocolMethod) { + case openView: + final TrackableCollection myPlayers = (TrackableCollection) args[0]; + client.setGameControllers(myPlayers); + break; + default: + break; + } + } + @Override public void channelActive(final ChannelHandlerContext ctx) { // Don't use send() here, as this.channel is not yet set! ctx.channel().writeAndFlush(new LoginEvent(FModel.getPreferences().getPref(FPref.PLAYER_NAME), Integer.parseInt(FModel.getPreferences().getPref(FPref.UI_AVATARS).split(",")[0]))); } - @SuppressWarnings("unchecked") - @Override - public void channelRead(final ChannelHandlerContext ctx, final Object msg) { - System.out.println("Client received: " + msg); - if (msg instanceof ReplyEvent) { - final ReplyEvent event = (ReplyEvent) msg; - client.getReplyPool().complete(event.getIndex(), event.getReply()); - } else if (msg instanceof GuiGameEvent) { - final GuiGameEvent event = (GuiGameEvent) msg; - final String methodName = event.getMethod(); - - final Object[] originalArgs = event.getObjects(); - final ProtocolMethod protocolMethod = GameProtocol.getProtocolMethod(methodName); - if (protocolMethod == null) { - throw new IllegalStateException(String.format("Protocol method %s unknown", methodName)); - } - final Method method = protocolMethod.getMethod(); - if (method == null) { - throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name())); - } - - final Object toInvoke; - final Object[] args; - if (protocolMethod.invokeOnButton()) { - toInvoke = originalArgs[1] == MatchButtonType.OK ? gui.getBtnOK((PlayerView) originalArgs[0]) : gui.getBtnCancel((PlayerView) originalArgs[0]); - // Remove the player and button type from the args passed to the method - args = Arrays.copyOfRange(originalArgs, 2, originalArgs.length); - } else { - toInvoke = gui; - args = originalArgs; - } - - // Pre-call actions - switch (protocolMethod) { - case openView: - final TrackableCollection myPlayers = (TrackableCollection) args[0]; - client.setGameControllers(myPlayers); - break; - default: - break; - } - - final Class returnType = method.getReturnType(); - if (returnType.equals(Void.TYPE)) { - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override public final void run() { - try { - method.invoke(toInvoke, args); - } catch (final IllegalAccessException | IllegalArgumentException e) { - System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length)); - } catch (final InvocationTargetException e) { - throw new RuntimeException(e); - } - } - }); - } else { - Serializable reply = null; - try { - final Object theReply = method.invoke(toInvoke, args); - if (theReply instanceof Serializable) { - reply = (Serializable) method.invoke(toInvoke, args); - } else { - System.err.println(String.format("Non-serializable return type %s for method %s", returnType.getName(), methodName)); - } - } catch (final IllegalAccessException | IllegalArgumentException e) { - System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length)); - } catch (final InvocationTargetException e) { - throw new RuntimeException(e); - } - client.send(new ReplyEvent(event.getId(), reply)); - } - } - } - - @Override - public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { - cause.printStackTrace(); - ctx.close(); - } } \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/net/client/NetGameController.java b/forge-gui/src/main/java/forge/net/client/NetGameController.java index 84b1329478e..8762b20eef7 100644 --- a/forge-gui/src/main/java/forge/net/client/NetGameController.java +++ b/forge-gui/src/main/java/forge/net/client/NetGameController.java @@ -1,7 +1,6 @@ package forge.net.client; import java.util.List; -import java.util.concurrent.TimeoutException; import forge.game.card.CardView; import forge.game.player.PlayerView; @@ -9,90 +8,77 @@ import forge.game.spellability.SpellAbilityView; import forge.interfaces.IDevModeCheats; import forge.interfaces.IGameController; import forge.match.NextGameDecision; -import forge.net.event.GuiGameEvent; +import forge.net.GameProtocol.ProtocolMethod; +import forge.net.GameProtocolSender; import forge.util.ITriggerEvent; public class NetGameController implements IGameController { - private final IToServer server; + private final GameProtocolSender sender; public NetGameController(final IToServer server) { - this.server = server; + this.sender = new GameProtocolSender(server); } - private String methodName() { - boolean passedFirst = false; - for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) { - if (ste.getClassName() == getClass().getName()) { - if (passedFirst) { - return ste.getMethodName(); - } - passedFirst = true; - } - } - return null; + private void send(final ProtocolMethod method, final Object... args) { + sender.send(method, args); } - private void send(final String method, final Object... args) { - server.send(new GuiGameEvent(method, args)); - } - @SuppressWarnings("unchecked") - private T sendAndWait(final String method, final Object... args) { - try { - return (T) server.sendAndWait(new GuiGameEvent(method, args)); - } catch (final TimeoutException e) { - e.printStackTrace(); - } - return null; + private T sendAndWait(final ProtocolMethod method, final Object... args) { + return sender.sendAndWait(method, args); } @Override - public boolean useMana(final byte color) { - return sendAndWait(methodName(), color); + public void useMana(final byte color) { + send(ProtocolMethod.useMana, Byte.valueOf(color)); } @Override - public boolean tryUndoLastAction() { - return sendAndWait(methodName()); + public void undoLastAction() { + send(ProtocolMethod.undoLastAction); } @Override public void selectPlayer(final PlayerView playerView, final ITriggerEvent triggerEvent) { - send(methodName(), playerView, triggerEvent); + send(ProtocolMethod.selectPlayer, playerView, triggerEvent); } @Override public boolean selectCard(final CardView cardView, final List otherCardViewsToSelect, final ITriggerEvent triggerEvent) { - return sendAndWait(methodName(), cardView, otherCardViewsToSelect, triggerEvent); + send(ProtocolMethod.selectCard, cardView, otherCardViewsToSelect, triggerEvent); + // Difference from local games! Always consider a card as successfully selected, + // to avoid blocks where server and client wait for each other to respond. + // Some cost in functionality but a huge gain in stability & speed. + return true; } @Override public void selectButtonOk() { - send(methodName()); + send(ProtocolMethod.selectButtonOk); } @Override public void selectButtonCancel() { - send(methodName()); + send(ProtocolMethod.selectButtonCancel); } @Override public void selectAbility(final SpellAbilityView sa) { - send(methodName(), sa); + send(ProtocolMethod.selectAbility, sa); } @Override - public boolean passPriorityUntilEndOfTurn() { - return sendAndWait(methodName()); + public void passPriorityUntilEndOfTurn() { + send(ProtocolMethod.passPriorityUntilEndOfTurn); } @Override - public boolean passPriority() { - return sendAndWait(methodName()); + public void passPriority() { + send(ProtocolMethod.passPriority); } @Override public void nextGameDecision(final NextGameDecision decision) { - send(methodName(), decision); + send(ProtocolMethod.nextGameDecision, decision); } @Override @@ -102,31 +88,33 @@ public class NetGameController implements IGameController { } public String getActivateDescription(final CardView card) { - return sendAndWait(methodName(), card); + return sendAndWait(ProtocolMethod.getActivateDescription, card); } @Override public void concede() { - send(methodName()); + send(ProtocolMethod.concede); } @Override public IDevModeCheats cheat() { + // No cheating in network games! return IDevModeCheats.NO_CHEAT; } @Override public boolean canPlayUnlimitedLands() { + // Don't do this over network return false; } @Override public void alphaStrike() { - send(methodName()); + send(ProtocolMethod.alphaStrike); } @Override public void reorderHand(CardView card, int index) { - send(methodName(), card, index); + send(ProtocolMethod.reorderHand, card, Integer.valueOf(index)); } } diff --git a/forge-gui/src/main/java/forge/net/event/GuiGameEvent.java b/forge-gui/src/main/java/forge/net/event/GuiGameEvent.java index 3ab428092fb..a74d3454a87 100644 --- a/forge-gui/src/main/java/forge/net/event/GuiGameEvent.java +++ b/forge-gui/src/main/java/forge/net/event/GuiGameEvent.java @@ -1,5 +1,6 @@ package forge.net.event; +import forge.net.GameProtocol.ProtocolMethod; import forge.net.server.RemoteClient; public final class GuiGameEvent implements IdentifiableNetEvent { @@ -7,10 +8,10 @@ public final class GuiGameEvent implements IdentifiableNetEvent { private static int staticId = 0; private final int id; - private final String method; + private final ProtocolMethod method; private final Object[] objects; - public GuiGameEvent(final String method, final Object ... objects) { + public GuiGameEvent(final ProtocolMethod method, final Object ... objects) { this.id = staticId++; this.method = method; this.objects = objects == null ? new Object[0] : objects; @@ -30,7 +31,7 @@ public final class GuiGameEvent implements IdentifiableNetEvent { return id; } - public String getMethod() { + public ProtocolMethod getMethod() { return method; } diff --git a/forge-gui/src/main/java/forge/net/server/FServerManager.java b/forge-gui/src/main/java/forge/net/server/FServerManager.java index e2c505e0805..95b580cb5b7 100644 --- a/forge-gui/src/main/java/forge/net/server/FServerManager.java +++ b/forge-gui/src/main/java/forge/net/server/FServerManager.java @@ -1,28 +1,19 @@ package forge.net.server; -import forge.FThreads; import forge.GuiBase; -import forge.LobbyPlayer; import forge.game.GameView; -import forge.game.card.CardView; -import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbilityView; import forge.interfaces.IGameController; import forge.interfaces.IGuiGame; import forge.interfaces.ILobbyListener; import forge.match.LobbySlot; import forge.match.LobbySlotType; -import forge.match.NextGameDecision; -import forge.net.event.GuiGameEvent; import forge.net.event.LobbyUpdateEvent; import forge.net.event.LoginEvent; import forge.net.event.LogoutEvent; import forge.net.event.MessageEvent; import forge.net.event.NetEvent; -import forge.net.event.ReplyEvent; import forge.net.event.UpdateLobbyPlayerEvent; import forge.properties.ForgeConstants; -import forge.util.ITriggerEvent; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -40,12 +31,10 @@ import io.netty.handler.codec.serialization.ObjectEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; -import java.io.Serializable; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Map; import org.apache.log4j.PropertyConfigurator; @@ -79,6 +68,16 @@ public final class FServerManager { private FServerManager() { } + RemoteClient getClient(final Channel ch) { + return clients.get(ch); + } + GameView getGameView() { + return localLobby.getGameView(); + } + IGameController getController(final int index) { + return localLobby.getController(index); + } + /** * Get the singleton instance of {@link FServerManager}. * @@ -126,7 +125,7 @@ public final class FServerManager { } }).start(); - //mapNatPort(port); + mapNatPort(port); Runtime.getRuntime().addShutdownHook(shutdownHook); isHosting = true; } catch (final InterruptedException e) { @@ -229,197 +228,6 @@ public final class FServerManager { } } - private class GameServerHandler extends ChannelInboundHandlerAdapter { - @SuppressWarnings("unchecked") - @Override - public void channelRead(final ChannelHandlerContext ctx, final Object msg) { - System.out.println("Server received: " + msg); - final RemoteClient client = clients.get(ctx.channel()); - if (msg instanceof ReplyEvent) { - client.setReply(((ReplyEvent) msg).getIndex(), ((ReplyEvent) msg).getReply()); - } else if (msg instanceof GuiGameEvent) { - final GuiGameEvent event = (GuiGameEvent) msg; - final GameView gameView = localLobby.getGameView(); - final IGameController controller = localLobby.getController(client.getIndex()); - final Object[] args = event.getObjects(); - - FThreads.invokeInBackgroundThread(new Runnable() { - @Override public final void run() { - Serializable reply = null; - boolean doReply = false; - - switch (event.getMethod()) { - // From GameController - case "useMana": - controller.useMana((byte) args[0]); - break; - case "tryUndoLastAction": - reply = controller.tryUndoLastAction(); - doReply = true; - break; - case "selectPlayer": - controller.selectPlayer((PlayerView) args[0], (ITriggerEvent) args[1]); - break; - case "selectCard": - reply = controller.selectCard((CardView) args[0], (List) args[1], (ITriggerEvent) args[2]); - doReply = true; - break; - case "selectButtonOk": - controller.selectButtonOk(); - break; - case "selectButtonCancel": - controller.selectButtonCancel(); - break; - case "selectAbility": - controller.selectAbility((SpellAbilityView) args[0]); - break; - case "passPriorityUntilEndOfTurn": - reply = controller.passPriorityUntilEndOfTurn(); - doReply = true; - break; - case "passPriority": - reply = controller.passPriority(); - doReply = true; - break; - case "nextGameDecision": - controller.nextGameDecision((NextGameDecision) args[0]); - break; - case "mayLookAtAllCards": - reply = controller.mayLookAtAllCards(); - doReply = true; - break; - case "getActivateDescription": - reply = controller.getActivateDescription((CardView) args[0]); - doReply = true; - break; - case "concede": - controller.concede(); - break; - case "alphaStrike": - controller.alphaStrike(); - break; - // From GameView - case "getPlayers": - reply = (Serializable) gameView.getPlayers(); - doReply = true; - break; - case "getTitle": - reply = gameView.getTitle(); - doReply = true; - break; - case "isCommander": - reply = gameView.isCommander(); - doReply = true; - break; - case "getGameType": - reply = gameView.getGameType(); - doReply = true; - break; - case "getPoisonCountersToLose": - reply = gameView.getPoisonCountersToLose(); - doReply = true; - break; - case "getNumGamesInMatch": - reply = gameView.getNumGamesInMatch(); - doReply = true; - break; - case "getTurn": - reply = gameView.getTurn(); - doReply = true; - break; - case "getPhase": - reply = gameView.getPhase(); - doReply = true; - break; - case "getPlayerTurn": - reply = gameView.getPlayerTurn(); - doReply = true; - break; - case "getStack": - reply = (Serializable) gameView.getStack(); - doReply = true; - break; - case "peekStack": - reply = gameView.peekStack(); - doReply = true; - break; - case "getStormCount": - reply = gameView.getStormCount(); - doReply = true; - break; - case "isFirstGameInMatch": - reply = gameView.isFirstGameInMatch(); - doReply = true; - break; - case "getNumPlayedGamesInMatch": - reply = gameView.getNumPlayedGamesInMatch(); - doReply = true; - break; - case "isGameOver": - reply = gameView.isGameOver(); - doReply = true; - break; - case "isMatchOver": - reply = gameView.isMatchOver(); - doReply = true; - break; - case "getWinningTeam": - reply = gameView.getWinningTeam(); - doReply = true; - break; - case "getGameLog": - reply = gameView.getGameLog(); - doReply = true; - break; - case "getCombat": - reply = gameView.getCombat(); - doReply = true; - break; - case "isMatchWonBy": - reply = gameView.isMatchWonBy((LobbyPlayer) args[0]); - doReply = true; - break; - case "getOutcomesOfMatch": - reply = (Serializable) gameView.getOutcomesOfMatch(); - doReply = true; - break; - // TODO case "getWinningPlayer": - case "isWinner": - reply = gameView.isWinner((LobbyPlayer) args[0]); - doReply = true; - break; - case "getGamesWonBy": - reply = gameView.getGamesWonBy((LobbyPlayer) args[0]); - doReply = true; - break; - case "getDeck": - reply = gameView.getDeck((String) args[0]); - doReply = true; - break; - case "getAnteResult": - reply = gameView.getAnteResult((PlayerView) args[0]); - doReply = true; - break; - default: - System.err.println(String.format("Unknown incoming client command %s", event.getMethod())); - break; - } - - if (doReply) { - client.send(new ReplyEvent(event.getId(), reply)); - } - } - }); - } - } - - @Override - public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { - cause.printStackTrace(); - ctx.close(); - } - } - private class RegisterClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(final ChannelHandlerContext ctx) throws Exception { diff --git a/forge-gui/src/main/java/forge/net/server/GameServerHandler.java b/forge-gui/src/main/java/forge/net/server/GameServerHandler.java new file mode 100644 index 00000000000..0eab072777e --- /dev/null +++ b/forge-gui/src/main/java/forge/net/server/GameServerHandler.java @@ -0,0 +1,41 @@ +package forge.net.server; + +import io.netty.channel.ChannelHandlerContext; +import forge.interfaces.IGameController; +import forge.net.GameProtocol.ProtocolMethod; +import forge.net.GameProtocolHandler; +import forge.net.IRemote; +import forge.net.ReplyPool; + +final class GameServerHandler extends GameProtocolHandler { + + private final FServerManager server = FServerManager.getInstance(); + GameServerHandler() { + super(false); + } + + private RemoteClient getClient(final ChannelHandlerContext ctx) { + return server.getClient(ctx.channel()); + } + + @Override + protected ReplyPool getReplyPool(final ChannelHandlerContext ctx) { + return getClient(ctx).getReplyPool(); + } + + @Override + protected IRemote getRemote(final ChannelHandlerContext ctx) { + return getClient(ctx); + } + + @Override + protected IGameController getToInvoke(final ChannelHandlerContext ctx) { + return server.getController(getClient(ctx).getIndex()); + } + + @Override + protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) { + // Nothing needs to be done here + } + +} \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/net/server/NetGuiGame.java b/forge-gui/src/main/java/forge/net/server/NetGuiGame.java index 44923d46f8a..cea3f6d684c 100644 --- a/forge-gui/src/main/java/forge/net/server/NetGuiGame.java +++ b/forge-gui/src/main/java/forge/net/server/NetGuiGame.java @@ -1,16 +1,12 @@ package forge.net.server; import java.util.Collection; -import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeoutException; import com.google.common.base.Function; -import com.google.common.collect.Maps; import forge.LobbyPlayer; -import forge.UiCommand; import forge.assets.FSkinProp; import forge.deck.CardPool; import forge.game.GameEntityView; @@ -22,54 +18,31 @@ import forge.game.player.IHasIcon; import forge.game.player.PlayerView; import forge.game.spellability.SpellAbilityView; import forge.game.zone.ZoneType; -import forge.interfaces.IButton; import forge.item.PaperCard; import forge.match.AbstractGuiGame; -import forge.match.MatchButtonType; -import forge.net.event.GuiGameEvent; +import forge.net.GameProtocol.ProtocolMethod; +import forge.net.GameProtocolSender; import forge.player.PlayerZoneUpdate; import forge.trackable.TrackableCollection; import forge.util.ITriggerEvent; public class NetGuiGame extends AbstractGuiGame { - private final IToClient client; - private final Map> btns = new EnumMap>(MatchButtonType.class); + private final GameProtocolSender sender; public NetGuiGame(final IToClient client) { - this.client = client; - for (final MatchButtonType type : MatchButtonType.values()) { - btns.put(type, Maps.newHashMap()); - } + this.sender = new GameProtocolSender(client); } - private String methodName() { - boolean passedFirst = false; - for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) { - if (ste.getClassName() == getClass().getName()) { - if (passedFirst) { - return ste.getMethodName(); - } - passedFirst = true; - } - } - return null; + private void send(final ProtocolMethod method, final Object... args) { + sender.send(method, args); } - private void send(final String method, final Object... args) { - client.send(new GuiGameEvent(method, args)); - } - @SuppressWarnings("unchecked") - private T sendAndWait(final String method, final Object... args) { - try { - return (T) client.sendAndWait(new GuiGameEvent(method, args)); - } catch (final TimeoutException e) { - e.printStackTrace(); - } - return null; + private T sendAndWait(final ProtocolMethod method, final Object... args) { + return sender.sendAndWait(method, args); } private void updateGameView() { - send("setGameView", getGameView()); + send(ProtocolMethod.setGameView, getGameView()); } @Override @@ -80,227 +53,206 @@ public class NetGuiGame extends AbstractGuiGame { @Override public void openView(final TrackableCollection myPlayers) { - for (final MatchButtonType type : MatchButtonType.values()) { - btns.get(type).clear(); - for (final PlayerView player : myPlayers) { - btns.get(type).put(player, new NetButton(player, type)); - } - } - send(methodName(), myPlayers); + send(ProtocolMethod.openView, myPlayers); updateGameView(); } @Override public void afterGameEnd() { - send(methodName()); + send(ProtocolMethod.afterGameEnd); } @Override public void showCombat() { - send(methodName()); + send(ProtocolMethod.showCombat); } @Override public void showPromptMessage(final PlayerView playerView, final String message) { updateGameView(); - send(methodName(), playerView, message); + send(ProtocolMethod.showPromptMessage, playerView, message); } @Override - public boolean stopAtPhase(final PlayerView playerTurn, final PhaseType phase) { - return sendAndWait(methodName(), playerTurn, phase); - } - - @Override - public IButton getBtnOK(final PlayerView playerView) { - return btns.get(MatchButtonType.OK).get(playerView); - } - - @Override - public IButton getBtnCancel(final PlayerView playerView) { - return btns.get(MatchButtonType.CANCEL).get(playerView); - } - - @Override - public void focusButton(final MatchButtonType button) { - send(methodName(), button); + public void updateButtons(final PlayerView owner, final String label1, final String label2, final boolean enable1, final boolean enable2, final boolean focus1) { + send(ProtocolMethod.updateButtons, owner, label1, label2, enable1, enable2, focus1); } @Override public void flashIncorrectAction() { - send(methodName()); + send(ProtocolMethod.flashIncorrectAction); } @Override public void updatePhase() { updateGameView(); - send(methodName()); + send(ProtocolMethod.updatePhase); } @Override public void updateTurn(final PlayerView player) { updateGameView(); - send(methodName(), player); + send(ProtocolMethod.updateTurn, player); } @Override public void updatePlayerControl() { updateGameView(); - send(methodName()); + send(ProtocolMethod.updatePlayerControl); } @Override public void enableOverlay() { - send(methodName()); + send(ProtocolMethod.enableOverlay); } @Override public void disableOverlay() { - send(methodName()); + send(ProtocolMethod.disableOverlay); } @Override public void finishGame() { - send(methodName()); + send(ProtocolMethod.finishGame); } @Override public Object showManaPool(final PlayerView player) { - send(methodName(), player); + send(ProtocolMethod.showManaPool, player); return null; } @Override public void hideManaPool(final PlayerView player, final Object zoneToRestore) { - send(methodName(), player, zoneToRestore); + send(ProtocolMethod.hideManaPool, player, zoneToRestore); } @Override public void updateStack() { updateGameView(); - send(methodName()); + send(ProtocolMethod.updateStack); } @Override public void updateZones(final Iterable zonesToUpdate) { updateGameView(); - send(methodName(), zonesToUpdate); + send(ProtocolMethod.updateZones, zonesToUpdate); } @Override public void updateCards(final Iterable cards) { updateGameView(); - send(methodName(), cards); + send(ProtocolMethod.updateCards, cards); } @Override public void updateManaPool(final Iterable manaPoolUpdate) { updateGameView(); - send(methodName(), manaPoolUpdate); + send(ProtocolMethod.updateManaPool, manaPoolUpdate); } @Override public void updateLives(final Iterable livesUpdate) { updateGameView(); - send(methodName(), livesUpdate); + send(ProtocolMethod.updateLives, livesUpdate); } @Override public void setPanelSelection(final CardView hostCard) { updateGameView(); - send(methodName(), hostCard); + send(ProtocolMethod.setPanelSelection, hostCard); } @Override - public SpellAbilityView getAbilityToPlay(final List abilities, final ITriggerEvent triggerEvent) { - return sendAndWait(methodName(), abilities, triggerEvent); + public SpellAbilityView getAbilityToPlay(final CardView hostCard, final List abilities, final ITriggerEvent triggerEvent) { + return sendAndWait(ProtocolMethod.getAbilityToPlay, hostCard, abilities, triggerEvent); } @Override public Map assignDamage(final CardView attacker, final List blockers, final int damage, final GameEntityView defender, final boolean overrideOrder) { - return sendAndWait(methodName(), attacker, blockers, damage, defender, overrideOrder); + return sendAndWait(ProtocolMethod.assignDamage, attacker, blockers, damage, defender, overrideOrder); } @Override public void message(final String message, final String title) { - send(methodName(), message, title); + send(ProtocolMethod.message, message, title); } @Override public void showErrorDialog(final String message, final String title) { - send(methodName(), message, title); + send(ProtocolMethod.showErrorDialog, message, title); } @Override public boolean showConfirmDialog(final String message, final String title, final String yesButtonText, final String noButtonText, final boolean defaultYes) { - return sendAndWait(methodName(), message, title, yesButtonText, noButtonText, defaultYes); + return sendAndWait(ProtocolMethod.showConfirmDialog, message, title, yesButtonText, noButtonText, defaultYes); } @Override public int showOptionDialog(final String message, final String title, final FSkinProp icon, final String[] options, final int defaultOption) { - return sendAndWait(methodName(), message, title, icon, options, defaultOption); + return sendAndWait(ProtocolMethod.showOptionDialog, message, title, icon, options, defaultOption); } @Override public int showCardOptionDialog(final CardView card, final String message, final String title, final FSkinProp icon, final String[] options, final int defaultOption) { - return sendAndWait(methodName(), card, message, title, icon, options, defaultOption); + return sendAndWait(ProtocolMethod.showCardOptionDialog, card, message, title, icon, options, defaultOption); } @Override public String showInputDialog(final String message, final String title, final FSkinProp icon, final String initialInput, final String[] inputOptions) { - return sendAndWait(methodName(), message, title, icon, initialInput, inputOptions); + return sendAndWait(ProtocolMethod.showInputDialog, message, title, icon, initialInput, inputOptions); } @Override public boolean confirm(final CardView c, final String question, final boolean defaultIsYes, final String[] options) { - return sendAndWait(methodName(), c, question, defaultIsYes, options); + return sendAndWait(ProtocolMethod.confirm, c, question, defaultIsYes, options); } @Override public List getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display) { - return sendAndWait(methodName(), message, min, max, choices, selected, display); + return sendAndWait(ProtocolMethod.getChoices, message, min, max, choices, selected, display); } @Override public List order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax, final List sourceChoices, final List destChoices, final CardView referenceCard, final boolean sideboardingMode) { - return sendAndWait(methodName(), title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, referenceCard, sideboardingMode); + return sendAndWait(ProtocolMethod.order, title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, referenceCard, sideboardingMode); } @Override public List sideboard(final CardPool sideboard, final CardPool main) { - return sendAndWait(methodName(), sideboard, main); + return sendAndWait(ProtocolMethod.sideboard, sideboard, main); } @Override public GameEntityView chooseSingleEntityForEffect(final String title, final Collection optionList, final DelayedReveal delayedReveal, final boolean isOptional) { - return sendAndWait(methodName(), title, optionList, delayedReveal, isOptional); + return sendAndWait(ProtocolMethod.chooseSingleEntityForEffect, title, optionList, delayedReveal, isOptional); } @Override public void setCard(final CardView card) { updateGameView(); - send(methodName(), card); + send(ProtocolMethod.setCard, card); } @Override - public void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi) { + public void setPlayerAvatar(final LobbyPlayer player, final IHasIcon ihi) { // TODO Auto-generated method stub } @Override public boolean openZones(final Collection zones, final Map players) { updateGameView(); - return sendAndWait(methodName(), zones, players); + return sendAndWait(ProtocolMethod.openZones, zones, players); } @Override public void restoreOldZones(final Map playersToRestoreZonesFor) { - send(methodName(), playersToRestoreZonesFor); + send(ProtocolMethod.restoreOldZones, playersToRestoreZonesFor); } @Override public boolean isUiSetToSkipPhase(final PlayerView playerTurn, final PhaseType phase) { - return sendAndWait(methodName(), playerTurn, phase); + return sendAndWait(ProtocolMethod.isUiSetToSkipPhase, playerTurn, phase); } @Override @@ -308,71 +260,4 @@ public class NetGuiGame extends AbstractGuiGame { // TODO Auto-generated method stub } - private final class NetButton implements IButton { - - private String methodName() { - boolean passedFirst = false; - for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) { - if (ste.getClassName() == getClass().getName()) { - if (passedFirst) { - return ste.getMethodName(); - } - passedFirst = true; - } - } - return null; - } - - private final PlayerView playerView; - private final MatchButtonType type; - private NetButton(final PlayerView playerView, final MatchButtonType type) { - this.playerView = playerView; - this.type = type; - } - - @Override - public void setEnabled(final boolean b0) { - send("btn_" + methodName(), playerView, type, b0); - } - - @Override - public void setVisible(final boolean b0) { - send("btn_" + methodName(), playerView, type, b0); - } - - @Override - public void setText(final String text0) { - send("btn_" + methodName(), playerView, type, text0); - } - - @Override - public boolean isSelected() { - return sendAndWait("btn_" + methodName(), playerView, type); - } - - @Override - public void setSelected(final boolean b0) { - send("btn_" + methodName(), playerView, type, b0); - } - - @Override - public boolean requestFocusInWindow() { - return sendAndWait("btn_" + methodName(), playerView, type); - } - - @Override - public void setCommand(final UiCommand command0) { - send("btn_" + methodName(), playerView, type, command0); - } - - @Override - public void setImage(final FSkinProp color) { - send("btn_" + methodName(), playerView, type, color); - } - - @Override - public void setTextColor(final int r, final int g, final int b) { - send("btn_" + methodName(), playerView, type, r, g, b); - } - } } diff --git a/forge-gui/src/main/java/forge/net/server/RemoteClient.java b/forge-gui/src/main/java/forge/net/server/RemoteClient.java index a2675746f17..7897904292f 100644 --- a/forge-gui/src/main/java/forge/net/server/RemoteClient.java +++ b/forge-gui/src/main/java/forge/net/server/RemoteClient.java @@ -46,7 +46,7 @@ public final class RemoteClient implements IToClient { this.index = index; } - public void setReply(final int id, final Object reply) { - replies.complete(id, reply); + ReplyPool getReplyPool() { + return replies; } } diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 14f9ab7017d..59782a5283b 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -144,7 +144,7 @@ public class HumanPlay { return original; } final List abilities = GameActionUtil.getOptionalCosts(original); - return p.getController().getAbilityToPlay(abilities); + return p.getController().getAbilityToPlay(original.getHostCard(), abilities); } private static boolean payManaCostIfNeeded(final PlayerControllerHuman controller, final Player p, final SpellAbility sa) { diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index c3119647595..b79d86f2515 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -219,8 +219,8 @@ public class PlayerControllerHuman /** * Uses GUI to learn which spell the player (human in our case) would like to play */ - public SpellAbility getAbilityToPlay(final List abilities, final ITriggerEvent triggerEvent) { - final SpellAbilityView resultView = getGui().getAbilityToPlay(SpellAbilityView.getCollection(abilities), triggerEvent); + public SpellAbility getAbilityToPlay(final Card hostCard, final List abilities, final ITriggerEvent triggerEvent) { + final SpellAbilityView resultView = getGui().getAbilityToPlay(CardView.get(hostCard), SpellAbilityView.getCollection(abilities), triggerEvent); return getGame().getSpellAbility(resultView); } @@ -1304,6 +1304,12 @@ public class PlayerControllerHuman return true; } + + @Override + public void undoLastAction() { + tryUndoLastAction(); + } + public boolean tryUndoLastAction() { if (!canUndoLastAction()) { return false; @@ -1333,37 +1339,33 @@ public class PlayerControllerHuman } } - public boolean passPriority() { - return passPriority(false); + public void passPriority() { + passPriority(false); } - public boolean passPriorityUntilEndOfTurn() { - return passPriority(true); + public void passPriorityUntilEndOfTurn() { + passPriority(true); } - private boolean passPriority(final boolean passUntilEndOfTurn) { + private void passPriority(final boolean passUntilEndOfTurn) { final Input inp = inputProxy.getInput(); if (inp instanceof InputPassPriority) { if (passUntilEndOfTurn) { autoPassUntilEndOfTurn(); } inp.selectButtonOK(); - return true; + } else { + FThreads.invokeInEdtNowOrLater(new Runnable() { + @Override public final void run() { + getGui().message("Cannot pass priority at this time."); + } + }); } - - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override - public void run() { - getGui().message("Cannot pass priority at this time."); - } - }); - return false; } - public boolean useMana(final byte mana) { + public void useMana(final byte mana) { final Input input = inputQueue.getInput(); if (input instanceof InputPayMana) { - return ((InputPayMana) input).useManaFromPool(mana); + ((InputPayMana) input).useManaFromPool(mana); } - return false; } public void selectPlayer(final PlayerView playerView, final ITriggerEvent triggerEvent) { diff --git a/forge-gui/src/main/java/forge/player/PlayerZoneUpdates.java b/forge-gui/src/main/java/forge/player/PlayerZoneUpdates.java index 914d196aafd..7d48de0651f 100644 --- a/forge-gui/src/main/java/forge/player/PlayerZoneUpdates.java +++ b/forge-gui/src/main/java/forge/player/PlayerZoneUpdates.java @@ -1,6 +1,7 @@ package forge.player; import java.io.Serializable; +import java.util.Collections; import java.util.Iterator; import java.util.Map; @@ -11,11 +12,21 @@ import forge.game.player.PlayerView; public class PlayerZoneUpdates implements Iterable, Serializable { private static final long serialVersionUID = 7023549243041119023L; - private final Map updates = Maps.newHashMap(); + private final Map updates = Collections.synchronizedMap(Maps.newHashMap()); public PlayerZoneUpdates() { } + /** + * Copy constructor. + * + * @param other + * the {@link PlayerZoneUpdates} to copy. + */ + public PlayerZoneUpdates(final PlayerZoneUpdates other) { + this.updates.putAll(other.updates); + } + @Override public Iterator iterator() { return updates.values().iterator();