From fac7ea13881ae5bde27bbb66a773270afc4f1878 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 23 Dec 2019 21:47:01 +0400 Subject: [PATCH] * AI: fixed rollback errors with copy spell abilities; Tests: added copy spell support for test player; --- .../java/mage/player/ai/ComputerPlayer.java | 53 ++++++----- .../cards/triggers/ZadaHedronGrinderTest.java | 65 ++++++++++++- .../java/org/mage/test/player/TestPlayer.java | 92 +++++++++---------- 3 files changed, 137 insertions(+), 73 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index b6b29f7aaf7..d83abf266f7 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -146,6 +146,12 @@ public class ComputerPlayer extends PlayerImpl implements Player { abilityControllerId = target.getAbilityController(); } + boolean required = target.isRequired(sourceId, game); + Set possibleTargets = target.possibleTargets(sourceId, abilityControllerId, game); + if (possibleTargets.isEmpty() || target.getTargets().size() >= target.getNumberOfTargets()) { + required = false; + } + UUID randomOpponentId; if (target.getTargetController() != null) { randomOpponentId = getRandomOpponent(target.getTargetController(), game); @@ -156,7 +162,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (target.getOriginalTarget() instanceof TargetPlayer) { - return setTargetPlayer(outcome, target, null, sourceId, abilityControllerId, randomOpponentId, game); + return setTargetPlayer(outcome, target, null, sourceId, abilityControllerId, randomOpponentId, game, required); } if (target.getOriginalTarget() instanceof TargetDiscard) { @@ -287,7 +293,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { target.add(randomOpponentId, game); return true; } - if (!target.isRequired(sourceId, game)) { + if (!required) { return false; } } @@ -318,7 +324,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { target.add(randomOpponentId, game); return true; } - if (!target.isRequired(sourceId, game)) { + if (!required) { return false; } } @@ -405,7 +411,8 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard || target.getOriginalTarget() instanceof TargetCardInASingleGraveyard) { List alreadyTargeted = target.getTargets(); - List cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards((FilterCard) target.getFilter(), game)); + TargetCard originalTarget = (TargetCard) target.getOriginalTarget(); + List cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards(originalTarget.getFilter(), game)); while (!cards.isEmpty()) { Card card = pickTarget(abilityControllerId, cards, outcome, target, null, game); if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) { @@ -433,7 +440,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } } - if (!target.isRequired(sourceId, game)) { + if (!required) { return false; } throw new IllegalStateException("TargetSource wasn't handled. class: " + target.getClass().toString()); @@ -448,6 +455,10 @@ public class ComputerPlayer extends PlayerImpl implements Player { log.debug("chooseTarget: " + outcome.toString() + ':' + target.toString()); } + // target - real target, make all changes and add targets to it + // target.getOriginalTarget() - copy spell effect replaces original target with TargetWithAdditionalFilter + // use originalTarget to get filters and target class info + // source can be null (as example: legendary rule permanent selection) UUID sourceId = source != null ? source.getSourceId() : null; @@ -482,7 +493,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (target.getOriginalTarget() instanceof TargetPlayer) { - return setTargetPlayer(outcome, target, source, sourceId, abilityControllerId, randomOpponentId, game); + return setTargetPlayer(outcome, target, source, sourceId, abilityControllerId, randomOpponentId, game, required); } if (target.getOriginalTarget() instanceof TargetDiscard @@ -555,15 +566,8 @@ public class ComputerPlayer extends PlayerImpl implements Player { // TODO: add findBest*Targets for all target types if (target.getOriginalTarget() instanceof TargetPermanent) { TargetPermanent origTarget = (TargetPermanent) target.getOriginalTarget(); - findBestPermanentTargets(outcome, abilityControllerId, sourceId, ((TargetPermanent) target).getFilter(), - game, target, goodList, badList, allList); findBestPermanentTargets(outcome, abilityControllerId, sourceId, origTarget.getFilter(), - game, target, goodList2, badList2, allList2); - if (goodList.size() != goodList2.size() || badList.size() != badList2.size() || allList.size() != allList2.size() - || !origTarget.getFilter().equals(target.getFilter())) { - // TODO: remove double check after servers testing - log.error("Different filters in target and origTarget: " + target.getClass().getName() + " - " + origTarget.getClass().getName()); - } + game, target, goodList, badList, allList); // use good list all the time and add maximum targets for (Permanent permanent : goodList) { @@ -589,7 +593,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (target.getOriginalTarget() instanceof TargetCreatureOrPlayer) { List targets; - TargetCreatureOrPlayer origTarget = ((TargetCreatureOrPlayer) target); + TargetCreatureOrPlayer origTarget = ((TargetCreatureOrPlayer) target.getOriginalTarget()); if (outcome.isGood()) { targets = threats(abilityControllerId, sourceId, ((FilterCreatureOrPlayer) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } else { @@ -626,13 +630,12 @@ public class ComputerPlayer extends PlayerImpl implements Player { return tryAddTarget(target, randomOpponentId, source, game); } - //if (!target.isRequired()) return false; } if (target.getOriginalTarget() instanceof TargetAnyTarget) { List targets; - TargetAnyTarget origTarget = ((TargetAnyTarget) target); + TargetAnyTarget origTarget = ((TargetAnyTarget) target.getOriginalTarget()); if (outcome.isGood()) { targets = threats(abilityControllerId, sourceId, ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets()); } else { @@ -649,7 +652,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } - if (targets.isEmpty() && target.isRequired(source)) { + if (targets.isEmpty() && required) { targets = game.getBattlefield().getActivePermanents(((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), playerId, game); } for (Permanent permanent : targets) { @@ -675,7 +678,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) { List targets; - TargetPermanentOrPlayer origTarget = ((TargetPermanentOrPlayer) target); + TargetPermanentOrPlayer origTarget = ((TargetPermanentOrPlayer) target.getOriginalTarget()); if (outcome.isGood()) { targets = threats(abilityControllerId, source.getSourceId(), ((FilterPermanentOrPlayer) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); } else { @@ -707,7 +710,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (target.getOriginalTarget() instanceof TargetPlayerOrPlaneswalker) { List targets; - TargetPlayerOrPlaneswalker origTarget = ((TargetPlayerOrPlaneswalker) target); + TargetPlayerOrPlaneswalker origTarget = ((TargetPlayerOrPlaneswalker) target.getOriginalTarget()); // TODO: if effect is bad and no opponent's targets available then AI can't target yourself but must by rules /* @@ -739,7 +742,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } // can't find targets (e.g. effect is bad, but you need take targets from yourself) - if (targets.isEmpty() && target.isRequired(source)) { + if (targets.isEmpty() && required) { targets = game.getBattlefield().getActivePermanents(origTarget.getFilterPermanent(), playerId, game); } @@ -762,7 +765,6 @@ public class ComputerPlayer extends PlayerImpl implements Player { return tryAddTarget(target, randomOpponentId, source, game); } - //if (!target.isRequired()) return false; } @@ -821,7 +823,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } else { targets = threats(randomOpponentId, source == null ? null : source.getSourceId(), origTarget.getPermanentFilter(), game, target.getTargets()); } - if (targets.isEmpty() && target.isRequired(source)) { + if (targets.isEmpty() && required) { targets = threats(null, source == null ? null : source.getSourceId(), origTarget.getPermanentFilter(), game, target.getTargets()); Collections.reverse(targets); outcomeTargets = false; @@ -1315,6 +1317,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } } + // TODO: wtf?! change to player.getPlayable for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) { for (ActivatedAbility ability : permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD)) { if (!(ability instanceof ActivatedManaAbilityImpl) && ability.canActivate(playerId, game).canActivate()) { @@ -2654,7 +2657,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { /** * Sets a possible target player */ - private boolean setTargetPlayer(Outcome outcome, Target target, Ability source, UUID sourceId, UUID abilityControllerId, UUID randomOpponentId, Game game) { + private boolean setTargetPlayer(Outcome outcome, Target target, Ability source, UUID sourceId, UUID abilityControllerId, UUID randomOpponentId, Game game, boolean required) { if (target.getOriginalTarget() instanceof TargetOpponent) { if (source == null) { if (target.canTarget(randomOpponentId, game)) { @@ -2721,7 +2724,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { target.add(randomOpponentId, game); return true; } - if (target.isRequired(sourceId, game)) { + if (required) { if (target.canTarget(abilityControllerId, game)) { target.add(abilityControllerId, game); return true; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZadaHedronGrinderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZadaHedronGrinderTest.java index 4a66a8e5baf..2152c47107a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZadaHedronGrinderTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZadaHedronGrinderTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.triggers; import mage.abilities.keyword.TrampleAbility; @@ -8,7 +7,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class ZadaHedronGrinderTest extends CardTestPlayerBase { @@ -43,7 +41,70 @@ public class ZadaHedronGrinderTest extends CardTestPlayerBase { assertAbility(playerA, "Zada, Hedron Grinder", TrampleAbility.getInstance(), true); assertPowerToughness(playerA, "Silvercoat Lion", 4, 2); assertAbility(playerA, "Silvercoat Lion", TrampleAbility.getInstance(), true); + } + @Test + public void testTargetsByTestPlayer() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + // Whenever you cast an instant or sorcery spell that targets only Zada, Hedron Grinder, copy that spell for + // each other creature you control that the spell could target. Each copy targets a different one of those creatures. + addCard(Zone.BATTLEFIELD, playerA, "Zada, Hedron Grinder", 1); // 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + // + // Put a +1/+1 counter on target creature. That creature gains reach until end of turn. + addCard(Zone.HAND, playerA, "Arbor Armament", 1); // {G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + + // cast + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Armament", "Zada, Hedron Grinder"); + addTarget(playerA, "Balduvian Bears^Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerA, "Arbor Armament", 1); + assertPowerToughness(playerA, "Zada, Hedron Grinder", 3 + 1, 3 + 1); + assertPowerToughness(playerA, "Silvercoat Lion", 2 + 1, 2 + 1); + assertPowerToughness(playerA, "Balduvian Bears", 2 + 1, 2 + 1); + } + + @Test + public void testTargetsByAI() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + // Whenever you cast an instant or sorcery spell that targets only Zada, Hedron Grinder, copy that spell for + // each other creature you control that the spell could target. Each copy targets a different one of those creatures. + addCard(Zone.BATTLEFIELD, playerA, "Zada, Hedron Grinder", 1); // 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); + // + // Put a +1/+1 counter on target creature. That creature gains reach until end of turn. + addCard(Zone.HAND, playerA, "Arbor Armament", 1); // {G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + + // cast + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Armament", "Zada, Hedron Grinder"); + //addTarget(playerA, "Balduvian Bears^Silvercoat Lion"); + + //setStrictChooseMode(true); // no strict mode for AI + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerA, "Arbor Armament", 1); + assertPowerToughness(playerA, "Zada, Hedron Grinder", 3 + 1, 3 + 1); + assertPowerToughness(playerA, "Silvercoat Lion", 2 + 1, 2 + 1); + assertPowerToughness(playerA, "Balduvian Bears", 2 + 1, 2 + 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 be52fba7552..3816f591c7a 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 @@ -453,9 +453,9 @@ public class TestPlayer implements Player { if (currentTarget.getNumberOfTargets() == 1) { currentTarget.clearChosen(); } - if (currentTarget instanceof TargetCreaturePermanentAmount) { + if (currentTarget.getOriginalTarget() instanceof TargetCreaturePermanentAmount) { // supports only to set the complete amount to one target - TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget; + TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget.getOriginalTarget(); targetAmount.setAmount(ability, game); int amount = targetAmount.getAmountRemaining(); targetAmount.addTarget(id, amount, ability, game); @@ -1540,12 +1540,13 @@ public class TestPlayer implements Player { source = stackObject.getStackAbility(); } - if ((target instanceof TargetPermanent) || (target instanceof TargetPermanentOrPlayer)) { // player target not implemted yet + if ((target.getOriginalTarget() instanceof TargetPermanent) + || (target.getOriginalTarget() instanceof TargetPermanentOrPlayer)) { // player target not implemted yet FilterPermanent filterPermanent; - if (target instanceof TargetPermanentOrPlayer) { - filterPermanent = ((TargetPermanentOrPlayer) target).getFilterPermanent(); + if (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) { + filterPermanent = ((TargetPermanentOrPlayer) target.getOriginalTarget()).getFilterPermanent(); } else { - filterPermanent = ((TargetPermanent) target).getFilter(); + filterPermanent = ((TargetPermanent) target.getOriginalTarget()).getFilter(); } for (String choose2 : choices) { String[] targetList = choose2.split("\\^"); @@ -1608,7 +1609,7 @@ public class TestPlayer implements Player { } // TODO: add same choices fixes for other target types (one choice must uses only one time for one target) - if (target instanceof TargetCard) { + if (target.getOriginalTarget() instanceof TargetCard) { // one choice per target // only unique targets //TargetCard targetFull = ((TargetCard) target); @@ -1674,9 +1675,9 @@ public class TestPlayer implements Player { } } - if (target instanceof TargetSource) { + if (target.getOriginalTarget() instanceof TargetSource) { Set possibleTargets; - TargetSource t = ((TargetSource) target); + TargetSource t = ((TargetSource) target.getOriginalTarget()); possibleTargets = t.possibleTargets(sourceId, abilityControllerId, game); for (String choose2 : choices) { String[] targetList = choose2.split("\\^"); @@ -1760,11 +1761,11 @@ public class TestPlayer implements Player { } // player - if (target instanceof TargetPlayer - || target instanceof TargetAnyTarget - || target instanceof TargetCreatureOrPlayer - || target instanceof TargetPermanentOrPlayer - || target instanceof TargetDefender) { + if (target.getOriginalTarget() instanceof TargetPlayer + || target.getOriginalTarget() instanceof TargetAnyTarget + || target.getOriginalTarget() instanceof TargetCreatureOrPlayer + || target.getOriginalTarget() instanceof TargetPermanentOrPlayer + || target.getOriginalTarget() instanceof TargetDefender) { for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "="); if (targetDefinition.startsWith("targetPlayer=")) { @@ -1779,15 +1780,14 @@ public class TestPlayer implements Player { } } } - } // permanent in battlefield - if ((target instanceof TargetPermanent) - || (target instanceof TargetPermanentOrPlayer) - || (target instanceof TargetAnyTarget) - || (target instanceof TargetCreatureOrPlayer) - || (target instanceof TargetDefender)) { + if ((target.getOriginalTarget() instanceof TargetPermanent) + || (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) + || (target.getOriginalTarget() instanceof TargetAnyTarget) + || (target.getOriginalTarget() instanceof TargetCreatureOrPlayer) + || (target.getOriginalTarget() instanceof TargetDefender)) { for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^[]"); String[] targetList = targetDefinition.split("\\^"); @@ -1805,7 +1805,7 @@ public class TestPlayer implements Player { targetName = targetName.substring(0, targetName.length() - 11); } } - Filter filter = target.getFilter(); + Filter filter = target.getOriginalTarget().getFilter(); if (filter instanceof FilterCreatureOrPlayer) { filter = ((FilterCreatureOrPlayer) filter).getCreatureFilter(); } @@ -1824,12 +1824,11 @@ public class TestPlayer implements Player { if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) { target.add(permanent.getId(), game); targetFound = true; - break; + break; // return to for (String targetName } } } } - } if (targetFound) { targets.remove(targetDefinition); @@ -1839,18 +1838,18 @@ public class TestPlayer implements Player { } // card in hand - if (target instanceof TargetCardInHand) { + if (target.getOriginalTarget() instanceof TargetCardInHand) { for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { - for (Card card : computerPlayer.getHand().getCards(((TargetCardInHand) target).getFilter(), game)) { + for (Card card : computerPlayer.getHand().getCards(((TargetCardInHand) target.getOriginalTarget()).getFilter(), game)) { if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { target.add(card.getId(), game); targetFound = true; - break; + break; // return to for (String targetName } } } @@ -1863,8 +1862,8 @@ public class TestPlayer implements Player { } // card in exile - if (target instanceof TargetCardInExile) { - TargetCardInExile targetFull = (TargetCardInExile) target; + if (target.getOriginalTarget() instanceof TargetCardInExile) { + TargetCardInExile targetFull = (TargetCardInExile) target.getOriginalTarget(); for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); String[] targetList = targetDefinition.split("\\^"); @@ -1872,10 +1871,10 @@ public class TestPlayer implements Player { for (String targetName : targetList) { for (Card card : game.getExile().getCards(targetFull.getFilter(), game)) { if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { - if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) { - targetFull.add(card.getId(), game); + if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { + target.add(card.getId(), game); targetFound = true; - break; + break; // return to for (String targetName } } } @@ -1900,7 +1899,7 @@ public class TestPlayer implements Player { if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) { targetFull.add(card.getId(), game); targetFound = true; - break; + break; // return to for (String targetName } } } @@ -1914,22 +1913,22 @@ public class TestPlayer implements Player { // card in graveyard - if (target instanceof TargetCardInOpponentsGraveyard - || target instanceof TargetCardInYourGraveyard - || target instanceof TargetCardInGraveyard - || target instanceof TargetCardInGraveyardOrBattlefield) { - TargetCard targetFull = (TargetCard) target; + if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard + || target.getOriginalTarget() instanceof TargetCardInYourGraveyard + || target.getOriginalTarget() instanceof TargetCardInGraveyard + || target.getOriginalTarget() instanceof TargetCardInGraveyardOrBattlefield) { + TargetCard targetFull = (TargetCard) target.getOriginalTarget(); List needPlayers = game.getState().getPlayersInRange(getId(), game).toList(); // fix for opponent graveyard - if (target instanceof TargetCardInOpponentsGraveyard) { + if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard) { // current player remove Assert.assertTrue(needPlayers.contains(getId())); needPlayers.remove(getId()); Assert.assertFalse(needPlayers.contains(getId())); } // fix for your graveyard - if (target instanceof TargetCardInYourGraveyard) { + if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard) { // only current player Assert.assertTrue(needPlayers.contains(getId())); needPlayers.clear(); @@ -1948,17 +1947,16 @@ public class TestPlayer implements Player { Player player = game.getPlayer(playerId); for (Card card : player.getGraveyard().getCards(targetFull.getFilter(), game)) { if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { - if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { + if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { target.add(card.getId(), game); targetFound = true; - break IterateGraveyards; + break IterateGraveyards; // return to for (String targetName } } } } } - if (targetFound) { targets.remove(targetDefinition); return true; @@ -1968,7 +1966,7 @@ public class TestPlayer implements Player { } // stack - if (target instanceof TargetSpell) { + if (target.getOriginalTarget() instanceof TargetSpell) { for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); String[] targetList = targetDefinition.split("\\^"); @@ -1976,9 +1974,11 @@ public class TestPlayer implements Player { for (String targetName : targetList) { for (StackObject stackObject : game.getStack()) { if (stackObject.getName().equals(targetName)) { - target.add(stackObject.getId(), game); - targetFound = true; - break; + if (target.canTarget(abilityControllerId, stackObject.getId(), source, game) && !target.getTargets().contains(stackObject.getId())) { + target.add(stackObject.getId(), game); + targetFound = true; + break; // return to for (String targetName + } } } }