diff --git a/Mage.Client/src/main/java/mage/client/cards/Cards.java b/Mage.Client/src/main/java/mage/client/cards/Cards.java index cff58b56142..31a6df9fe0f 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Cards.java +++ b/Mage.Client/src/main/java/mage/client/cards/Cards.java @@ -20,7 +20,7 @@ import java.util.*; /** - * Panel for stack and hand zones + * Panel for stack and hand zones, component for lookAt and reveal windows (CardInfoWindowDialog) * * @author BetaSteward_at_googlemail.com, JayDi85 */ @@ -386,4 +386,14 @@ public void setZone(Zone zone) { this.zone = zone; } + + /** + * For GUI: get mage card components for update (example: change playable status) + * Warning, do not change the list + * + * @return + */ + public Map getMageCardsForUpdate() { + return this.cards; + } } diff --git a/Mage.Client/src/main/java/mage/client/dialog/CardInfoWindowDialog.java b/Mage.Client/src/main/java/mage/client/dialog/CardInfoWindowDialog.java index 4e9b78ed3f4..f4d178ea4fc 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/CardInfoWindowDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/CardInfoWindowDialog.java @@ -4,15 +4,13 @@ import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyVetoException; -import java.util.EnumSet; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.UUID; +import java.util.*; import javax.swing.*; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; import javax.swing.plaf.basic.BasicInternalFrameUI; +import mage.cards.MageCard; import mage.client.cards.BigCard; import mage.client.util.GUISizeHelper; import mage.client.util.ImageHelper; @@ -36,7 +34,7 @@ public class CardInfoWindowDialog extends MageDialog { private static final Logger LOGGER = Logger.getLogger(CardInfoWindowDialog.class); public enum ShowType { - REVEAL, REVEAL_TOP_LIBRARY, LOOKED_AT, EXILE, GRAVEYARD, COMPANION, OTHER + REVEAL, REVEAL_TOP_LIBRARY, LOOKED_AT, EXILE, GRAVEYARD, COMPANION, SIDEBOARD, OTHER } private final ShowType showType; @@ -91,6 +89,17 @@ public class CardInfoWindowDialog extends MageDialog { } }); break; + case SIDEBOARD: + this.setFrameIcon(new ImageIcon(ImageHelper.getImageFromResources("/info/library.png"))); + this.setClosable(true); + this.setDefaultCloseOperation(HIDE_ON_CLOSE); + this.addInternalFrameListener(new InternalFrameAdapter() { + @Override + public void internalFrameClosing(InternalFrameEvent e) { + CardInfoWindowDialog.this.hideDialog(); + } + }); + break; case EXILE: this.setFrameIcon(new ImageIcon(ImageManagerImpl.instance.getExileImage())); break; @@ -98,6 +107,7 @@ public class CardInfoWindowDialog extends MageDialog { this.setFrameIcon(new ImageIcon(ImageManagerImpl.instance.getTokenIconImage())); this.setClosable(false); break; + case OTHER: default: // no icon yet } @@ -151,15 +161,35 @@ public class CardInfoWindowDialog extends MageDialog { public void loadCards(CardsView showCards, BigCard bigCard, UUID gameId, boolean revertOrder) { cards.loadCards(showCards, bigCard, gameId, revertOrder); + + // additional info for grave windows if (showType == ShowType.GRAVEYARD) { int qty = qtyCardTypes(showCards); - String titel = name + "'s Graveyard (" + showCards.size() + ") - " + qty + ((qty == 1) ? " Card Type" : " Card Types"); - setTitle(titel); - this.setTitelBarToolTip(titel); + String newTitle = name + "'s graveyard (" + showCards.size() + ") - " + qty + ((qty == 1) ? " card type" : " card types"); + setTitle(newTitle); + this.setTitelBarToolTip(newTitle); } + + // additional info for sideboard window + if (showType == ShowType.SIDEBOARD) { + String newTitle = name + "'s sideboard"; + setTitle(newTitle); + this.setTitelBarToolTip(newTitle); + } + showAndPositionWindow(); } + /** + * For GUI: get mage card components for update (example: change playable status) + * Warning, do not change the list + * + * @return + */ + public Map getMageCardsForUpdate() { + return this.cards.getMageCardsForUpdate(); + } + @Override public void show() { if (showType == ShowType.EXILE) { diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index f33661e1226..d1bd6e8eefa 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -26,6 +26,7 @@ import mage.client.util.gui.ArrowBuilder; import mage.client.util.gui.MageDialogState; import mage.constants.*; import mage.game.events.PlayerQueryEvent; +import mage.players.PlayableObjectStats; import mage.players.PlayableObjectsList; import mage.view.*; import org.apache.log4j.Logger; @@ -76,9 +77,11 @@ public final class GamePanel extends javax.swing.JPanel { private final Map exiles = new HashMap<>(); private final Map revealed = new HashMap<>(); private final Map lookedAt = new HashMap<>(); + private final Map graveyards = new HashMap<>(); // need to sync selection private final Map graveyardWindows = new HashMap<>(); private final Map companion = new HashMap<>(); - private final Map graveyards = new HashMap<>(); + private final Map sideboards = new HashMap<>(); // need to sync selection + private final Map sideboardWindows = new HashMap<>(); private final ArrayList pickTarget = new ArrayList<>(); private final ArrayList pickPile = new ArrayList<>(); private UUID gameId; @@ -245,25 +248,29 @@ public final class GamePanel extends javax.swing.JPanel { if (pickMultiNumber != null) { pickMultiNumber.removeDialog(); } - for (CardInfoWindowDialog exileDialog : exiles.values()) { - exileDialog.cleanUp(); - exileDialog.removeDialog(); + for (CardInfoWindowDialog windowDialog : exiles.values()) { + windowDialog.cleanUp(); + windowDialog.removeDialog(); } - for (CardInfoWindowDialog graveyardDialog : graveyardWindows.values()) { - graveyardDialog.cleanUp(); - graveyardDialog.removeDialog(); + for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) { + windowDialog.cleanUp(); + windowDialog.removeDialog(); } - for (CardInfoWindowDialog revealDialog : revealed.values()) { - revealDialog.cleanUp(); - revealDialog.removeDialog(); + for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) { + windowDialog.cleanUp(); + windowDialog.removeDialog(); } - for (CardInfoWindowDialog lookedAtDialog : lookedAt.values()) { - lookedAtDialog.cleanUp(); - lookedAtDialog.removeDialog(); + for (CardInfoWindowDialog windowDialog : revealed.values()) { + windowDialog.cleanUp(); + windowDialog.removeDialog(); } - for (CardInfoWindowDialog companionDialog : companion.values()) { - companionDialog.cleanUp(); - companionDialog.removeDialog(); + for (CardInfoWindowDialog windowDialog : lookedAt.values()) { + windowDialog.cleanUp(); + windowDialog.removeDialog(); + } + for (CardInfoWindowDialog windowDialog : companion.values()) { + windowDialog.cleanUp(); + windowDialog.removeDialog(); } clearPickTargetDialogs(); @@ -307,26 +314,29 @@ public final class GamePanel extends javax.swing.JPanel { playAreaPanel.changeGUISize(); } - for (CardInfoWindowDialog cardInfoWindowDialog : exiles.values()) { - cardInfoWindowDialog.changeGUISize(); + for (CardInfoWindowDialog windowDialog : exiles.values()) { + windowDialog.changeGUISize(); } - for (CardInfoWindowDialog cardInfoWindowDialog : revealed.values()) { - cardInfoWindowDialog.changeGUISize(); + for (CardInfoWindowDialog windowDialog : revealed.values()) { + windowDialog.changeGUISize(); } - for (CardInfoWindowDialog cardInfoWindowDialog : lookedAt.values()) { - cardInfoWindowDialog.changeGUISize(); + for (CardInfoWindowDialog windowDialog : lookedAt.values()) { + windowDialog.changeGUISize(); } - for (CardInfoWindowDialog cardInfoWindowDialog : companion.values()) { - cardInfoWindowDialog.changeGUISize(); + for (CardInfoWindowDialog windowDialog : companion.values()) { + windowDialog.changeGUISize(); } - for (CardInfoWindowDialog cardInfoWindowDialog : graveyardWindows.values()) { - cardInfoWindowDialog.changeGUISize(); + for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) { + windowDialog.changeGUISize(); } - for (ShowCardsDialog showCardsDialog : pickTarget) { - showCardsDialog.changeGUISize(); + for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) { + windowDialog.changeGUISize(); } - for (PickPileDialog pickPileDialog : pickPile) { - pickPileDialog.changeGUISize(); + for (ShowCardsDialog windowDialog : pickTarget) { + windowDialog.changeGUISize(); + } + for (PickPileDialog windowDialog : pickPile) { + windowDialog.changeGUISize(); } this.revalidate(); @@ -574,7 +584,7 @@ public final class GamePanel extends javax.swing.JPanel { } PlayerView player = game.getPlayers().get(playerSeat); PlayAreaPanel playAreaPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this, - new PlayAreaPanelOptions(game.isPlayer(), game.isPlayer(), game.isRollbackTurnsAllowed(), row == 0)); + new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), game.isPlayer(), game.isRollbackTurnsAllowed(), row == 0)); players.put(player.getPlayerId(), playAreaPanel); playersWhoLeft.put(player.getPlayerId(), false); GridBagConstraints c = new GridBagConstraints(); @@ -618,7 +628,7 @@ public final class GamePanel extends javax.swing.JPanel { } player = game.getPlayers().get(playerNum); PlayAreaPanel playerPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this, - new PlayAreaPanelOptions(game.isPlayer(), false, game.isRollbackTurnsAllowed(), row == 0)); + new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), false, game.isRollbackTurnsAllowed(), row == 0)); players.put(player.getPlayerId(), playerPanel); playersWhoLeft.put(player.getPlayerId(), false); c = new GridBagConstraints(); @@ -789,16 +799,29 @@ public final class GamePanel extends javax.swing.JPanel { if (player.getPlayerId().equals(playerId)) { skipButtons.updateFromPlayer(player); } + // update open or remove closed graveyard windows graveyards.put(player.getName(), player.getGraveyard()); if (graveyardWindows.containsKey(player.getName())) { - CardInfoWindowDialog cardInfoWindowDialog = graveyardWindows.get(player.getName()); - if (cardInfoWindowDialog.isClosed()) { + CardInfoWindowDialog windowDialog = graveyardWindows.get(player.getName()); + if (windowDialog.isClosed()) { graveyardWindows.remove(player.getName()); } else { - cardInfoWindowDialog.loadCards(player.getGraveyard(), bigCard, gameId, false); + windowDialog.loadCards(player.getGraveyard(), bigCard, gameId, false); } } + + // update open or remove closed sideboard windows + sideboards.put(player.getName(), player.getSideboard()); + if (sideboardWindows.containsKey(player.getName())) { + CardInfoWindowDialog windowDialog = sideboardWindows.get(player.getName()); + if (windowDialog.isClosed()) { + sideboardWindows.remove(player.getName()); + } else { + windowDialog.loadCards(player.getSideboard(), bigCard, gameId, false); + } + } + // show top card window if (player.getTopCard() != null) { CardsView cardsView = new CardsView(); @@ -851,8 +874,12 @@ public final class GamePanel extends javax.swing.JPanel { exiles.get(exile.getId()).loadCards(exile, bigCard, gameId); } + // reveal and look at dialogs can unattached, so windows opened by game doesn't have it showRevealed(lastGameData.game); showLookedAt(lastGameData.game); + + // sideboard dialogs is unattached all the time -- user opens it by command + showCompanion(lastGameData.game); if (!lastGameData.game.getCombat().isEmpty()) { CombatManager.instance.showCombat(lastGameData.game.getCombat(), gameId); @@ -1146,40 +1173,46 @@ public final class GamePanel extends javax.swing.JPanel { // Called if the game frame is deactivated because the tabled the deck editor or other frames go to foreground public void deactivated() { // hide the non modal windows (because otherwise they are shown on top of the new active pane) - for (CardInfoWindowDialog exileDialog : exiles.values()) { - exileDialog.hideDialog(); + for (CardInfoWindowDialog windowDialog : exiles.values()) { + windowDialog.hideDialog(); } - for (CardInfoWindowDialog graveyardDialog : graveyardWindows.values()) { - graveyardDialog.hideDialog(); + for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) { + windowDialog.hideDialog(); } - for (CardInfoWindowDialog revealDialog : revealed.values()) { - revealDialog.hideDialog(); + for (CardInfoWindowDialog windowDialog : revealed.values()) { + windowDialog.hideDialog(); } - for (CardInfoWindowDialog lookedAtDialog : lookedAt.values()) { - lookedAtDialog.hideDialog(); + for (CardInfoWindowDialog windowDialog : lookedAt.values()) { + windowDialog.hideDialog(); } - for (CardInfoWindowDialog companionDialog : companion.values()) { - companionDialog.hideDialog(); + for (CardInfoWindowDialog windowDialog : companion.values()) { + windowDialog.hideDialog(); + } + for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) { + windowDialog.hideDialog(); } } // Called if the game frame comes to front again public void activated() { // hide the non modal windows (because otherwise they are shown on top of the new active pane) - for (CardInfoWindowDialog exileDialog : exiles.values()) { - exileDialog.show(); + for (CardInfoWindowDialog windowDialog : exiles.values()) { + windowDialog.show(); } - for (CardInfoWindowDialog graveyardDialog : graveyardWindows.values()) { - graveyardDialog.show(); + for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) { + windowDialog.show(); } - for (CardInfoWindowDialog revealDialog : revealed.values()) { - revealDialog.show(); + for (CardInfoWindowDialog windowDialog : revealed.values()) { + windowDialog.show(); } - for (CardInfoWindowDialog lookedAtDialog : lookedAt.values()) { - lookedAtDialog.show(); + for (CardInfoWindowDialog windowDialog : lookedAt.values()) { + windowDialog.show(); } - for (CardInfoWindowDialog companionDialog : companion.values()) { - companionDialog.show(); + for (CardInfoWindowDialog windowDialog : companion.values()) { + windowDialog.show(); + } + for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) { + windowDialog.show(); } } @@ -1196,9 +1229,40 @@ public final class GamePanel extends javax.swing.JPanel { CardInfoWindowDialog newGraveyard = new CardInfoWindowDialog(ShowType.GRAVEYARD, playerName); graveyardWindows.put(playerName, newGraveyard); MageFrame.getDesktop().add(newGraveyard, JLayeredPane.PALETTE_LAYER); + // use graveyards to sync selection (don't use player data here) newGraveyard.loadCards(graveyards.get(playerName), bigCard, gameId, false); } + public void openSideboardWindow(UUID playerId) { + if (lastGameData == null) { + return; + } + + PlayerView playerView = lastGameData.game.getPlayers().stream() + .filter(p -> p.getPlayerId().equals(playerId)) + .findFirst() + .orElse(null); + if (playerView == null) { + return; + } + + if (sideboardWindows.containsKey(playerView.getName())) { + CardInfoWindowDialog windowDialog = sideboardWindows.get(playerView.getName()); + if (windowDialog.isVisible()) { + windowDialog.hideDialog(); + } else { + windowDialog.show(); + } + return; + } + + CardInfoWindowDialog windowDialog = new CardInfoWindowDialog(ShowType.SIDEBOARD, playerView.getName()); + sideboardWindows.put(playerView.getName(), windowDialog); + MageFrame.getDesktop().add(windowDialog, JLayeredPane.PALETTE_LAYER); + // use sideboards to sync selection (don't use player data here) + windowDialog.loadCards(sideboards.get(playerView.getName()), bigCard, gameId, false); + } + public void openTopLibraryWindow(String playerName) { String title = playerName + "'s top library card"; if (revealed.containsKey(title)) { @@ -1386,6 +1450,25 @@ public final class GamePanel extends javax.swing.JPanel { } } + // sideboard + if (needZone == Zone.OUTSIDE || needZone == Zone.ALL) { + for (PlayerView player : lastGameData.game.getPlayers()) { + for (Map.Entry card : player.getSideboard().entrySet()) { + if (needSelectable.contains(card.getKey())) { + card.getValue().setChoosable(true); + } + if (needChoosen.contains(card.getKey())) { + card.getValue().setSelected(true); + } + if (needPlayable.containsObject(card.getKey())) { + card.getValue().setPlayableStats(needPlayable.getStats(card.getKey())); + } + } + } + } + // sideboards (old windows all the time, e.g. unattached from game data) + prepareSelectableWindows(sideboardWindows.values(), needSelectable, needChoosen, needPlayable); + // exile if (needZone == Zone.EXILED || needZone == Zone.ALL) { // exile from player panel @@ -1402,6 +1485,7 @@ public final class GamePanel extends javax.swing.JPanel { } } } + // exile from windows for (ExileView exile : lastGameData.game.getExile()) { for (Map.Entry card : exile.entrySet()) { @@ -1435,21 +1519,6 @@ public final class GamePanel extends javax.swing.JPanel { } } - // revealed - for (RevealedView rev : lastGameData.game.getRevealed()) { - for (Map.Entry card : rev.getCards().entrySet()) { - if (needSelectable.contains(card.getKey())) { - card.getValue().setChoosable(true); - } - if (needChoosen.contains(card.getKey())) { - card.getValue().setSelected(true); - } - if (needPlayable.containsObject(card.getKey())) { - card.getValue().setPlayableStats(needPlayable.getStats(card.getKey())); - } - } - } - // companion for (RevealedView rev : lastGameData.game.getCompanion()) { for (Map.Entry card : rev.getCards().entrySet()) { @@ -1465,7 +1534,24 @@ public final class GamePanel extends javax.swing.JPanel { } } - // looked at + // revealed (current cards) + for (RevealedView rev : lastGameData.game.getRevealed()) { + for (Map.Entry card : rev.getCards().entrySet()) { + if (needSelectable.contains(card.getKey())) { + card.getValue().setChoosable(true); + } + if (needChoosen.contains(card.getKey())) { + card.getValue().setSelected(true); + } + if (needPlayable.containsObject(card.getKey())) { + card.getValue().setPlayableStats(needPlayable.getStats(card.getKey())); + } + } + } + // revealed (old windows) + prepareSelectableWindows(revealed.values(), needSelectable, needChoosen, needPlayable); + + // looked at (current cards) for (LookedAtView look : lastGameData.game.getLookedAt()) { for (Map.Entry card : look.getCards().entrySet()) { if (needPlayable.containsObject(card.getKey())) { @@ -1473,6 +1559,32 @@ public final class GamePanel extends javax.swing.JPanel { } } } + // looked at (old windows) + prepareSelectableWindows(lookedAt.values(), needSelectable, needChoosen, needPlayable); + } + + private void prepareSelectableWindows( + Collection windows, + Set needSelectable, + List needChoosen, + PlayableObjectsList needPlayable + ) { + // lookAt or reveals windows clean up on next priority, so users can see dialogs, but xmage can't restore it + // so it must be updated manually (it's ok to keep outdated cards in dialog, but not ok to show wrong selections) + for (CardInfoWindowDialog window : windows) { + for (MageCard mageCard : window.getMageCardsForUpdate().values()) { + CardView cardView = mageCard.getOriginal(); + cardView.setChoosable(needSelectable.contains(cardView.getId())); + cardView.setSelected(needChoosen.contains(cardView.getId())); + if (needPlayable.containsObject(cardView.getId())) { + cardView.setPlayableStats(needPlayable.getStats(cardView.getId())); + } else { + cardView.setPlayableStats(new PlayableObjectStats()); + } + // TODO: little bug with toggled night card after update/clicks, but that's ok (can't click on second side) + mageCard.update(cardView); + } + } } /** diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.form b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.form deleted file mode 100644 index 6c0820794f3..00000000000 --- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.form +++ /dev/null @@ -1,89 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java index 6d6ead40a66..fbde9c3a336 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java @@ -24,6 +24,8 @@ import java.util.UUID; import static mage.client.dialog.PreferencesDialog.*; /** + * GUI: play area panel (player with avatar/mana panel + battlefield panel) + * * @author BetaSteward_at_googlemail.com */ public class PlayAreaPanel extends javax.swing.JPanel { @@ -441,14 +443,28 @@ public class PlayAreaPanel extends javax.swing.JPanel { popupMenu.addSeparator(); - menuItem = new JMenuItem("View current deck"); - menuItem.setMnemonic(KeyEvent.VK_V); + // view deck + menuItem = new JMenuItem("View player's deck"); + menuItem.setMnemonic(KeyEvent.VK_D); popupMenu.add(menuItem); - - // View limited deck menuItem.addActionListener(e -> { SessionHandler.sendPlayerAction(PlayerAction.VIEW_LIMITED_DECK, gameId, null); }); + + // view sideboard (allows to view only own sideboard or computer) + // it's a client side checks... same checks must be on server side too (see PlayerView) + if (options.playerItself || !options.isHuman) { + String menuCaption = "View my sideboard"; + if (!options.isHuman) { + menuCaption = "View computer's sideboard"; + } + menuItem = new JMenuItem(menuCaption); + menuItem.setMnemonic(KeyEvent.VK_S); + popupMenu.add(menuItem); + menuItem.addActionListener(e -> { + SessionHandler.sendPlayerAction(PlayerAction.VIEW_SIDEBOARD, gameId, playerId); + }); + } } private void addPopupMenuWatcher() { diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanelOptions.java b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanelOptions.java index 32c3927727e..164d6d4a414 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanelOptions.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanelOptions.java @@ -8,8 +8,9 @@ package mage.client.game; */ public class PlayAreaPanelOptions { - public PlayAreaPanelOptions(boolean isPlayer, boolean playerItself, boolean rollbackTurnsAllowed, boolean topRow) { + public PlayAreaPanelOptions(boolean isPlayer, boolean isHuman, boolean playerItself, boolean rollbackTurnsAllowed, boolean topRow) { this.isPlayer = isPlayer; + this.isHuman = isHuman; this.playerItself = playerItself; this.rollbackTurnsAllowed = rollbackTurnsAllowed; this.topRow = topRow; @@ -18,22 +19,27 @@ public class PlayAreaPanelOptions { /** * true if the client is a player / false if the client is a watcher */ - public boolean isPlayer = false; + public boolean isPlayer; + + /** + * true if the player is the human, not computer + */ + public boolean isHuman; /** * true if the player is the client player itself, false if the player is - * another player playing with the clinet player + * another player playing with the client player */ - public boolean playerItself = false; + public boolean playerItself; /** * true if the player can rollback turns if all players agree */ - public boolean rollbackTurnsAllowed = false; + public boolean rollbackTurnsAllowed; /** * true if the battlefield is on the top row of player areas */ - public boolean topRow = false; + public boolean topRow; } diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 8b7e42099dd..e256f7307c2 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -391,6 +391,12 @@ public class CallbackClientImpl implements CallbackClient { break; } + case VIEW_SIDEBOARD: { + TableClientMessage message = (TableClientMessage) callback.getData(); + viewSideboard(message.getGameId(), message.getPlayerId()); + break; + } + case CONSTRUCT: { TableClientMessage message = (TableClientMessage) callback.getData(); DeckView deckView = message.getDeck(); @@ -616,6 +622,15 @@ public class CallbackClientImpl implements CallbackClient { frame.showDeckEditor(DeckEditorMode.VIEW_LIMITED_DECK, deck, tableId, time); } + protected void viewSideboard(UUID gameId, UUID playerId) { + SwingUtilities.invokeLater(() -> { + GamePanel panel = MageFrame.getGame(gameId); + if (panel != null) { + panel.openSideboardWindow(playerId); + } + }); + } + private void handleException(Exception ex) { logger.fatal("Client error\n", ex); String errorMessage = ex.getMessage(); diff --git a/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallbackMethod.java b/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallbackMethod.java index 5fff7629ebd..53ec476e7fe 100644 --- a/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallbackMethod.java +++ b/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallbackMethod.java @@ -14,6 +14,7 @@ public enum ClientCallbackMethod { START_TOURNAMENT("startTournament"), SIDEBOARD("sideboard"), VIEW_LIMITED_DECK("viewLimitedDeck"), + VIEW_SIDEBOARD("viewSideboard"), CONSTRUCT("construct"), SHOW_USERMESSAGE("showUserMessage"), WATCHGAME("watchGame"), diff --git a/Mage.Common/src/main/java/mage/view/PlayerView.java b/Mage.Common/src/main/java/mage/view/PlayerView.java index 0d0f4336057..30784ba083b 100644 --- a/Mage.Common/src/main/java/mage/view/PlayerView.java +++ b/Mage.Common/src/main/java/mage/view/PlayerView.java @@ -24,6 +24,7 @@ public class PlayerView implements Serializable { private final UUID playerId; private final String name; private final boolean controlled; // gui: player is current user + private final boolean isHuman; // human or computer private final int life; private final Counters counters; private final int wins; @@ -38,6 +39,7 @@ public class PlayerView implements Serializable { private final ManaPoolView manaPool; private final CardsView graveyard = new CardsView(); private final CardsView exile = new CardsView(); + private final CardsView sideboard = new CardsView(); private final Map battlefield = new LinkedHashMap<>(); private final CardView topCard; private final UserData userData; @@ -58,6 +60,7 @@ public class PlayerView implements Serializable { this.playerId = player.getId(); this.name = player.getName(); this.controlled = player.getId().equals(createdForPlayerId); + this.isHuman = player.isHuman(); this.life = player.getLife(); this.counters = player.getCounters(); this.wins = player.getMatchPlayer().getWins(); @@ -85,6 +88,13 @@ public class PlayerView implements Serializable { } } } + if (this.controlled || !player.isHuman()) { + // sideboard available for itself or for computer only + for (Card card : player.getSideboard().getCards(game)) { + sideboard.put(card.getId(), new CardView(card, game, false)); + } + } + try { for (Permanent permanent : state.getBattlefield().getAllPermanents()) { if (showInBattlefield(permanent, state)) { @@ -167,6 +177,10 @@ public class PlayerView implements Serializable { return this.controlled; } + public boolean isHuman() { + return this.isHuman; + } + public int getLife() { return this.life; } @@ -207,6 +221,10 @@ public class PlayerView implements Serializable { return exile; } + public CardsView getSideboard() { + return this.sideboard; + } + public Map getBattlefield() { return this.battlefield; } diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index cb0dba3047b..bf7f025da4a 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -259,6 +259,10 @@ public class User { fireCallback(new ClientCallback(ClientCallbackMethod.VIEW_LIMITED_DECK, tableId, new TableClientMessage(deck, tableId, time, limited))); } + public void ccViewSideboard(final UUID tableId, final UUID gameId, final UUID targetPlayerId) { + fireCallback(new ClientCallback(ClientCallbackMethod.VIEW_SIDEBOARD, tableId, new TableClientMessage(gameId, targetPlayerId))); + } + public void ccConstruct(final Deck deck, final UUID tableId, final int time) { fireCallback(new ClientCallback(ClientCallbackMethod.CONSTRUCT, tableId, new TableClientMessage(deck, tableId, time))); } diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 8317ea91cd1..59b7ca32514 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -598,6 +598,12 @@ public class GameController implements GameCallback { case VIEW_LIMITED_DECK: viewLimitedDeck(getPlayerId(userId), userId); break; + case VIEW_SIDEBOARD: + if (data instanceof UUID) { + UUID targetPlayerId = (UUID) data; + viewSideboard(getPlayerId(userId), userId, targetPlayerId); + } + break; default: game.sendPlayerAction(playerAction, getPlayerId(userId), data); } @@ -656,13 +662,13 @@ public class GameController implements GameCallback { } } - private void viewLimitedDeck(UUID userIdRequester, UUID origId) { - Player viewLimitedDeckPlayer = game.getPlayer(userIdRequester); + private void viewLimitedDeck(UUID playerId, UUID userId) { + Player viewLimitedDeckPlayer = game.getPlayer(playerId); if (viewLimitedDeckPlayer != null) { if (viewLimitedDeckPlayer.isHuman()) { for (MatchPlayer p : managerFactory.tableManager().getTable(tableId).getMatch().getPlayers()) { - if (p.getPlayer().getId().equals(userIdRequester)) { - Optional u = managerFactory.userManager().getUser(origId); + if (p.getPlayer().getId().equals(playerId)) { + Optional u = managerFactory.userManager().getUser(userId); if (u.isPresent() && p.getDeck() != null) { u.get().ccViewLimitedDeck(p.getDeck(), tableId, requestsOpen, true); } @@ -672,6 +678,18 @@ public class GameController implements GameCallback { } } + private void viewSideboard(UUID playerId, UUID userId, UUID targetPlayerId) { + Player needPlayer = game.getPlayer(playerId); + if (needPlayer != null && needPlayer.isHuman()) { + for (MatchPlayer p : managerFactory.tableManager().getTable(tableId).getMatch().getPlayers()) { + if (p.getPlayer().getId().equals(playerId)) { + Optional u = managerFactory.userManager().getUser(userId); + u.ifPresent(user -> user.ccViewSideboard(tableId, game.getId(), targetPlayerId)); + } + } + } + } + public void cheat(UUID userId, UUID playerId, DeckCardLists deckList) { try { Deck deck = Deck.load(deckList, false, false); diff --git a/Mage.Sets/src/mage/cards/a/AcademicDispute.java b/Mage.Sets/src/mage/cards/a/AcademicDispute.java index 2e35b10d99f..580f41daeb8 100644 --- a/Mage.Sets/src/mage/cards/a/AcademicDispute.java +++ b/Mage.Sets/src/mage/cards/a/AcademicDispute.java @@ -7,6 +7,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.combat.BlocksIfAbleTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -34,6 +35,7 @@ public final class AcademicDispute extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private AcademicDispute(final AcademicDispute card) { diff --git a/Mage.Sets/src/mage/cards/a/ArcaneSubtraction.java b/Mage.Sets/src/mage/cards/a/ArcaneSubtraction.java index 91310eeee2a..ea2f815b30b 100644 --- a/Mage.Sets/src/mage/cards/a/ArcaneSubtraction.java +++ b/Mage.Sets/src/mage/cards/a/ArcaneSubtraction.java @@ -2,6 +2,7 @@ package mage.cards.a; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,6 +24,7 @@ public final class ArcaneSubtraction extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private ArcaneSubtraction(final ArcaneSubtraction card) { diff --git a/Mage.Sets/src/mage/cards/b/BurningWish.java b/Mage.Sets/src/mage/cards/b/BurningWish.java index 1d2bf6d72b0..39f191083a5 100644 --- a/Mage.Sets/src/mage/cards/b/BurningWish.java +++ b/Mage.Sets/src/mage/cards/b/BurningWish.java @@ -4,6 +4,7 @@ package mage.cards.b; import java.util.UUID; import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -26,6 +27,7 @@ public final class BurningWish extends CardImpl { // You may choose a sorcery card you own from outside the game, reveal that card, and put it into your hand. this.getSpellAbility().addEffect(new WishEffect(filter)); + this.getSpellAbility().addHint(OpenSideboardHint.instance); // Exile Burning Wish. this.getSpellAbility().addEffect(new ExileSpellEffect()); diff --git a/Mage.Sets/src/mage/cards/c/CoaxFromTheBlindEternities.java b/Mage.Sets/src/mage/cards/c/CoaxFromTheBlindEternities.java index cf2975bbf43..8c94e731250 100644 --- a/Mage.Sets/src/mage/cards/c/CoaxFromTheBlindEternities.java +++ b/Mage.Sets/src/mage/cards/c/CoaxFromTheBlindEternities.java @@ -3,6 +3,7 @@ package mage.cards.c; import java.util.UUID; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -26,6 +27,7 @@ public final class CoaxFromTheBlindEternities extends CardImpl { // You may choose an Eldrazi card you own from outside the game or in exile, reveal that card, and put it into your hand. this.getSpellAbility().addEffect(new WishEffect(filter, true, true)); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private CoaxFromTheBlindEternities(final CoaxFromTheBlindEternities card) { diff --git a/Mage.Sets/src/mage/cards/c/CramSession.java b/Mage.Sets/src/mage/cards/c/CramSession.java index fd6f3574255..dec38d8d71b 100644 --- a/Mage.Sets/src/mage/cards/c/CramSession.java +++ b/Mage.Sets/src/mage/cards/c/CramSession.java @@ -2,6 +2,7 @@ package mage.cards.c; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -21,6 +22,7 @@ public final class CramSession extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private CramSession(final CramSession card) { diff --git a/Mage.Sets/src/mage/cards/c/CunningWish.java b/Mage.Sets/src/mage/cards/c/CunningWish.java index cc61a71f38a..1b085f99d38 100644 --- a/Mage.Sets/src/mage/cards/c/CunningWish.java +++ b/Mage.Sets/src/mage/cards/c/CunningWish.java @@ -4,6 +4,7 @@ package mage.cards.c; import java.util.UUID; import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -26,6 +27,7 @@ public final class CunningWish extends CardImpl { // You may choose an instant card you own from outside the game, reveal that card, and put it into your hand. this.getSpellAbility().addEffect(new WishEffect(filter)); + this.getSpellAbility().addHint(OpenSideboardHint.instance); // Exile Cunning Wish. this.getSpellAbility().addEffect(new ExileSpellEffect()); diff --git a/Mage.Sets/src/mage/cards/d/DeathWish.java b/Mage.Sets/src/mage/cards/d/DeathWish.java index 6b133b73fae..526112d3cf8 100644 --- a/Mage.Sets/src/mage/cards/d/DeathWish.java +++ b/Mage.Sets/src/mage/cards/d/DeathWish.java @@ -3,6 +3,7 @@ package mage.cards.d; import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.LoseHalfLifeEffect; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -20,6 +21,7 @@ public final class DeathWish extends CardImpl { // You may choose a card you own from outside the game and put it into your hand. this.getSpellAbility().addEffect(new WishEffect(StaticFilters.FILTER_CARD_A, false)); + this.getSpellAbility().addHint(OpenSideboardHint.instance); // You lose half your life, rounded up. this.getSpellAbility().addEffect(new LoseHalfLifeEffect()); diff --git a/Mage.Sets/src/mage/cards/d/DivideByZero.java b/Mage.Sets/src/mage/cards/d/DivideByZero.java index 4852b204f0e..5a8161ecff4 100644 --- a/Mage.Sets/src/mage/cards/d/DivideByZero.java +++ b/Mage.Sets/src/mage/cards/d/DivideByZero.java @@ -2,6 +2,7 @@ package mage.cards.d; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -37,6 +38,7 @@ public final class DivideByZero extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private DivideByZero(final DivideByZero card) { diff --git a/Mage.Sets/src/mage/cards/d/DreamStrix.java b/Mage.Sets/src/mage/cards/d/DreamStrix.java index 68382471fbe..f0a4a8c08c2 100644 --- a/Mage.Sets/src/mage/cards/d/DreamStrix.java +++ b/Mage.Sets/src/mage/cards/d/DreamStrix.java @@ -6,6 +6,7 @@ import mage.abilities.common.BecomesTargetTriggeredAbility; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -36,7 +37,8 @@ public final class DreamStrix extends CardImpl { )); // When Dream Strix dies, learn. - this.addAbility(new DiesSourceTriggeredAbility(new LearnEffect())); + this.addAbility(new DiesSourceTriggeredAbility(new LearnEffect()) + .addHint(OpenSideboardHint.instance)); } private DreamStrix(final DreamStrix card) { diff --git a/Mage.Sets/src/mage/cards/e/EnthusiasticStudy.java b/Mage.Sets/src/mage/cards/e/EnthusiasticStudy.java index 6a283b1a70d..c08e92fa4b8 100644 --- a/Mage.Sets/src/mage/cards/e/EnthusiasticStudy.java +++ b/Mage.Sets/src/mage/cards/e/EnthusiasticStudy.java @@ -3,6 +3,7 @@ package mage.cards.e; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -31,6 +32,7 @@ public final class EnthusiasticStudy extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private EnthusiasticStudy(final EnthusiasticStudy card) { diff --git a/Mage.Sets/src/mage/cards/e/Eyetwitch.java b/Mage.Sets/src/mage/cards/e/Eyetwitch.java index 335438d0d14..3927db7680c 100644 --- a/Mage.Sets/src/mage/cards/e/Eyetwitch.java +++ b/Mage.Sets/src/mage/cards/e/Eyetwitch.java @@ -3,6 +3,7 @@ package mage.cards.e; import mage.MageInt; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -28,7 +29,8 @@ public final class Eyetwitch extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Eyetwitch dies, learn. - this.addAbility(new DiesSourceTriggeredAbility(new LearnEffect())); + this.addAbility(new DiesSourceTriggeredAbility(new LearnEffect()) + .addHint(OpenSideboardHint.instance)); } private Eyetwitch(final Eyetwitch card) { diff --git a/Mage.Sets/src/mage/cards/f/FaeOfWishes.java b/Mage.Sets/src/mage/cards/f/FaeOfWishes.java index 473bcb4a062..ab1a4ce4652 100644 --- a/Mage.Sets/src/mage/cards/f/FaeOfWishes.java +++ b/Mage.Sets/src/mage/cards/f/FaeOfWishes.java @@ -7,6 +7,7 @@ import mage.abilities.costs.common.DiscardTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.ReturnToHandSourceEffect; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.AdventureCard; import mage.cards.CardSetInfo; @@ -46,6 +47,7 @@ public final class FaeOfWishes extends AdventureCard { // Granted // You may choose a noncreature card you own from outside the game, reveal it, and put it into your hand. this.getSpellCard().getSpellAbility().addEffect(new WishEffect(StaticFilters.FILTER_CARD_A_NON_CREATURE)); + this.getSpellCard().getSpellAbility().addHint(OpenSideboardHint.instance); } private FaeOfWishes(final FaeOfWishes card) { diff --git a/Mage.Sets/src/mage/cards/f/FieldTrip.java b/Mage.Sets/src/mage/cards/f/FieldTrip.java index a76ce68b19d..34cd8fc8709 100644 --- a/Mage.Sets/src/mage/cards/f/FieldTrip.java +++ b/Mage.Sets/src/mage/cards/f/FieldTrip.java @@ -2,6 +2,7 @@ package mage.cards.f; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -32,6 +33,7 @@ public final class FieldTrip extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private FieldTrip(final FieldTrip card) { diff --git a/Mage.Sets/src/mage/cards/f/FirstDayOfClass.java b/Mage.Sets/src/mage/cards/f/FirstDayOfClass.java index 48c8052b6bb..14562d20848 100644 --- a/Mage.Sets/src/mage/cards/f/FirstDayOfClass.java +++ b/Mage.Sets/src/mage/cards/f/FirstDayOfClass.java @@ -5,6 +5,7 @@ import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -32,6 +33,7 @@ public final class FirstDayOfClass extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private FirstDayOfClass(final FirstDayOfClass card) { diff --git a/Mage.Sets/src/mage/cards/g/GlitteringWish.java b/Mage.Sets/src/mage/cards/g/GlitteringWish.java index 9a68a3525a0..4faed702145 100644 --- a/Mage.Sets/src/mage/cards/g/GlitteringWish.java +++ b/Mage.Sets/src/mage/cards/g/GlitteringWish.java @@ -4,6 +4,7 @@ package mage.cards.g; import java.util.UUID; import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -27,6 +28,7 @@ public final class GlitteringWish extends CardImpl { // You may choose a multicolored card you own from outside the game, reveal that card, and put it into your hand. this.getSpellAbility().addEffect(new WishEffect(filter)); + this.getSpellAbility().addHint(OpenSideboardHint.instance); // Exile Glittering Wish. this.getSpellAbility().addEffect(new ExileSpellEffect()); diff --git a/Mage.Sets/src/mage/cards/g/GnarledProfessor.java b/Mage.Sets/src/mage/cards/g/GnarledProfessor.java index 7d17ce9c005..43bedb8dc24 100644 --- a/Mage.Sets/src/mage/cards/g/GnarledProfessor.java +++ b/Mage.Sets/src/mage/cards/g/GnarledProfessor.java @@ -3,6 +3,7 @@ package mage.cards.g; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -28,7 +29,8 @@ public final class GnarledProfessor extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // When Gnarled Professor enters the battlefield, learn. - this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect()) + .addHint(OpenSideboardHint.instance)); } private GnarledProfessor(final GnarledProfessor card) { diff --git a/Mage.Sets/src/mage/cards/g/GoldenWish.java b/Mage.Sets/src/mage/cards/g/GoldenWish.java index 276d79134d9..b7e35b52e1d 100644 --- a/Mage.Sets/src/mage/cards/g/GoldenWish.java +++ b/Mage.Sets/src/mage/cards/g/GoldenWish.java @@ -4,6 +4,7 @@ package mage.cards.g; import java.util.UUID; import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -29,6 +30,7 @@ public final class GoldenWish extends CardImpl { // You may choose an artifact or enchantment card you own from outside the game, reveal that card, and put it into your hand. this.getSpellAbility().addEffect(new WishEffect(filter)); + this.getSpellAbility().addHint(OpenSideboardHint.instance); // Exile Golden Wish. this.getSpellAbility().addEffect(new ExileSpellEffect()); diff --git a/Mage.Sets/src/mage/cards/g/GuidingVoice.java b/Mage.Sets/src/mage/cards/g/GuidingVoice.java index 70097043a0c..1095588c6fd 100644 --- a/Mage.Sets/src/mage/cards/g/GuidingVoice.java +++ b/Mage.Sets/src/mage/cards/g/GuidingVoice.java @@ -2,6 +2,7 @@ package mage.cards.g; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -22,8 +23,9 @@ public final class GuidingVoice extends CardImpl { this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - // Learn (You may reveal a Lesson card you own from outside the game and p + // Learn (You may reveal a Lesson card you own from outside the game and put it into your hand, or discard a card to draw a card.) this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private GuidingVoice(final GuidingVoice card) { diff --git a/Mage.Sets/src/mage/cards/h/HuntForSpecimens.java b/Mage.Sets/src/mage/cards/h/HuntForSpecimens.java index 4d2878f0ffc..9c27136c097 100644 --- a/Mage.Sets/src/mage/cards/h/HuntForSpecimens.java +++ b/Mage.Sets/src/mage/cards/h/HuntForSpecimens.java @@ -2,6 +2,7 @@ package mage.cards.h; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -22,6 +23,7 @@ public final class HuntForSpecimens extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private HuntForSpecimens(final HuntForSpecimens card) { diff --git a/Mage.Sets/src/mage/cards/i/IgneousInspiration.java b/Mage.Sets/src/mage/cards/i/IgneousInspiration.java index ab28922c5b6..e300cb28cea 100644 --- a/Mage.Sets/src/mage/cards/i/IgneousInspiration.java +++ b/Mage.Sets/src/mage/cards/i/IgneousInspiration.java @@ -2,6 +2,7 @@ package mage.cards.i; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,6 +24,7 @@ public final class IgneousInspiration extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private IgneousInspiration(final IgneousInspiration card) { diff --git a/Mage.Sets/src/mage/cards/k/KarnTheGreatCreator.java b/Mage.Sets/src/mage/cards/k/KarnTheGreatCreator.java index 9a25f543317..9b03477534b 100644 --- a/Mage.Sets/src/mage/cards/k/KarnTheGreatCreator.java +++ b/Mage.Sets/src/mage/cards/k/KarnTheGreatCreator.java @@ -7,6 +7,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -50,7 +51,7 @@ public final class KarnTheGreatCreator extends CardImpl { // -2: You may choose an artifact card you own from outside the game or in exile, reveal that card, and put it into your hand. this.addAbility(new LoyaltyAbility(new WishEffect( StaticFilters.FILTER_CARD_ARTIFACT_AN, true, true - ), -2)); + ), -2).addHint(OpenSideboardHint.instance)); } private KarnTheGreatCreator(final KarnTheGreatCreator card) { diff --git a/Mage.Sets/src/mage/cards/l/LegionAngel.java b/Mage.Sets/src/mage/cards/l/LegionAngel.java index 7b4398ea5ad..c9ed5e7a014 100644 --- a/Mage.Sets/src/mage/cards/l/LegionAngel.java +++ b/Mage.Sets/src/mage/cards/l/LegionAngel.java @@ -5,6 +5,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -37,7 +38,8 @@ public final class LegionAngel extends CardImpl { // When Legion Angel enters the battlefield, you may reveal a card you own named Legion Angel from outside the game and put it into your hand. this.addAbility(new EntersBattlefieldTriggeredAbility(new WishEffect(filter, true, false) - .setText("you may reveal a card you own named Legion Angel from outside the game and put it into your hand"))); + .setText("you may reveal a card you own named Legion Angel from outside the game and put it into your hand")) + .addHint(OpenSideboardHint.instance)); } private LegionAngel(final LegionAngel card) { diff --git a/Mage.Sets/src/mage/cards/l/LivingWish.java b/Mage.Sets/src/mage/cards/l/LivingWish.java index 8302027b299..b6c62a9977d 100644 --- a/Mage.Sets/src/mage/cards/l/LivingWish.java +++ b/Mage.Sets/src/mage/cards/l/LivingWish.java @@ -4,6 +4,7 @@ package mage.cards.l; import java.util.UUID; import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -29,6 +30,7 @@ public final class LivingWish extends CardImpl { // You may choose a creature or land card you own from outside the game, reveal that card, and put it into your hand. this.getSpellAbility().addEffect(new WishEffect(filter)); + this.getSpellAbility().addHint(OpenSideboardHint.instance); // Exile Living Wish. this.getSpellAbility().addEffect(new ExileSpellEffect()); diff --git a/Mage.Sets/src/mage/cards/m/MastermindsAcquisition.java b/Mage.Sets/src/mage/cards/m/MastermindsAcquisition.java index da1af878cc5..d1324c9b0c1 100644 --- a/Mage.Sets/src/mage/cards/m/MastermindsAcquisition.java +++ b/Mage.Sets/src/mage/cards/m/MastermindsAcquisition.java @@ -3,6 +3,7 @@ package mage.cards.m; import mage.abilities.Mode; import mage.abilities.effects.common.WishEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -27,6 +28,7 @@ public final class MastermindsAcquisition extends CardImpl { Mode mode = new Mode(new WishEffect(StaticFilters.FILTER_CARD_A, false) .setText("Put a card you own from outside the game into your hand")); this.getSpellAbility().addMode(mode); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private MastermindsAcquisition(final MastermindsAcquisition card) { diff --git a/Mage.Sets/src/mage/cards/o/OvergrownArch.java b/Mage.Sets/src/mage/cards/o/OvergrownArch.java index fca357f306c..9d05efaa4d9 100644 --- a/Mage.Sets/src/mage/cards/o/OvergrownArch.java +++ b/Mage.Sets/src/mage/cards/o/OvergrownArch.java @@ -8,6 +8,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.DefenderAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -38,6 +39,7 @@ public final class OvergrownArch extends CardImpl { // {2}, Sacrifice Overgrown Arch: Learn. Ability ability = new SimpleActivatedAbility(new LearnEffect(), new GenericManaCost(2)); ability.addCost(new SacrificeSourceCost()); + ability.addHint(OpenSideboardHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/p/PoetsQuill.java b/Mage.Sets/src/mage/cards/p/PoetsQuill.java index a77aa3f4f75..7895c32a99e 100644 --- a/Mage.Sets/src/mage/cards/p/PoetsQuill.java +++ b/Mage.Sets/src/mage/cards/p/PoetsQuill.java @@ -7,6 +7,7 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; @@ -29,7 +30,8 @@ public final class PoetsQuill extends CardImpl { this.subtype.add(SubType.EQUIPMENT); // When Poet's Quill enters the battlefield, learn. - this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect()) + .addHint(OpenSideboardHint.instance)); // Equipped creature gets +1/+1 and has lifelink. Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1)); diff --git a/Mage.Sets/src/mage/cards/p/PopQuiz.java b/Mage.Sets/src/mage/cards/p/PopQuiz.java index 719da61d175..0b32b6d700c 100644 --- a/Mage.Sets/src/mage/cards/p/PopQuiz.java +++ b/Mage.Sets/src/mage/cards/p/PopQuiz.java @@ -2,6 +2,7 @@ package mage.cards.p; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -21,6 +22,7 @@ public final class PopQuiz extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private PopQuiz(final PopQuiz card) { diff --git a/Mage.Sets/src/mage/cards/p/ProfessorOfSymbology.java b/Mage.Sets/src/mage/cards/p/ProfessorOfSymbology.java index a81f4db5759..a606ff80cbd 100644 --- a/Mage.Sets/src/mage/cards/p/ProfessorOfSymbology.java +++ b/Mage.Sets/src/mage/cards/p/ProfessorOfSymbology.java @@ -3,6 +3,7 @@ package mage.cards.p; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -24,7 +25,8 @@ public final class ProfessorOfSymbology extends CardImpl { this.toughness = new MageInt(1); // When Professor of Symbology enters the battlefield, learn. - this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect()) + .addHint(OpenSideboardHint.instance)); } private ProfessorOfSymbology(final ProfessorOfSymbology card) { diff --git a/Mage.Sets/src/mage/cards/r/ResearchDevelopment.java b/Mage.Sets/src/mage/cards/r/ResearchDevelopment.java index 28280a74b19..9381fc18d83 100644 --- a/Mage.Sets/src/mage/cards/r/ResearchDevelopment.java +++ b/Mage.Sets/src/mage/cards/r/ResearchDevelopment.java @@ -5,6 +5,7 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; @@ -26,6 +27,7 @@ public final class ResearchDevelopment extends SplitCard { // Choose up to four cards you own from outside the game and shuffle them into your library. getLeftHalfCard().getSpellAbility().addEffect(new ResearchEffect()); + getLeftHalfCard().getSpellAbility().addHint(OpenSideboardHint.instance); // Create a 3/1 red Elemental creature token unless any opponent has you draw a card. Repeat this process two more times. getRightHalfCard().getSpellAbility().addEffect(new DevelopmentEffect()); diff --git a/Mage.Sets/src/mage/cards/r/RetrieverPhoenix.java b/Mage.Sets/src/mage/cards/r/RetrieverPhoenix.java index 518e2e28397..ea75c81b534 100644 --- a/Mage.Sets/src/mage/cards/r/RetrieverPhoenix.java +++ b/Mage.Sets/src/mage/cards/r/RetrieverPhoenix.java @@ -9,6 +9,7 @@ import mage.abilities.condition.common.CastFromEverywhereSourceCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HasteAbility; import mage.cards.Card; @@ -43,7 +44,7 @@ public final class RetrieverPhoenix extends CardImpl { this.addAbility(new ConditionalInterveningIfTriggeredAbility( new EntersBattlefieldTriggeredAbility(new LearnEffect()), CastFromEverywhereSourceCondition.instance, "When {this} enters the battlefield, if you cast it, " + LearnEffect.getDefaultText() - )); + ).addHint(OpenSideboardHint.instance)); // As long as Retriever Phoenix is in your graveyard, if you would learn, you may instead return Retriever Phoenix to the battlefield. this.addAbility(new SimpleStaticAbility(Zone.GRAVEYARD, new RetrieverPhoenixEffect())); diff --git a/Mage.Sets/src/mage/cards/r/RingOfMaruf.java b/Mage.Sets/src/mage/cards/r/RingOfMaruf.java index aa46e2d060c..b0e284423d7 100644 --- a/Mage.Sets/src/mage/cards/r/RingOfMaruf.java +++ b/Mage.Sets/src/mage/cards/r/RingOfMaruf.java @@ -9,6 +9,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.WishEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -33,6 +34,7 @@ public final class RingOfMaruf extends CardImpl { Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RingOfMarufEffect(), new ManaCostsImpl("{5}")); ability.addCost(new TapSourceCost()); ability.addCost(new ExileSourceCost()); + ability.addHint(OpenSideboardHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/RiseOfExtus.java b/Mage.Sets/src/mage/cards/r/RiseOfExtus.java index e3de6d030ea..1f9769c771d 100644 --- a/Mage.Sets/src/mage/cards/r/RiseOfExtus.java +++ b/Mage.Sets/src/mage/cards/r/RiseOfExtus.java @@ -2,6 +2,7 @@ package mage.cards.r; import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.LearnEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -29,6 +30,7 @@ public final class RiseOfExtus extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private RiseOfExtus(final RiseOfExtus card) { diff --git a/Mage.Sets/src/mage/cards/s/SparringRegimen.java b/Mage.Sets/src/mage/cards/s/SparringRegimen.java index ee78fc10596..ef7ef8a13e5 100644 --- a/Mage.Sets/src/mage/cards/s/SparringRegimen.java +++ b/Mage.Sets/src/mage/cards/s/SparringRegimen.java @@ -6,6 +6,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.UntapTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,7 +24,8 @@ public final class SparringRegimen extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); // When Sparring Regimen enters the battlefield, learn. - this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect()) + .addHint(OpenSideboardHint.instance)); // Whenever you attack, put a +1/+1 counter on target attacking creature and untap it. Ability ability = new AttacksWithCreaturesTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/s/SpawnsireOfUlamog.java b/Mage.Sets/src/mage/cards/s/SpawnsireOfUlamog.java index 3a0bf92581c..2abbb5e5ce8 100644 --- a/Mage.Sets/src/mage/cards/s/SpawnsireOfUlamog.java +++ b/Mage.Sets/src/mage/cards/s/SpawnsireOfUlamog.java @@ -7,6 +7,7 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CastCardFromOutsideTheGameEffect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.AnnihilatorAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -43,7 +44,9 @@ public final class SpawnsireOfUlamog extends CardImpl { this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new EldraziSpawnToken(), 2), new GenericManaCost(4))); // {20}: Cast any number of Eldrazi cards you own from outside the game without paying their mana costs. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new CastCardFromOutsideTheGameEffect(filter, ruleText), new GenericManaCost(20))); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, + new CastCardFromOutsideTheGameEffect(filter, ruleText), new GenericManaCost(20) + ).addHint(OpenSideboardHint.instance)); } private SpawnsireOfUlamog(final SpawnsireOfUlamog card) { diff --git a/Mage.Sets/src/mage/cards/s/StudyBreak.java b/Mage.Sets/src/mage/cards/s/StudyBreak.java index 7ee654547fd..08077090057 100644 --- a/Mage.Sets/src/mage/cards/s/StudyBreak.java +++ b/Mage.Sets/src/mage/cards/s/StudyBreak.java @@ -2,6 +2,7 @@ package mage.cards.s; import mage.abilities.effects.common.LearnEffect; import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,6 +24,7 @@ public final class StudyBreak extends CardImpl { // Learn. this.getSpellAbility().addEffect(new LearnEffect().concatBy("
")); + this.getSpellAbility().addHint(OpenSideboardHint.instance); } private StudyBreak(final StudyBreak card) { diff --git a/Mage.Sets/src/mage/cards/t/TheRavensWarning.java b/Mage.Sets/src/mage/cards/t/TheRavensWarning.java index 832b944f9a9..f6aeef0abb0 100644 --- a/Mage.Sets/src/mage/cards/t/TheRavensWarning.java +++ b/Mage.Sets/src/mage/cards/t/TheRavensWarning.java @@ -7,6 +7,7 @@ import java.util.UUID; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.SagaAbility; import mage.abilities.effects.common.*; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.FlyingAbility; import mage.constants.Duration; import mage.constants.SagaChapter; @@ -51,6 +52,7 @@ public final class TheRavensWarning extends CardImpl { sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new WishEffect(StaticFilters.FILTER_CARD_A, false, false, true) ); + sagaAbility.addHint(OpenSideboardHint.instance); this.addAbility(sagaAbility); } diff --git a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java index e1c24f89b50..1bab419d820 100644 --- a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java +++ b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java @@ -7,6 +7,7 @@ import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect import mage.abilities.effects.common.WishEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.counter.DistributeCountersEffect; +import mage.abilities.hint.common.OpenSideboardHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -58,7 +59,8 @@ public final class VivienArkbowRanger extends CardImpl { this.addAbility(ability); // −5: You may choose a creature card you own from outside the game, reveal it, and put it into your hand. - this.addAbility(new LoyaltyAbility(new WishEffect(StaticFilters.FILTER_CARD_CREATURE_A), -5)); + this.addAbility(new LoyaltyAbility(new WishEffect(StaticFilters.FILTER_CARD_CREATURE_A), -5) + .addHint(OpenSideboardHint.instance)); } private VivienArkbowRanger(final VivienArkbowRanger card) { diff --git a/Mage.Sets/src/mage/cards/w/Wish.java b/Mage.Sets/src/mage/cards/w/Wish.java new file mode 100644 index 00000000000..7b0d8ab81cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/Wish.java @@ -0,0 +1,137 @@ +package mage.cards.w; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import mage.MageIdentifier; +import mage.MageObject; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.common.OpenSideboardHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +/** + * + * @author weirddan455 + */ +public final class Wish extends CardImpl { + + public Wish(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); + + // You may play a card you own from outside the game this turn. + this.getSpellAbility().addEffect(new WishEffect()); + this.getSpellAbility().setIdentifier(MageIdentifier.WishWatcher); + this.getSpellAbility().addWatcher(new WishWatcher()); + this.getSpellAbility().addHint(OpenSideboardHint.instance); + } + + private Wish(final Wish card) { + super(card); + } + + @Override + public Wish copy() { + return new Wish(this); + } +} + +class WishEffect extends OneShotEffect { + + public WishEffect() { + super(Outcome.Benefit); + this.staticText = "You may play a card you own from outside the game this turn"; + } + + private WishEffect(final WishEffect effect) { + super(effect); + } + + @Override + public WishEffect copy() { + return new WishEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null && !controller.getSideboard().isEmpty()) { + controller.lookAtCards(source, "Sideboard", controller.getSideboard(), game); + game.addEffect(new WishPlayFromSideboardEffect(), source); + return true; + } + return false; + } +} + +class WishPlayFromSideboardEffect extends AsThoughEffectImpl { + + public WishPlayFromSideboardEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit, true); + } + + private WishPlayFromSideboardEffect(final WishPlayFromSideboardEffect effect) { + super(effect); + } + + @Override + public WishPlayFromSideboardEffect copy() { + return new WishPlayFromSideboardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (source.getControllerId().equals(affectedControllerId)) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + UUID mainCardId = CardUtil.getMainCardId(game, objectId); + if (controller != null && sourceObject != null && controller.getSideboard().contains(mainCardId)) { + WishWatcher watcher = game.getState().getWatcher(WishWatcher.class); + return watcher != null && !watcher.isAbilityUsed(new MageObjectReference(sourceObject, game)); + } + } + return false; + } +} + +class WishWatcher extends Watcher { + + private final Set usedFrom = new HashSet<>(); + + WishWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if ((GameEvent.EventType.SPELL_CAST.equals(event.getType()) || GameEvent.EventType.LAND_PLAYED.equals(event.getType())) + && event.hasApprovingIdentifier(MageIdentifier.WishWatcher)) { + usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + } + } + + @Override + public void reset() { + super.reset(); + usedFrom.clear(); + } + + boolean isAbilityUsed(MageObjectReference mor) { + return usedFrom.contains(mor); + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 29d2e93a519..5b6a6c2898d 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -263,6 +263,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("White Dragon", 41, Rarity.UNCOMMON, mage.cards.w.WhiteDragon.class)); cards.add(new SetCardInfo("Wight", 127, Rarity.RARE, mage.cards.w.Wight.class)); cards.add(new SetCardInfo("Wild Shape", 212, Rarity.UNCOMMON, mage.cards.w.WildShape.class)); + cards.add(new SetCardInfo("Wish", 166, Rarity.RARE, mage.cards.w.Wish.class)); cards.add(new SetCardInfo("Wizard Class", 81, Rarity.UNCOMMON, mage.cards.w.WizardClass.class)); cards.add(new SetCardInfo("Wizard's Spellbook", 82, Rarity.RARE, mage.cards.w.WizardsSpellbook.class)); cards.add(new SetCardInfo("Xorn", 167, Rarity.RARE, mage.cards.x.Xorn.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/KaradorGhostChieftainTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/KaradorGhostChieftainTest.java index a791be9331b..0a77d8d8aea 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/KaradorGhostChieftainTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/KaradorGhostChieftainTest.java @@ -94,47 +94,48 @@ public class KaradorGhostChieftainTest extends CardTestPlayerBase { } @Test - // @Ignore // It's not possible yet to select which ability to use to allow a asThoughtAs effect public void test_castFromGraveyardWithDifferentApprovers() { - setStrictChooseMode(true); + skipInitShuffling(); addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); addCard(Zone.BATTLEFIELD, playerA, "Island", 3); - + // // {1}{B}: Target attacking Zombie gains indestructible until end of turn. addCard(Zone.LIBRARY, playerA, "Accursed Horde", 1); // Creature Zombie {3}{B} - skipInitShuffling(); - - addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 5); - + // + addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 5); // Creature {1}{W} + // // Karador, Ghost Chieftain costs {1} less to cast for each creature card in your graveyard. // During each of your turns, you may cast one creature card from your graveyard. addCard(Zone.HAND, playerA, "Karador, Ghost Chieftain");// {5}{B}{G}{W} - + // // When Gisa and Geralf enters the battlefield, put the top four cards of your library into your graveyard. // During each of your turns, you may cast a Zombie creature card from your graveyard. addCard(Zone.HAND, playerA, "Gisa and Geralf"); // CREATURE {2}{U}{B} (4/4) + // prepare spels with same AsThough effects and puts creature to graveyard castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Karador, Ghost Chieftain"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gisa and Geralf"); + // you play any creatures due to two approve objects + checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", true); + checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Accursed Horde", true); + + // cast zombie creature and approves by Karagar castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Accursed Horde"); - setChoice(playerA, "During each of your turns, you may cast a Zombie creature card from your graveyard"); // Choose the permitting object + setChoice(playerA, "Karador, Ghost Chieftain"); // choose the permitting object + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + // you can't cast lion due to approving object (Gisa needs zombie) + checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Accursed Horde", false); + setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Karador, Ghost Chieftain", 1); - assertPermanentCount(playerA, "Gisa and Geralf", 1); - - assertPermanentCount(playerA, "Silvercoat Lion", 1); - assertPermanentCount(playerA, "Accursed Horde", 1); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 96e0636487b..7d1aeeccbd6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1977,7 +1977,14 @@ public class TestPlayer implements Player { //Assert.fail("Wrong choice"); } - this.chooseStrictModeFailed("choice", game, choice.getMessage()); + String choicesInfo; + if (choice.isKeyChoice()) { + choicesInfo = String.join("\n", choice.getKeyChoices().values()); + } else { + choicesInfo = String.join("\n", choice.getChoices()); + } + this.chooseStrictModeFailed("choice", game, + "Message: " + choice.getMessage() + "\nPossible choices:\n" + choicesInfo); return computerPlayer.choose(outcome, choice, game); } diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index 7a50d469882..09d0c38892e 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -1,15 +1,16 @@ -package mage; - -/** - * Used to identify specific actions/events and to be able to assign them to the - * correct watcher or other processing. - * - * @author LevelX2 - */ -public enum MageIdentifier { - GisaAndGeralfWatcher, - KaradorGhostChieftainWatcher, - KessDissidentMageWatcher, - LurrusOfTheDreamDenWatcher, - MuldrothaTheGravetideWatcher -} +package mage; + +/** + * Used to identify specific actions/events and to be able to assign them to the + * correct watcher or other processing. + * + * @author LevelX2 + */ +public enum MageIdentifier { + GisaAndGeralfWatcher, + KaradorGhostChieftainWatcher, + KessDissidentMageWatcher, + LurrusOfTheDreamDenWatcher, + MuldrothaTheGravetideWatcher, + WishWatcher +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SideboardCardsYouControlCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SideboardCardsYouControlCount.java new file mode 100644 index 00000000000..71e09969397 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SideboardCardsYouControlCount.java @@ -0,0 +1,39 @@ +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.game.Game; +import mage.players.Player; + +/** + * @author JayDi85 + */ +public enum SideboardCardsYouControlCount implements DynamicValue { + + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Player player = game.getPlayer(sourceAbility.getControllerId()); + if (player == null) { + return 0; + } + return player.getSideboard().size(); + } + + @Override + public SideboardCardsYouControlCount copy() { + return instance; + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "cards in your sideboard"; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 35659974ee7..434286e542b 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -592,9 +592,14 @@ public class ContinuousEffects implements Serializable { Map keyChoices = new HashMap<>(); for (ApprovingObject approvingObject : possibleApprovingObjects) { MageObject mageObject = game.getObject(approvingObject.getApprovingAbility().getSourceId()); - keyChoices.put(approvingObject.getApprovingAbility().getId().toString(), - (approvingObject.getApprovingAbility().getRule(mageObject == null ? "" : mageObject.getName())) - + (mageObject == null ? "" : " (" + mageObject.getIdName() + ")")); + String choiceKey = approvingObject.getApprovingAbility().getId().toString(); + String choiceValue; + if (mageObject == null) { + choiceValue = approvingObject.getApprovingAbility().getRule(); + } else { + choiceValue = mageObject.getIdName() + ": " + approvingObject.getApprovingAbility().getRule(mageObject.getName()); + } + keyChoices.put(choiceKey, choiceValue); } Choice choicePermitting = new ChoiceImpl(true); choicePermitting.setMessage("Choose the permitting object"); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CastCardFromOutsideTheGameEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CastCardFromOutsideTheGameEffect.java index 5ca6bc1e486..a566afcc6ac 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CastCardFromOutsideTheGameEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CastCardFromOutsideTheGameEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common; import java.util.Set; diff --git a/Mage/src/main/java/mage/abilities/hint/common/OpenSideboardHint.java b/Mage/src/main/java/mage/abilities/hint/common/OpenSideboardHint.java new file mode 100644 index 00000000000..f99a83bedc9 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/OpenSideboardHint.java @@ -0,0 +1,26 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.SideboardCardsYouControlCount; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.game.Game; + +/** + * @author JayDi85 + */ +public enum OpenSideboardHint implements Hint { + + instance; + private static final Hint hint = new ValueHint("Cards in your sideboard", SideboardCardsYouControlCount.instance); + + @Override + public String getText(Game game, Ability ability) { + return hint.getText(game, ability) + " (Right click on battlefield to open player's sideboard at any time)"; + } + + @Override + public Hint copy() { + return instance; + } +} diff --git a/Mage/src/main/java/mage/constants/PlayerAction.java b/Mage/src/main/java/mage/constants/PlayerAction.java index ef43bfdb9f2..052d42a67ab 100644 --- a/Mage/src/main/java/mage/constants/PlayerAction.java +++ b/Mage/src/main/java/mage/constants/PlayerAction.java @@ -59,5 +59,6 @@ public enum PlayerAction { HOLD_PRIORITY, UNHOLD_PRIORITY, VIEW_LIMITED_DECK, + VIEW_SIDEBOARD, TOGGLE_RECORD_MACRO } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 6a83c8c31b5..6454109ed00 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3646,13 +3646,22 @@ public abstract class PlayerImpl implements Player, Serializable { } } - // check to play companion cards + // outside cards if (fromAll || fromZone == Zone.OUTSIDE) { + // companion cards for (Cards companionCards : game.getState().getCompanion().values()) { for (Card card : companionCards.getCards(game)) { getPlayableFromObjectAll(game, Zone.OUTSIDE, card, availableMana, playable); } } + + // sideboard cards (example: Wish) + for (UUID sideboardCardId : this.getSideboard()) { + Card sideboardCard = game.getCard(sideboardCardId); + if (sideboardCard != null) { + getPlayableFromObjectAll(game, Zone.OUTSIDE, sideboardCard, availableMana, playable); + } + } } // check if it's possible to play the top card of a library