From 574e12d2e39ff10e79fbf1c9b0030b52bb2484a7 Mon Sep 17 00:00:00 2001 From: elcnesh Date: Wed, 10 Sep 2014 12:08:48 +0000 Subject: [PATCH] Lots of small fixes and cleanup for the GUI refactoring. --- .gitattributes | 3 +- .../src/main/java/forge/control/FControl.java | 33 +- .../java/forge/screens/match/CMatchUI.java | 15 +- .../forge/screens/match/VAssignDamage.java | 7 +- .../forge/screens/match/views/VCommand.java | 3 +- .../forge/screens/match/views/VField.java | 3 +- .../main/java/forge/view/arcane/PlayArea.java | 11 +- forge-gui-mobile-dev/src/forge/app/Main.java | 2 + forge-gui-mobile/src/forge/Forge.java | 7 +- forge-gui-mobile/src/forge/GuiMobile.java | 4 +- .../src/forge/screens/match/FControl.java | 17 +- .../control/FControlGameEventHandler.java | 53 +- .../forge/control/FControlGamePlayback.java | 31 +- .../forge/match/input/InputPassPriority.java | 4 +- .../java/forge/player/LobbyPlayerHuman.java | 6 +- .../forge/player/PlayerControllerHuman.java | 1714 ++++++++++++++-- .../forge/player/PlayerControllerLocal.java | 1770 ----------------- .../java/forge/properties/ForgeConstants.java | 3 +- .../java/forge/sound/EventVisualizer.java | 5 +- .../main/java/forge/sound/SoundSystem.java | 2 +- forge-gui/src/main/java/forge/view/Cache.java | 61 + .../src/main/java/forge/view/IGameView.java | 1 + .../main/java/forge/view/LocalGameView.java | 671 +++++++ .../src/main/java/forge/view/ViewUtil.java | 11 +- 24 files changed, 2435 insertions(+), 2002 deletions(-) delete mode 100644 forge-gui/src/main/java/forge/player/PlayerControllerLocal.java create mode 100644 forge-gui/src/main/java/forge/view/Cache.java create mode 100644 forge-gui/src/main/java/forge/view/LocalGameView.java diff --git a/.gitattributes b/.gitattributes index 86fb0f8d3e5..c983334e0a4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16762,7 +16762,6 @@ forge-gui/src/main/java/forge/player/HumanPlay.java -text forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java -text forge-gui/src/main/java/forge/player/LobbyPlayerHuman.java -text forge-gui/src/main/java/forge/player/PlayerControllerHuman.java -text -forge-gui/src/main/java/forge/player/PlayerControllerLocal.java -text forge-gui/src/main/java/forge/player/TargetSelection.java -text forge-gui/src/main/java/forge/player/package-info.java -text forge-gui/src/main/java/forge/properties/ForgeConstants.java -text @@ -16847,10 +16846,12 @@ forge-gui/src/main/java/forge/util/gui/SGuiChoose.java -text forge-gui/src/main/java/forge/util/gui/SGuiDialog.java -text forge-gui/src/main/java/forge/util/gui/SOptionPane.java -text forge-gui/src/main/java/forge/util/package-info.java -text +forge-gui/src/main/java/forge/view/Cache.java -text forge-gui/src/main/java/forge/view/CardView.java -text forge-gui/src/main/java/forge/view/CombatView.java -text forge-gui/src/main/java/forge/view/GameEntityView.java -text forge-gui/src/main/java/forge/view/IGameView.java -text +forge-gui/src/main/java/forge/view/LocalGameView.java -text forge-gui/src/main/java/forge/view/PlayerView.java -text forge-gui/src/main/java/forge/view/SpellAbilityView.java -text forge-gui/src/main/java/forge/view/StackItemView.java -text diff --git a/forge-gui-desktop/src/main/java/forge/control/FControl.java b/forge-gui-desktop/src/main/java/forge/control/FControl.java index 5020326859d..2cf01b18d44 100644 --- a/forge-gui-desktop/src/main/java/forge/control/FControl.java +++ b/forge-gui-desktop/src/main/java/forge/control/FControl.java @@ -67,7 +67,6 @@ import forge.model.FModel; import forge.player.GamePlayerUtil; import forge.player.LobbyPlayerHuman; import forge.player.PlayerControllerHuman; -import forge.player.PlayerControllerLocal; import forge.properties.ForgeConstants; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; @@ -95,6 +94,7 @@ import forge.util.NameGenerator; import forge.view.FFrame; import forge.view.FView; import forge.view.IGameView; +import forge.view.LocalGameView; import forge.view.PlayerView; /** @@ -452,12 +452,23 @@ public enum FControl implements KeyEventDispatcher { final LobbyPlayer me = getGuiPlayer(); for (final Player p : game.getPlayers()) { if (p.getLobbyPlayer().equals(me)) { - this.gameView = (IGameView) p.getController(); - fcVisitor = new FControlGameEventHandler((PlayerControllerHuman) p.getController()); + final PlayerControllerHuman controller = (PlayerControllerHuman) p.getController(); + this.gameView = controller.getGameView(); + this.fcVisitor = new FControlGameEventHandler(GuiBase.getInterface(), controller.getGameView()); break; } } + if (this.gameView == null) { + // Watch game but do not participate + final LocalGameView gameView = new LocalGameView(game); + this.gameView = gameView; + this.fcVisitor = new FControlGameEventHandler(GuiBase.getInterface(), gameView); + this.playbackControl = new FControlGamePlayback(GuiBase.getInterface(), gameView); + this.playbackControl.setGame(game); + this.game.subscribeToEvents(playbackControl); + } + attachToGame(this.gameView); // It's important to run match in a different thread to allow GUI inputs to be invoked from inside game. @@ -523,20 +534,12 @@ public enum FControl implements KeyEventDispatcher { // Listen to DuelOutcome event to show ViewWinLose game0.subscribeToEvents(fcVisitor); - // Add playback controls to match if needed - if (localPlayer == null) { - // Create dummy controller - final PlayerControllerHuman controller = - new PlayerControllerLocal(game, null, humanLobbyPlayer, GuiBase.getInterface()); - playbackControl = new FControlGamePlayback(controller); - playbackControl.setGame(game); - game0.subscribeToEvents(playbackControl); - } - // per player observers were set in CMatchUI.SINGLETON_INSTANCE.initMatch //Set Field shown to current player. - final VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(localPlayer); - SDisplayUtil.showTab(nextField); + if (localPlayer != null) { + final VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(localPlayer); + SDisplayUtil.showTab(nextField); + } } /* (non-Javadoc) 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 ef8c673c71a..97fa5a12980 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 @@ -168,7 +168,7 @@ public enum CMatchUI implements ICDoc, IMenuProvider { int i = 0; for (final PlayerView p : sortedPlayers) { - if (p.getLobbyPlayer() == localPlayer) { + if (localPlayer == null || p.getLobbyPlayer() == localPlayer) { VHand newHand = new VHand(EDocID.Hands[i], p); newHand.getLayoutControl().initialize(); hands.add(newHand); @@ -176,11 +176,6 @@ public enum CMatchUI implements ICDoc, IMenuProvider { i++; } - if (hands.isEmpty()) { // add empty hand for matches without human - VHand newHand = new VHand(EDocID.Hands[0], null); - newHand.getLayoutControl().initialize(); - hands.add(newHand); - } view.setHandViews(hands); } @@ -277,7 +272,8 @@ public enum CMatchUI implements ICDoc, IMenuProvider { */ public final boolean stopAtPhase(final PlayerView turn, final PhaseType phase) { VField vf = getFieldViewFor(turn); - PhaseLabel label = vf.getPhaseIndicator().getLabelFor(phase); + PhaseLabel label = vf.getPhaseIndicator() + .getLabelFor(phase); return label == null || label.getEnabled(); } @@ -367,7 +363,10 @@ public enum CMatchUI implements ICDoc, IMenuProvider { } else if (zt == ZoneType.Ante) { CAntes.SINGLETON_INSTANCE.update(); } else { - getFieldViewFor(owner).getDetailsPanel().updateZones(); + final VField vf = getFieldViewFor(owner); + if (vf != null) { + vf.getDetailsPanel().updateZones(); + } } } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignDamage.java b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignDamage.java index 842b205a6a9..f4465cfc5d8 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignDamage.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignDamage.java @@ -193,7 +193,7 @@ public class VAssignDamage { DamageTarget dt = new DamageTarget(null, new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build()); this.damage.put(null, dt); this.defenders.add(dt); - final CardView fakeCard; + CardView fakeCard = null; if (defender instanceof CardView) fakeCard = (CardView)defender; else if (defender instanceof PlayerView) { @@ -201,11 +201,10 @@ public class VAssignDamage { fakeCard.getOriginal().setName(this.defender.toString()); final PlayerView p = (PlayerView)defender; fakeCard.setOwner(p); + fakeCard.setController(p); fakeCard.getOriginal().setImageKey(CMatchUI.SINGLETON_INSTANCE.avatarImages.get(p.getLobbyPlayer())); - } else { - fakeCard = new CardView(true); - fakeCard.getOriginal().setName(this.defender.toString()); } + addPanelForDefender(pnlDefenders, fakeCard); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VCommand.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VCommand.java index f9e3170777c..3834bd6cad0 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VCommand.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VCommand.java @@ -20,6 +20,7 @@ package forge.screens.match.views; import javax.swing.JPanel; import net.miginfocom.swing.MigLayout; +import forge.game.zone.ZoneType; import forge.gui.framework.DragCell; import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; @@ -66,7 +67,7 @@ public class VCommand implements IVDoc { // TODO player is hard-coded into tabletop...should be dynamic // (haven't looked into it too deeply). Doublestrike 12-04-12 - tabletop = new PlayArea(scroller, id0 == EDocID.COMMAND_0, player); + tabletop = new PlayArea(scroller, id0 == EDocID.COMMAND_0, player, ZoneType.Command); control = new CCommand(player, this); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java index 6115150dd24..9dd552b4e20 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java @@ -29,6 +29,7 @@ import javax.swing.border.LineBorder; import net.miginfocom.swing.MigLayout; import forge.LobbyPlayer; +import forge.game.zone.ZoneType; import forge.gui.framework.DragCell; import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; @@ -95,7 +96,7 @@ public class VField implements IVDoc { // TODO player is hard-coded into tabletop...should be dynamic // (haven't looked into it too deeply). Doublestrike 12-04-12 - tabletop = new PlayArea(scroller, id0 == EDocID.FIELD_1, player); + tabletop = new PlayArea(scroller, id0 == EDocID.FIELD_1, player, ZoneType.Battlefield); control = new CField(player, this, playerViewer); 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 529ed83d75f..e9ba6fae228 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 @@ -29,6 +29,7 @@ import com.google.common.collect.Lists; import forge.FThreads; import forge.GuiBase; +import forge.game.zone.ZoneType; import forge.screens.match.CMatchUI; import forge.screens.match.controllers.CPrompt; import forge.toolbox.FScrollPane; @@ -78,6 +79,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen private int stackSpacingX, stackSpacingY; private final PlayerView model; + private final ZoneType zone; /** *

@@ -88,11 +90,12 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen * @param mirror * @param player */ - public PlayArea(final FScrollPane scrollPane, final boolean mirror, final PlayerView player) { + public PlayArea(final FScrollPane scrollPane, final boolean mirror, final PlayerView player, final ZoneType zone) { super(scrollPane); this.setBackground(Color.white); this.mirror = mirror; this.model = player; + this.zone = zone; } private final CardStackRow collectAllLands() { @@ -593,13 +596,13 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen */ public void setupPlayZone() { FThreads.assertExecutedByEdt(GuiBase.getInterface(), true); - recalculateCardPanels(model); + recalculateCardPanels(model, zone); } - private void recalculateCardPanels(final PlayerView model) { + private void recalculateCardPanels(final PlayerView model, final ZoneType zone) { final List modelCopy; synchronized (model) { - modelCopy = Lists.newArrayList(model.getBfCards()); + modelCopy = Lists.newArrayList(model.getCards(zone)); } final List oldCards = Lists.newArrayList(); diff --git a/forge-gui-mobile-dev/src/forge/app/Main.java b/forge-gui-mobile-dev/src/forge/app/Main.java index c50702e3664..e9716e9a0a2 100644 --- a/forge-gui-mobile-dev/src/forge/app/Main.java +++ b/forge-gui-mobile-dev/src/forge/app/Main.java @@ -11,12 +11,14 @@ import com.badlogic.gdx.backends.lwjgl.LwjglClipboard; import forge.Forge; import forge.assets.AssetsDownloader; import forge.interfaces.IDeviceAdapter; +import forge.properties.ForgeConstants; import forge.util.FileUtil; import forge.util.Utils; public class Main { public static void main(String[] args) { String assetsDir = AssetsDownloader.SHARE_DESKTOP_ASSETS ? "../forge-gui/" : "testAssets/"; + ForgeConstants.init(assetsDir); if (!AssetsDownloader.SHARE_DESKTOP_ASSETS) { FileUtil.ensureDirectoryExists(assetsDir); } diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 5eaf12ae8d5..15c2b92fc49 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -17,6 +17,7 @@ import forge.assets.ImageCache; import forge.error.BugReporter; import forge.error.ExceptionHandler; import forge.interfaces.IDeviceAdapter; +import forge.interfaces.IGuiBase; import forge.model.FModel; import forge.properties.ForgeConstants; import forge.properties.ForgePreferences; @@ -49,7 +50,7 @@ public class Forge implements ApplicationListener { private static SplashScreen splashScreen; private static KeyInputAdapter keyInputAdapter; private static boolean exited; - private static final SoundSystem soundSystem = new SoundSystem(GuiBase.getInterface()); + private static SoundSystem soundSystem; private static final Stack screens = new Stack(); public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0) { @@ -57,7 +58,9 @@ public class Forge implements ApplicationListener { ForgeConstants.init(assetDir0); clipboard = clipboard0; deviceAdapter = deviceAdapter0; - GuiBase.setInterface(new GuiMobile()); + final IGuiBase gui = new GuiMobile(); + GuiBase.setInterface(gui); + soundSystem = new SoundSystem(gui); } return app; } diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index 86fb5ed6319..99aab8917d7 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -35,6 +35,7 @@ import forge.interfaces.IButton; import forge.interfaces.IGuiBase; import forge.item.PaperCard; import forge.match.input.InputQueue; +import forge.player.LobbyPlayerHuman; import forge.properties.ForgeConstants; import forge.screens.match.FControl; import forge.screens.match.views.VPlayerPanel; @@ -442,9 +443,10 @@ public class GuiMobile implements IGuiBase { Gdx.net.openURI(url); } + private final LobbyPlayer guiPlayer = new LobbyPlayerHuman("Human", this); @Override public LobbyPlayer getGuiPlayer() { - return FControl.getGuiPlayer(); + return guiPlayer; } @Override diff --git a/forge-gui-mobile/src/forge/screens/match/FControl.java b/forge-gui-mobile/src/forge/screens/match/FControl.java index 725a5f34636..424140589f1 100644 --- a/forge-gui-mobile/src/forge/screens/match/FControl.java +++ b/forge-gui-mobile/src/forge/screens/match/FControl.java @@ -54,7 +54,6 @@ import forge.game.zone.ZoneType; import forge.match.input.InputPlaybackControl; import forge.match.input.InputQueue; import forge.model.FModel; -import forge.player.LobbyPlayerHuman; import forge.player.PlayerControllerHuman; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; @@ -79,6 +78,7 @@ import forge.view.CardView.CardStateView; import forge.view.CombatView; import forge.view.GameEntityView; import forge.view.IGameView; +import forge.view.LocalGameView; import forge.view.PlayerView; public class FControl { @@ -247,14 +247,14 @@ public class FControl { } final PlayerControllerHuman humanController = (PlayerControllerHuman) localPlayer.getController(); - gameView = (IGameView) humanController; - fcVisitor = new FControlGameEventHandler(humanController); - playbackControl = new FControlGamePlayback(humanController); + final LocalGameView localGameView = humanController.getGameView(); + gameView = localGameView; + fcVisitor = new FControlGameEventHandler(GuiBase.getInterface(), localGameView); for (Player p : sortedPlayers) { playerPanels.add(new VPlayerPanel(humanController.getPlayerView(p))); } - view = new MatchScreen(gameView, humanController.getPlayerView(localPlayer), playerPanels); + view = new MatchScreen(localGameView, humanController.getPlayerView(localPlayer), playerPanels); } private static List shiftPlayersPlaceLocalFirst(final List players, Player localPlayer) { @@ -533,7 +533,9 @@ public class FControl { inputQueue.onGameOver(false); //release any waiting input, effectively passing priority } - playbackControl.onGameStopRequested(); + if (playbackControl != null) { + playbackControl.onGameStopRequested(); + } } public static void endCurrentGame() { @@ -852,9 +854,8 @@ public class FControl { return player; } - private final static LobbyPlayer guiPlayer = new LobbyPlayerHuman("Human", GuiBase.getInterface()); public final static LobbyPlayer getGuiPlayer() { - return guiPlayer; + return GuiBase.getInterface().getGuiPlayer(); } public static FImage getPlayerAvatar(final PlayerView p) { diff --git a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java index 97ce8606734..cfb6aca676c 100644 --- a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java @@ -22,6 +22,7 @@ import forge.game.event.GameEventAnteCardsSelected; import forge.game.event.GameEventAttackersDeclared; import forge.game.event.GameEventBlockersDeclared; import forge.game.event.GameEventCardAttachment; +import forge.game.event.GameEventCardChangeZone; import forge.game.event.GameEventCardCounters; import forge.game.event.GameEventCardDamaged; import forge.game.event.GameEventCardPhased; @@ -52,20 +53,21 @@ import forge.interfaces.IGuiBase; import forge.match.input.ButtonUtil; import forge.match.input.InputBase; import forge.model.FModel; -import forge.player.PlayerControllerHuman; import forge.properties.ForgePreferences.FPref; import forge.util.Lang; import forge.util.gui.SGuiChoose; import forge.util.maps.MapOfLists; import forge.view.CardView; +import forge.view.LocalGameView; import forge.view.PlayerView; public class FControlGameEventHandler extends IGameEventVisitor.Base { - private final PlayerControllerHuman controller; + private final IGuiBase gui; - public FControlGameEventHandler(final PlayerControllerHuman controller) { - this.controller = controller; - this.gui = controller.getGui(); + private final LocalGameView gameView; + public FControlGameEventHandler(final IGuiBase gui, final LocalGameView gameView) { + this.gui = gui; + this.gameView = gameView; } @Subscribe @@ -99,7 +101,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { @Override public void run() { combatUpdPlanned.set(false); - gui.showCombat(controller.getCombat()); + gui.showCombat(gameView.getCombat()); } }); return null; @@ -110,7 +112,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { public Void visit(final GameEventTurnBegan event) { if (FModel.getPreferences().getPrefBoolean(FPref.UI_STACK_CREATURES) && event.turnOwner != null) { // anything except stack will get here - updateZone(Pair.of(controller.getPlayerView(event.turnOwner), ZoneType.Battlefield)); + updateZone(Pair.of(gameView.getPlayerView(event.turnOwner), ZoneType.Battlefield)); } if (turnUpdPlanned.getAndSet(true)) { return null; } @@ -119,7 +121,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { @Override public void run() { turnUpdPlanned.set(false); - gui.updateTurn(controller.getPlayerView(event.turnOwner)); + gui.updateTurn(gameView.getPlayerView(event.turnOwner)); } }); return null; @@ -226,7 +228,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { public Void visit(GameEventZone event) { if (event.player != null) { // anything except stack will get here - updateZone(Pair.of(controller.getPlayerView(event.player), event.zoneType)); + updateZone(Pair.of(gameView.getPlayerView(event.player), event.zoneType)); } return null; } @@ -245,7 +247,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { } private Void updateZone(final Zone z) { - return updateZone(Pair.of(controller.getPlayerView(z.getPlayer()), z.getZoneType())); + return updateZone(Pair.of(gameView.getPlayerView(z.getPlayer()), z.getZoneType())); } private Void updateZone(final Pair kv) { @@ -275,29 +277,29 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { @Override public Void visit(final GameEventCardTapped event) { - return updateSingleCard(controller.getCardView(event.card)); + return updateSingleCard(gameView.getCardView(event.card)); } @Override public Void visit(final GameEventCardPhased event) { - return updateSingleCard(controller.getCardView(event.card)); + return updateSingleCard(gameView.getCardView(event.card)); } @Override public Void visit(final GameEventCardDamaged event) { - return updateSingleCard(controller.getCardView(event.card)); + return updateSingleCard(gameView.getCardView(event.card)); } @Override public Void visit(final GameEventCardCounters event) { - return updateSingleCard(controller.getCardView(event.card)); + return updateSingleCard(gameView.getCardView(event.card)); } @Override public Void visit(final GameEventBlockersDeclared event) { // This is to draw icons on blockers declared by AI for (MapOfLists kv : event.blockers.values()) { for (Collection blockers : kv.values()) { - updateManyCards(controller.getCardViews(blockers)); + updateManyCards(gameView.getCardViews(blockers)); } } return super.visit(event); @@ -312,7 +314,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { // Update all attackers. // Although they might have been updated when they were apped, there could be someone with vigilance, not redrawn yet. - updateManyCards(controller.getCardViews(event.attackersMap.values())); + updateManyCards(gameView.getCardViews(event.attackersMap.values())); return super.visit(event); } @@ -320,8 +322,8 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { @Override public Void visit(GameEventCombatEnded event) { // This should remove sword/shield icons from combatants by the time game moves to M2 - updateManyCards(controller.getCardViews(event.attackers)); - updateManyCards(controller.getCardViews(event.blockers)); + updateManyCards(gameView.getCardViews(event.attackers)); + updateManyCards(gameView.getCardViews(event.blockers)); return null; } @@ -351,12 +353,19 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { return null; } + @Override + public Void visit(GameEventCardChangeZone event) { + updateZone(event.from); + updateZone(event.to); + return null; + } + /* (non-Javadoc) * @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventCardStatsChanged) */ @Override public Void visit(GameEventCardStatsChanged event) { - final Iterable cardViews = controller.getCardViews(event.cards); + final Iterable cardViews = gameView.getCardViews(event.cards); gui.refreshCardDetails(cardViews); return updateManyCards(cardViews); } @@ -364,7 +373,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { @Override public Void visit(GameEventPlayerStatsChanged event) { for (final Player p : event.players) { - gui.refreshCardDetails(controller.getCardViews(p.getAllCards())); + gui.refreshCardDetails(gameView.getCardViews(p.getAllCards())); } return null; } @@ -380,7 +389,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { private final Runnable updManaPool = new Runnable() { @Override public void run() { synchronized (manaPoolUpdate) { - gui.updateManaPool(controller.getPlayerViews(manaPoolUpdate)); + gui.updateManaPool(gameView.getPlayerViews(manaPoolUpdate)); manaPoolUpdate.clear(); } } @@ -406,7 +415,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { private final Runnable updLives = new Runnable() { @Override public void run() { synchronized (livesUpdate) { - gui.updateLives(controller.getPlayerViews(livesUpdate)); + gui.updateLives(gameView.getPlayerViews(livesUpdate)); livesUpdate.clear(); } } diff --git a/forge-gui/src/main/java/forge/control/FControlGamePlayback.java b/forge-gui/src/main/java/forge/control/FControlGamePlayback.java index 988ea1f612b..60da2f16cd2 100644 --- a/forge-gui/src/main/java/forge/control/FControlGamePlayback.java +++ b/forge-gui/src/main/java/forge/control/FControlGamePlayback.java @@ -18,8 +18,9 @@ import forge.game.event.GameEventSpellAbilityCast; import forge.game.event.GameEventSpellResolved; import forge.game.event.GameEventTurnPhase; import forge.game.event.IGameEventVisitor; +import forge.interfaces.IGuiBase; import forge.match.input.InputPlaybackControl; -import forge.player.PlayerControllerHuman; +import forge.view.LocalGameView; public class FControlGamePlayback extends IGameEventVisitor.Base { private InputPlaybackControl inputPlayback; @@ -27,9 +28,11 @@ public class FControlGamePlayback extends IGameEventVisitor.Base { private final CyclicBarrier gameThreadPauser = new CyclicBarrier(2); - private final PlayerControllerHuman controller; - public FControlGamePlayback(final PlayerControllerHuman controller) { - this.controller = controller; + private final IGuiBase gui; + private final LocalGameView gameView; + public FControlGamePlayback(final IGuiBase gui, final LocalGameView gameView) { + this.gui = gui; + this.gameView = gameView; } private Game game; @@ -40,7 +43,7 @@ public class FControlGamePlayback extends IGameEventVisitor.Base { public void setGame(Game game) { this.game = game; - this.inputPlayback = new InputPlaybackControl(controller.getGui(), game, this); + this.inputPlayback = new InputPlaybackControl(gui, game, this); } @Subscribe @@ -73,8 +76,8 @@ public class FControlGamePlayback extends IGameEventVisitor.Base { */ @Override public Void visit(GameEventTurnPhase ev) { - boolean isUiToStop = controller.getGui().stopAtPhase( - controller.getPlayerView(ev.playerTurn), ev.phase); + boolean isUiToStop = gui.stopAtPhase( + gameView.getPlayerView(ev.playerTurn), ev.phase); switch(ev.phase) { case COMBAT_END: @@ -99,13 +102,13 @@ public class FControlGamePlayback extends IGameEventVisitor.Base { */ @Override public Void visit(GameEventGameFinished event) { - controller.getGui().getInputQueue().removeInput(inputPlayback); + gui.getInputQueue().removeInput(inputPlayback); return null; } @Override public Void visit(GameEventGameStarted event) { - controller.getGui().getInputQueue().setInput(inputPlayback); + gui.getInputQueue().setInput(inputPlayback); return null; } @@ -117,11 +120,10 @@ public class FControlGamePlayback extends IGameEventVisitor.Base { @Override public Void visit(final GameEventSpellResolved event) { - FThreads.invokeInEdtNowOrLater(controller.getGui(), new Runnable() { + FThreads.invokeInEdtNowOrLater(gui, new Runnable() { @Override public void run() { - controller.getGui().setCard( - controller.getCardView(event.spell.getHostCard())); + gui.setCard(gameView.getCardView(event.spell.getHostCard())); } }); pauseForEvent(resolveDelay); @@ -133,11 +135,10 @@ public class FControlGamePlayback extends IGameEventVisitor.Base { */ @Override public Void visit(final GameEventSpellAbilityCast event) { - FThreads.invokeInEdtNowOrLater(controller.getGui(), new Runnable() { + FThreads.invokeInEdtNowOrLater(gui, new Runnable() { @Override public void run() { - controller.getGui().setCard( - controller.getCardView(event.sa.getHostCard())); + gui.setCard(gameView.getCardView(event.sa.getHostCard())); } }); pauseForEvent(castDelay); 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 354888457bd..3107eae0460 100644 --- a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java +++ b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java @@ -55,7 +55,7 @@ public class InputPassPriority extends InputSyncronizedBase { public final void showMessage() { showMessage(getTurnPhasePriorityMessage(player.getGame())); chosenSa = null; - if (getController().canUndoLastAction()) { //allow undoing with cancel button if can undo last action + if (getController().getGameView().canUndoLastAction()) { //allow undoing with cancel button if can undo last action ButtonUtil.update(getGui(), "OK", "Undo", true, true, true); } else { //otherwise allow ending turn with cancel button @@ -77,7 +77,7 @@ public class InputPassPriority extends InputSyncronizedBase { /** {@inheritDoc} */ @Override protected final void onCancel() { - if (!getController().tryUndoLastAction()) { //undo if possible + if (!getController().getGameView().tryUndoLastAction()) { //undo if possible //otherwise end turn passPriority(new Runnable() { @Override diff --git a/forge-gui/src/main/java/forge/player/LobbyPlayerHuman.java b/forge-gui/src/main/java/forge/player/LobbyPlayerHuman.java index 5a1d36cde26..3b94542b0ba 100644 --- a/forge-gui/src/main/java/forge/player/LobbyPlayerHuman.java +++ b/forge-gui/src/main/java/forge/player/LobbyPlayerHuman.java @@ -21,13 +21,13 @@ public class LobbyPlayerHuman extends LobbyPlayer implements IGameEntitiesFactor @Override public PlayerController createControllerFor(Player human) { - return new PlayerControllerLocal(human.getGame(), human, this, gui); + return new PlayerControllerHuman(human.getGame(), human, this, gui); } - + @Override public Player createIngamePlayer(Game game) { Player player = new Player(GuiDisplayUtil.personalizeHuman(getName()), game); - player.setFirstController(new PlayerControllerLocal(game, player, this, gui)); + player.setFirstController(new PlayerControllerHuman(game, player, this, gui)); if (ForgePreferences.DEV_MODE && FModel.getPreferences().getPrefBoolean(FPref.DEV_UNLIMITED_LAND)) { player.canCheatPlayUnlimitedLands = true; diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index e1ca44edea7..4a2eb57849c 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1,138 +1,1580 @@ -package forge.player; +package forge.player; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +import forge.LobbyPlayer; +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostShard; +import forge.control.FControlGamePlayback; +import forge.deck.CardPool; +import forge.deck.Deck; +import forge.deck.DeckSection; +import forge.events.UiEventAttackerDeclared; +import forge.game.Game; +import forge.game.GameEntity; +import forge.game.GameLogEntryType; +import forge.game.GameObject; +import forge.game.GameOutcome; +import forge.game.GameType; +import forge.game.ability.effects.CharmEffect; +import forge.game.card.Card; +import forge.game.card.CardShields; +import forge.game.card.CounterType; +import forge.game.combat.Combat; +import forge.game.combat.CombatUtil; +import forge.game.cost.Cost; +import forge.game.cost.CostPart; +import forge.game.cost.CostPartMana; +import forge.game.mana.Mana; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.player.PlayerActionConfirmMode; +import forge.game.player.PlayerController; +import forge.game.replacement.ReplacementEffect; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.TargetChoices; +import forge.game.trigger.Trigger; +import forge.game.trigger.WrappedAbility; +import forge.game.zone.MagicStack; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.interfaces.IGuiBase; +import forge.item.PaperCard; +import forge.match.input.ButtonUtil; +import forge.match.input.Input; +import forge.match.input.InputAttack; +import forge.match.input.InputBase; +import forge.match.input.InputBlock; +import forge.match.input.InputConfirm; +import forge.match.input.InputConfirmMulligan; +import forge.match.input.InputPassPriority; +import forge.match.input.InputPayMana; +import forge.match.input.InputProliferate; +import forge.match.input.InputProxy; +import forge.match.input.InputSelectCardsForConvoke; +import forge.match.input.InputSelectCardsFromList; +import forge.match.input.InputSelectEntitiesFromList; +import forge.model.FModel; +import forge.properties.ForgePreferences.FPref; +import forge.util.DevModeUtil; +import forge.util.ITriggerEvent; +import forge.util.Lang; +import forge.util.TextUtil; +import forge.util.gui.SGuiChoose; +import forge.util.gui.SGuiDialog; +import forge.util.gui.SOptionPane; +import forge.view.CardView; +import forge.view.CombatView; +import forge.view.GameEntityView; +import forge.view.LocalGameView; +import forge.view.PlayerView; +import forge.view.SpellAbilityView; +import forge.view.StackItemView; + +/** + * A prototype for player controller class + * + * Handles phase skips for now. + */ +public class PlayerControllerHuman extends PlayerController { + + private final IGuiBase gui; + private final InputProxy inputProxy; + private final GameView gameView; + + public PlayerControllerHuman(final Game game0, final Player p, final LobbyPlayer lp, final IGuiBase gui) { + super(game0, p, lp); + this.gui = gui; + this.inputProxy = new InputProxy(this, game0); + this.gameView = new GameView(game0); + + // aggressively cache a view for each player (also caches cards) + for (final Player player : game.getRegisteredPlayers()) { + gameView.getPlayerView(player); + } + } + + public IGuiBase getGui() { + return gui; + } + + public InputProxy getInputProxy() { + return inputProxy; + } + + public LocalGameView getGameView() { + return gameView; + } + + public boolean isUiSetToSkipPhase(final Player turn, final PhaseType phase) { + return !getGui().stopAtPhase(gameView.getPlayerView(turn), phase); + } + + /** + * 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 choice = getGui().getAbilityToPlay(gameView.getSpellAbilityViews(abilities), triggerEvent); + return gameView.getSpellAbility(choice); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#mayPlaySpellAbilityForFree(forge.card.spellability.SpellAbility) + */ + @Override + public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChoseNewTargets) { + HumanPlay.playSaWithoutPayingManaCost(this, player.getGame(), copySA, mayChoseNewTargets); + } + + @Override + public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) { + HumanPlay.playSpellAbilityNoStack(this, player, effectSA, !canSetupTargets); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#sideboard(forge.deck.Deck) + */ + @Override + public List sideboard(Deck deck, GameType gameType) { + CardPool sideboard = deck.get(DeckSection.Sideboard); + if (sideboard == null) { + // Use an empty cardpool instead of null for 75/0 sideboarding scenario. + sideboard = new CardPool(); + } + + CardPool main = deck.get(DeckSection.Main); + + int mainSize = main.countAll(); + int sbSize = sideboard.countAll(); + int combinedDeckSize = mainSize + sbSize; + + int deckMinSize = Math.min(mainSize, gameType.getDeckFormat().getMainRange().getMinimum()); + Range sbRange = gameType.getDeckFormat().getSideRange(); + // Limited doesn't have a sideboard max, so let the Main min take care of things. + int sbMax = sbRange == null ? combinedDeckSize : sbRange.getMaximum(); + + List newMain = null; + + //Skip sideboard loop if there are no sideboarding opportunities + if (sbSize == 0 && mainSize == deckMinSize) { return null; } + + // conformance should not be checked here + boolean conform = FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY); + do { + if (newMain != null) { + String errMsg; + if (newMain.size() < deckMinSize) { + errMsg = String.format("Too few cards in your main deck (minimum %d), please make modifications to your deck again.", deckMinSize); + } + else { + errMsg = String.format("Too many cards in your sideboard (maximum %d), please make modifications to your deck again.", sbMax); + } + SOptionPane.showErrorDialog(getGui(), errMsg, "Invalid Deck"); + } + // Sideboard rules have changed for M14, just need to consider min maindeck and max sideboard sizes + // No longer need 1:1 sideboarding in non-limited formats + newMain = getGui().sideboard(sideboard, main); + } while (conform && (newMain.size() < deckMinSize || combinedDeckSize - newMain.size() > sbMax)); + + return newMain; + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#assignCombatDamage() + */ + @Override + public Map assignCombatDamage(final Card attacker, + final List blockers, final int damageDealt, + final GameEntity defender, final boolean overrideOrder) { + // Attacker is a poor name here, since the creature assigning damage + // could just as easily be the blocker. + final Map map = Maps.newHashMap(); + if (defender != null && assignDamageAsIfNotBlocked(attacker)) { + map.put(null, damageDealt); + } else { + final List vBlockers = gameView.getCardViews(blockers); + if ((attacker.hasKeyword("Trample") && defender != null) || (blockers.size() > 1)) { + final CardView vAttacker = gameView.getCardView(attacker); + final GameEntityView vDefender = gameView.getGameEntityView(defender); + final Map result = getGui().getDamageToAssign(vAttacker, vBlockers, damageDealt, vDefender, overrideOrder); + for (final Entry e : result.entrySet()) { + map.put(gameView.getCard(e.getKey()), e.getValue()); + } + } else { + map.put(blockers.get(0), damageDealt); + } + } + return map; + } + + private final boolean assignDamageAsIfNotBlocked(final Card attacker) { + return attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.") + || (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") + && SGuiDialog.confirm(getGui(), gameView.getCardView(attacker), "Do you want to assign its combat damage as though it weren't blocked?")); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#announceRequirements(java.lang.String) + */ + @Override + public Integer announceRequirements(SpellAbility ability, String announce, boolean canChooseZero) { + int min = canChooseZero ? 0 : 1; + return SGuiChoose.getInteger(getGui(), "Choose " + announce + " for " + ability.getHostCard().getName(), + min, Integer.MAX_VALUE, min + 9); + } + + @Override + public List choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List valid, String message) { + String outerMessage = "Select %d " + message + "(s) to sacrifice"; + return choosePermanentsTo(min, max, valid, outerMessage); + } + + @Override + public List choosePermanentsToDestroy(SpellAbility sa, int min, int max, List valid, String message) { + String outerMessage = "Select %d " + message + "(s) to be destroyed"; + return choosePermanentsTo(min, max, valid, outerMessage); + } + + private List choosePermanentsTo(int min, int max, List valid, String outerMessage) { + max = Math.min(max, valid.size()); + if (max <= 0) { + return new ArrayList(); + } + + InputSelectCardsFromList inp = new InputSelectCardsFromList(this, min == 0 ? 1 : min, max, valid); + inp.setMessage(outerMessage); + inp.setCancelAllowed(min == 0); + inp.showAndWait(); + return Lists.newArrayList(inp.getSelected()); + } + + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseCardsForEffect(java.util.Collection, forge.card.spellability.SpellAbility, java.lang.String, int, boolean) + */ + @Override + public List chooseCardsForEffect(List sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) { + // If only one card to choose, use a dialog box. + // Otherwise, use the order dialog to be able to grab multiple cards in one shot + if (max == 1) { + Card singleChosen = chooseSingleEntityForEffect(sourceList, sa, title, isOptional); + return singleChosen == null ? Lists.newArrayList() : Lists.newArrayList(singleChosen); + } + + getGui().setPanelSelection(gameView.getCardView(sa.getHostCard())); + + // try to use InputSelectCardsFromList when possible + boolean cardsAreInMyHandOrBattlefield = true; + for(Card c : sourceList) { + Zone z = c.getZone(); + if (z != null && (z.is(ZoneType.Battlefield) || z.is(ZoneType.Hand, player))) + continue; + cardsAreInMyHandOrBattlefield = false; + break; + } + + if(cardsAreInMyHandOrBattlefield) { + InputSelectCardsFromList sc = new InputSelectCardsFromList(this, min, max, sourceList); + sc.setMessage(title); + sc.setCancelAllowed(isOptional); + sc.showAndWait(); + return Lists.newArrayList(sc.getSelected()); + } + + return SGuiChoose.many(getGui(), title, "Chosen Cards", min, max, sourceList, gameView.getCardView(sa.getHostCard())); + } + + @Override + public T chooseSingleEntityForEffect(Collection options, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) { + // Human is supposed to read the message and understand from it what to choose + if (options.isEmpty()) { + return null; + } + if (!isOptional && options.size() == 1) { + return Iterables.getFirst(options, null); + } + + boolean canUseSelectCardsInput = true; + for (GameEntity c : options) { + if (c instanceof Player) + continue; + Zone cz = ((Card)c).getZone(); + // can point at cards in own hand and anyone's battlefield + boolean canUiPointAtCards = cz != null && (cz.is(ZoneType.Hand) && cz.getPlayer() == player || cz.is(ZoneType.Battlefield)); + if (!canUiPointAtCards) { + canUseSelectCardsInput = false; + break; + } + } + + if (canUseSelectCardsInput) { + InputSelectEntitiesFromList input = new InputSelectEntitiesFromList(this, isOptional ? 0 : 1, 1, options); + input.setCancelAllowed(isOptional); + input.setMessage(formatMessage(title, targetedPlayer)); + input.showAndWait(); + return Iterables.getFirst(input.getSelected(), null); + } + + return isOptional ? SGuiChoose.oneOrNone(getGui(), title, options) : SGuiChoose.one(getGui(), title, options); + } + + @Override + public int chooseNumber(SpellAbility sa, String title, int min, int max) { + final Integer[] choices = new Integer[max + 1 - min]; + for (int i = 0; i <= max - min; i++) { + choices[i] = Integer.valueOf(i + min); + } + return SGuiChoose.one(getGui(), title, choices).intValue(); + } + + @Override + public int chooseNumber(SpellAbility sa, String title, List choices, Player relatedPlayer) { + return SGuiChoose.one(getGui(), title, choices).intValue(); + } + + @Override + public SpellAbility chooseSingleSpellForEffect(java.util.List spells, SpellAbility sa, String title) { + // Human is supposed to read the message and understand from it what to choose + return spells.size() < 2 ? spells.get(0) : SGuiChoose.one(getGui(), title, spells); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#confirmAction(forge.card.spellability.SpellAbility, java.lang.String, java.lang.String) + */ + @Override + public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { + return SGuiDialog.confirm(getGui(), gameView.getCardView(sa.getHostCard()), message); + } + + @Override + public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode bidlife, + String string, int bid, Player winner) { + return SGuiDialog.confirm(getGui(), gameView.getCardView(sa.getHostCard()), string + " Highest Bidder " + winner); + } + + @Override + public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) { + return SGuiDialog.confirm(getGui(), gameView.getCardView(hostCard), message); + } + + @Override + public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map triggerParams, boolean isMandatory) { + if (this.shouldAlwaysAcceptTrigger(regtrig.getId())) { + return true; + } + if (this.shouldAlwaysDeclineTrigger(regtrig.getId())) { + return false; + } + + final StringBuilder buildQuestion = new StringBuilder("Use triggered ability of "); + buildQuestion.append(regtrig.getHostCard().toString()).append("?"); + if (!FModel.getPreferences().getPrefBoolean(FPref.UI_COMPACT_PROMPT)) { + //append trigger description unless prompt is compact + buildQuestion.append("\n("); + buildQuestion.append(triggerParams.get("TriggerDescription").replace("CARDNAME", regtrig.getHostCard().getName())); + buildQuestion.append(")"); + } + HashMap tos = sa.getTriggeringObjects(); + if (tos.containsKey("Attacker")) { + buildQuestion.append("\nAttacker: " + tos.get("Attacker")); + } + if (tos.containsKey("Card")) { + Card card = (Card) tos.get("Card"); + if (card != null && (card.getController() == player || game.getZoneOf(card) == null + || game.getZoneOf(card).getZoneType().isKnown())) { + buildQuestion.append("\nTriggered by: " + tos.get("Card")); + } + } + + InputConfirm inp = new InputConfirm(this, buildQuestion.toString()); + inp.showAndWait(); + return inp.getResult(); + } + + @Override + public Player chooseStartingPlayer(boolean isFirstGame) { + if (game.getPlayers().size() == 2) { + final String prompt = String.format("%s, you %s\n\nWould you like to play or draw?", + player.getName(), isFirstGame ? " have won the coin toss." : " lost the last game."); + final InputConfirm inp = new InputConfirm(this, prompt, "Play", "Draw"); + inp.showAndWait(); + return inp.getResult() ? this.player : this.player.getOpponents().get(0); + } else { + final String prompt = String.format("%s, you %s\n\nWho would you like to start this game?", + player.getName(), isFirstGame ? " have won the coin toss." : " lost the last game."); + final InputSelectEntitiesFromList input = new InputSelectEntitiesFromList<>(this, 1, 1, game.getPlayersInTurnOrder()); + input.setMessage(prompt); + input.showAndWait(); + return input.getFirstSelected(); + } + } + + @Override + public List orderBlockers(final Card attacker, final List blockers) { + final CardView vAttacker = gameView.getCardView(attacker); + getGui().setPanelSelection(vAttacker); + final List choices = SGuiChoose.order(getGui(), "Choose Damage Order for " + vAttacker, "Damaged First", gameView.getCardViews(blockers), vAttacker); + return gameView.getCards(choices); + } + + @Override + public List orderBlocker(final Card attacker, final Card blocker, final List oldBlockers) { + final CardView vAttacker = gameView.getCardView(attacker); + getGui().setPanelSelection(vAttacker); + final List choices = SGuiChoose.insertInList(getGui(), "Choose blocker after which to place " + vAttacker + " in damage order; cancel to place it first", gameView.getCardView(blocker), gameView.getCardViews(oldBlockers)); + return gameView.getCards(choices); + } + + @Override + public List orderAttackers(final Card blocker, final List attackers) { + final CardView vBlocker = gameView.getCardView(blocker); + getGui().setPanelSelection(vBlocker); + final List choices = SGuiChoose.order(getGui(), "Choose Damage Order for " + vBlocker, "Damaged First", gameView.getCardViews(attackers), vBlocker); + return gameView.getCards(choices); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#reveal(java.lang.String, java.util.List, forge.game.zone.ZoneType, forge.game.player.Player) + */ + @Override + public void reveal(Collection cards, ZoneType zone, Player owner, String message) { + if (StringUtils.isBlank(message)) { + message = "Looking at cards in {player's} " + zone.name().toLowerCase(); + } + else { + message += "{player's} " + zone.name().toLowerCase(); + } + String fm = formatMessage(message, owner); + if (!cards.isEmpty()) { + SGuiChoose.reveal(getGui(), fm, cards); + } + else { + SGuiDialog.message(getGui(), formatMessage("There are no cards in {player's} " + + zone.name().toLowerCase(), owner), fm); + } + } + + @Override + public ImmutablePair, List> arrangeForScry(List topN) { + List toBottom = null; + List toTop = null; + + if (topN.size() == 1) { + if (willPutCardOnTop(topN.get(0))) { + toTop = topN; + } + else { + toBottom = topN; + } + } + else { + toBottom = SGuiChoose.many(getGui(), "Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, topN, null); + topN.removeAll(toBottom); + if (topN.isEmpty()) { + toTop = null; + } + else if (topN.size() == 1) { + toTop = topN; + } + else { + toTop = SGuiChoose.order(getGui(), "Arrange cards to be put on top of your library", "Cards arranged", topN, null); + } + } + return ImmutablePair.of(toTop, toBottom); + } + + @Override + public boolean willPutCardOnTop(final Card c) { + final PaperCard pc = FModel.getMagicDb().getCommonCards().getCard(c.getName()); + final Card c1 = (pc != null ? Card.fromPaperCard(pc, null) : c); + final CardView view = gameView.getCardView(c1); + return SGuiDialog.confirm(getGui(), view, "Put " + view + " on the top or bottom of your library?", new String[]{"Top", "Bottom"}); + } + + @Override + public List orderMoveToZoneList(List cards, ZoneType destinationZone) { + switch (destinationZone) { + case Library: + return SGuiChoose.order(getGui(), "Choose order of cards to put into the library", "Closest to top", cards, null); + case Battlefield: + return SGuiChoose.order(getGui(), "Choose order of cards to put onto the battlefield", "Put first", cards, null); + case Graveyard: + return SGuiChoose.order(getGui(), "Choose order of cards to put into the graveyard", "Closest to bottom", cards, null); + case PlanarDeck: + return SGuiChoose.order(getGui(), "Choose order of cards to put into the planar deck", "Closest to top", cards, null); + case SchemeDeck: + return SGuiChoose.order(getGui(), "Choose order of cards to put into the scheme deck", "Closest to top", cards, null); + case Stack: + return SGuiChoose.order(getGui(), "Choose order of copies to cast", "Put first", cards, null); + default: + System.out.println("ZoneType " + destinationZone + " - Not Ordered"); + break; + } + return cards; + } + + @Override + public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List valid, int min, int max) { + if (p != player) { + return SGuiChoose.many(getGui(), "Choose " + min + " card" + (min != 1 ? "s" : "") + " to discard", + "Discarded", min, min, valid, null); + } + + InputSelectCardsFromList inp = new InputSelectCardsFromList(this, min, max, valid); + inp.setMessage(sa.hasParam("AnyNumber") ? "Discard up to %d card(s)" : "Discard %d card(s)"); + inp.showAndWait(); + return Lists.newArrayList(inp.getSelected()); + } + + @Override + public void playMiracle(final SpellAbility miracle, final Card card) { + final CardView view = gameView.getCardView(card); + if (SGuiDialog.confirm(getGui(), view, view + " - Drawn. Play for Miracle Cost?")) { + HumanPlay.playSpellAbility(this, player, miracle); + } + } + + @Override + public List chooseCardsToDelve(int colorLessAmount, List grave) { + List toExile = new ArrayList(); + int cardsInGrave = grave.size(); + final Integer[] cntChoice = new Integer[cardsInGrave + 1]; + for (int i = 0; i <= cardsInGrave; i++) { + cntChoice[i] = Integer.valueOf(i); + } + + final Integer chosenAmount = SGuiChoose.one(getGui(), "Exile how many cards?", cntChoice); + System.out.println("Delve for " + chosenAmount); + + for (int i = 0; i < chosenAmount; i++) { + final Card nowChosen = SGuiChoose.oneOrNone(getGui(), "Exile which card?", grave); + + if (nowChosen == null) { + // User canceled,abort delving. + toExile.clear(); + break; + } + + grave.remove(nowChosen); + toExile.add(nowChosen); + } + return toExile; + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseTargets(forge.card.spellability.SpellAbility, forge.card.spellability.SpellAbilityStackInstance) + */ + @Override + public TargetChoices chooseNewTargetsFor(SpellAbility ability) { + SpellAbility sa = ability.isWrapper() ? ((WrappedAbility) ability).getWrappedAbility() : ability; + if (sa.getTargetRestrictions() == null) { + return null; + } + TargetChoices oldTarget = sa.getTargets(); + TargetSelection select = new TargetSelection(this, sa); + sa.resetTargets(); + if (select.chooseTargets(oldTarget.getNumTargeted())) { + return sa.getTargets(); + } + else { + // Return old target, since we had to reset them above + return oldTarget; + } + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseCardsToDiscardUnlessType(int, java.lang.String, forge.card.spellability.SpellAbility) + */ + @Override + public List chooseCardsToDiscardUnlessType(int num, List hand, final String uType, SpellAbility sa) { + final InputSelectEntitiesFromList target = new InputSelectEntitiesFromList(this, num, num, hand) { + private static final long serialVersionUID = -5774108410928795591L; + + @Override + protected boolean hasAllTargets() { + for (Card c : selected) { + if (c.isType(uType)) { + return true; + } + } + return super.hasAllTargets(); + } + }; + target.setMessage("Select %d card(s) to discard, unless you discard a " + uType + "."); + target.showAndWait(); + return Lists.newArrayList(target.getSelected()); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseManaFromPool(java.util.List) + */ + @Override + public Mana chooseManaFromPool(List manaChoices) { + List options = new ArrayList(); + for (int i = 0; i < manaChoices.size(); i++) { + Mana m = manaChoices.get(i); + options.add(String.format("%d. %s mana from %s", 1+i, MagicColor.toLongString(m.getColor()), m.getSourceCard())); + } + String chosen = SGuiChoose.one(getGui(), "Pay Mana from Mana Pool", options); + String idx = TextUtil.split(chosen, '.')[0]; + return manaChoices.get(Integer.parseInt(idx)-1); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseSomeType(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.String) + */ + @Override + public String chooseSomeType(final String kindOfType, final SpellAbility sa, final List validTypes, List invalidTypes, final boolean isOptional) { + final List types = Lists.newArrayList(validTypes); + if (invalidTypes != null && !invalidTypes.isEmpty()) { + Iterables.removeAll(types, invalidTypes); + } + if(isOptional) + return SGuiChoose.oneOrNone(getGui(), "Choose a " + kindOfType.toLowerCase() + " type", types); + else + return SGuiChoose.one(getGui(), "Choose a " + kindOfType.toLowerCase() + " type", types); + } + + @Override + public Object vote(SpellAbility sa, String prompt, List options, ArrayListMultimap votes) { + return SGuiChoose.one(getGui(), prompt, options); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#confirmReplacementEffect(forge.card.replacement.ReplacementEffect, forge.card.spellability.SpellAbility, java.lang.String) + */ + @Override + public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) { + return SGuiDialog.confirm(getGui(), gameView.getCardView(replacementEffect.getHostCard()), question); + } + + @Override + public List getCardsToMulligan(boolean isCommander, Player firstPlayer) { + final InputConfirmMulligan inp = new InputConfirmMulligan(this, player, firstPlayer, isCommander); + inp.showAndWait(); + return inp.isKeepHand() ? null : isCommander ? inp.getSelectedCards() : player.getCardsIn(ZoneType.Hand); + } + + @Override + public void declareAttackers(Player attackingPlayer, Combat combat) { + if (mayAutoPass()) { + List> mandatoryAttackers = CombatUtil.getMandatoryAttackers(attackingPlayer, combat, combat.getDefenders()); + if (!mandatoryAttackers.isEmpty()) { + //even if auto-passing attack phase, if there are any mandatory attackers, + //ensure they're declared and then delay slightly so user can see as much + for (Pair attacker : mandatoryAttackers) { + combat.addAttacker(attacker.getLeft(), attacker.getRight()); + getGui().fireEvent(new UiEventAttackerDeclared(gameView.getCardView(attacker.getLeft()), gameView.getGameEntityView(attacker.getRight()))); + } + try { + Thread.sleep(FControlGamePlayback.combatDelay); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + return; //don't prompt to declare attackers if user chose to end the turn + } + + // This input should not modify combat object itself, but should return user choice + final InputAttack inpAttack = new InputAttack(this, attackingPlayer, combat); + inpAttack.showAndWait(); + } + + @Override + public void declareBlockers(Player defender, Combat combat) { + // This input should not modify combat object itself, but should return user choice + final InputBlock inpBlock = new InputBlock(this, defender, combat); + inpBlock.showAndWait(); + updateAutoPassPrompt(); + } + + public void updateAutoPassPrompt() { + if (mayAutoPass()) { + //allow user to cancel auto-pass + InputBase.cancelAwaitNextInput(); //don't overwrite prompt with awaiting opponent + PhaseType phase = getAutoPassUntilPhase(); + getGui().showPromptMessage("Yielding until " + (phase == PhaseType.CLEANUP ? "end of turn" : phase.nameForUi.toString()) + + ".\nYou may cancel this yield to take an action."); + ButtonUtil.update(getGui(), false, true, false); + } + } + + @Override + public void autoPassUntilEndOfTurn() { + super.autoPassUntilEndOfTurn(); + updateAutoPassPrompt(); + } + + @Override + public void autoPassCancel() { + if (getAutoPassUntilPhase() == null) { return; } + super.autoPassCancel(); + + //prevent prompt getting stuck on yielding message while actually waiting for next input opportunity + getGui().showPromptMessage(""); + ButtonUtil.update(getGui(), false, false, false); + InputBase.awaitNextInput(getGui()); + } + + @Override + public SpellAbility chooseSpellAbilityToPlay() { + MagicStack stack = game.getStack(); + + if (mayAutoPass()) { + //avoid prompting for input if current phase is set to be auto-passed + //instead posing a short delay if needed to prevent the game jumping ahead too quick + int delay = 0; + if (stack.isEmpty()) { + //make sure to briefly pause at phases you're not set up to skip + if (!isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), game.getPhaseHandler().getPhase())) { + delay = FControlGamePlayback.phasesDelay; + } + } + else { + //pause slightly longer for spells and abilities on the stack resolving + delay = FControlGamePlayback.resolveDelay; + } + if (delay > 0) { + try { + Thread.sleep(delay); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + return null; + } + + if (stack.isEmpty()) { + if (isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), game.getPhaseHandler().getPhase())) { + return null; //avoid prompt for input if stack is empty and player is set to skip the current phase + } + } + else if (!game.getDisableAutoYields()) { + SpellAbility ability = stack.peekAbility(); + if (ability != null && ability.isAbility() && shouldAutoYield(ability.toUnsuppressedString())) { + //avoid prompt for input if top ability of stack is set to auto-yield + try { + Thread.sleep(FControlGamePlayback.resolveDelay); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + return null; + } + } + + InputPassPriority defaultInput = new InputPassPriority(this, player); + defaultInput.showAndWait(); + return defaultInput.getChosenSa(); + } + + @Override + public void playChosenSpellAbility(SpellAbility chosenSa) { + HumanPlay.playSpellAbility(this, player, chosenSa); + } + + @Override + public List chooseCardsToDiscardToMaximumHandSize(int nDiscard) { + final int max = player.getMaxHandSize(); + + InputSelectCardsFromList inp = new InputSelectCardsFromList(this, nDiscard, nDiscard, player.getZone(ZoneType.Hand).getCards()); + String message = "Cleanup Phase\nSelect " + nDiscard + " card" + (nDiscard > 1 ? "s" : "") + + " to discard to bring your hand down to the maximum of " + max + " cards."; + inp.setMessage(message); + inp.setCancelAllowed(false); + inp.showAndWait(); + return Lists.newArrayList(inp.getSelected()); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseCardsToRevealFromHand(int, int, java.util.List) + */ + @Override + public List chooseCardsToRevealFromHand(int min, int max, List valid) { + max = Math.min(max, valid.size()); + min = Math.min(min, max); + InputSelectCardsFromList inp = new InputSelectCardsFromList(this, min, max, valid); + inp.setMessage("Choose Which Cards to Reveal"); + inp.showAndWait(); + return Lists.newArrayList(inp.getSelected()); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#payManaOptional(forge.Card, forge.card.cost.Cost) + */ + @Override + public boolean payManaOptional(Card c, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) { + if (sa == null && cost.isOnlyManaCost() && cost.getTotalMana().isZero() + && !FModel.getPreferences().getPrefBoolean(FPref.MATCHPREF_PROMPT_FREE_BLOCKS)) + return true; + + return HumanPlay.payCostDuringAbilityResolve(this, player, c, cost, sa, prompt); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseSaToActivateFromOpeningHand(java.util.List) + */ + @Override + public List chooseSaToActivateFromOpeningHand(List usableFromOpeningHand) { + List srcCards = new ArrayList(); + for (SpellAbility sa : usableFromOpeningHand) { + srcCards.add(sa.getHostCard()); + } + List result = new ArrayList(); + if (srcCards.isEmpty()) { + return result; + } + List chosen = SGuiChoose.many(getGui(), "Choose cards to activate from opening hand and their order", "Activate first", -1, srcCards, null); + for (Card c : chosen) { + for (SpellAbility sa : usableFromOpeningHand) { + if (sa.getHostCard() == c) { + result.add(sa); + break; + } + } + } + return result; + } + + // end of not related candidates for move. + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseBinary(java.lang.String, boolean) + */ + @Override + public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultVal) { + String[] labels = new String[]{"Option1", "Option2"}; + switch(kindOfChoice) { + case HeadsOrTails: labels = new String[]{"Heads", "Tails"}; break; + case TapOrUntap: labels = new String[]{"Tap", "Untap"}; break; + case OddsOrEvens: labels = new String[]{"Odds", "Evens"}; break; + case UntapOrLeaveTapped: labels = new String[]{"Untap", "Leave tapped"}; break; + case UntapTimeVault: labels = new String[]{"Untap (and skip this turn)", "Leave tapped"}; break; + case PlayOrDraw: labels = new String[]{"Play", "Draw"}; break; + default: labels = kindOfChoice.toString().split("Or"); + + } + return SGuiDialog.confirm(getGui(), gameView.getCardView(sa.getHostCard()), question, defaultVal == null || defaultVal.booleanValue(), labels); + } + + @Override + public boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call) { + String[] labelsSrc = call ? new String[]{"heads", "tails"} : new String[]{"win the flip", "lose the flip"}; + String[] strResults = new String[results.length]; + for (int i = 0; i < results.length; i++) { + strResults[i] = labelsSrc[results[i] ? 0 : 1]; + } + return SGuiChoose.one(getGui(), sa.getHostCard().getName() + " - Choose a result", strResults) == labelsSrc[0]; + } + + @Override + public Card chooseProtectionShield(GameEntity entityBeingDamaged, List options, Map choiceMap) { + String title = entityBeingDamaged + " - select which prevention shield to use"; + return choiceMap.get(SGuiChoose.one(getGui(), title, options)); + } + + @Override + public Pair chooseAndRemoveOrPutCounter(Card cardWithCounter) { + if (!cardWithCounter.hasCounters()) { + System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier"); + return null; + } + + String counterChoiceTitle = "Choose a counter type on " + cardWithCounter; + final CounterType chosen = SGuiChoose.one(getGui(), counterChoiceTitle, cardWithCounter.getCounters().keySet()); + + String putOrRemoveTitle = "What to do with that '" + chosen.getName() + "' counter "; + final String putString = "Put another " + chosen.getName() + " counter on " + cardWithCounter; + final String removeString = "Remove a " + chosen.getName() + " counter from " + cardWithCounter; + final String addOrRemove = SGuiChoose.one(getGui(), putOrRemoveTitle, new String[]{putString,removeString}); + + return new ImmutablePair(chosen,addOrRemove); + } + + @Override + public Pair chooseTarget(SpellAbility saSpellskite, List> allTargets) { + if (allTargets.size() < 2) { + return Iterables.getFirst(allTargets, null); + } + + final Function, String> fnToString = new Function, String>() { + @Override + public String apply(Pair targ) { + return targ.getRight().toString() + " - " + targ.getLeft().getStackDescription(); + } + }; + + List> chosen = SGuiChoose.getChoices(getGui(), saSpellskite.getHostCard().getName(), 1, 1, allTargets, null, fnToString); + return Iterables.getFirst(chosen, null); + } + + @Override + public void notifyOfValue(SpellAbility sa, GameObject realtedTarget, String value) { + String message = formatNotificationMessage(sa, realtedTarget, value); + if (sa.isManaAbility()) { + game.getGameLog().add(GameLogEntryType.LAND, message); + } else { + SGuiDialog.message(getGui(), message, sa.getHostCard() == null ? "" : sa.getHostCard().getName()); + } + } + + private String formatMessage(String message, Object related) { + if(related instanceof Player && message.indexOf("{player") >= 0) + message = message.replace("{player}", mayBeYou(related)).replace("{player's}", Lang.getPossesive(mayBeYou(related))); + + return message; + } + + // These are not much related to PlayerController + private String formatNotificationMessage(SpellAbility sa, GameObject target, String value) { + if (sa.getApi() == null || sa.getHostCard() == null) { + return ("Result: " + value); + } + switch(sa.getApi()) { + case ChooseDirection: + return value; + case ChooseNumber: + if (sa.hasParam("SecretlyChoose")) { + return value; + } + final boolean random = sa.hasParam("Random"); + return String.format(random ? "Randomly chosen number for %s is %s" : "%s choses number: %s", mayBeYou(target), value); + case FlipACoin: + String flipper = StringUtils.capitalize(mayBeYou(target)); + return sa.hasParam("NoCall") + ? String.format("%s flip comes up %s", Lang.getPossesive(flipper), value) + : String.format("%s %s the flip", flipper, Lang.joinVerb(flipper, value)); + case Protection: + String choser = StringUtils.capitalize(mayBeYou(target)); + return String.format("%s %s protection from %s", choser, Lang.joinVerb(choser, "choose"), value); + case Vote: + String chooser = StringUtils.capitalize(mayBeYou(target)); + return String.format("%s %s %s", chooser, Lang.joinVerb(chooser, "vote"), value); + default: + return String.format("%s effect's value for %s is %s", sa.getHostCard().getName(), mayBeYou(target), value); + } + } + + private String mayBeYou(Object what) { + return what == null ? "(null)" : what == player ? "you" : what.toString(); + } + + // end of not related candidates for move. + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseModeForAbility(forge.card.spellability.SpellAbility, java.util.List, int, int) + */ + @Override + public List chooseModeForAbility(SpellAbility sa, int min, int num) { + List choices = CharmEffect.makePossibleOptions(sa); + String modeTitle = String.format("%s activated %s - Choose a mode", sa.getActivatingPlayer(), sa.getHostCard()); + List chosen = new ArrayList(); + for (int i = 0; i < num; i++) { + AbilitySub a; + if (i < min) { + a = SGuiChoose.one(getGui(), modeTitle, choices); + } + else { + a = SGuiChoose.oneOrNone(getGui(), modeTitle, choices); + } + if (null == a) { + break; + } + + choices.remove(a); + chosen.add(a); + } + return chosen; + } + + @Override + public List chooseColors(String message, SpellAbility sa, int min, int max, List options) { + return SGuiChoose.getChoices(getGui(), message, min, max, options); + } + + @Override + public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { + int cntColors = colors.countColors(); + switch (cntColors) { + case 0: return 0; + case 1: return colors.getColor(); + default: return chooseColorCommon(message, sa == null ? null : sa.getHostCard(), colors, false); + } + } + + @Override + public byte chooseColorAllowColorless(String message, Card c, ColorSet colors) { + int cntColors = 1 + colors.countColors(); + switch (cntColors) { + case 1: return 0; + default: return chooseColorCommon(message, c, colors, true); + } + } + + private byte chooseColorCommon(String message, Card c, ColorSet colors, boolean withColorless) { + int cntColors = colors.countColors(); + if(withColorless) cntColors++; + String[] colorNames = new String[cntColors]; + int i = 0; + if(withColorless) + colorNames[i++] = MagicColor.toLongString((byte)0); + for (byte b : colors) { + colorNames[i++] = MagicColor.toLongString(b); + } + if (colorNames.length > 2) { + return MagicColor.fromName(SGuiChoose.one(getGui(), message, colorNames)); + } + int idxChosen = SGuiDialog.confirm(getGui(), gameView.getCardView(c), message, colorNames) ? 0 : 1; + return MagicColor.fromName(colorNames[idxChosen]); + } + + @Override + public PaperCard chooseSinglePaperCard(SpellAbility sa, String message, Predicate cpp, String name) { + Iterable cardsFromDb = FModel.getMagicDb().getCommonCards().getUniqueCards(); + List cards = Lists.newArrayList(Iterables.filter(cardsFromDb, cpp)); + Collections.sort(cards); + return SGuiChoose.one(getGui(), message, cards); + } + + @Override + public CounterType chooseCounterType(Collection options, SpellAbility sa, String prompt) { + if (options.size() <= 1) { + return Iterables.getFirst(options, null); + } + return SGuiChoose.one(getGui(), prompt, options); + } + + @Override + public boolean confirmPayment(CostPart costPart, String question) { + InputConfirm inp = new InputConfirm(this, question); + inp.showAndWait(); + return inp.getResult(); + } + + @Override + public ReplacementEffect chooseSingleReplacementEffect(String prompt, List possibleReplacers, HashMap runParams) { + if(possibleReplacers.size() == 1) + return possibleReplacers.get(0); + return SGuiChoose.one(getGui(), prompt, possibleReplacers); + } + + @Override + public String chooseProtectionType(String string, SpellAbility sa, List choices) { + return SGuiChoose.one(getGui(), string, choices); + } + + @Override + public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, List allPayers) { + // if it's paid by the AI already the human can pay, but it won't change anything + return HumanPlay.payCostDuringAbilityResolve(this, player, sa.getHostCard(), cost, sa, null); + } + + @Override + public void orderAndPlaySimultaneousSa(List activePlayerSAs) { + List orderedSAs = activePlayerSAs; + if (activePlayerSAs.size() > 1) { // give a dual list form to create instead of needing to do it one at a time + orderedSAs = SGuiChoose.order(getGui(), "Select order for Simultaneous Spell Abilities", "Resolve first", activePlayerSAs, null); + } + int size = orderedSAs.size(); + for (int i = size - 1; i >= 0; i--) { + SpellAbility next = orderedSAs.get(i); + if (next.isTrigger()) { + HumanPlay.playSpellAbility(this, player, next); + } else { + player.getGame().getStack().add(next); + } + } + } + + @Override + public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) { + HumanPlay.playSpellAbilityNoStack(this, player, wrapperAbility); + } + + @Override + public boolean playSaFromPlayEffect(SpellAbility tgtSA) { + HumanPlay.playSpellAbility(this, player, tgtSA); + return true; + } + + @Override + public Map chooseProliferation() { + InputProliferate inp = new InputProliferate(this); + inp.setCancelAllowed(true); + inp.showAndWait(); + if (inp.hasCancelled()) { + return null; + } + return inp.getProliferationMap(); + } + + @Override + public boolean chooseTargetsFor(SpellAbility currentAbility) { + final TargetSelection select = new TargetSelection(this, currentAbility); + return select.chooseTargets(null); + } + + @Override + public boolean chooseCardsPile(SpellAbility sa, List pile1, List pile2, boolean faceUp) { + if (!faceUp) { + final String p1Str = String.format("Pile 1 (%s cards)", pile1.size()); + final String p2Str = String.format("Pile 2 (%s cards)", pile2.size()); + final String[] possibleValues = { p1Str , p2Str }; + return SGuiDialog.confirm(getGui(), gameView.getCardView(sa.getHostCard()), "Choose a Pile", possibleValues); + } else { + final Card[] disp = new Card[pile1.size() + pile2.size() + 2]; + disp[0] = new Card(-1); + disp[0].setName("Pile 1"); + for (int i = 0; i < pile1.size(); i++) { + disp[1 + i] = pile1.get(i); + } + disp[pile1.size() + 1] = new Card(-2); + disp[pile1.size() + 1].setName("Pile 2"); + for (int i = 0; i < pile2.size(); i++) { + disp[pile1.size() + i + 2] = pile2.get(i); + } + + // make sure Pile 1 or Pile 2 is clicked on + while (true) { + final Object o = SGuiChoose.one(getGui(), "Choose a pile", disp); + final Card c = (Card) o; + String name = c.getName(); + + if (!(name.equals("Pile 1") || name.equals("Pile 2"))) { + continue; + } + + return name.equals("Pile 1"); + } + } + } + + @Override + public void revealAnte(String message, Multimap removedAnteCards) { + for (Player p : removedAnteCards.keySet()) { + SGuiChoose.reveal(getGui(), message + " from " + Lang.getPossessedObject(mayBeYou(p), "deck"), removedAnteCards.get(p)); + } + } + + @Override + public CardShields chooseRegenerationShield(Card c) { + if (c.getShield().size() < 2) { + return Iterables.getFirst(c.getShield(), null); + } + return SGuiChoose.one(getGui(), "Choose a regeneration shield:", c.getShield()); + } + + @Override + public List chooseCardsYouWonToAddToDeck(List losses) { + return SGuiChoose.many(getGui(), "Select cards to add to your deck", "Add these to my deck", 0, losses.size(), losses, null); + } + + @Override + public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt, boolean isActivatedSa) { + return HumanPlay.payManaCost(this, toPay, costPartMana, sa, player, prompt, isActivatedSa); + } + + @Override + public Map chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost, List untappedCreats) { + InputSelectCardsForConvoke inp = new InputSelectCardsForConvoke(this, player, manaCost, untappedCreats); + inp.showAndWait(); + return inp.getConvokeMap(); + } + + @Override + public String chooseCardName(SpellAbility sa, Predicate cpp, String valid, String message) { + PaperCard cp = null; + while(true) { + cp = chooseSinglePaperCard(sa, message, cpp, sa.getHostCard().getName()); + Card instanceForPlayer = Card.fromPaperCard(cp, player); // the Card instance for test needs a game to be tested + if (instanceForPlayer.isValid(valid, sa.getHostCard().getController(), sa.getHostCard())) + return cp.getName(); + } + } + + @Override + public Card chooseSingleCardForZoneChange(ZoneType destination, List origin, SpellAbility sa, List fetchList, String selectPrompt, boolean b, Player decider) { + return chooseSingleEntityForEffect(fetchList, sa, selectPrompt, b, decider); + } + + public boolean isGuiPlayer() { + return lobbyPlayer == getGui().getGuiPlayer(); + } -import java.util.List; + /* + * What follows are the View methods. + */ + + private class GameView extends LocalGameView { + + public GameView(Game game) { + super(game); + } + + @Override + public GameOutcome.AnteResult getAnteResult() { + return this.getGame().getOutcome().anteResult.get(player); + } + + @Override + public boolean canUndoLastAction() { + if (!game.stack.canUndo()) { + return false; + } + final Player priorityPlayer = game.getPhaseHandler().getPriorityPlayer(); + if (priorityPlayer == null || priorityPlayer != player) { + return false; + } + return true; + } + + @Override + public boolean tryUndoLastAction() { + if (!canUndoLastAction()) { + return false; + } + + if (game.getStack().undo()) {//canUndoLastAction(gui) && GuiBase.getInterface().getGame().stack.undo()) { + final Input currentInput = getGui().getInputQueue().getInput(); + if (currentInput instanceof InputPassPriority) { + currentInput.showMessageInitial(); //ensure prompt updated if needed + } + return true; + } + return false; + } + + @Override + public void selectButtonOk() { + getInputProxy().selectButtonOK(); + } + + @Override + public void selectButtonCancel() { + getInputProxy().selectButtonCancel(); + } + + @Override + public void confirm() { + if (getGui().getInputQueue().getInput() instanceof InputConfirm) { + selectButtonOk(); + } + } + + @Override + public boolean passPriority() { + return getInputProxy().passPriority(); + } + + @Override + public boolean passPriorityUntilEndOfTurn() { + return getInputProxy().passPriorityUntilEndOfTurn(); + } + + @Override + public void useMana(final byte mana) { + final Input input = getGui().getInputQueue().getInput(); + if (input instanceof InputPayMana) { + ((InputPayMana) input).useManaFromPool(mana); + } + } + + @Override + public void selectPlayer(final PlayerView player, final ITriggerEvent triggerEvent) { + getInputProxy().selectPlayer(player, triggerEvent); + } + + @Override + public boolean selectCard(final CardView card, final ITriggerEvent triggerEvent) { + return getInputProxy().selectCard(card, triggerEvent); + } + + @Override + public void selectAbility(final SpellAbilityView sa) { + getInputProxy().selectAbility(sa); + } + + @Override + public boolean mayShowCard(final CardView c) { + final Card card = getCard(c); + return card == null || card.canBeShownTo(player); + } + + @Override + public boolean mayShowCardFace(final CardView c) { + final Card card = getCard(c); + return card == null || !c.isFaceDown() || card.canCardFaceBeShownTo(player); + } + + + @Override + public boolean getDisableAutoYields() { + return this.getGame().getDisableAutoYields(); + } + @Override + public void setDisableAutoYields(final boolean b) { + this.getGame().setDisableAutoYields(b); + } + + // Dev mode functions + @Override + public void devTogglePlayManyLands(final boolean b) { + player.canCheatPlayUnlimitedLands = b; + } + @Override + public void devGenerateMana() { + DevModeUtil.devModeGenerateMana(game, PlayerControllerHuman.this); + } + @Override + public void devSetupGameState() { + DevModeUtil.devSetupGameState(game, PlayerControllerHuman.this); + } + @Override + public void devTutorForCard() { + DevModeUtil.devModeTutor(game, PlayerControllerHuman.this); + } + @Override + public void devAddCardToHand() { + DevModeUtil.devModeCardToHand(game, PlayerControllerHuman.this); + } + @Override + public void devAddCounterToPermanent() { + DevModeUtil.devModeAddCounter(game, PlayerControllerHuman.this); + } + @Override + public void devTapPermanent() { + DevModeUtil.devModeTapPerm(game, PlayerControllerHuman.this); + } + @Override + public void devUntapPermanent() { + DevModeUtil.devModeUntapPerm(game, PlayerControllerHuman.this); + } + @Override + public void devSetPlayerLife() { + DevModeUtil.devModeSetLife(game, PlayerControllerHuman.this); + } + @Override + public void devWinGame() { + DevModeUtil.devModeWinGame(game, PlayerControllerHuman.this); + } + @Override + public void devAddCardToBattlefield() { + DevModeUtil.devModeCardToBattlefield(game, PlayerControllerHuman.this); + } + @Override + public void devRiggedPlanerRoll() { + DevModeUtil.devModeRiggedPlanarRoll(game, PlayerControllerHuman.this); + } + @Override + public void devPlaneswalkTo() { + DevModeUtil.devModeRiggedPlanarRoll(game, PlayerControllerHuman.this); + } + + } + + /** + * @return + * @see forge.view.LocalGameView#getPlayers() + */ + public List getPlayers() { + return gameView.getPlayers(); + } + + /** + * @return + * @see forge.view.LocalGameView#getPlayerTurn() + */ + public PlayerView getPlayerTurn() { + return gameView.getPlayerTurn(); + } + + /** + * @return + * @see forge.view.LocalGameView#getCombat() + */ + public CombatView getCombat() { + return gameView.getCombat(); + } + + /** + * @param c + * @return + * @see forge.view.LocalGameView#getCombat(forge.game.combat.Combat) + */ + public CombatView getCombat(Combat c) { + return gameView.getCombat(c); + } + + /** + * @param si + * @return + * @see forge.view.LocalGameView#getStackItemView(forge.game.spellability.SpellAbilityStackInstance) + */ + public StackItemView getStackItemView(SpellAbilityStackInstance si) { + return gameView.getStackItemView(si); + } + + /** + * @param view + * @return + * @see forge.view.LocalGameView#getStackItem(forge.view.StackItemView) + */ + public SpellAbilityStackInstance getStackItem(StackItemView view) { + return gameView.getStackItem(view); + } + + /** + * @param e + * @return + * @see forge.view.LocalGameView#getGameEntityView(forge.game.GameEntity) + */ + public final GameEntityView getGameEntityView(GameEntity e) { + return gameView.getGameEntityView(e); + } + + /** + * @param players + * @return + * @see forge.view.LocalGameView#getPlayerViews(java.util.List) + */ + public final List getPlayerViews(List players) { + return gameView.getPlayerViews(players); + } + + /** + * @param players + * @return + * @see forge.view.LocalGameView#getPlayerViews(java.lang.Iterable) + */ + public final Iterable getPlayerViews(Iterable players) { + return gameView.getPlayerViews(players); + } + + /** + * @param p + * @return + * @see forge.view.LocalGameView#getPlayerView(forge.game.player.Player) + */ + public PlayerView getPlayerView(Player p) { + return gameView.getPlayerView(p); + } + + /** + * @param p + * @return + * @see forge.view.LocalGameView#getPlayer(forge.view.PlayerView) + */ + public Player getPlayer(PlayerView p) { + return gameView.getPlayer(p); + } + + /** + * @param c + * @return + * @see forge.view.LocalGameView#getCardView(forge.game.card.Card) + */ + public CardView getCardView(Card c) { + return gameView.getCardView(c); + } + + /** + * @param cards + * @return + * @see forge.view.LocalGameView#getCardViews(java.util.List) + */ + public final List getCardViews(List cards) { + return gameView.getCardViews(cards); + } + + /** + * @param cards + * @return + * @see forge.view.LocalGameView#getCardViews(java.lang.Iterable) + */ + public final Iterable getCardViews(Iterable cards) { + return gameView.getCardViews(cards); + } + + /** + * @param c + * @return + * @see forge.view.LocalGameView#getCard(forge.view.CardView) + */ + public Card getCard(CardView c) { + return gameView.getCard(c); + } + + /** + * @param cards + * @return + * @see forge.view.LocalGameView#getCards(java.util.List) + */ + public final List getCards(List cards) { + return gameView.getCards(cards); + } + + /** + * @param sa + * @return + * @see forge.view.LocalGameView#getSpellAbilityView(forge.game.spellability.SpellAbility) + */ + public SpellAbilityView getSpellAbilityView(SpellAbility sa) { + return gameView.getSpellAbilityView(sa); + } + + /** + * @param cards + * @return + * @see forge.view.LocalGameView#getSpellAbilityViews(java.util.List) + */ + public final List getSpellAbilityViews( + List cards) { + return gameView.getSpellAbilityViews(cards); + } + + /** + * @param c + * @return + * @see forge.view.LocalGameView#getSpellAbility(forge.view.SpellAbilityView) + */ + public SpellAbility getSpellAbility(SpellAbilityView c) { + return gameView.getSpellAbility(c); + } + + /** + * @param cards + * @return + * @see forge.view.LocalGameView#getSpellAbilities(java.util.List) + */ + public final List getSpellAbilities( + List cards) { + return gameView.getSpellAbilities(cards); + } + -import com.google.common.base.Function; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - -import forge.LobbyPlayer; -import forge.game.Game; -import forge.game.GameEntity; -import forge.game.card.Card; -import forge.game.combat.Combat; -import forge.game.player.Player; -import forge.game.player.PlayerController; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityStackInstance; -import forge.interfaces.IGuiBase; -import forge.match.input.InputProxy; -import forge.view.CardView; -import forge.view.CombatView; -import forge.view.GameEntityView; -import forge.view.PlayerView; -import forge.view.SpellAbilityView; -import forge.view.StackItemView; - -public abstract class PlayerControllerHuman extends PlayerController { - - private final IGuiBase gui; - private final InputProxy inputProxy; - - public PlayerControllerHuman(final Game game0, final Player p, final LobbyPlayer lp, final IGuiBase gui) { - super(game0, p, lp); - this.gui = gui; - this.inputProxy = new InputProxy(this, game0); - } - - public final IGuiBase getGui() { - return this.gui; - } - - public final InputProxy getInputProxy() { - return this.inputProxy; - } - - public abstract boolean canUndoLastAction(); - public abstract boolean tryUndoLastAction(); - - public abstract CardView getCardView(Card c); - - private final Function FN_GET_CARD_VIEW = new Function() { - @Override - public CardView apply(final Card input) { - return getCardView(input); - } - }; - - public final List getCardViews(final List cards) { - return Lists.transform(cards, FN_GET_CARD_VIEW); - } - public final Iterable getCardViews(final Iterable cards) { - return Iterables.transform(cards, FN_GET_CARD_VIEW); - } - public abstract Card getCard(CardView c); - - private final Function FN_GET_CARD = new Function() { - @Override - public Card apply(final CardView input) { - return getCard(input); - } - }; - - protected final List getCards(final List cards) { - return Lists.transform(cards, FN_GET_CARD); - } - - public final GameEntityView getGameEntityView(final GameEntity e) { - if (e instanceof Card) { - return getCardView((Card)e); - } else if (e instanceof Player) { - return getPlayerView((Player)e); - } - return null; - } - - public abstract PlayerView getPlayerView(Player p); - - private final Function FN_GET_PLAYER_VIEW = new Function() { - @Override - public PlayerView apply(final Player input) { - return getPlayerView(input); - } - }; - - public final List getPlayerViews(final List players) { - return Lists.transform(players, FN_GET_PLAYER_VIEW); - } - - public final Iterable getPlayerViews(final Iterable players) { - return Iterables.transform(players, FN_GET_PLAYER_VIEW); - } - - public abstract Player getPlayer(PlayerView p); - - public abstract SpellAbilityView getSpellAbilityView(SpellAbility sa); - - private final Function FN_GET_SPAB_VIEW = new Function() { - @Override - public SpellAbilityView apply(final SpellAbility input) { - return getSpellAbilityView(input); - } - }; - - public final List getSpellAbilityViews(final List cards) { - return Lists.transform(cards, FN_GET_SPAB_VIEW); - } - - public abstract SpellAbility getSpellAbility(SpellAbilityView c); - - private final Function FN_GET_SPAB = new Function() { - @Override - public SpellAbility apply(final SpellAbilityView input) { - return getSpellAbility(input); - } - }; - - public final List getSpellAbilities(final List cards) { - return Lists.transform(cards, FN_GET_SPAB); - } - - public final CombatView getCombat() { - return getCombat(game.getCombat()); - } - public abstract CombatView getCombat(Combat c); - - public abstract StackItemView getStackItemView(SpellAbilityStackInstance si); - public abstract SpellAbilityStackInstance getStackItem(StackItemView view); -} +} diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerLocal.java b/forge-gui/src/main/java/forge/player/PlayerControllerLocal.java deleted file mode 100644 index 44e9cbbdc68..00000000000 --- a/forge-gui/src/main/java/forge/player/PlayerControllerLocal.java +++ /dev/null @@ -1,1770 +0,0 @@ -package forge.player; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Observer; - -import org.apache.commons.lang3.Range; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; - -import forge.LobbyPlayer; -import forge.card.ColorSet; -import forge.card.MagicColor; -import forge.card.mana.ManaCost; -import forge.card.mana.ManaCostShard; -import forge.control.FControlGamePlayback; -import forge.deck.CardPool; -import forge.deck.Deck; -import forge.deck.DeckSection; -import forge.events.UiEventAttackerDeclared; -import forge.game.Game; -import forge.game.GameEntity; -import forge.game.GameLogEntry; -import forge.game.GameLogEntryType; -import forge.game.GameObject; -import forge.game.GameOutcome; -import forge.game.GameType; -import forge.game.ability.effects.CharmEffect; -import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardShields; -import forge.game.card.CounterType; -import forge.game.combat.AttackingBand; -import forge.game.combat.Combat; -import forge.game.combat.CombatUtil; -import forge.game.cost.Cost; -import forge.game.cost.CostPart; -import forge.game.cost.CostPartMana; -import forge.game.mana.Mana; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.player.PlayerActionConfirmMode; -import forge.game.player.RegisteredPlayer; -import forge.game.replacement.ReplacementEffect; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityStackInstance; -import forge.game.spellability.TargetChoices; -import forge.game.trigger.Trigger; -import forge.game.trigger.WrappedAbility; -import forge.game.zone.MagicStack; -import forge.game.zone.Zone; -import forge.game.zone.ZoneType; -import forge.interfaces.IGuiBase; -import forge.item.PaperCard; -import forge.match.input.ButtonUtil; -import forge.match.input.Input; -import forge.match.input.InputAttack; -import forge.match.input.InputBase; -import forge.match.input.InputBlock; -import forge.match.input.InputConfirm; -import forge.match.input.InputConfirmMulligan; -import forge.match.input.InputPassPriority; -import forge.match.input.InputPayMana; -import forge.match.input.InputProliferate; -import forge.match.input.InputSelectCardsForConvoke; -import forge.match.input.InputSelectCardsFromList; -import forge.match.input.InputSelectEntitiesFromList; -import forge.model.FModel; -import forge.properties.ForgePreferences.FPref; -import forge.util.DevModeUtil; -import forge.util.ITriggerEvent; -import forge.util.Lang; -import forge.util.TextUtil; -import forge.util.gui.SGuiChoose; -import forge.util.gui.SGuiDialog; -import forge.util.gui.SOptionPane; -import forge.view.CardView; -import forge.view.CombatView; -import forge.view.GameEntityView; -import forge.view.IGameView; -import forge.view.PlayerView; -import forge.view.SpellAbilityView; -import forge.view.StackItemView; -import forge.view.ViewUtil; - -/** - * A prototype for player controller class - * - * Handles phase skips for now. - */ -public class PlayerControllerLocal extends PlayerControllerHuman implements IGameView { - public PlayerControllerLocal(final Game game0, final Player p, final LobbyPlayer lp, final IGuiBase gui) { - super(game0, p, lp, gui); - // aggressively cache a view for each player (also caches cards) - for (final Player player : game.getRegisteredPlayers()) { - getPlayerView(player); - } - } - - public boolean isUiSetToSkipPhase(final Player turn, final PhaseType phase) { - return !getGui().stopAtPhase(getPlayerView(turn), phase); - } - - /** - * 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 choice = getGui().getAbilityToPlay(getSpellAbilityViews(abilities), triggerEvent); - return getSpellAbility(choice); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#mayPlaySpellAbilityForFree(forge.card.spellability.SpellAbility) - */ - @Override - public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChoseNewTargets) { - HumanPlay.playSaWithoutPayingManaCost(this, player.getGame(), copySA, mayChoseNewTargets); - } - - @Override - public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) { - HumanPlay.playSpellAbilityNoStack(this, player, effectSA, !canSetupTargets); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#sideboard(forge.deck.Deck) - */ - @Override - public List sideboard(Deck deck, GameType gameType) { - CardPool sideboard = deck.get(DeckSection.Sideboard); - if (sideboard == null) { - // Use an empty cardpool instead of null for 75/0 sideboarding scenario. - sideboard = new CardPool(); - } - - CardPool main = deck.get(DeckSection.Main); - - int mainSize = main.countAll(); - int sbSize = sideboard.countAll(); - int combinedDeckSize = mainSize + sbSize; - - int deckMinSize = Math.min(mainSize, gameType.getDeckFormat().getMainRange().getMinimum()); - Range sbRange = gameType.getDeckFormat().getSideRange(); - // Limited doesn't have a sideboard max, so let the Main min take care of things. - int sbMax = sbRange == null ? combinedDeckSize : sbRange.getMaximum(); - - List newMain = null; - - //Skip sideboard loop if there are no sideboarding opportunities - if (sbSize == 0 && mainSize == deckMinSize) { return null; } - - // conformance should not be checked here - boolean conform = FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY); - do { - if (newMain != null) { - String errMsg; - if (newMain.size() < deckMinSize) { - errMsg = String.format("Too few cards in your main deck (minimum %d), please make modifications to your deck again.", deckMinSize); - } - else { - errMsg = String.format("Too many cards in your sideboard (maximum %d), please make modifications to your deck again.", sbMax); - } - SOptionPane.showErrorDialog(getGui(), errMsg, "Invalid Deck"); - } - // Sideboard rules have changed for M14, just need to consider min maindeck and max sideboard sizes - // No longer need 1:1 sideboarding in non-limited formats - newMain = getGui().sideboard(sideboard, main); - } while (conform && (newMain.size() < deckMinSize || combinedDeckSize - newMain.size() > sbMax)); - - return newMain; - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#assignCombatDamage() - */ - @Override - public Map assignCombatDamage(final Card attacker, - final List blockers, final int damageDealt, - final GameEntity defender, final boolean overrideOrder) { - // Attacker is a poor name here, since the creature assigning damage - // could just as easily be the blocker. - final Map map = Maps.newHashMap(); - if (defender != null && assignDamageAsIfNotBlocked(attacker)) { - map.put(null, damageDealt); - } else { - final List vBlockers = getCardViews(blockers); - if ((attacker.hasKeyword("Trample") && defender != null) || (blockers.size() > 1)) { - final CardView vAttacker = getCardView(attacker); - final GameEntityView vDefender = getGameEntityView(defender); - final Map result = getGui().getDamageToAssign(vAttacker, vBlockers, damageDealt, vDefender, overrideOrder); - for (final Entry e : result.entrySet()) { - map.put(getCard(e.getKey()), e.getValue()); - } - } else { - map.put(blockers.get(0), damageDealt); - } - } - return map; - } - - private final boolean assignDamageAsIfNotBlocked(final Card attacker) { - return attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.") - || (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") - && SGuiDialog.confirm(getGui(), getCardView(attacker), "Do you want to assign its combat damage as though it weren't blocked?")); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#announceRequirements(java.lang.String) - */ - @Override - public Integer announceRequirements(SpellAbility ability, String announce, boolean canChooseZero) { - int min = canChooseZero ? 0 : 1; - return SGuiChoose.getInteger(getGui(), "Choose " + announce + " for " + ability.getHostCard().getName(), - min, Integer.MAX_VALUE, min + 9); - } - - @Override - public List choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List valid, String message) { - String outerMessage = "Select %d " + message + "(s) to sacrifice"; - return choosePermanentsTo(min, max, valid, outerMessage); - } - - @Override - public List choosePermanentsToDestroy(SpellAbility sa, int min, int max, List valid, String message) { - String outerMessage = "Select %d " + message + "(s) to be destroyed"; - return choosePermanentsTo(min, max, valid, outerMessage); - } - - private List choosePermanentsTo(int min, int max, List valid, String outerMessage) { - max = Math.min(max, valid.size()); - if (max <= 0) { - return new ArrayList(); - } - - InputSelectCardsFromList inp = new InputSelectCardsFromList(this, min == 0 ? 1 : min, max, valid); - inp.setMessage(outerMessage); - inp.setCancelAllowed(min == 0); - inp.showAndWait(); - return Lists.newArrayList(inp.getSelected()); - } - - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseCardsForEffect(java.util.Collection, forge.card.spellability.SpellAbility, java.lang.String, int, boolean) - */ - @Override - public List chooseCardsForEffect(List sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) { - // If only one card to choose, use a dialog box. - // Otherwise, use the order dialog to be able to grab multiple cards in one shot - if (max == 1) { - Card singleChosen = chooseSingleEntityForEffect(sourceList, sa, title, isOptional); - return singleChosen == null ? Lists.newArrayList() : Lists.newArrayList(singleChosen); - } - - getGui().setPanelSelection(getCardView(sa.getHostCard())); - - // try to use InputSelectCardsFromList when possible - boolean cardsAreInMyHandOrBattlefield = true; - for(Card c : sourceList) { - Zone z = c.getZone(); - if (z != null && (z.is(ZoneType.Battlefield) || z.is(ZoneType.Hand, player))) - continue; - cardsAreInMyHandOrBattlefield = false; - break; - } - - if(cardsAreInMyHandOrBattlefield) { - InputSelectCardsFromList sc = new InputSelectCardsFromList(this, min, max, sourceList); - sc.setMessage(title); - sc.setCancelAllowed(isOptional); - sc.showAndWait(); - return Lists.newArrayList(sc.getSelected()); - } - - return SGuiChoose.many(getGui(), title, "Chosen Cards", min, max, sourceList, getCardView(sa.getHostCard())); - } - - @Override - public T chooseSingleEntityForEffect(Collection options, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) { - // Human is supposed to read the message and understand from it what to choose - if (options.isEmpty()) { - return null; - } - if (!isOptional && options.size() == 1) { - return Iterables.getFirst(options, null); - } - - boolean canUseSelectCardsInput = true; - for (GameEntity c : options) { - if (c instanceof Player) - continue; - Zone cz = ((Card)c).getZone(); - // can point at cards in own hand and anyone's battlefield - boolean canUiPointAtCards = cz != null && (cz.is(ZoneType.Hand) && cz.getPlayer() == player || cz.is(ZoneType.Battlefield)); - if (!canUiPointAtCards) { - canUseSelectCardsInput = false; - break; - } - } - - if (canUseSelectCardsInput) { - InputSelectEntitiesFromList input = new InputSelectEntitiesFromList(this, isOptional ? 0 : 1, 1, options); - input.setCancelAllowed(isOptional); - input.setMessage(formatMessage(title, targetedPlayer)); - input.showAndWait(); - return Iterables.getFirst(input.getSelected(), null); - } - - return isOptional ? SGuiChoose.oneOrNone(getGui(), title, options) : SGuiChoose.one(getGui(), title, options); - } - - @Override - public int chooseNumber(SpellAbility sa, String title, int min, int max) { - final Integer[] choices = new Integer[max + 1 - min]; - for (int i = 0; i <= max - min; i++) { - choices[i] = Integer.valueOf(i + min); - } - return SGuiChoose.one(getGui(), title, choices).intValue(); - } - - @Override - public int chooseNumber(SpellAbility sa, String title, List choices, Player relatedPlayer) { - return SGuiChoose.one(getGui(), title, choices).intValue(); - } - - @Override - public SpellAbility chooseSingleSpellForEffect(java.util.List spells, SpellAbility sa, String title) { - // Human is supposed to read the message and understand from it what to choose - return spells.size() < 2 ? spells.get(0) : SGuiChoose.one(getGui(), title, spells); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#confirmAction(forge.card.spellability.SpellAbility, java.lang.String, java.lang.String) - */ - @Override - public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { - return SGuiDialog.confirm(getGui(), getCardView(sa.getHostCard()), message); - } - - @Override - public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode bidlife, - String string, int bid, Player winner) { - return SGuiDialog.confirm(getGui(), getCardView(sa.getHostCard()), string + " Highest Bidder " + winner); - } - - @Override - public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) { - return SGuiDialog.confirm(getGui(), getCardView(hostCard), message); - } - - @Override - public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map triggerParams, boolean isMandatory) { - if (this.shouldAlwaysAcceptTrigger(regtrig.getId())) { - return true; - } - if (this.shouldAlwaysDeclineTrigger(regtrig.getId())) { - return false; - } - - final StringBuilder buildQuestion = new StringBuilder("Use triggered ability of "); - buildQuestion.append(regtrig.getHostCard().toString()).append("?"); - if (!FModel.getPreferences().getPrefBoolean(FPref.UI_COMPACT_PROMPT)) { - //append trigger description unless prompt is compact - buildQuestion.append("\n("); - buildQuestion.append(triggerParams.get("TriggerDescription").replace("CARDNAME", regtrig.getHostCard().getName())); - buildQuestion.append(")"); - } - HashMap tos = sa.getTriggeringObjects(); - if (tos.containsKey("Attacker")) { - buildQuestion.append("\nAttacker: " + tos.get("Attacker")); - } - if (tos.containsKey("Card")) { - Card card = (Card) tos.get("Card"); - if (card != null && (card.getController() == player || game.getZoneOf(card) == null - || game.getZoneOf(card).getZoneType().isKnown())) { - buildQuestion.append("\nTriggered by: " + tos.get("Card")); - } - } - - InputConfirm inp = new InputConfirm(this, buildQuestion.toString()); - inp.showAndWait(); - return inp.getResult(); - } - - @Override - public Player chooseStartingPlayer(boolean isFirstGame) { - if (game.getPlayers().size() == 2) { - final String prompt = String.format("%s, you %s\n\nWould you like to play or draw?", - player.getName(), isFirstGame ? " have won the coin toss." : " lost the last game."); - final InputConfirm inp = new InputConfirm(this, prompt, "Play", "Draw"); - inp.showAndWait(); - return inp.getResult() ? this.player : this.player.getOpponents().get(0); - } else { - final String prompt = String.format("%s, you %s\n\nWho would you like to start this game?", - player.getName(), isFirstGame ? " have won the coin toss." : " lost the last game."); - final InputSelectEntitiesFromList input = new InputSelectEntitiesFromList<>(this, 1, 1, game.getPlayersInTurnOrder()); - input.setMessage(prompt); - input.showAndWait(); - return input.getFirstSelected(); - } - } - - @Override - public List orderBlockers(final Card attacker, final List blockers) { - final CardView vAttacker = getCardView(attacker); - getGui().setPanelSelection(vAttacker); - final List choices = SGuiChoose.order(getGui(), "Choose Damage Order for " + vAttacker, "Damaged First", getCardViews(blockers), vAttacker); - return getCards(choices); - } - - @Override - public List orderBlocker(final Card attacker, final Card blocker, final List oldBlockers) { - final CardView vAttacker = getCardView(attacker); - getGui().setPanelSelection(vAttacker); - final List choices = SGuiChoose.insertInList(getGui(), "Choose blocker after which to place " + vAttacker + " in damage order; cancel to place it first", getCardView(blocker), getCardViews(oldBlockers)); - return getCards(choices); - } - - @Override - public List orderAttackers(final Card blocker, final List attackers) { - final CardView vBlocker = getCardView(blocker); - getGui().setPanelSelection(vBlocker); - final List choices = SGuiChoose.order(getGui(), "Choose Damage Order for " + vBlocker, "Damaged First", getCardViews(attackers), vBlocker); - return getCards(choices); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#reveal(java.lang.String, java.util.List, forge.game.zone.ZoneType, forge.game.player.Player) - */ - @Override - public void reveal(Collection cards, ZoneType zone, Player owner, String message) { - if (StringUtils.isBlank(message)) { - message = "Looking at cards in {player's} " + zone.name().toLowerCase(); - } - else { - message += "{player's} " + zone.name().toLowerCase(); - } - String fm = formatMessage(message, owner); - if (!cards.isEmpty()) { - SGuiChoose.reveal(getGui(), fm, cards); - } - else { - SGuiDialog.message(getGui(), formatMessage("There are no cards in {player's} " + - zone.name().toLowerCase(), owner), fm); - } - } - - @Override - public ImmutablePair, List> arrangeForScry(List topN) { - List toBottom = null; - List toTop = null; - - if (topN.size() == 1) { - if (willPutCardOnTop(topN.get(0))) { - toTop = topN; - } - else { - toBottom = topN; - } - } - else { - toBottom = SGuiChoose.many(getGui(), "Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, topN, null); - topN.removeAll(toBottom); - if (topN.isEmpty()) { - toTop = null; - } - else if (topN.size() == 1) { - toTop = topN; - } - else { - toTop = SGuiChoose.order(getGui(), "Arrange cards to be put on top of your library", "Cards arranged", topN, null); - } - } - return ImmutablePair.of(toTop, toBottom); - } - - @Override - public boolean willPutCardOnTop(final Card c) { - final PaperCard pc = FModel.getMagicDb().getCommonCards().getCard(c.getName()); - final Card c1 = (pc != null ? Card.fromPaperCard(pc, null) : c); - final CardView view = getCardView(c1); - return SGuiDialog.confirm(getGui(), view, "Put " + view + " on the top or bottom of your library?", new String[]{"Top", "Bottom"}); - } - - @Override - public List orderMoveToZoneList(List cards, ZoneType destinationZone) { - switch (destinationZone) { - case Library: - return SGuiChoose.order(getGui(), "Choose order of cards to put into the library", "Closest to top", cards, null); - case Battlefield: - return SGuiChoose.order(getGui(), "Choose order of cards to put onto the battlefield", "Put first", cards, null); - case Graveyard: - return SGuiChoose.order(getGui(), "Choose order of cards to put into the graveyard", "Closest to bottom", cards, null); - case PlanarDeck: - return SGuiChoose.order(getGui(), "Choose order of cards to put into the planar deck", "Closest to top", cards, null); - case SchemeDeck: - return SGuiChoose.order(getGui(), "Choose order of cards to put into the scheme deck", "Closest to top", cards, null); - case Stack: - return SGuiChoose.order(getGui(), "Choose order of copies to cast", "Put first", cards, null); - default: - System.out.println("ZoneType " + destinationZone + " - Not Ordered"); - break; - } - return cards; - } - - @Override - public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List valid, int min, int max) { - if (p != player) { - return SGuiChoose.many(getGui(), "Choose " + min + " card" + (min != 1 ? "s" : "") + " to discard", - "Discarded", min, min, valid, null); - } - - InputSelectCardsFromList inp = new InputSelectCardsFromList(this, min, max, valid); - inp.setMessage(sa.hasParam("AnyNumber") ? "Discard up to %d card(s)" : "Discard %d card(s)"); - inp.showAndWait(); - return Lists.newArrayList(inp.getSelected()); - } - - @Override - public void playMiracle(final SpellAbility miracle, final Card card) { - final CardView view = getCardView(card); - if (SGuiDialog.confirm(getGui(), view, view + " - Drawn. Play for Miracle Cost?")) { - HumanPlay.playSpellAbility(this, player, miracle); - } - } - - @Override - public List chooseCardsToDelve(int colorLessAmount, List grave) { - List toExile = new ArrayList(); - int cardsInGrave = grave.size(); - final Integer[] cntChoice = new Integer[cardsInGrave + 1]; - for (int i = 0; i <= cardsInGrave; i++) { - cntChoice[i] = Integer.valueOf(i); - } - - final Integer chosenAmount = SGuiChoose.one(getGui(), "Exile how many cards?", cntChoice); - System.out.println("Delve for " + chosenAmount); - - for (int i = 0; i < chosenAmount; i++) { - final Card nowChosen = SGuiChoose.oneOrNone(getGui(), "Exile which card?", grave); - - if (nowChosen == null) { - // User canceled,abort delving. - toExile.clear(); - break; - } - - grave.remove(nowChosen); - toExile.add(nowChosen); - } - return toExile; - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseTargets(forge.card.spellability.SpellAbility, forge.card.spellability.SpellAbilityStackInstance) - */ - @Override - public TargetChoices chooseNewTargetsFor(SpellAbility ability) { - SpellAbility sa = ability.isWrapper() ? ((WrappedAbility) ability).getWrappedAbility() : ability; - if (sa.getTargetRestrictions() == null) { - return null; - } - TargetChoices oldTarget = sa.getTargets(); - TargetSelection select = new TargetSelection(this, sa); - sa.resetTargets(); - if (select.chooseTargets(oldTarget.getNumTargeted())) { - return sa.getTargets(); - } - else { - // Return old target, since we had to reset them above - return oldTarget; - } - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseCardsToDiscardUnlessType(int, java.lang.String, forge.card.spellability.SpellAbility) - */ - @Override - public List chooseCardsToDiscardUnlessType(int num, List hand, final String uType, SpellAbility sa) { - final InputSelectEntitiesFromList target = new InputSelectEntitiesFromList(this, num, num, hand) { - private static final long serialVersionUID = -5774108410928795591L; - - @Override - protected boolean hasAllTargets() { - for (Card c : selected) { - if (c.isType(uType)) { - return true; - } - } - return super.hasAllTargets(); - } - }; - target.setMessage("Select %d card(s) to discard, unless you discard a " + uType + "."); - target.showAndWait(); - return Lists.newArrayList(target.getSelected()); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseManaFromPool(java.util.List) - */ - @Override - public Mana chooseManaFromPool(List manaChoices) { - List options = new ArrayList(); - for (int i = 0; i < manaChoices.size(); i++) { - Mana m = manaChoices.get(i); - options.add(String.format("%d. %s mana from %s", 1+i, MagicColor.toLongString(m.getColor()), m.getSourceCard())); - } - String chosen = SGuiChoose.one(getGui(), "Pay Mana from Mana Pool", options); - String idx = TextUtil.split(chosen, '.')[0]; - return manaChoices.get(Integer.parseInt(idx)-1); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseSomeType(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.String) - */ - @Override - public String chooseSomeType(final String kindOfType, final SpellAbility sa, final List validTypes, List invalidTypes, final boolean isOptional) { - final List types = Lists.newArrayList(validTypes); - if (invalidTypes != null && !invalidTypes.isEmpty()) { - Iterables.removeAll(types, invalidTypes); - } - if(isOptional) - return SGuiChoose.oneOrNone(getGui(), "Choose a " + kindOfType.toLowerCase() + " type", types); - else - return SGuiChoose.one(getGui(), "Choose a " + kindOfType.toLowerCase() + " type", types); - } - - @Override - public Object vote(SpellAbility sa, String prompt, List options, ArrayListMultimap votes) { - return SGuiChoose.one(getGui(), prompt, options); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#confirmReplacementEffect(forge.card.replacement.ReplacementEffect, forge.card.spellability.SpellAbility, java.lang.String) - */ - @Override - public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) { - return SGuiDialog.confirm(getGui(), getCardView(replacementEffect.getHostCard()), question); - } - - @Override - public List getCardsToMulligan(boolean isCommander, Player firstPlayer) { - final InputConfirmMulligan inp = new InputConfirmMulligan(this, player, firstPlayer, isCommander); - inp.showAndWait(); - return inp.isKeepHand() ? null : isCommander ? inp.getSelectedCards() : player.getCardsIn(ZoneType.Hand); - } - - @Override - public void declareAttackers(Player attackingPlayer, Combat combat) { - if (mayAutoPass()) { - List> mandatoryAttackers = CombatUtil.getMandatoryAttackers(attackingPlayer, combat, combat.getDefenders()); - if (!mandatoryAttackers.isEmpty()) { - //even if auto-passing attack phase, if there are any mandatory attackers, - //ensure they're declared and then delay slightly so user can see as much - for (Pair attacker : mandatoryAttackers) { - combat.addAttacker(attacker.getLeft(), attacker.getRight()); - getGui().fireEvent(new UiEventAttackerDeclared(getCardView(attacker.getLeft()), getGameEntityView(attacker.getRight()))); - } - try { - Thread.sleep(FControlGamePlayback.combatDelay); - } - catch (InterruptedException e) { - e.printStackTrace(); - } - } - return; //don't prompt to declare attackers if user chose to end the turn - } - - // This input should not modify combat object itself, but should return user choice - final InputAttack inpAttack = new InputAttack(this, attackingPlayer, combat); - inpAttack.showAndWait(); - } - - @Override - public void declareBlockers(Player defender, Combat combat) { - // This input should not modify combat object itself, but should return user choice - final InputBlock inpBlock = new InputBlock(this, defender, combat); - inpBlock.showAndWait(); - updateAutoPassPrompt(); - } - - public void updateAutoPassPrompt() { - if (mayAutoPass()) { - //allow user to cancel auto-pass - InputBase.cancelAwaitNextInput(); //don't overwrite prompt with awaiting opponent - PhaseType phase = getAutoPassUntilPhase(); - getGui().showPromptMessage("Yielding until " + (phase == PhaseType.CLEANUP ? "end of turn" : phase.nameForUi.toString()) + - ".\nYou may cancel this yield to take an action."); - ButtonUtil.update(getGui(), false, true, false); - } - } - - @Override - public void autoPassUntilEndOfTurn() { - super.autoPassUntilEndOfTurn(); - updateAutoPassPrompt(); - } - - @Override - public void autoPassCancel() { - if (getAutoPassUntilPhase() == null) { return; } - super.autoPassCancel(); - - //prevent prompt getting stuck on yielding message while actually waiting for next input opportunity - getGui().showPromptMessage(""); - ButtonUtil.update(getGui(), false, false, false); - InputBase.awaitNextInput(getGui()); - } - - @Override - public SpellAbility chooseSpellAbilityToPlay() { - MagicStack stack = game.getStack(); - - if (mayAutoPass()) { - //avoid prompting for input if current phase is set to be auto-passed - //instead posing a short delay if needed to prevent the game jumping ahead too quick - int delay = 0; - if (stack.isEmpty()) { - //make sure to briefly pause at phases you're not set up to skip - if (!isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), game.getPhaseHandler().getPhase())) { - delay = FControlGamePlayback.phasesDelay; - } - } - else { - //pause slightly longer for spells and abilities on the stack resolving - delay = FControlGamePlayback.resolveDelay; - } - if (delay > 0) { - try { - Thread.sleep(delay); - } - catch (InterruptedException e) { - e.printStackTrace(); - } - } - return null; - } - - if (stack.isEmpty()) { - if (isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), game.getPhaseHandler().getPhase())) { - return null; //avoid prompt for input if stack is empty and player is set to skip the current phase - } - } - else if (!game.getDisableAutoYields()) { - SpellAbility ability = stack.peekAbility(); - if (ability != null && ability.isAbility() && shouldAutoYield(ability.toUnsuppressedString())) { - //avoid prompt for input if top ability of stack is set to auto-yield - try { - Thread.sleep(FControlGamePlayback.resolveDelay); - } - catch (InterruptedException e) { - e.printStackTrace(); - } - return null; - } - } - - InputPassPriority defaultInput = new InputPassPriority(this, player); - defaultInput.showAndWait(); - return defaultInput.getChosenSa(); - } - - @Override - public void playChosenSpellAbility(SpellAbility chosenSa) { - HumanPlay.playSpellAbility(this, player, chosenSa); - } - - @Override - public List chooseCardsToDiscardToMaximumHandSize(int nDiscard) { - final int max = player.getMaxHandSize(); - - InputSelectCardsFromList inp = new InputSelectCardsFromList(this, nDiscard, nDiscard, player.getZone(ZoneType.Hand).getCards()); - String message = "Cleanup Phase\nSelect " + nDiscard + " card" + (nDiscard > 1 ? "s" : "") + - " to discard to bring your hand down to the maximum of " + max + " cards."; - inp.setMessage(message); - inp.setCancelAllowed(false); - inp.showAndWait(); - return Lists.newArrayList(inp.getSelected()); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseCardsToRevealFromHand(int, int, java.util.List) - */ - @Override - public List chooseCardsToRevealFromHand(int min, int max, List valid) { - max = Math.min(max, valid.size()); - min = Math.min(min, max); - InputSelectCardsFromList inp = new InputSelectCardsFromList(this, min, max, valid); - inp.setMessage("Choose Which Cards to Reveal"); - inp.showAndWait(); - return Lists.newArrayList(inp.getSelected()); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#payManaOptional(forge.Card, forge.card.cost.Cost) - */ - @Override - public boolean payManaOptional(Card c, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) { - if (sa == null && cost.isOnlyManaCost() && cost.getTotalMana().isZero() - && !FModel.getPreferences().getPrefBoolean(FPref.MATCHPREF_PROMPT_FREE_BLOCKS)) - return true; - - return HumanPlay.payCostDuringAbilityResolve(this, player, c, cost, sa, prompt); - } - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseSaToActivateFromOpeningHand(java.util.List) - */ - @Override - public List chooseSaToActivateFromOpeningHand(List usableFromOpeningHand) { - List srcCards = new ArrayList(); - for (SpellAbility sa : usableFromOpeningHand) { - srcCards.add(sa.getHostCard()); - } - List result = new ArrayList(); - if (srcCards.isEmpty()) { - return result; - } - List chosen = SGuiChoose.many(getGui(), "Choose cards to activate from opening hand and their order", "Activate first", -1, srcCards, null); - for (Card c : chosen) { - for (SpellAbility sa : usableFromOpeningHand) { - if (sa.getHostCard() == c) { - result.add(sa); - break; - } - } - } - return result; - } - - // end of not related candidates for move. - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseBinary(java.lang.String, boolean) - */ - @Override - public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultVal) { - String[] labels = new String[]{"Option1", "Option2"}; - switch(kindOfChoice) { - case HeadsOrTails: labels = new String[]{"Heads", "Tails"}; break; - case TapOrUntap: labels = new String[]{"Tap", "Untap"}; break; - case OddsOrEvens: labels = new String[]{"Odds", "Evens"}; break; - case UntapOrLeaveTapped: labels = new String[]{"Untap", "Leave tapped"}; break; - case UntapTimeVault: labels = new String[]{"Untap (and skip this turn)", "Leave tapped"}; break; - case PlayOrDraw: labels = new String[]{"Play", "Draw"}; break; - default: labels = kindOfChoice.toString().split("Or"); - - } - return SGuiDialog.confirm(getGui(), getCardView(sa.getHostCard()), question, defaultVal == null || defaultVal.booleanValue(), labels); - } - - @Override - public boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call) { - String[] labelsSrc = call ? new String[]{"heads", "tails"} : new String[]{"win the flip", "lose the flip"}; - String[] strResults = new String[results.length]; - for (int i = 0; i < results.length; i++) { - strResults[i] = labelsSrc[results[i] ? 0 : 1]; - } - return SGuiChoose.one(getGui(), sa.getHostCard().getName() + " - Choose a result", strResults) == labelsSrc[0]; - } - - @Override - public Card chooseProtectionShield(GameEntity entityBeingDamaged, List options, Map choiceMap) { - String title = entityBeingDamaged + " - select which prevention shield to use"; - return choiceMap.get(SGuiChoose.one(getGui(), title, options)); - } - - @Override - public Pair chooseAndRemoveOrPutCounter(Card cardWithCounter) { - if (!cardWithCounter.hasCounters()) { - System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier"); - return null; - } - - String counterChoiceTitle = "Choose a counter type on " + cardWithCounter; - final CounterType chosen = SGuiChoose.one(getGui(), counterChoiceTitle, cardWithCounter.getCounters().keySet()); - - String putOrRemoveTitle = "What to do with that '" + chosen.getName() + "' counter "; - final String putString = "Put another " + chosen.getName() + " counter on " + cardWithCounter; - final String removeString = "Remove a " + chosen.getName() + " counter from " + cardWithCounter; - final String addOrRemove = SGuiChoose.one(getGui(), putOrRemoveTitle, new String[]{putString,removeString}); - - return new ImmutablePair(chosen,addOrRemove); - } - - @Override - public Pair chooseTarget(SpellAbility saSpellskite, List> allTargets) { - if (allTargets.size() < 2) { - return Iterables.getFirst(allTargets, null); - } - - final Function, String> fnToString = new Function, String>() { - @Override - public String apply(Pair targ) { - return targ.getRight().toString() + " - " + targ.getLeft().getStackDescription(); - } - }; - - List> chosen = SGuiChoose.getChoices(getGui(), saSpellskite.getHostCard().getName(), 1, 1, allTargets, null, fnToString); - return Iterables.getFirst(chosen, null); - } - - @Override - public void notifyOfValue(SpellAbility sa, GameObject realtedTarget, String value) { - String message = formatNotificationMessage(sa, realtedTarget, value); - if (sa.isManaAbility()) { - game.getGameLog().add(GameLogEntryType.LAND, message); - } else { - SGuiDialog.message(getGui(), message, sa.getHostCard() == null ? "" : sa.getHostCard().getName()); - } - } - - private String formatMessage(String message, Object related) { - if(related instanceof Player && message.indexOf("{player") >= 0) - message = message.replace("{player}", mayBeYou(related)).replace("{player's}", Lang.getPossesive(mayBeYou(related))); - - return message; - } - - // These are not much related to PlayerController - private String formatNotificationMessage(SpellAbility sa, GameObject target, String value) { - if (sa.getApi() == null || sa.getHostCard() == null) { - return ("Result: " + value); - } - switch(sa.getApi()) { - case ChooseDirection: - return value; - case ChooseNumber: - if (sa.hasParam("SecretlyChoose")) { - return value; - } - final boolean random = sa.hasParam("Random"); - return String.format(random ? "Randomly chosen number for %s is %s" : "%s choses number: %s", mayBeYou(target), value); - case FlipACoin: - String flipper = StringUtils.capitalize(mayBeYou(target)); - return sa.hasParam("NoCall") - ? String.format("%s flip comes up %s", Lang.getPossesive(flipper), value) - : String.format("%s %s the flip", flipper, Lang.joinVerb(flipper, value)); - case Protection: - String choser = StringUtils.capitalize(mayBeYou(target)); - return String.format("%s %s protection from %s", choser, Lang.joinVerb(choser, "choose"), value); - case Vote: - String chooser = StringUtils.capitalize(mayBeYou(target)); - return String.format("%s %s %s", chooser, Lang.joinVerb(chooser, "vote"), value); - default: - return String.format("%s effect's value for %s is %s", sa.getHostCard().getName(), mayBeYou(target), value); - } - } - - private String mayBeYou(Object what) { - return what == null ? "(null)" : what == player ? "you" : what.toString(); - } - - // end of not related candidates for move. - - /* (non-Javadoc) - * @see forge.game.player.PlayerController#chooseModeForAbility(forge.card.spellability.SpellAbility, java.util.List, int, int) - */ - @Override - public List chooseModeForAbility(SpellAbility sa, int min, int num) { - List choices = CharmEffect.makePossibleOptions(sa); - String modeTitle = String.format("%s activated %s - Choose a mode", sa.getActivatingPlayer(), sa.getHostCard()); - List chosen = new ArrayList(); - for (int i = 0; i < num; i++) { - AbilitySub a; - if (i < min) { - a = SGuiChoose.one(getGui(), modeTitle, choices); - } - else { - a = SGuiChoose.oneOrNone(getGui(), modeTitle, choices); - } - if (null == a) { - break; - } - - choices.remove(a); - chosen.add(a); - } - return chosen; - } - - @Override - public List chooseColors(String message, SpellAbility sa, int min, int max, List options) { - return SGuiChoose.getChoices(getGui(), message, min, max, options); - } - - @Override - public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { - int cntColors = colors.countColors(); - switch (cntColors) { - case 0: return 0; - case 1: return colors.getColor(); - default: return chooseColorCommon(message, sa == null ? null : sa.getHostCard(), colors, false); - } - } - - @Override - public byte chooseColorAllowColorless(String message, Card c, ColorSet colors) { - int cntColors = 1 + colors.countColors(); - switch (cntColors) { - case 1: return 0; - default: return chooseColorCommon(message, c, colors, true); - } - } - - private byte chooseColorCommon(String message, Card c, ColorSet colors, boolean withColorless) { - int cntColors = colors.countColors(); - if(withColorless) cntColors++; - String[] colorNames = new String[cntColors]; - int i = 0; - if(withColorless) - colorNames[i++] = MagicColor.toLongString((byte)0); - for (byte b : colors) { - colorNames[i++] = MagicColor.toLongString(b); - } - if (colorNames.length > 2) { - return MagicColor.fromName(SGuiChoose.one(getGui(), message, colorNames)); - } - int idxChosen = SGuiDialog.confirm(getGui(), getCardView(c), message, colorNames) ? 0 : 1; - return MagicColor.fromName(colorNames[idxChosen]); - } - - @Override - public PaperCard chooseSinglePaperCard(SpellAbility sa, String message, Predicate cpp, String name) { - Iterable cardsFromDb = FModel.getMagicDb().getCommonCards().getUniqueCards(); - List cards = Lists.newArrayList(Iterables.filter(cardsFromDb, cpp)); - Collections.sort(cards); - return SGuiChoose.one(getGui(), message, cards); - } - - @Override - public CounterType chooseCounterType(Collection options, SpellAbility sa, String prompt) { - if (options.size() <= 1) { - return Iterables.getFirst(options, null); - } - return SGuiChoose.one(getGui(), prompt, options); - } - - @Override - public boolean confirmPayment(CostPart costPart, String question) { - InputConfirm inp = new InputConfirm(this, question); - inp.showAndWait(); - return inp.getResult(); - } - - @Override - public ReplacementEffect chooseSingleReplacementEffect(String prompt, List possibleReplacers, HashMap runParams) { - if(possibleReplacers.size() == 1) - return possibleReplacers.get(0); - return SGuiChoose.one(getGui(), prompt, possibleReplacers); - } - - @Override - public String chooseProtectionType(String string, SpellAbility sa, List choices) { - return SGuiChoose.one(getGui(), string, choices); - } - - @Override - public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, List allPayers) { - // if it's paid by the AI already the human can pay, but it won't change anything - return HumanPlay.payCostDuringAbilityResolve(this, player, sa.getHostCard(), cost, sa, null); - } - - @Override - public void orderAndPlaySimultaneousSa(List activePlayerSAs) { - List orderedSAs = activePlayerSAs; - if (activePlayerSAs.size() > 1) { // give a dual list form to create instead of needing to do it one at a time - orderedSAs = SGuiChoose.order(getGui(), "Select order for Simultaneous Spell Abilities", "Resolve first", activePlayerSAs, null); - } - int size = orderedSAs.size(); - for (int i = size - 1; i >= 0; i--) { - SpellAbility next = orderedSAs.get(i); - if (next.isTrigger()) { - HumanPlay.playSpellAbility(this, player, next); - } else { - player.getGame().getStack().add(next); - } - } - } - - @Override - public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) { - HumanPlay.playSpellAbilityNoStack(this, player, wrapperAbility); - } - - @Override - public boolean playSaFromPlayEffect(SpellAbility tgtSA) { - HumanPlay.playSpellAbility(this, player, tgtSA); - return true; - } - - @Override - public Map chooseProliferation() { - InputProliferate inp = new InputProliferate(this); - inp.setCancelAllowed(true); - inp.showAndWait(); - if (inp.hasCancelled()) { - return null; - } - return inp.getProliferationMap(); - } - - @Override - public boolean chooseTargetsFor(SpellAbility currentAbility) { - final TargetSelection select = new TargetSelection(this, currentAbility); - return select.chooseTargets(null); - } - - @Override - public boolean chooseCardsPile(SpellAbility sa, List pile1, List pile2, boolean faceUp) { - if (!faceUp) { - final String p1Str = String.format("Pile 1 (%s cards)", pile1.size()); - final String p2Str = String.format("Pile 2 (%s cards)", pile2.size()); - final String[] possibleValues = { p1Str , p2Str }; - return SGuiDialog.confirm(getGui(), getCardView(sa.getHostCard()), "Choose a Pile", possibleValues); - } else { - final Card[] disp = new Card[pile1.size() + pile2.size() + 2]; - disp[0] = new Card(-1); - disp[0].setName("Pile 1"); - for (int i = 0; i < pile1.size(); i++) { - disp[1 + i] = pile1.get(i); - } - disp[pile1.size() + 1] = new Card(-2); - disp[pile1.size() + 1].setName("Pile 2"); - for (int i = 0; i < pile2.size(); i++) { - disp[pile1.size() + i + 2] = pile2.get(i); - } - - // make sure Pile 1 or Pile 2 is clicked on - while (true) { - final Object o = SGuiChoose.one(getGui(), "Choose a pile", disp); - final Card c = (Card) o; - String name = c.getName(); - - if (!(name.equals("Pile 1") || name.equals("Pile 2"))) { - continue; - } - - return name.equals("Pile 1"); - } - } - } - - @Override - public void revealAnte(String message, Multimap removedAnteCards) { - for (Player p : removedAnteCards.keySet()) { - SGuiChoose.reveal(getGui(), message + " from " + Lang.getPossessedObject(mayBeYou(p), "deck"), removedAnteCards.get(p)); - } - } - - @Override - public CardShields chooseRegenerationShield(Card c) { - if (c.getShield().size() < 2) { - return Iterables.getFirst(c.getShield(), null); - } - return SGuiChoose.one(getGui(), "Choose a regeneration shield:", c.getShield()); - } - - @Override - public List chooseCardsYouWonToAddToDeck(List losses) { - return SGuiChoose.many(getGui(), "Select cards to add to your deck", "Add these to my deck", 0, losses.size(), losses, null); - } - - @Override - public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt, boolean isActivatedSa) { - return HumanPlay.payManaCost(this, toPay, costPartMana, sa, player, prompt, isActivatedSa); - } - - @Override - public Map chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost, List untappedCreats) { - InputSelectCardsForConvoke inp = new InputSelectCardsForConvoke(this, player, manaCost, untappedCreats); - inp.showAndWait(); - return inp.getConvokeMap(); - } - - @Override - public String chooseCardName(SpellAbility sa, Predicate cpp, String valid, String message) { - PaperCard cp = null; - while(true) { - cp = chooseSinglePaperCard(sa, message, cpp, sa.getHostCard().getName()); - Card instanceForPlayer = Card.fromPaperCard(cp, player); // the Card instance for test needs a game to be tested - if (instanceForPlayer.isValid(valid, sa.getHostCard().getController(), sa.getHostCard())) - return cp.getName(); - } - } - - @Override - public Card chooseSingleCardForZoneChange(ZoneType destination, List origin, SpellAbility sa, List fetchList, String selectPrompt, boolean b, Player decider) { - return chooseSingleEntityForEffect(fetchList, sa, selectPrompt, b, decider); - } - - public boolean isGuiPlayer() { - return lobbyPlayer == getGui().getGuiPlayer(); - } - - /* - * What follows are the View methods. - */ - - /** Cache of players. */ - private final BiMap players - = HashBiMap.create(); - /** Cache of cards. */ - private final BiMap cards - = HashBiMap.create(); - /** Cache of spellabilities. */ - private final BiMap spabs - = HashBiMap.create(); - /** Cache of stack items. */ - private final BiMap stackItems - = HashBiMap.create(); - /** Combat view. */ - private final CombatView combatView = new CombatView(); - - /* (non-Javadoc) - * @see forge.view.IGameView#isCommander() - */ - @Override - public boolean isCommander() { - return game.getRules().hasAppliedVariant(GameType.Commander); - } - /* (non-Javadoc) - * @see forge.view.IGameView#getGameType() - */ - @Override - public GameType getGameType() { - return this.game.getMatch().getRules().getGameType(); - } - - @Override - public int getTurnNumber() { - return this.game.getPhaseHandler().getTurn(); - } - - @Override - public boolean isCommandZoneNeeded() { - return this.game.getMatch().getRules().getGameType().isCommandZoneNeeded(); - } - - @Override - public boolean isWinner(final LobbyPlayer p) { - return game.getOutcome() == null ? null : game.getOutcome().isWinner(p); - } - - @Override - public LobbyPlayer getWinningPlayer() { - return game.getOutcome() == null ? null : game.getOutcome().getWinningLobbyPlayer(); - } - - @Override - public int getWinningTeam() { - return game.getOutcome() == null ? -1 : game.getOutcome().getWinningTeam(); - } - - /* (non-Javadoc) - * @see forge.view.IGameView#isFirstGameInMatch() - */ - @Override - public boolean isFirstGameInMatch() { - return this.game.getMatch().getPlayedGames().isEmpty(); - } - - @Override - public boolean isMatchOver() { - return this.game.getMatch().isMatchOver(); - } - - @Override - public int getNumGamesInMatch() { - return this.game.getMatch().getRules().getGamesPerMatch(); - } - - @Override - public int getNumPlayedGamesInMatch() { - return this.game.getMatch().getPlayedGames().size(); - } - - @Override - public Iterable getOutcomesOfMatch() { - return Iterables.unmodifiableIterable(this.game.getMatch().getPlayedGames()); - } - - @Override - public boolean isMatchWonBy(final LobbyPlayer p) { - return this.game.getMatch().isWonBy(p); - } - - @Override - public int getGamesWonBy(LobbyPlayer p) { - return this.game.getMatch().getGamesWonBy(p); - } - - @Override - public GameOutcome.AnteResult getAnteResult() { - return this.game.getOutcome().anteResult.get(player); - } - - /* (non-Javadoc) - * @see forge.view.IGameView#isCombatDeclareAttackers() - */ - @Override - public boolean isCombatDeclareAttackers() { - return game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS) - && game.getCombat() != null; - } - - /* (non-Javadoc) - * @see forge.view.IGameView#isGameOver() - */ - @Override - public boolean isGameOver() { - return game.isGameOver(); - } - - @Override - public int getPoisonCountersToLose() { - return game.getRules().getPoisonCountersToLose(); - } - - /* (non-Javadoc) - * @see forge.view.IGameView#subscribeToEvents(java.lang.Object) - */ - @Override - public void subscribeToEvents(final Object subscriber) { - game.subscribeToEvents(subscriber); - } - - /* (non-Javadoc) - * @see forge.view.IGameView#getCombat() - */ - @Override - public CombatView getCombat(final Combat c) { - if (c == null) { - return null; - } - updateCombatView(c); - return combatView; - } - - private final void updateCombatView(final Combat combat) { - combatView.reset(); - for (final AttackingBand b : combat.getAttackingBands()) { - if (b == null) continue; - final GameEntity defender = combat.getDefenderByAttacker(b); - final List blockers = b.isBlocked() == null ? null : combat.getBlockers(b); - combatView.addAttackingBand(getCardViews(b.getAttackers()), getGameEntityView(defender), blockers == null ? null : getCardViews(blockers)); - } - } - - @Override - public void addLogObserver(final Observer o) { - game.getGameLog().addObserver(o); - } - - @Override - public List getLogEntries(final GameLogEntryType maxLogLevel) { - return game.getGameLog().getLogEntries(maxLogLevel); - } - - @Override - public List getLogEntriesExact(final GameLogEntryType logLevel) { - return game.getGameLog().getLogEntriesExact(logLevel); - } - - public boolean canUndoLastAction() { - if (!game.stack.canUndo()) { - return false; - } - final Player priorityPlayer = game.getPhaseHandler().getPriorityPlayer(); - if (priorityPlayer == null || priorityPlayer != this.player) { - return false; - } - return true; - } - - @Override - public boolean tryUndoLastAction() { - if (!canUndoLastAction()) { - return false; - } - - if (game.getStack().undo()) {//canUndoLastAction(gui) && GuiBase.getInterface().getGame().stack.undo()) { - final Input currentInput = getGui().getInputQueue().getInput(); - if (currentInput instanceof InputPassPriority) { - currentInput.showMessageInitial(); //ensure prompt updated if needed - } - return true; - } - return false; - } - - @Override - public void selectButtonOk() { - getInputProxy().selectButtonOK(); - } - - @Override - public void selectButtonCancel() { - getInputProxy().selectButtonCancel(); - } - - @Override - public void confirm() { - if (getGui().getInputQueue().getInput() instanceof InputConfirm) { - selectButtonOk(); - } - } - - @Override - public boolean passPriority() { - return getInputProxy().passPriority(); - } - - @Override - public boolean passPriorityUntilEndOfTurn() { - return getInputProxy().passPriorityUntilEndOfTurn(); - } - - @Override - public void useMana(final byte mana) { - final Input input = getGui().getInputQueue().getInput(); - if (input instanceof InputPayMana) { - ((InputPayMana) input).useManaFromPool(mana); - } - } - - @Override - public void selectPlayer(final PlayerView player, final ITriggerEvent triggerEvent) { - getInputProxy().selectPlayer(player, triggerEvent); - } - - @Override - public boolean selectCard(final CardView card, final ITriggerEvent triggerEvent) { - return getInputProxy().selectCard(card, triggerEvent); - } - - @Override - public void selectAbility(final SpellAbilityView sa) { - getInputProxy().selectAbility(sa); - } - - /* (non-Javadoc) - * @see forge.view.IGameView#getGuiRegisteredPlayer(forge.LobbyPlayer) - */ - @Override - public RegisteredPlayer getGuiRegisteredPlayer(final LobbyPlayer p) { - for (final RegisteredPlayer player : game.getMatch().getPlayers()) { - if (player.getPlayer() == p) { - return player; - } - } - return null; - } - - /* (non-Javadoc) - * @see forge.view.IGameView#getRegisteredPlayers() - */ - @Override - public List getPlayers() { - return getPlayerViews(game.getRegisteredPlayers()); - } - - @Override - public PlayerView getPlayerTurn() { - return getPlayerView(game.getPhaseHandler().getPlayerTurn()); - } - - @Override - public PhaseType getPhase() { - return game.getPhaseHandler().getPhase(); - } - - /* (non-Javadoc) - * @see forge.view.IGameView#getStack() - */ - @Override - public List getStack() { - final List items = Collections.unmodifiableList(getStack(game.getStack())); - // clear the cache - stackItems.keySet().retainAll(items); - return items; - } - - /* (non-Javadoc) - * @see forge.view.IGameView#peekStack() - */ - @Override - public StackItemView peekStack() { - final SpellAbilityStackInstance top = - Iterables.getFirst(game.getStack(), null); - if (top == null) { - return null; - } - return getStack(Lists.newArrayList(top)).iterator().next(); - } - - private List getStack(final Iterable stack) { - synchronized (this) { - stackItems.keySet().retainAll(Lists.newArrayList(stack)); - final List items = Lists.newLinkedList(); - for (final SpellAbilityStackInstance si : stack) { - if (stackItems.containsKey(si)) { - items.add(stackItems.get(si)); - } else { - items.add(getStackItemView(si)); - } - } - return items; - } - } - - @Override - public StackItemView getStackItemView(final SpellAbilityStackInstance si) { - final StackItemView newItem = new StackItemView( - si.getSpellAbility().toUnsuppressedString(), - si.getSpellAbility().getSourceTrigger(), - si.getStackDescription(), getCardView(si.getSourceCard()), - getPlayerView(si.getActivator()), getCardViews(si.getTargetChoices().getTargetCards()), - getPlayerViews(si.getTargetChoices().getTargetPlayers()), si.isAbility(), si.isOptionalTrigger()); - stackItems.put(si, newItem); - return newItem; - } - - @Override - public SpellAbilityStackInstance getStackItem(final StackItemView view) { - return stackItems.inverse().get(view); - } - - @Override - public PlayerView getPlayerView(final Player p) { - if (p == null) { - return null; - } - - final PlayerView view; - if (players.containsKey(p)) { - view = players.get(p); - getPlayerView(p, view); - } else { - view = new PlayerView(p.getLobbyPlayer(), p.getController()); - players.put(p, view); - getPlayerView(p, view); - view.setOpponents(getPlayerViews(p.getOpponents())); - } - return view; - } - - private PlayerView getPlayerViewFast(final Player p) { - return players.get(p); - } - - @Override - public Player getPlayer(final PlayerView p) { - return players.inverse().get(p); - } - - private void getPlayerView(final Player p, final PlayerView view) { - view.setCommanderInfo(CardFactoryUtil.getCommanderInfo(p).trim().replace("\r\n", "; ")); - view.setKeywords(p.getKeywords()); - view.setLife(p.getLife()); - view.setMaxHandSize(p.getMaxHandSize()); - view.setNumDrawnThisTurn(p.getNumDrawnThisTurn()); - view.setPoisonCounters(p.getPoisonCounters()); - view.setPreventNextDamage(p.getPreventNextDamageTotalShields()); - view.setHasUnlimitedHandSize(p.isUnlimitedHandSize()); - view.setAnteCards(getCardViews(p.getCardsIn(ZoneType.Ante))); - view.setBfCards(getCardViews(p.getCardsIn(ZoneType.Battlefield))); - view.setExileCards(getCardViews(p.getCardsIn(ZoneType.Exile))); - view.setFlashbackCards(getCardViews(p.getCardsActivableInExternalZones(false))); - view.setGraveCards(getCardViews(p.getCardsIn(ZoneType.Graveyard))); - view.setHandCards(getCardViews(p.getCardsIn(ZoneType.Hand))); - view.setLibraryCards(getCardViews(p.getCardsIn(ZoneType.Library))); - - for (final byte b : MagicColor.WUBRGC) { - view.setMana(b, p.getManaPool().getAmountOfColor(b)); - } - } - - public CardView getCardView(final Card c) { - if (c == null) { - return null; - } - - final Card cUi = c.getCardForUi(); - final CardView view; - if (cards.containsKey(cUi)) { - view = cards.get(cUi); - } else { - view = new CardView(cUi == c); - cards.put(cUi, view); - } - - if (mayShowCard(view)) { - writeCardToView(cUi, view); - } else { - view.reset(); - } - - return view; - } - - private CardView getCardViewFast(final Card c) { - if (c == null) { - return null; - } - - final CardView view = cards.get(c); - if (!mayShowCard(view)) { - view.reset(); - } - return view; - } - - private final Function FN_GET_CARDVIEW_FAST = new Function() { - @Override - public CardView apply(Card input) { - return getCardViewFast(input); - } - }; - - private Iterable getCardViewsFast(final Iterable cards) { - return Iterables.transform(cards, FN_GET_CARDVIEW_FAST); - } - - public Card getCard(final CardView c) { - return cards.inverse().get(c); - } - - private void writeCardToView(final Card c, final CardView view) { - // First, write the values independent of other views. - ViewUtil.writeNonDependentCardViewProperties(c, view, player); - // Next, write the values that depend on other views. - view.setOwner(getPlayerViewFast(c.getOwner())); - view.setController(getPlayerViewFast(c.getController())); - view.setAttacking(game.getCombat() != null && game.getCombat().isAttacking(c)); - view.setBlocking(game.getCombat() != null && game.getCombat().isBlocking(c)); - view.setChosenPlayer(getPlayerViewFast(c.getChosenPlayer())); - view.setEquipping(getCardViewFast(Iterables.getFirst(c.getEquipping(), null))); - view.setEquippedBy(getCardViewsFast(c.getEquippedBy())); - view.setEnchantingCard(getCardViewFast(c.getEnchantingCard())); - view.setEnchantingPlayer(getPlayerViewFast(c.getEnchantingPlayer())); - view.setEnchantedBy(getCardViewsFast(c.getEnchantedBy())); - view.setFortifiedBy(getCardViewsFast(c.getFortifiedBy())); - view.setGainControlTargets(getCardViewsFast(c.getGainControlTargets())); - view.setCloneOrigin(getCardViewFast(c.getCloneOrigin())); - view.setImprinted(getCardViewsFast(c.getImprinted())); - view.setHauntedBy(getCardViewsFast(c.getHauntedBy())); - view.setHaunting(getCardViewFast(c.getHaunting())); - view.setMustBlock(c.getMustBlockCards() == null ? Collections.emptySet() : getCardViewsFast(c.getMustBlockCards())); - view.setPairedWith(getCardViewFast(c.getPairedWith())); - } - - @Override - public boolean mayShowCard(final CardView c) { - final Card card = getCard(c); - return card == null || card.canBeShownTo(player); - } - - @Override - public SpellAbilityView getSpellAbilityView(final SpellAbility sa) { - if (sa == null) { - return null; - } - - final SpellAbilityView view; - if (spabs.containsKey(sa)) { - view = spabs.get(sa); - writeSpellAbilityToView(sa, view); - } else { - view = new SpellAbilityView(); - writeSpellAbilityToView(sa, view); - spabs.put(sa, view); - } - return view; - } - - @Override - public SpellAbility getSpellAbility(final SpellAbilityView c) { - return spabs.inverse().get(c); - } - - private void writeSpellAbilityToView(final SpellAbility sa, final SpellAbilityView view) { - view.setHostCard(getCardView(sa.getHostCard())); - view.setDescription(sa.getDescription()); - view.setCanPlay(sa.canPlay()); - view.setPromptIfOnlyPossibleAbility(sa.promptIfOnlyPossibleAbility()); - } - - @Override - public boolean getDisableAutoYields() { - return this.game.getDisableAutoYields(); - } - @Override - public void setDisableAutoYields(final boolean b) { - this.game.setDisableAutoYields(b); - } - - // Dev mode functions - @Override - public void devTogglePlayManyLands(final boolean b) { - player.canCheatPlayUnlimitedLands = b; - } - @Override - public void devGenerateMana() { - DevModeUtil.devModeGenerateMana(game, this); - } - @Override - public void devSetupGameState() { - DevModeUtil.devSetupGameState(game, this); - } - @Override - public void devTutorForCard() { - DevModeUtil.devModeTutor(game, this); - } - @Override - public void devAddCardToHand() { - DevModeUtil.devModeCardToHand(game, this); - } - @Override - public void devAddCounterToPermanent() { - DevModeUtil.devModeAddCounter(game, this); - } - @Override - public void devTapPermanent() { - DevModeUtil.devModeTapPerm(game, this); - } - @Override - public void devUntapPermanent() { - DevModeUtil.devModeUntapPerm(game, this); - } - @Override - public void devSetPlayerLife() { - DevModeUtil.devModeSetLife(game, this); - } - @Override - public void devWinGame() { - DevModeUtil.devModeWinGame(game, this); - } - @Override - public void devAddCardToBattlefield() { - DevModeUtil.devModeCardToBattlefield(game, this); - } - @Override - public void devRiggedPlanerRoll() { - DevModeUtil.devModeRiggedPlanarRoll(game, this); - } - @Override - public void devPlaneswalkTo() { - DevModeUtil.devModeRiggedPlanarRoll(game, this); - } - -} diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java index eee3396bc9a..5a8456239b8 100644 --- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java @@ -108,7 +108,7 @@ public final class ForgeConstants { EDITOR_LAYOUT_FILE = new FileLocation(_DEFAULTS_DIR, USER_PREFS_DIR, "editor.xml"); GAUNTLET_DIR = new FileLocation(_DEFAULTS_DIR, USER_DIR, "gauntlet/"); - PICS_DIR = CACHE_DIR + "pics/"; + PICS_DIR = CACHE_DIR + "pics/"; DB_DIR = CACHE_DIR + "db/"; FONTS_DIR = CACHE_DIR + "fonts/"; CACHE_TOKEN_PICS_DIR = PICS_DIR + "tokens/"; @@ -119,6 +119,7 @@ public final class ForgeConstants { CACHE_BOOSTERBOX_PICS_DIR = PICS_DIR + "boosterboxes/"; CACHE_PRECON_PICS_DIR = PICS_DIR + "precons/"; CACHE_TOURNAMENTPACK_PICS_DIR = PICS_DIR + "tournamentpacks/"; + QUEST_CARD_PRICE_FILE = DB_DIR + "all-prices.txt";; PROFILE_DIRS = new String[] { USER_DIR, diff --git a/forge-gui/src/main/java/forge/sound/EventVisualizer.java b/forge-gui/src/main/java/forge/sound/EventVisualizer.java index 06fc794b450..c1f8bc82098 100644 --- a/forge-gui/src/main/java/forge/sound/EventVisualizer.java +++ b/forge-gui/src/main/java/forge/sound/EventVisualizer.java @@ -30,7 +30,6 @@ import forge.game.event.GameEventTurnEnded; import forge.game.event.IGameEventVisitor; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; -import forge.interfaces.IGuiBase; import forge.util.maps.MapOfLists; /** @@ -40,8 +39,8 @@ import forge.util.maps.MapOfLists; public class EventVisualizer extends IGameEventVisitor.Base implements IUiEventVisitor { final LobbyPlayer player; - public EventVisualizer(final IGuiBase gui) { - this.player = gui.getGuiPlayer(); + public EventVisualizer(final LobbyPlayer lobbyPlayer) { + this.player = lobbyPlayer; } public SoundEffectType visit(GameEventCardDamaged event) { return SoundEffectType.Damage; } diff --git a/forge-gui/src/main/java/forge/sound/SoundSystem.java b/forge-gui/src/main/java/forge/sound/SoundSystem.java index e36ae011731..faf8ce04219 100644 --- a/forge-gui/src/main/java/forge/sound/SoundSystem.java +++ b/forge-gui/src/main/java/forge/sound/SoundSystem.java @@ -29,7 +29,7 @@ public class SoundSystem { public SoundSystem(final IGuiBase gui) { this.gui = gui; - this.visualizer = new EventVisualizer(gui); + this.visualizer = new EventVisualizer(gui.getGuiPlayer()); } private boolean isUsingAltSystem() { return FModel.getPreferences().getPrefBoolean(FPref.UI_ALT_SOUND_SYSTEM); diff --git a/forge-gui/src/main/java/forge/view/Cache.java b/forge-gui/src/main/java/forge/view/Cache.java new file mode 100644 index 00000000000..166dca69b6d --- /dev/null +++ b/forge-gui/src/main/java/forge/view/Cache.java @@ -0,0 +1,61 @@ +package forge.view; + +import java.util.Collection; +import java.util.Map; + +import com.google.common.collect.Maps; + +public class Cache { + + private final Map cache; + private final Map inverseCache; + public Cache() { + this.cache = Maps.newHashMap(); + this.inverseCache = Maps.newHashMap(); + } + + public boolean containsKey(final K key) { + return cache.containsKey(key); + } + + /** + * @param key + * @return + * @see java.util.Map#get(java.lang.Object) + */ + public V get(final K key) { + return cache.get(key); + } + + public K getKey(final V value) { + return inverseCache.get(value); + } + + /** + * @param key + * @param value + * @return + * @see java.util.Map#put(java.lang.Object, java.lang.Object) + */ + public void put(final K key, final V value) { + if (key == null || value == null) { + return; + } + + synchronized (this) { + if (inverseCache.containsKey(value)) { + cache.remove(inverseCache.get(value)); + inverseCache.remove(value); + } + + final V oldValue = cache.put(key, value); + inverseCache.remove(oldValue); + inverseCache.put(value, key); + } + } + + public synchronized void retainAllKeys(final Collection keys) { + cache.keySet().retainAll(keys); + inverseCache.values().retainAll(keys); + } +} diff --git a/forge-gui/src/main/java/forge/view/IGameView.java b/forge-gui/src/main/java/forge/view/IGameView.java index 30841292f5d..f0aed92bbcb 100644 --- a/forge-gui/src/main/java/forge/view/IGameView.java +++ b/forge-gui/src/main/java/forge/view/IGameView.java @@ -77,6 +77,7 @@ public interface IGameView { public abstract StackItemView peekStack(); public abstract boolean mayShowCard(CardView c); + public abstract boolean mayShowCardFace(CardView c); // Auto-yield related methods public abstract Iterable getAutoYields(); diff --git a/forge-gui/src/main/java/forge/view/LocalGameView.java b/forge-gui/src/main/java/forge/view/LocalGameView.java new file mode 100644 index 00000000000..400ab76137a --- /dev/null +++ b/forge-gui/src/main/java/forge/view/LocalGameView.java @@ -0,0 +1,671 @@ +package forge.view; + +import java.util.Collections; +import java.util.List; +import java.util.Observer; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import forge.LobbyPlayer; +import forge.card.MagicColor; +import forge.game.Game; +import forge.game.GameEntity; +import forge.game.GameLogEntry; +import forge.game.GameLogEntryType; +import forge.game.GameOutcome; +import forge.game.GameType; +import forge.game.card.Card; +import forge.game.card.CardFactoryUtil; +import forge.game.combat.AttackingBand; +import forge.game.combat.Combat; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.player.RegisteredPlayer; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.zone.ZoneType; +import forge.util.ITriggerEvent; + +public class LocalGameView implements IGameView { + + private final Game game; + public LocalGameView(final Game game) { + this.game = game; + } + + protected final Game getGame() { + return this.game; + } + + /** Cache of players. */ + private final Cache players = new Cache<>(); + /** Cache of cards. */ + private final Cache cards = new Cache<>(); + /** Cache of spellabilities. */ + private final Cache spabs = new Cache<>(); + /** Cache of stack items. */ + private final Cache stackItems = new Cache<>(); + /** Combat view. */ + private final CombatView combatView = new CombatView(); + + /* (non-Javadoc) + * @see forge.view.IGameView#isCommander() + */ + @Override + public boolean isCommander() { + return game.getRules().hasAppliedVariant(GameType.Commander); + } + /* (non-Javadoc) + * @see forge.view.IGameView#getGameType() + */ + @Override + public GameType getGameType() { + return this.game.getMatch().getRules().getGameType(); + } + + @Override + public int getTurnNumber() { + return this.game.getPhaseHandler().getTurn(); + } + + @Override + public boolean isCommandZoneNeeded() { + return this.game.getMatch().getRules().getGameType().isCommandZoneNeeded(); + } + + @Override + public boolean isWinner(final LobbyPlayer p) { + return game.getOutcome() == null ? null : game.getOutcome().isWinner(p); + } + + @Override + public LobbyPlayer getWinningPlayer() { + return game.getOutcome() == null ? null : game.getOutcome().getWinningLobbyPlayer(); + } + + @Override + public int getWinningTeam() { + return game.getOutcome() == null ? -1 : game.getOutcome().getWinningTeam(); + } + + /* (non-Javadoc) + * @see forge.view.IGameView#isFirstGameInMatch() + */ + @Override + public boolean isFirstGameInMatch() { + return this.game.getMatch().getPlayedGames().isEmpty(); + } + + @Override + public boolean isMatchOver() { + return this.game.getMatch().isMatchOver(); + } + + @Override + public int getNumGamesInMatch() { + return this.game.getMatch().getRules().getGamesPerMatch(); + } + + @Override + public int getNumPlayedGamesInMatch() { + return this.game.getMatch().getPlayedGames().size(); + } + + @Override + public Iterable getOutcomesOfMatch() { + return Iterables.unmodifiableIterable(this.game.getMatch().getPlayedGames()); + } + + @Override + public boolean isMatchWonBy(final LobbyPlayer p) { + return this.game.getMatch().isWonBy(p); + } + + @Override + public int getGamesWonBy(LobbyPlayer p) { + return this.game.getMatch().getGamesWonBy(p); + } + + @Override + public GameOutcome.AnteResult getAnteResult() { + return null; + } + + /* (non-Javadoc) + * @see forge.view.IGameView#isCombatDeclareAttackers() + */ + @Override + public boolean isCombatDeclareAttackers() { + return game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS) + && game.getCombat() != null; + } + + /* (non-Javadoc) + * @see forge.view.IGameView#isGameOver() + */ + @Override + public boolean isGameOver() { + return game.isGameOver(); + } + + @Override + public int getPoisonCountersToLose() { + return game.getRules().getPoisonCountersToLose(); + } + + /* (non-Javadoc) + * @see forge.view.IGameView#subscribeToEvents(java.lang.Object) + */ + @Override + public void subscribeToEvents(final Object subscriber) { + game.subscribeToEvents(subscriber); + } + + @Override + public CombatView getCombat() { + return getCombat(game.getCombat()); + } + + /* (non-Javadoc) + * @see forge.view.IGameView#getCombat() + */ + public CombatView getCombat(final Combat c) { + if (c == null) { + return null; + } + updateCombatView(c); + return combatView; + } + + private final void updateCombatView(final Combat combat) { + combatView.reset(); + for (final AttackingBand b : combat.getAttackingBands()) { + if (b == null) continue; + final GameEntity defender = combat.getDefenderByAttacker(b); + final List blockers = b.isBlocked() == null ? null : combat.getBlockers(b); + combatView.addAttackingBand(getCardViews(b.getAttackers()), getGameEntityView(defender), blockers == null ? null : getCardViews(blockers)); + } + } + + @Override + public void addLogObserver(final Observer o) { + game.getGameLog().addObserver(o); + } + + @Override + public List getLogEntries(final GameLogEntryType maxLogLevel) { + return game.getGameLog().getLogEntries(maxLogLevel); + } + + @Override + public List getLogEntriesExact(final GameLogEntryType logLevel) { + return game.getGameLog().getLogEntriesExact(logLevel); + } + + public boolean canUndoLastAction() { + return false; + } + + @Override + public boolean tryUndoLastAction() { + return false; + } + + @Override + public void selectButtonOk() { + } + + @Override + public void selectButtonCancel() { + } + + @Override + public void confirm() { + } + + @Override + public boolean passPriority() { + return false; + } + + @Override + public boolean passPriorityUntilEndOfTurn() { + return false; + } + + @Override + public void autoPassUntilEndOfTurn() { + } + + @Override + public void useMana(final byte mana) { + } + + @Override + public void selectPlayer(final PlayerView player, final ITriggerEvent triggerEvent) { + } + + @Override + public boolean selectCard(final CardView card, final ITriggerEvent triggerEvent) { + return false; + } + + @Override + public void selectAbility(final SpellAbilityView sa) { + } + + /* (non-Javadoc) + * @see forge.view.IGameView#getGuiRegisteredPlayer(forge.LobbyPlayer) + */ + @Override + public RegisteredPlayer getGuiRegisteredPlayer(final LobbyPlayer p) { + for (final RegisteredPlayer player : game.getMatch().getPlayers()) { + if (player.getPlayer() == p) { + return player; + } + } + return null; + } + + /* (non-Javadoc) + * @see forge.view.IGameView#getRegisteredPlayers() + */ + @Override + public List getPlayers() { + return getPlayerViews(game.getRegisteredPlayers()); + } + + @Override + public PlayerView getPlayerTurn() { + return getPlayerView(game.getPhaseHandler().getPlayerTurn()); + } + + @Override + public PhaseType getPhase() { + return game.getPhaseHandler().getPhase(); + } + + /* (non-Javadoc) + * @see forge.view.IGameView#getStack() + */ + @Override + public List getStack() { + final List stack = Lists.newArrayList(game.getStack()); + final List items = Collections.unmodifiableList(getStack(stack)); + // clear the cache + stackItems.retainAllKeys(stack); + return items; + } + + /* (non-Javadoc) + * @see forge.view.IGameView#peekStack() + */ + @Override + public StackItemView peekStack() { + final SpellAbilityStackInstance top = + Iterables.getFirst(game.getStack(), null); + if (top == null) { + return null; + } + return getStack(Lists.newArrayList(top)).iterator().next(); + } + + private List getStack(final Iterable stack) { + synchronized (this) { + stackItems.retainAllKeys(Lists.newArrayList(stack)); + final List items = Lists.newLinkedList(); + for (final SpellAbilityStackInstance si : stack) { + if (stackItems.containsKey(si)) { + items.add(stackItems.get(si)); + } else { + items.add(getStackItemView(si)); + } + } + return items; + } + } + + public StackItemView getStackItemView(final SpellAbilityStackInstance si) { + final StackItemView newItem = new StackItemView( + si.getSpellAbility().toUnsuppressedString(), + si.getSpellAbility().getSourceTrigger(), + si.getStackDescription(), getCardView(si.getSourceCard()), + getPlayerView(si.getActivator()), getCardViews(si.getTargetChoices().getTargetCards()), + getPlayerViews(si.getTargetChoices().getTargetPlayers()), si.isAbility(), si.isOptionalTrigger()); + stackItems.put(si, newItem); + return newItem; + } + + public SpellAbilityStackInstance getStackItem(final StackItemView view) { + return stackItems.getKey(view); + } + + public final GameEntityView getGameEntityView(final GameEntity e) { + if (e instanceof Card) { + return getCardView((Card)e); + } else if (e instanceof Player) { + return getPlayerView((Player)e); + } + return null; + } + + private final Function FN_GET_PLAYER_VIEW = new Function() { + @Override + public PlayerView apply(final Player input) { + return getPlayerView(input); + } + }; + + public final List getPlayerViews(final List players) { + return Lists.transform(players, FN_GET_PLAYER_VIEW); + } + + public final Iterable getPlayerViews(final Iterable players) { + return Iterables.transform(players, FN_GET_PLAYER_VIEW); + } + + public PlayerView getPlayerView(final Player p) { + if (p == null) { + return null; + } + + final PlayerView view; + if (players.containsKey(p)) { + view = players.get(p); + getPlayerView(p, view); + } else { + view = new PlayerView(p.getLobbyPlayer(), p.getController()); + players.put(p, view); + getPlayerView(p, view); + view.setOpponents(getPlayerViews(p.getOpponents())); + } + return view; + } + + private PlayerView getPlayerViewFast(final Player p) { + return players.get(p); + } + + public Player getPlayer(final PlayerView p) { + return players.getKey(p); + } + + private void getPlayerView(final Player p, final PlayerView view) { + view.setCommanderInfo(CardFactoryUtil.getCommanderInfo(p).trim().replace("\r\n", "; ")); + view.setKeywords(p.getKeywords()); + view.setLife(p.getLife()); + view.setMaxHandSize(p.getMaxHandSize()); + view.setNumDrawnThisTurn(p.getNumDrawnThisTurn()); + view.setPoisonCounters(p.getPoisonCounters()); + view.setPreventNextDamage(p.getPreventNextDamageTotalShields()); + view.setHasUnlimitedHandSize(p.isUnlimitedHandSize()); + view.setAnteCards(getCardViews(p.getCardsIn(ZoneType.Ante))); + view.setBfCards(getCardViews(p.getCardsIn(ZoneType.Battlefield))); + view.setCommandCards(getCardViews(p.getCardsIn(ZoneType.Command))); + view.setExileCards(getCardViews(p.getCardsIn(ZoneType.Exile))); + view.setFlashbackCards(getCardViews(p.getCardsActivableInExternalZones(false))); + view.setGraveCards(getCardViews(p.getCardsIn(ZoneType.Graveyard))); + view.setHandCards(getCardViews(p.getCardsIn(ZoneType.Hand))); + view.setLibraryCards(getCardViews(p.getCardsIn(ZoneType.Library))); + + for (final byte b : MagicColor.WUBRGC) { + view.setMana(b, p.getManaPool().getAmountOfColor(b)); + } + } + + public CardView getCardView(final Card c) { + if (c == null) { + return null; + } + + final Card cUi = c.getCardForUi(); + final boolean isDisplayable = cUi == c; + + CardView view = cards.get(c); + final boolean mayShow; + if (view != null) { + // Put here again to ensure the Card reference in the cache + // is not an outdated Card. + cards.put(c, view); + mayShow = mayShowCard(view); + } else { + view = new CardView(isDisplayable); + mayShow = mayShowCard(view); + if (isDisplayable && mayShow) { + cards.put(c, view); + } + } + + if (isDisplayable && mayShow) { + writeCardToView(cUi, view); + } else { + view.reset(); + } + + return view; + } + + private final Function FN_GET_CARD_VIEW = new Function() { + @Override + public CardView apply(final Card input) { + return getCardView(input); + } + }; + + public final List getCardViews(final List cards) { + return Lists.transform(cards, FN_GET_CARD_VIEW); + } + public final Iterable getCardViews(final Iterable cards) { + return Iterables.transform(cards, FN_GET_CARD_VIEW); + } + + private CardView getCardViewFast(final Card c) { + if (c == null) { + return null; + } + + final CardView view = cards.get(c); + if (!mayShowCard(view)) { + view.reset(); + } + return view; + } + + private final Function FN_GET_CARDVIEW_FAST = new Function() { + @Override + public CardView apply(Card input) { + return getCardViewFast(input); + } + }; + + private Iterable getCardViewsFast(final Iterable cards) { + return Iterables.transform(cards, FN_GET_CARDVIEW_FAST); + } + + public Card getCard(final CardView c) { + return cards.getKey(c); + } + + private final Function FN_GET_CARD = new Function() { + @Override + public Card apply(final CardView input) { + return getCard(input); + } + }; + + public final List getCards(final List cards) { + return Lists.transform(cards, FN_GET_CARD); + } + + private void writeCardToView(final Card c, final CardView view) { + // First, write the values independent of other views. + ViewUtil.writeNonDependentCardViewProperties(c, view, mayShowCardFace(view)); + // Next, write the values that depend on other views. + view.setOwner(getPlayerViewFast(c.getOwner())); + view.setController(getPlayerViewFast(c.getController())); + view.setAttacking(game.getCombat() != null && game.getCombat().isAttacking(c)); + view.setBlocking(game.getCombat() != null && game.getCombat().isBlocking(c)); + view.setChosenPlayer(getPlayerViewFast(c.getChosenPlayer())); + view.setEquipping(getCardViewFast(Iterables.getFirst(c.getEquipping(), null))); + view.setEquippedBy(getCardViewsFast(c.getEquippedBy())); + view.setEnchantingCard(getCardViewFast(c.getEnchantingCard())); + view.setEnchantingPlayer(getPlayerViewFast(c.getEnchantingPlayer())); + view.setEnchantedBy(getCardViewsFast(c.getEnchantedBy())); + view.setFortifiedBy(getCardViewsFast(c.getFortifiedBy())); + view.setGainControlTargets(getCardViewsFast(c.getGainControlTargets())); + view.setCloneOrigin(getCardViewFast(c.getCloneOrigin())); + view.setImprinted(getCardViewsFast(c.getImprinted())); + view.setHauntedBy(getCardViewsFast(c.getHauntedBy())); + view.setHaunting(getCardViewFast(c.getHaunting())); + view.setMustBlock(c.getMustBlockCards() == null ? Collections.emptySet() : getCardViewsFast(c.getMustBlockCards())); + view.setPairedWith(getCardViewFast(c.getPairedWith())); + } + + @Override + public boolean mayShowCard(final CardView c) { + return true; + } + + @Override + public boolean mayShowCardFace(final CardView c) { + return true; + } + + public SpellAbilityView getSpellAbilityView(final SpellAbility sa) { + if (sa == null) { + return null; + } + + final SpellAbilityView view; + if (spabs.containsKey(sa)) { + view = spabs.get(sa); + writeSpellAbilityToView(sa, view); + } else { + view = new SpellAbilityView(); + writeSpellAbilityToView(sa, view); + spabs.put(sa, view); + } + return view; + } + + private final Function FN_GET_SPAB_VIEW = new Function() { + @Override + public SpellAbilityView apply(final SpellAbility input) { + return getSpellAbilityView(input); + } + }; + + public final List getSpellAbilityViews(final List cards) { + return Lists.transform(cards, FN_GET_SPAB_VIEW); + } + + public SpellAbility getSpellAbility(final SpellAbilityView c) { + return spabs.getKey(c); + } + + private final Function FN_GET_SPAB = new Function() { + @Override + public SpellAbility apply(final SpellAbilityView input) { + return getSpellAbility(input); + } + }; + + public final List getSpellAbilities(final List cards) { + return Lists.transform(cards, FN_GET_SPAB); + } + + private void writeSpellAbilityToView(final SpellAbility sa, final SpellAbilityView view) { + view.setHostCard(getCardView(sa.getHostCard())); + view.setDescription(sa.getDescription()); + view.setCanPlay(sa.canPlay()); + view.setPromptIfOnlyPossibleAbility(sa.promptIfOnlyPossibleAbility()); + } + + @Override + public boolean getDisableAutoYields() { + return this.game.getDisableAutoYields(); + } + @Override + public void setDisableAutoYields(final boolean b) { + this.game.setDisableAutoYields(b); + } + + // Dev mode functions + @Override + public void devTogglePlayManyLands(final boolean b) { + } + @Override + public void devGenerateMana() { + } + @Override + public void devSetupGameState() { + } + @Override + public void devTutorForCard() { + } + @Override + public void devAddCardToHand() { + } + @Override + public void devAddCounterToPermanent() { + } + @Override + public void devTapPermanent() { + } + @Override + public void devUntapPermanent() { + } + @Override + public void devSetPlayerLife() { + } + @Override + public void devWinGame() { + } + @Override + public void devAddCardToBattlefield() { + } + @Override + public void devRiggedPlanerRoll() { + } + @Override + public void devPlaneswalkTo() { + } + + @Override + public Iterable getAutoYields() { + return null; + } + @Override + public boolean shouldAutoYield(String key) { + return false; + } + @Override + public void setShouldAutoYield(String key, boolean autoYield) { + } + @Override + public boolean shouldAlwaysAcceptTrigger(Integer trigger) { + return false; + } + @Override + public boolean shouldAlwaysDeclineTrigger(Integer trigger) { + return false; + } + @Override + public boolean shouldAlwaysAskTrigger(Integer trigger) { + return false; + } + @Override + public void setShouldAlwaysAcceptTrigger(Integer trigger) { + } + @Override + public void setShouldAlwaysDeclineTrigger(Integer trigger) { + } + @Override + public void setShouldAlwaysAskTrigger(Integer trigger) { + } + @Override + public void autoPassCancel() { + } +} diff --git a/forge-gui/src/main/java/forge/view/ViewUtil.java b/forge-gui/src/main/java/forge/view/ViewUtil.java index 3cd44f414a8..71e092874db 100644 --- a/forge-gui/src/main/java/forge/view/ViewUtil.java +++ b/forge-gui/src/main/java/forge/view/ViewUtil.java @@ -5,7 +5,6 @@ import java.util.Collections; import forge.card.CardCharacteristicName; import forge.game.card.Card; import forge.game.card.CardCharacteristics; -import forge.game.player.Player; import forge.item.IPaperCard; import forge.view.CardView.CardStateView; @@ -23,8 +22,8 @@ public final class ViewUtil { * @param view * the {@link CardView} to write to. */ - public static void writeNonDependentCardViewProperties(final Card c, final CardView view, final Player viewer) { - final boolean hasAltState = c.isDoubleFaced() || c.isFlipCard() || c.isSplitCard() || (c.isFaceDown() && (viewer == null || c.canCardFaceBeShownTo(viewer))); + public static void writeNonDependentCardViewProperties(final Card c, final CardView view, final boolean mayShowCardFace) { + final boolean hasAltState = c.isDoubleFaced() || c.isFlipCard() || c.isSplitCard() || (c.isFaceDown() && mayShowCardFace); view.setId(c.getUniqueNumber()); view.setZone(c.getZone() == null ? null : c.getZone().getZoneType()); view.setHasAltState(hasAltState); @@ -70,6 +69,10 @@ public final class ViewUtil { origView.setChangedColorWords(c.getChangedTextColorWords()); origView.setChangedTypes(c.getChangedTextTypeWords()); origView.setManaCost(c.getManaCost()); + origView.setHasDeathtouch(c.hasKeyword("Deathtouch")); + origView.setHasInfect(c.hasKeyword("Infect")); + origView.setHasStorm(c.hasKeyword("Storm")); + origView.setHasTrample(c.hasKeyword("Trample")); final CardStateView altView = view.getAlternate(); CardCharacteristicName altState = null; @@ -108,7 +111,7 @@ public final class ViewUtil { public static CardView getCardForUi(final IPaperCard pc) { final Card c = Card.getCardForUi(pc); final CardView view = new CardView(true); - writeNonDependentCardViewProperties(c, view, null); + writeNonDependentCardViewProperties(c, view, c.getCardForUi() == c); return view; } }