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 d4839275a9..70c53a2c55 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -606,10 +606,13 @@ public final class GamePanel extends javax.swing.JPanel { } public synchronized void updateGame(GameView game) { - updateGame(game, null); + updateGame(game, false, null, null); } - public synchronized void updateGame(GameView game, Map options) { + public synchronized void updateGame(GameView game, boolean showPlayable, Map options, Set targets) { + + prepareSelectableView(game, showPlayable, options, targets); + if (playerId == null && game.getWatchedHands() == null) { this.handContainer.setVisible(false); } else { @@ -622,14 +625,6 @@ public final class GamePanel extends javax.swing.JPanel { } if (playerId != null) { handCards.put(YOUR_HAND, game.getHand()); - // Mark playable - if (game.getCanPlayInHand() != null) { - for (CardView card : handCards.get(YOUR_HAND).values()) { - if (game.getCanPlayInHand().contains(card.getId())) { - card.setPlayable(true); - } - } - } // Get opponents hand cards if available (only possible for players) if (game.getOpponentHands() != null) { for (Map.Entry hand : game.getOpponentHands().entrySet()) { @@ -1183,25 +1178,23 @@ public final class GamePanel extends javax.swing.JPanel { } public void ask(String question, GameView gameView, int messageId, Map options) { - updateGame(gameView); + updateGame(gameView, false, options, null); this.feedbackPanel.getFeedback(FeedbackMode.QUESTION, question, false, options, messageId, true, gameView.getPhase()); } - private void prepareSelectableView(GameView gameView, Map options, Set targets) { - // make cards/perm selectable - // highlighting chosen - // code calls after each selects or updates, no needs in switch off cards + private void prepareSelectableView(GameView gameView, boolean showPlayable, Map options, Set targets) { + // make cards/perm selectable/chooseable/playable + // playable must be used for ask dialog only (priority and mana pay) Zone needZone = Zone.ALL; - if (options.containsKey("targetZone")) { + if (options != null && options.containsKey("targetZone")) { needZone = (Zone) options.get("targetZone"); } - List needChoosen = null; - if (options.containsKey("chosen")) { + List needChoosen; + if (options != null && options.containsKey("chosen")) { needChoosen = (List) options.get("chosen"); - } - if (needChoosen == null) { + } else { needChoosen = new ArrayList<>(); } @@ -1212,7 +1205,14 @@ public final class GamePanel extends javax.swing.JPanel { needSelectable = new HashSet<>(); } - if (needChoosen.size() == 0 && needSelectable.size() == 0) { + Set needPlayable; + if (showPlayable && gameView.getCanPlayObjects() != null) { + needPlayable = gameView.getCanPlayObjects(); + } else { + needPlayable = new HashSet<>(); + } + + if (needChoosen.size() == 0 && needSelectable.size() == 0 && needPlayable.size() == 0) { return; } @@ -1225,6 +1225,9 @@ public final class GamePanel extends javax.swing.JPanel { if (needChoosen.contains(card.getId())) { card.setSelected(true); } + if (needPlayable.contains(card.getId())) { + card.setPlayable(true); + } } } @@ -1237,6 +1240,7 @@ public final class GamePanel extends javax.swing.JPanel { if (needChoosen.contains(card.getKey())) { card.getValue().setSelected(true); } + // play from stack unsupported } } @@ -1250,6 +1254,9 @@ public final class GamePanel extends javax.swing.JPanel { if (needChoosen.contains(perm.getKey())) { perm.getValue().setSelected(true); } + if (needPlayable.contains(perm.getKey())) { + perm.getValue().setPlayable(true); + } } } } @@ -1264,6 +1271,9 @@ public final class GamePanel extends javax.swing.JPanel { if (needChoosen.contains(card.getKey())) { card.getValue().setSelected(true); } + if (needPlayable.contains(card.getKey())) { + card.getValue().setPlayable(true); + } } } } @@ -1278,6 +1288,26 @@ public final class GamePanel extends javax.swing.JPanel { if (needChoosen.contains(card.getKey())) { card.getValue().setSelected(true); } + if (needPlayable.contains(card.getKey())) { + card.getValue().setPlayable(true); + } + } + } + } + + // command + if (needZone == Zone.COMMAND || needZone == Zone.ALL) { + for (PlayerView player : gameView.getPlayers()) { + for (CommandObjectView com : player.getCommandObjectList()) { + if (needSelectable.contains(com.getId())) { + com.setChoosable(true); + } + if (needChoosen.contains(com.getId())) { + com.setSelected(true); + } + if (needPlayable.contains(com.getId())) { + com.setPlayable(true); + } } } } @@ -1291,6 +1321,9 @@ public final class GamePanel extends javax.swing.JPanel { if (needChoosen.contains(card.getKey())) { card.getValue().setSelected(true); } + if (needPlayable.contains(card.getKey())) { + card.getValue().setPlayable(true); + } } } } @@ -1315,10 +1348,8 @@ public final class GamePanel extends javax.swing.JPanel { switch (needType) { case PICK_ABILITY: popupMenuType = PopUpMenuType.TRIGGER_ORDER; - prepareSelectableView(gameView, options, targets); break; case PICK_TARGET: - prepareSelectableView(gameView, options, targets); break; default: logger.warn("Unknown query type in pick target: " + needType + " in " + message); @@ -1327,7 +1358,8 @@ public final class GamePanel extends javax.swing.JPanel { } } - updateGame(gameView); + updateGame(gameView, false, options, targets); + Map options0 = options == null ? new HashMap<>() : options; ShowCardsDialog dialog = null; if (cardView != null && !cardView.isEmpty()) { @@ -1359,7 +1391,8 @@ public final class GamePanel extends javax.swing.JPanel { PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"), false); - updateGame(gameView, options); + updateGame(gameView, true, options, null); + boolean controllingPlayer = false; for (PlayerView playerView : gameView.getPlayers()) { if (playerView.getPlayerId().equals(playerId)) { @@ -1393,13 +1426,13 @@ public final class GamePanel extends javax.swing.JPanel { } public void playMana(String message, GameView gameView, Map options, int messageId) { - updateGame(gameView); + updateGame(gameView, true, options, null); DialogManager.getManager(gameId).fadeOut(); this.feedbackPanel.getFeedback(FeedbackMode.CANCEL, message, gameView.getSpecial(), options, messageId, true, gameView.getPhase()); } public void playXMana(String message, GameView gameView, int messageId) { - updateGame(gameView); + updateGame(gameView, true, null, null); DialogManager.getManager(gameId).fadeOut(); this.feedbackPanel.getFeedback(FeedbackMode.CONFIRM, message, gameView.getSpecial(), null, messageId, true, gameView.getPhase()); } 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 3f7501c184..5f48cefadc 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -274,7 +274,7 @@ public class CallbackClientImpl implements CallbackClient { if (panel != null) { appendJsonEvent("GAME_UPDATE", callback.getObjectId(), callback.getData()); - panel.updateGame((GameView) callback.getData()); + panel.updateGame((GameView) callback.getData(), true, null, null); // update after undo } break; } diff --git a/Mage.Client/src/main/java/mage/client/util/CardsViewUtil.java b/Mage.Client/src/main/java/mage/client/util/CardsViewUtil.java index 2da9f4c736..727a19adf9 100644 --- a/Mage.Client/src/main/java/mage/client/util/CardsViewUtil.java +++ b/Mage.Client/src/main/java/mage/client/util/CardsViewUtil.java @@ -1,15 +1,14 @@ - package mage.client.util; -import java.util.List; -import java.util.Map; import mage.cards.Card; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.view.*; +import java.util.List; +import java.util.Map; + /** - * * @author BetaSteward_at_googlemail.com */ public final class CardsViewUtil { @@ -55,9 +54,8 @@ public final class CardsViewUtil { CardView cardView = new CardView((EmblemView) commandObject); cards.put(commandObject.getId(), cardView); } else if (commandObject instanceof PlaneView) { - CardView cardView = null; - cardView = new CardView((PlaneView) commandObject); - cards.put(commandObject.getId(), cardView); + CardView cardView = new CardView((PlaneView) commandObject); + cards.put(commandObject.getId(), cardView); } else if (commandObject instanceof CommanderView) { cards.put(commandObject.getId(), (CommanderView) commandObject); } diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index 8505589a47..027df80530 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -32,7 +32,7 @@ import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com */ -public class CardView extends SimpleCardView { +public class CardView extends SimpleCardView implements SelectableObjectView { private static final long serialVersionUID = 1L; diff --git a/Mage.Common/src/main/java/mage/view/CommandObjectView.java b/Mage.Common/src/main/java/mage/view/CommandObjectView.java index b3417f8394..96fe4b25fe 100644 --- a/Mage.Common/src/main/java/mage/view/CommandObjectView.java +++ b/Mage.Common/src/main/java/mage/view/CommandObjectView.java @@ -1,21 +1,18 @@ - - package mage.view; -import java.io.Serializable; import java.util.List; import java.util.UUID; /** - * * @author Plopman */ -public interface CommandObjectView extends Serializable { - public String getExpansionSetCode(); +public interface CommandObjectView extends SelectableObjectView { - public String getName(); + String getExpansionSetCode(); - public UUID getId(); + String getName(); - public List getRules(); + UUID getId(); + + List getRules(); } diff --git a/Mage.Common/src/main/java/mage/view/EmblemView.java b/Mage.Common/src/main/java/mage/view/EmblemView.java index 807bd0c1f5..2d3e11c96d 100644 --- a/Mage.Common/src/main/java/mage/view/EmblemView.java +++ b/Mage.Common/src/main/java/mage/view/EmblemView.java @@ -1,10 +1,11 @@ package mage.view; +import mage.cards.Card; +import mage.game.command.Emblem; + import java.io.Serializable; import java.util.List; import java.util.UUID; -import mage.cards.Card; -import mage.game.command.Emblem; /** * @author noxx @@ -15,24 +16,24 @@ public class EmblemView implements CommandObjectView, Serializable { protected String name; protected String expansionSetCode; protected List rules; + protected boolean isPlayable = false; public EmblemView(Emblem emblem, Card sourceCard) { - id = emblem.getId(); - name = "Emblem " + sourceCard.getName(); + this.id = emblem.getId(); + this.name = "Emblem " + sourceCard.getName(); if (emblem.getExpansionSetCodeForImage() == null) { - expansionSetCode = sourceCard.getExpansionSetCode(); + this.expansionSetCode = sourceCard.getExpansionSetCode(); } else { - expansionSetCode = emblem.getExpansionSetCodeForImage(); + this.expansionSetCode = emblem.getExpansionSetCodeForImage(); } - - rules = emblem.getAbilities().getRules(sourceCard.getName()); + this.rules = emblem.getAbilities().getRules(sourceCard.getName()); } public EmblemView(Emblem emblem) { - id = emblem.getId(); - name = emblem.getName(); - expansionSetCode = emblem.getExpansionSetCodeForImage(); - rules = emblem.getAbilities().getRules(emblem.getName()); + this.id = emblem.getId(); + this.name = emblem.getName(); + this.expansionSetCode = emblem.getExpansionSetCodeForImage(); + this.rules = emblem.getAbilities().getRules(emblem.getName()); } @Override @@ -54,4 +55,37 @@ public class EmblemView implements CommandObjectView, Serializable { public List getRules() { return rules; } + + + @Override + public boolean isPlayable() { + return isPlayable; + } + + @Override + public void setPlayable(boolean isPlayable) { + this.isPlayable = isPlayable; + } + + @Override + public boolean isChoosable() { + // unsupported + return false; + } + + @Override + public void setChoosable(boolean isChoosable) { + // unsupported + } + + @Override + public boolean isSelected() { + // unsupported + return false; + } + + @Override + public void setSelected(boolean selected) { + // unsupported + } } diff --git a/Mage.Common/src/main/java/mage/view/GameView.java b/Mage.Common/src/main/java/mage/view/GameView.java index f0466f6950..f2e920408a 100644 --- a/Mage.Common/src/main/java/mage/view/GameView.java +++ b/Mage.Common/src/main/java/mage/view/GameView.java @@ -39,7 +39,7 @@ public class GameView implements Serializable { private final int priorityTime; private final List players = new ArrayList<>(); private CardsView hand; - private Set canPlayInHand; + private Set canPlayObjects; private Map opponentHands; private Map watchedHands; private final CardsView stack = new CardsView(); @@ -300,12 +300,12 @@ public class GameView implements Serializable { return isPlayer; } - public Set getCanPlayInHand() { - return canPlayInHand; + public Set getCanPlayObjects() { + return canPlayObjects; } - public void setCanPlayInHand(Set canPlayInHand) { - this.canPlayInHand = canPlayInHand; + public void setCanPlayObjects(Set canPlayObjects) { + this.canPlayObjects = canPlayObjects; } public int getSpellsCastCurrentTurn() { diff --git a/Mage.Common/src/main/java/mage/view/PlaneView.java b/Mage.Common/src/main/java/mage/view/PlaneView.java index 7b1eea1dfe..702e9577c0 100644 --- a/Mage.Common/src/main/java/mage/view/PlaneView.java +++ b/Mage.Common/src/main/java/mage/view/PlaneView.java @@ -1,10 +1,11 @@ package mage.view; +import mage.cards.Card; +import mage.game.command.Plane; + import java.io.Serializable; import java.util.List; import java.util.UUID; -import mage.cards.Card; -import mage.game.command.Plane; /** * @author spjspj @@ -16,23 +17,24 @@ public class PlaneView implements CommandObjectView, Serializable { protected String expansionSetCode; protected List rules; - public PlaneView(Plane plane, Card sourceCard) { - id = plane.getId(); - name = "Plane " + sourceCard.getName(); - if (plane.getExpansionSetCodeForImage() == null) { - expansionSetCode = sourceCard.getExpansionSetCode(); - } else { - expansionSetCode = plane.getExpansionSetCodeForImage(); - } + protected boolean isPlayable = false; - rules = plane.getAbilities().getRules(sourceCard.getName()); + public PlaneView(Plane plane, Card sourceCard) { + this.id = plane.getId(); + this.name = "Plane " + sourceCard.getName(); + if (plane.getExpansionSetCodeForImage() == null) { + this.expansionSetCode = sourceCard.getExpansionSetCode(); + } else { + this.expansionSetCode = plane.getExpansionSetCodeForImage(); + } + this.rules = plane.getAbilities().getRules(sourceCard.getName()); } public PlaneView(Plane plane) { - id = plane.getId(); - name = plane.getName(); - expansionSetCode = plane.getExpansionSetCodeForImage(); - rules = plane.getAbilities().getRules(plane.getName()); + this.id = plane.getId(); + this.name = plane.getName(); + this.expansionSetCode = plane.getExpansionSetCodeForImage(); + this.rules = plane.getAbilities().getRules(plane.getName()); } @Override @@ -54,4 +56,36 @@ public class PlaneView implements CommandObjectView, Serializable { public List getRules() { return rules; } + + @Override + public boolean isPlayable() { + return isPlayable; + } + + @Override + public void setPlayable(boolean isPlayable) { + this.isPlayable = isPlayable; + } + + @Override + public boolean isChoosable() { + // unsupported + return false; + } + + @Override + public void setChoosable(boolean isChoosable) { + // unsupported + } + + @Override + public boolean isSelected() { + // unsupported + return false; + } + + @Override + public void setSelected(boolean selected) { + // unsupported + } } diff --git a/Mage.Common/src/main/java/mage/view/SelectableObjectView.java b/Mage.Common/src/main/java/mage/view/SelectableObjectView.java new file mode 100644 index 0000000000..d1ce8d0ec4 --- /dev/null +++ b/Mage.Common/src/main/java/mage/view/SelectableObjectView.java @@ -0,0 +1,19 @@ +package mage.view; + +/** + * @author JayDi85 + */ +public interface SelectableObjectView { + + boolean isPlayable(); + + void setPlayable(boolean isPlayable); + + boolean isChoosable(); + + void setChoosable(boolean isChoosable); + + boolean isSelected(); + + void setSelected(boolean selected); +} diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java index d51e3a2e0b..38e0ea9647 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java @@ -1,14 +1,10 @@ - package mage.server.game; -import java.io.Serializable; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.ExecutorService; import mage.cards.Cards; import mage.choices.Choice; import mage.constants.ManaType; import mage.constants.PlayerAction; +import mage.constants.Zone; import mage.game.Game; import mage.game.Table; import mage.interfaces.callback.ClientCallback; @@ -20,6 +16,11 @@ import mage.server.util.ThreadExecutor; import mage.view.*; import org.apache.log4j.Logger; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; + /** * @author BetaSteward_at_googlemail.com */ @@ -152,7 +153,7 @@ public class GameSessionPlayer extends GameSessionWatcher { UserRequestMessage userRequestMessage = new UserRequestMessage( "User request", "Allow user " + watcher.get().getName() + " for this match to see your hand cards?
" - + "(You can revoke this every time using related popup menu item of your battlefield.)"); + + "(You can revoke this every time using related popup menu item of your battlefield.)"); userRequestMessage.setRelatedUser(watcherId, watcher.get().getName()); userRequestMessage.setGameId(game.getId()); userRequestMessage.setButton1("Accept", PlayerAction.ADD_PERMISSION_TO_SEE_HAND_CARDS); @@ -188,7 +189,7 @@ public class GameSessionPlayer extends GameSessionWatcher { GameView gameView = new GameView(game.getState(), game, playerId, null); gameView.setHand(new CardsView(game, player.getHand().getCards(game))); if (gameView.getPriorityPlayerName().equals(player.getName())) { - gameView.setCanPlayInHand(player.getPlayableInHand(game)); + gameView.setCanPlayObjects(player.getPlayableObjects(game, Zone.ALL)); } processControlledPlayers(player, gameView); diff --git a/Mage.Sets/src/mage/cards/w/WordOfCommand.java b/Mage.Sets/src/mage/cards/w/WordOfCommand.java index 02f2be07df..68626c6457 100644 --- a/Mage.Sets/src/mage/cards/w/WordOfCommand.java +++ b/Mage.Sets/src/mage/cards/w/WordOfCommand.java @@ -169,7 +169,7 @@ class WordOfCommandEffect extends OneShotEffect { private boolean checkPlayability(Card card, Player targetPlayer, Game game, Ability source) { // check for card playability boolean canPlay = false; - if (card.isLand()) { // we can't use getPlayableInHand(game) in here because it disallows playing lands outside the main step + if (card.isLand()) { // we can't use getPlayableObjects(game) in here because it disallows playing lands outside the main step // TODO: replace to getPlayable() checks with disable step condition? if (targetPlayer.canPlayLand() && game.getActivePlayerId().equals(targetPlayer.getId())) { canPlay = true; @@ -182,7 +182,7 @@ class WordOfCommandEffect extends OneShotEffect { } else { // Word of Command allows the chosen card to be played "as if it had flash" so we need to invoke such effect to bypass the check AsThoughEffectImpl effect2 = new WordOfCommandTestFlashEffect(); game.addEffect(effect2, source); - if (targetPlayer.getPlayableInHand(game).contains(card.getId())) { + if (targetPlayer.getPlayableObjects(game, Zone.HAND).contains(card.getId())) { canPlay = true; } for (AsThoughEffect eff : game.getContinuousEffects().getApplicableAsThoughEffects(AsThoughEffectType.CAST_AS_INSTANT, game)) { 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 29ef18a65c..b06f4223a7 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 @@ -2809,8 +2809,8 @@ public class TestPlayer implements Player { } @Override - public Set getPlayableInHand(Game game) { - return computerPlayer.getPlayableInHand(game); + public Set getPlayableObjects(Game game, Zone zone) { + return computerPlayer.getPlayableObjects(game, zone); } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 8649ddeba9..a626175038 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -1047,7 +1047,7 @@ public class PlayerStub implements Player { } @Override - public Set getPlayableInHand(Game game) { + public Set getPlayableObjects(Game game, Zone zone) { return null; } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 9f52e0cf0a..be7cb71aaf 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -633,7 +633,7 @@ public interface Player extends MageItem, Copyable { List getPlayableOptions(Ability ability, Game game); - Set getPlayableInHand(Game game); + Set getPlayableObjects(Game game, Zone zone); LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 6dd3cbbe36..c783f1ad07 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3049,17 +3049,22 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public List getPlayable(Game game, boolean hidden) { + return getPlayable(game, hidden, Zone.ALL, true); + } + + public List getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) { List playable = new ArrayList<>(); if (!shouldSkipGettingPlayable(game)) { - ManaOptions availableMana = getManaAvailable(game); availableMana.addMana(manaPool.getMana()); for (ConditionalMana conditionalMana : manaPool.getConditionalMana()) { availableMana.addMana(conditionalMana); } - if (hidden) { + boolean fromAll = fromZone.equals(Zone.ALL); + + if (hidden && (fromAll || fromZone == Zone.HAND)) { for (Card card : hand.getUniqueCards(game)) { for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?) if (ability.getZone().match(Zone.HAND)) { @@ -3088,35 +3093,39 @@ public abstract class PlayerImpl implements Player, Serializable { } } - for (Card card : graveyard.getUniqueCards(game)) { - // Handle split cards in graveyard to support Aftermath - if (card instanceof SplitCard) { - SplitCard splitCard = (SplitCard) card; - getPlayableFromGraveyardCard(game, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, playable); - getPlayableFromGraveyardCard(game, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, playable); - getPlayableFromGraveyardCard(game, splitCard, splitCard.getSharedAbilities(), availableMana, playable); - } else { - getPlayableFromGraveyardCard(game, card, card.getAbilities(), availableMana, playable); - } + if (fromAll || fromZone == Zone.GRAVEYARD) { + for (Card card : graveyard.getUniqueCards(game)) { + // Handle split cards in graveyard to support Aftermath + if (card instanceof SplitCard) { + SplitCard splitCard = (SplitCard) card; + getPlayableFromGraveyardCard(game, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, playable); + getPlayableFromGraveyardCard(game, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, playable); + getPlayableFromGraveyardCard(game, splitCard, splitCard.getSharedAbilities(), availableMana, playable); + } else { + getPlayableFromGraveyardCard(game, card, card.getAbilities(), availableMana, playable); + } - // Other activated abilities - LinkedHashMap useable = new LinkedHashMap<>(); - getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable); - playable.addAll(useable.values()); + // Other activated abilities + LinkedHashMap useable = new LinkedHashMap<>(); + getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable); + playable.addAll(useable.values()); + } } - for (ExileZone exile : game.getExile().getExileZones()) { - for (Card card : exile.getCards(game)) { - if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) { - for (Ability ability : card.getAbilities()) { - if (ability.getZone().match(Zone.HAND)) { - ability.setControllerId(this.getId()); // controller must be set for case owner != caster - if (ability instanceof ActivatedAbility) { - if (((ActivatedAbility) ability).canActivate(playerId, game).canActivate()) { - playable.add(ability); + if (fromAll || fromZone == Zone.EXILED) { + for (ExileZone exile : game.getExile().getExileZones()) { + for (Card card : exile.getCards(game)) { + if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) { + for (Ability ability : card.getAbilities()) { + if (ability.getZone().match(Zone.HAND)) { + ability.setControllerId(this.getId()); // controller must be set for case owner != caster + if (ability instanceof ActivatedAbility) { + if (((ActivatedAbility) ability).canActivate(playerId, game).canActivate()) { + playable.add(ability); + } } + ability.setControllerId(card.getOwnerId()); } - ability.setControllerId(card.getOwnerId()); } } } @@ -3124,25 +3133,10 @@ public abstract class PlayerImpl implements Player, Serializable { } // check to play revealed cards - for (Cards cards : game.getState().getRevealed().values()) { - for (Card card : cards.getCards(game)) { - if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) { - for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) { - if (ability instanceof SpellAbility || ability instanceof PlayLandAbility) { - playable.add(ability); - } - } - } - } - } - - // check if it's possible to play the top card of a library - for (UUID playerInRangeId : game.getState().getPlayersInRange(getId(), game)) { - Player player = game.getPlayer(playerInRangeId); - if (player != null) { - if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) { - Card card = player.getLibrary().getFromTop(game); - if (card != null && null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), getId(), game)) { + if (fromAll) { + for (Cards cards : game.getState().getRevealed().values()) { + for (Card card : cards.getCards(game)) { + if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) { for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) { if (ability instanceof SpellAbility || ability instanceof PlayLandAbility) { playable.add(ability); @@ -3153,35 +3147,69 @@ public abstract class PlayerImpl implements Player, Serializable { } } - // eliminate duplicate activated abilities - Map playableActivated = new HashMap<>(); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) { - LinkedHashMap useableAbilities = getUseableActivatedAbilities(permanent, Zone.BATTLEFIELD, game); - for (ActivatedAbility ability : useableAbilities.values()) { - playableActivated.putIfAbsent(ability.toString(), ability); + // check if it's possible to play the top card of a library + if (fromAll || fromZone == Zone.LIBRARY) { + for (UUID playerInRangeId : game.getState().getPlayersInRange(getId(), game)) { + Player player = game.getPlayer(playerInRangeId); + if (player != null) { + if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) { + Card card = player.getLibrary().getFromTop(game); + if (card != null && null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), getId(), game)) { + for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) { + if (ability instanceof SpellAbility || ability instanceof PlayLandAbility) { + playable.add(ability); + } + } + } + } + } + } + } + + // eliminate duplicate activated abilities (uses for AI plays) + Map activatedUnique = new HashMap<>(); + List activatedAll = new ArrayList<>(); + + // activated abilities from battlefield objects + if (fromAll || fromZone == Zone.BATTLEFIELD) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) { + LinkedHashMap useableAbilities = getUseableActivatedAbilities(permanent, Zone.BATTLEFIELD, game); + for (ActivatedAbility ability : useableAbilities.values()) { + activatedUnique.putIfAbsent(ability.toString(), ability); + activatedAll.add(ability); + } } } // activated abilities from stack objects - for (StackObject stackObject : game.getState().getStack()) { - for (ActivatedAbility ability : stackObject.getAbilities().getActivatedAbilities(Zone.STACK)) { - if (ability != null && canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) { - playableActivated.put(ability.toString(), ability); + if (fromAll || fromZone == Zone.STACK) { + for (StackObject stackObject : game.getState().getStack()) { + for (ActivatedAbility ability : stackObject.getAbilities().getActivatedAbilities(Zone.STACK)) { + if (ability != null && canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) { + activatedUnique.put(ability.toString(), ability); + activatedAll.add(ability); + } } - } } // activated abilities from objects in the command zone (emblems or commanders) - for (CommandObject commandObject : game.getState().getCommand()) { - for (ActivatedAbility ability : commandObject.getAbilities().getActivatedAbilities(Zone.COMMAND)) { - if (ability.isControlledBy(getId()) && canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) { - playableActivated.put(ability.toString(), ability); + if (fromAll || fromZone == Zone.COMMAND) { + for (CommandObject commandObject : game.getState().getCommand()) { + for (ActivatedAbility ability : commandObject.getAbilities().getActivatedAbilities(Zone.COMMAND)) { + if (ability.isControlledBy(getId()) && canPlay(ability, availableMana, game.getObject(ability.getSourceId()), game)) { + activatedUnique.put(ability.toString(), ability); + activatedAll.add(ability); + } } } } - playable.addAll(playableActivated.values()); + if (hideDuplicatedAbilities) { + playable.addAll(activatedUnique.values()); + } else { + playable.addAll(activatedAll); + } } return playable; @@ -3195,50 +3223,16 @@ public abstract class PlayerImpl implements Player, Serializable { * @return A Set of cardIds that are playable */ @Override - public Set getPlayableInHand(Game game - ) { - Set playable = new HashSet<>(); - if (!shouldSkipGettingPlayable(game)) { - ManaOptions available = getManaAvailable(game); - available.addMana(manaPool.getMana()); + public Set getPlayableObjects(Game game, Zone zone) { - for (Card card : hand.getCards(game)) { - Abilities: - for (Ability ability : card.getAbilities()) { - if (ability.getZone().match(Zone.HAND)) { - switch (ability.getAbilityType()) { - case PLAY_LAND: - if (game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), ability.getSourceId(), playerId), ability, game, true)) { - break; - } - if (canPlay((ActivatedAbility) ability, available, card, game)) { - playable.add(card.getId()); - break Abilities; - } - break; - case ACTIVATED: - case SPELL: - if (canPlay((ActivatedAbility) ability, available, card, game)) { - playable.add(card.getId()); - break Abilities; - } - break; - case STATIC: - if (card.isLand() && ability instanceof AlternativeSourceCosts) { - if (canLandPlayAlternateSourceCostsAbility(card, available, ability, game)) { // e.g. Land with Morph - if (game.canPlaySorcery(getId())) { - playable.add(card.getId()); - } - break Abilities; - } - } - } - } - } + List playableAbilities = getPlayable(game, true, zone, false); // do not hide duplicated abilities + Set playableObjects = new HashSet<>(); + for (Ability ability : playableAbilities) { + if (ability.getSourceId() != null) { + playableObjects.add(ability.getSourceId()); } } - - return playable; + return playableObjects; } /**