From cce467a5ec8d4679f6118bdcdb15343ae2863f54 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 4 Jun 2020 03:21:18 +0400 Subject: [PATCH] Additional fix and simplified for playable abilities (see comments b94344341bf47968fcfa2018aa418547da25917e) --- .../src/mage/player/ai/SimulatedPlayer2.java | 13 +- .../src/mage/player/ai/MCTSPlayer.java | 9 +- .../mage/player/ai/SimulatedPlayerMCTS.java | 2 +- .../src/mage/player/ai/SimulatedPlayer.java | 7 +- .../src/mage/player/human/HumanPlayer.java | 2 +- .../java/mage/server/util/SystemUtil.java | 6 +- .../CastSplitCardsWithFlashbackTest.java | 34 +++- .../org/mage/test/player/RandomPlayer.java | 6 +- .../java/org/mage/test/player/TestPlayer.java | 20 +-- .../java/org/mage/test/stub/PlayerStub.java | 4 +- Mage/src/main/java/mage/cards/CardImpl.java | 16 ++ Mage/src/main/java/mage/players/Player.java | 4 +- .../main/java/mage/players/PlayerImpl.java | 156 +++++++----------- 13 files changed, 142 insertions(+), 137 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java index 54ac89b928e..771dc4afcd1 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java @@ -3,6 +3,7 @@ package mage.player.ai; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.AbilityImpl; +import mage.abilities.ActivatedAbility; import mage.abilities.TriggeredAbility; import mage.abilities.common.PassAbility; import mage.abilities.costs.mana.ManaCost; @@ -94,9 +95,9 @@ public class SimulatedPlayer2 extends ComputerPlayer { } protected void simulateOptions(Game game) { - List playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer); + List playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer); playables = filterAbilities(game, playables, suggested); - for (Ability ability : playables) { + for (ActivatedAbility ability : playables) { if (ability.getAbilityType() == AbilityType.MANA) { continue; } @@ -186,15 +187,15 @@ public class SimulatedPlayer2 extends ComputerPlayer { * @param suggested * @return */ - protected List filterAbilities(Game game, List playables, List suggested) { + protected List filterAbilities(Game game, List playables, List suggested) { if (playables.isEmpty()) { return playables; } if (suggested == null || suggested.isEmpty()) { return playables; } - List filtered = new ArrayList<>(); - for (Ability ability : playables) { + List filtered = new ArrayList<>(); + for (ActivatedAbility ability : playables) { Card card = game.getCard(ability.getSourceId()); if (card != null) { for (String s : suggested) { @@ -212,7 +213,7 @@ public class SimulatedPlayer2 extends ComputerPlayer { return playables; } - protected List filterOptions(Game game, List options, Ability ability, List suggested) { + protected List filterOptions(Game game, List options, ActivatedAbility ability, List suggested) { if (options.isEmpty()) { return options; } diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java index 5dedfe41d73..bdd731ffc40 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java @@ -2,6 +2,7 @@ package mage.player.ai; import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; import mage.abilities.SpellAbility; import mage.abilities.common.PassAbility; import mage.abilities.costs.mana.GenericManaCost; @@ -45,16 +46,16 @@ public class MCTSPlayer extends ComputerPlayer { return new MCTSPlayer(this); } - protected List getPlayableAbilities(Game game) { - List playables = getPlayable(game, true); + protected List getPlayableAbilities(Game game) { + List playables = getPlayable(game, true); playables.add(pass); return playables; } public List getPlayableOptions(Game game) { List all = new ArrayList<>(); - List playables = getPlayableAbilities(game); - for (Ability ability: playables) { + List playables = getPlayableAbilities(game); + for (ActivatedAbility ability: playables) { List options = game.getPlayer(playerId).getPlayableOptions(ability, game); if (options.isEmpty()) { if (!ability.getManaCosts().getVariableCosts().isEmpty()) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java index 0b3c44f268b..957f43e5ac1 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java @@ -73,7 +73,7 @@ public class SimulatedPlayerMCTS extends MCTSPlayer { } private Ability getAction(Game game) { - List playables = getPlayableAbilities(game); + List playables = getPlayableAbilities(game); Ability ability; while (true) { if (playables.size() == 1) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java index 3bc58353c73..4b141ec80a5 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java @@ -3,6 +3,7 @@ package mage.player.ai; import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; import mage.abilities.SpellAbility; import mage.abilities.TriggeredAbility; import mage.abilities.common.PassAbility; @@ -59,10 +60,10 @@ public class SimulatedPlayer extends ComputerPlayer { return list; } - protected void simulateOptions(Game game, Ability previousActions) { + protected void simulateOptions(Game game, ActivatedAbility previousActions) { allActions.add(previousActions); - List playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer); - for (Ability ability: playables) { + List playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer); + for (ActivatedAbility ability: playables) { List options = game.getPlayer(playerId).getPlayableOptions(ability, game); if (options.isEmpty()) { if (!ability.getManaCosts().getVariableCosts().isEmpty()) { diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 7455d093361..feb685d60cb 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -1056,7 +1056,7 @@ public class HumanPlayer extends PlayerImpl { actingPlayer = game.getPlayer(game.getPriorityPlayerId()); } if (actingPlayer != null) { - useableAbilities = actingPlayer.getUseableActivatedAbilities(object, zone, game); + useableAbilities = actingPlayer.getPlayableActivatedAbilities(object, zone, game); } if (object instanceof Card diff --git a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java index 58f8feb130c..1d64adf871f 100644 --- a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java @@ -425,7 +425,7 @@ public final class SystemUtil { game.firePriorityEvent(opponent.getId()); } - List abilities = opponent.getPlayable(game, true); + List abilities = opponent.getPlayable(game, true); Map choices = new HashMap<>(); abilities.forEach(ability -> { MageObject object = ability.getSourceObject(game); @@ -437,10 +437,10 @@ public final class SystemUtil { choice.setKeyChoices(choices); if (feedbackPlayer.choose(Outcome.Detriment, choice, game) && choice.getChoiceKey() != null) { String needId = choice.getChoiceKey(); - Optional ability = abilities.stream().filter(a -> a.getId().toString().equals(needId)).findFirst(); + Optional ability = abilities.stream().filter(a -> a.getId().toString().equals(needId)).findFirst(); if (ability.isPresent()) { // TODO: set priority for player? - ActivatedAbility activatedAbility = (ActivatedAbility) ability.get(); + ActivatedAbility activatedAbility = ability.get(); game.informPlayers(feedbackPlayer.getLogName() + " as another player " + opponent.getLogName() + " trying to force an activate ability: " + activatedAbility.getGameLogMessage(game)); if (opponent.activateAbility(activatedAbility, game)) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithFlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithFlashbackTest.java index d81189f6e06..9aa250eb7e6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithFlashbackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithFlashbackTest.java @@ -37,7 +37,7 @@ public class CastSplitCardsWithFlashbackTest extends CardTestPlayerBase { } @Test - public void test_Flashback_Split() { + public void test_Flashback_SplitLeft() { // {1}{U} // When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); @@ -67,4 +67,36 @@ public class CastSplitCardsWithFlashbackTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Bident of Thassa", 1); assertPermanentCount(playerB, "Bow of Nylea", 1); } + + @Test + public void test_Flashback_SplitRight() { + // {1}{U} + // When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. + addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // + // Wear {1}{R} Destroy target artifact. + // Tear {W} Destroy target enchantment. + addCard(Zone.GRAVEYARD, playerA, "Wear // Tear", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Bident of Thassa", 1); // Legendary Enchantment Artifact + addCard(Zone.BATTLEFIELD, playerB, "Bow of Nylea", 1); // Legendary Enchantment Artifact + + // add flashback + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); + addTarget(playerA, "Wear // Tear"); + + // cast as flashback + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback {W}", "Bident of Thassa"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerB, "Bident of Thassa", 1); + assertPermanentCount(playerB, "Bow of Nylea", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java index 3a5fdc13bff..f2db711c38b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java @@ -73,7 +73,7 @@ public class RandomPlayer extends ComputerPlayer { } private Ability getAction(Game game) { - List playables = getPlayableAbilities(game); + List playables = getPlayableAbilities(game); Ability ability; while (true) { if (playables.size() == 1) { @@ -115,8 +115,8 @@ public class RandomPlayer extends ComputerPlayer { return ability; } - protected List getPlayableAbilities(Game game) { - List playables = getPlayable(game, true); + protected List getPlayableAbilities(Game game) { + List playables = getPlayable(game, true); playables.add(pass); return playables; } 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 b0c26158742..b43b200626d 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 @@ -587,14 +587,14 @@ public class TestPlayer implements Player { if (groups.length > 2 && !checkExecuteCondition(groups, game)) { break; } - for (Ability ability : computerPlayer.getPlayable(game, true)) { // add wrong action log? + for (ActivatedAbility ability : computerPlayer.getPlayable(game, true)) { // add wrong action log? if (isAbilityHaveTargetNameOrAlias(game, ability, groups[0])) { int bookmark = game.bookmarkState(); - Ability newAbility = ability.copy(); + ActivatedAbility newAbility = ability.copy(); if (groups.length > 1 && !groups[1].equals("target=" + NO_TARGET)) { groupsForTargetHandling = groups; } - if (computerPlayer.activateAbility((ActivatedAbility) newAbility, game)) { + if (computerPlayer.activateAbility(newAbility, game)) { actions.remove(action); groupsForTargetHandling = null; foundNoAction = 0; // Reset enless loop check because of no action @@ -1078,7 +1078,7 @@ public class TestPlayer implements Player { }); } - private void printAbilities(Game game, List abilities) { + private void printAbilities(Game game, List abilities) { System.out.println("Total abilities: " + (abilities != null ? abilities.size() : 0)); if (abilities == null) { return; @@ -1714,6 +1714,8 @@ public class TestPlayer implements Player { private void chooseStrictModeFailed(String choiceType, Game game, String reason, boolean printAbilities) { if (strictChooseMode && !AICanChooseInStrictMode) { if (printAbilities) { + printStart("Available mana for " + computerPlayer.getName()); + printMana(game, computerPlayer.getManaAvailable(game)); printStart("Available abilities for " + computerPlayer.getName()); printAbilities(game, computerPlayer.getPlayable(game, true)); printEnd(); @@ -2712,8 +2714,8 @@ public class TestPlayer implements Player { } @Override - public LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { - return computerPlayer.getUseableActivatedAbilities(object, zone, game); + public LinkedHashMap getPlayableActivatedAbilities(MageObject object, Zone zone, Game game) { + return computerPlayer.getPlayableActivatedAbilities(object, zone, game); } @Override @@ -3171,12 +3173,8 @@ public class TestPlayer implements Player { return computerPlayer.getManaAvailable(game); } - public List getAvailableManaProducersWithCost(Game game) { - return computerPlayer.getAvailableManaProducersWithCost(game); - } - @Override - public List getPlayable(Game game, boolean hidden) { + public List getPlayable(Game game, boolean hidden) { return computerPlayer.getPlayable(game, hidden); } 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 d4b5f701cba..7093c322591 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 @@ -1023,7 +1023,7 @@ public class PlayerStub implements Player { } @Override - public List getPlayable(Game game, boolean hidden) { + public List getPlayable(Game game, boolean hidden) { return null; } @@ -1038,7 +1038,7 @@ public class PlayerStub implements Player { } @Override - public LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { + public LinkedHashMap getPlayableActivatedAbilities(MageObject object, Zone zone, Game game) { return null; } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index f7d168227b5..b968fbc342e 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -14,6 +14,7 @@ import mage.ObjectColor; import mage.abilities.*; import mage.abilities.hint.Hint; import mage.abilities.hint.HintUtils; +import mage.abilities.keyword.FlashbackAbility; import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.cards.repository.PluginClassloaderRegistery; import mage.constants.*; @@ -312,6 +313,21 @@ public abstract class CardImpl extends MageObjectImpl implements Card { // dynamic all.addAll(cardState.getAbilities()); + // workaround to add dynamic flashback ability from main card to all parts (example: Snapcaster Mage gives flashback to split card) + if (!this.getId().equals(this.getMainCard().getId())) { + CardState mainCardState = game.getState().getCardState(this.getMainCard().getId()); + if (mainCardState != null + && !mainCardState.hasLostAllAbilities() + && mainCardState.getAbilities().containsClass(FlashbackAbility.class)) { + FlashbackAbility flash = new FlashbackAbility(this.getManaCost(), this.isInstant() ? TimingRule.INSTANT : TimingRule.SORCERY); + flash.setSourceId(this.getId()); + flash.setControllerId(this.getOwnerId()); + flash.setSpellAbilityType(this.getSpellAbility().getSpellAbilityType()); + flash.setAbilityName(this.getName()); + all.add(flash); + } + } + return all; } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 8df2d477a3e..61e04700c69 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -627,13 +627,13 @@ public interface Player extends MageItem, Copyable { ManaOptions getManaAvailable(Game game); - List getPlayable(Game game, boolean hidden); + List getPlayable(Game game, boolean hidden); List getPlayableOptions(Ability ability, Game game); Map getPlayableObjects(Game game, Zone zone); - LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game); + LinkedHashMap getPlayableActivatedAbilities(MageObject object, Zone zone, Game game); boolean addCounters(Counter counter, Game game); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 9fa114ba307..8957b6905a4 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1499,7 +1499,7 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { + public LinkedHashMap getPlayableActivatedAbilities(MageObject object, Zone zone, Game game) { LinkedHashMap useable = new LinkedHashMap<>(); boolean previousState = game.inCheckPlayableState(); game.setCheckPlayableState(true); @@ -1510,12 +1510,33 @@ public abstract class PlayerImpl implements Player, Serializable { } // collect and filter playable activated abilities - List allPlayable = getPlayable(game, true, zone, false); - for (Ability ability : allPlayable) { - if (ability instanceof ActivatedAbility) { - if (object.hasAbility(ability, game)) { - useable.putIfAbsent(ability.getId(), (ActivatedAbility) ability); - } + // GUI: user clicks on card, but it must activate ability from any card's parts (main, left, right) + UUID needId1, needId2, needId3; + if (object instanceof SplitCard) { + needId1 = object.getId(); + needId2 = ((SplitCard) object).getLeftHalfCard().getId(); + needId3 = ((SplitCard) object).getRightHalfCard().getId(); + } else if (object instanceof AdventureCard) { + needId1 = object.getId(); + needId2 = ((AdventureCard) object).getMainCard().getId(); + needId3 = ((AdventureCard) object).getSpellCard().getId(); + } else if (object instanceof AdventureCardSpell) { + needId1 = object.getId(); + needId2 = ((AdventureCardSpell) object).getParentCard().getId(); + needId3 = object.getId(); + } else { + needId1 = object.getId(); + needId2 = object.getId(); + needId3 = object.getId(); + } + + // workaround to find all abilities first and filter it for one object + List allPlayable = getPlayable(game, true, zone, false); + for (ActivatedAbility ability : allPlayable) { + if (Objects.equals(ability.getSourceId(), needId1) + || Objects.equals(ability.getSourceId(), needId2) + || Objects.equals(ability.getSourceId(), needId3)) { + useable.putIfAbsent(ability.getId(), ability); } } } finally { @@ -1524,58 +1545,6 @@ public abstract class PlayerImpl implements Player, Serializable { return useable; } - // Adds special abilities that are given to non permanents by continuous effects - private void getOtherUseableActivatedAbilities(MageObject object, Zone zone, Game game, Map useable) { - Abilities otherAbilities = game.getState().getActivatedOtherAbilities(object.getId(), zone); - if (otherAbilities != null) { - boolean canUse = !(object instanceof Permanent) - || ((Permanent) object).canUseActivatedAbilities(game); - for (ActivatedAbility ability : otherAbilities) { - if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { - Card card = game.getCard(ability.getSourceId()); - if (card != null) { - if (card.isSplitCard() && ability instanceof FlashbackAbility) { - FlashbackAbility flashbackAbility; - // Left Half - if (card.isInstant()) { - flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), - TimingRule.INSTANT); - } else { - flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), - TimingRule.SORCERY); - } - flashbackAbility.setSourceId(card.getId()); - flashbackAbility.setControllerId(card.getOwnerId()); - flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_LEFT); - flashbackAbility.setAbilityName(((SplitCard) card).getLeftHalfCard().getName()); - if (flashbackAbility.canActivate(playerId, game).canActivate()) { - useable.put(flashbackAbility.getId(), flashbackAbility); - } - // Right Half - if (card.isInstant()) { - flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), - TimingRule.INSTANT); - } else { - flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), - TimingRule.SORCERY); - } - flashbackAbility.setSourceId(card.getId()); - flashbackAbility.setControllerId(card.getOwnerId()); - flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_RIGHT); - flashbackAbility.setAbilityName(((SplitCard) card).getRightHalfCard().getName()); - if (flashbackAbility.canActivate(playerId, game).canActivate()) { - useable.put(flashbackAbility.getId(), flashbackAbility); - } - - } else { - useable.put(ability.getId(), ability); - } - } - } - } - } - } - protected LinkedHashMap getUseableManaAbilities(MageObject object, Zone zone, Game game) { LinkedHashMap useable = new LinkedHashMap<>(); boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); @@ -3182,7 +3151,16 @@ public abstract class PlayerImpl implements Player, Serializable { return false; } - private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List output) { + private Abilities getActivatedOnly(Abilities list) { + Abilities res = new AbilitiesImpl<>(); + list.stream() + .filter(a -> a instanceof ActivatedAbility) + .map(a -> (ActivatedAbility) a) + .forEach(res::add); + return res; + } + + private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List output) { if (fromZone == null) { return; } @@ -3190,41 +3168,22 @@ public abstract class PlayerImpl implements Player, Serializable { // BASIC abilities if (card instanceof SplitCard) { SplitCard splitCard = (SplitCard) card; - getPlayableFromCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, output); - getPlayableFromCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, output); - getPlayableFromCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(game), availableMana, output); + getPlayableFromCardSingle(game, fromZone, splitCard.getLeftHalfCard(), getActivatedOnly(splitCard.getLeftHalfCard().getAbilities(game)), availableMana, output); + getPlayableFromCardSingle(game, fromZone, splitCard.getRightHalfCard(), getActivatedOnly(splitCard.getRightHalfCard().getAbilities(game)), availableMana, output); + getPlayableFromCardSingle(game, fromZone, splitCard, getActivatedOnly(splitCard.getSharedAbilities(game)), availableMana, output); } else if (card instanceof AdventureCard) { // adventure must use different card characteristics for different spells (main or adventure) AdventureCard adventureCard = (AdventureCard) card; - getPlayableFromCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(), availableMana, output); - getPlayableFromCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(game), availableMana, output); + getPlayableFromCardSingle(game, fromZone, adventureCard.getSpellCard(), getActivatedOnly(adventureCard.getSpellCard().getAbilities(game)), availableMana, output); + getPlayableFromCardSingle(game, fromZone, adventureCard, getActivatedOnly(adventureCard.getSharedAbilities(game)), availableMana, output); } else { - getPlayableFromCardSingle(game, fromZone, card, card.getAbilities(game), availableMana, output); + getPlayableFromCardSingle(game, fromZone, card, getActivatedOnly(card.getAbilities(game)), availableMana, output); } - // DYNAMIC ADDED abilities - if (fromZone != Zone.ALL) { // TODO: test revealed cards with dynamic added abilities - // Other activated abilities (added dynamic by effects) - LinkedHashMap useable; - if (card instanceof AdventureCard) { - // adventure cards (contains two different cards: main and adventure spell) - useable = new LinkedHashMap<>(); - getOtherUseableActivatedAbilities(((AdventureCard) card).getSpellCard(), fromZone, game, useable); - output.addAll(useable.values()); - - useable = new LinkedHashMap<>(); - getOtherUseableActivatedAbilities(card, fromZone, game, useable); - output.addAll(useable.values()); - } else { - // all other cards (TODO: check split cards with dynamic added abilities) - useable = new LinkedHashMap<>(); - getOtherUseableActivatedAbilities(card, fromZone, game, useable); - output.addAll(useable.values()); - } - } + // DYNAMIC ADDED abilities are adds in getAbilities(game) } - private void getPlayableFromCardSingle(Game game, Zone fromZone, Card card, Abilities candidateAbilities, ManaOptions availableMana, List output) { + private void getPlayableFromCardSingle(Game game, Zone fromZone, Card card, Abilities candidateAbilities, ManaOptions availableMana, List output) { // check "can play" condition as affected controller (BUT play from not own hand zone must be checked as original controller) for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) { boolean isPlaySpell = (ability instanceof SpellAbility); @@ -3300,12 +3259,12 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public List getPlayable(Game game, boolean hidden) { + 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<>(); + public List getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) { + List playable = new ArrayList<>(); if (shouldSkipGettingPlayable(game)) { return playable; } @@ -3402,19 +3361,17 @@ public abstract class PlayerImpl implements Player, Serializable { } // eliminate duplicate activated abilities (uses for AI plays) - Map activatedUnique = new HashMap<>(); - List activatedAll = new ArrayList<>(); + 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)) { - List battlePlayable = new ArrayList<>(); + List battlePlayable = new ArrayList<>(); getPlayableFromCardAll(game, Zone.BATTLEFIELD, permanent, availableMana, battlePlayable); - for (Ability ability : battlePlayable) { - if (ability instanceof ActivatedAbility) { - activatedUnique.putIfAbsent(ability.toString(), ability); - activatedAll.add(ability); - } + for (ActivatedAbility ability : battlePlayable) { + activatedUnique.putIfAbsent(ability.toString(), ability); + activatedAll.add(ability); } } } @@ -3467,7 +3424,7 @@ public abstract class PlayerImpl implements Player, Serializable { */ @Override public Map getPlayableObjects(Game game, Zone zone) { - List playableAbilities = getPlayable(game, true, zone, false); // do not hide duplicated abilities/cards + List playableAbilities = getPlayable(game, true, zone, false); // do not hide duplicated abilities/cards Map playableObjects = new HashMap<>(); for (Ability ability : playableAbilities) { if (ability.getSourceId() != null) { @@ -3518,7 +3475,6 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public List getPlayableOptions(Ability ability, Game game) { List options = new ArrayList<>(); - if (ability.isModal()) { addModeOptions(options, ability, game); } else if (!ability.getTargets().getUnchosen().isEmpty()) {