diff --git a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java index c2090ee031a..31cbd6f881b 100644 --- a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java +++ b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java @@ -23,7 +23,6 @@ import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; -import mage.util.CardUtil; import java.util.Collection; import java.util.List; @@ -90,7 +89,7 @@ class AgrusKosEternalSoldierTriggeredAbility extends TriggeredAbilityImpl { if (!event.getTargetId().equals(getSourceId())) { return false; } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game); + StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event); if (targetingObject == null || targetingObject instanceof Spell) { return false; } diff --git a/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java b/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java index 68b1b9ac44e..0c48850b2c3 100644 --- a/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java +++ b/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java @@ -1,18 +1,17 @@ package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.counter.AddCountersTargetEffect; -import mage.constants.*; import mage.abilities.keyword.OffspringAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.FilterPermanent; -import mage.filter.FilterStackObject; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledPermanent; @@ -23,7 +22,8 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; import mage.target.TargetPermanent; -import mage.util.CardUtil; + +import java.util.UUID; /** * @@ -63,9 +63,6 @@ public final class PawpatchRecruit extends CardImpl { class PawpatchRecruitTriggeredAbility extends TriggeredAbilityImpl { - private final FilterPermanent filterTarget = StaticFilters.FILTER_CONTROLLED_A_CREATURE; - private final FilterStackObject filterStack = StaticFilters.FILTER_SPELL_OR_ABILITY_OPPONENTS; - public PawpatchRecruitTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false); } @@ -93,11 +90,11 @@ class PawpatchRecruitTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); - if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) { + if (permanent == null || !StaticFilters.FILTER_CONTROLLED_A_CREATURE.match(permanent, getControllerId(), this, game)) { return false; } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game); - if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { + StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event); + if (targetingObject == null || !StaticFilters.FILTER_SPELL_OR_ABILITY_OPPONENTS.match(targetingObject, getControllerId(), this, game)) { return false; } this.getTargets().clear(); diff --git a/Mage.Sets/src/mage/cards/s/SurrakElusiveHunter.java b/Mage.Sets/src/mage/cards/s/SurrakElusiveHunter.java index 074aa4a8807..4b94ef71e87 100644 --- a/Mage.Sets/src/mage/cards/s/SurrakElusiveHunter.java +++ b/Mage.Sets/src/mage/cards/s/SurrakElusiveHunter.java @@ -16,7 +16,6 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.game.stack.StackObject; -import mage.util.CardUtil; import java.util.UUID; @@ -90,7 +89,7 @@ class SurrakElusiveHunterTriggeredAbility extends TriggeredAbilityImpl { if (!checkTargeted(event.getTargetId(), game)) { return false; } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game); + StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event); return targetingObject != null && game.getOpponents(getControllerId()).contains(targetingObject.getControllerId()); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java index 323d9b4cdd3..d34f5d2863c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java @@ -1,5 +1,6 @@ package org.mage.test.cards.abilities.keywords; +import mage.abilities.keyword.FlyingAbility; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; @@ -80,4 +81,115 @@ public class WardTest extends CardTestPlayerBase { assertPermanentCount(playerB, "Roaming Throne", 1); assertDamageReceived(playerB, "Roaming Throne", 0); } + + // Reported bug: #13523 - Ward not properly triggering when re-casting Aura + + private static final String creature = "Owlin Shieldmage"; // 3/3 Flying, Ward - Pay 3 life + private static final String aura = "Kenrith's Transformation"; // 1G enchanted creature loses all abilities and is 3/3 Elk (also draw card on ETB) + private static final String spell = "Beast Within"; // 2G destroy permanent, controller gets 3/3 Beast + private static final String regrowth = "Regrowth"; // 1G return target card from graveyard to hand + + @Test + public void wardRecastAuraNoPay() { + + addCard(Zone.BATTLEFIELD, playerB, creature); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 9); + addCard(Zone.HAND, playerA, aura); + addCard(Zone.HAND, playerA, spell); + addCard(Zone.HAND, playerA, regrowth); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aura, creature); + setChoice(playerA, false); // don't pay for ward + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, regrowth, aura); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aura, creature); + setChoice(playerA, false); // don't pay for ward + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + assertGraveyardCount(playerA, aura, 1); + assertGraveyardCount(playerA, regrowth, 1); + assertGraveyardCount(playerA, 2); + assertHandCount(playerA, spell, 1); + assertHandCount(playerA, 1); + assertAbility(playerB, creature, FlyingAbility.getInstance(), true); + + } + + @Test + public void wardRecastAuraPaySecond() { + + addCard(Zone.BATTLEFIELD, playerB, creature); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 9); + addCard(Zone.HAND, playerA, aura); + addCard(Zone.HAND, playerA, spell); + addCard(Zone.HAND, playerA, regrowth); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aura, creature); + setChoice(playerA, false); // don't pay for ward + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, regrowth, aura); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aura, creature); + setChoice(playerA, true); // pay ward + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 17); + assertGraveyardCount(playerA, aura, 0); + assertGraveyardCount(playerA, regrowth, 1); + assertGraveyardCount(playerA, 1); + assertHandCount(playerA, spell, 1); + assertHandCount(playerA, 2); // one draw from aura entering + assertAttachedTo(playerB, aura, creature, true); + assertAbility(playerB, creature, FlyingAbility.getInstance(), false); + + } + + @Test + public void wardRecastAuraPayBoth() { + + addCard(Zone.BATTLEFIELD, playerB, creature); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 9); + addCard(Zone.HAND, playerA, aura); + addCard(Zone.HAND, playerA, spell); + addCard(Zone.HAND, playerA, regrowth); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aura, creature); + setChoice(playerA, true); // pay for ward + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, spell, aura); // destroy aura + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, regrowth, aura); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aura, creature); + setChoice(playerA, true); // pay ward + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 14); + assertGraveyardCount(playerA, spell, 1); + assertGraveyardCount(playerA, regrowth, 1); + assertGraveyardCount(playerA, 2); + assertHandCount(playerA, 2); // two draws from aura entering + assertAttachedTo(playerB, aura, creature, true); + assertAbility(playerB, creature, FlyingAbility.getInstance(), false); + + } + } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java index 01816dc7446..bcd65966258 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java @@ -12,7 +12,6 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; import mage.target.targetpointer.FixedTarget; -import mage.util.CardUtil; /** * @author xenohedron @@ -70,7 +69,7 @@ public class BecomesTargetAnyTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) { return false; } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game); + StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java index 54481419723..93501b89c17 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java @@ -8,10 +8,9 @@ import mage.filter.FilterStackObject; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.stack.StackObject; import mage.game.permanent.Permanent; +import mage.game.stack.StackObject; import mage.target.targetpointer.FixedTarget; -import mage.util.CardUtil; /** * @author LoneFox @@ -54,7 +53,7 @@ public class BecomesTargetAttachedTriggeredAbility extends TriggeredAbilityImpl if (enchantment == null || enchantment.getAttachedTo() == null || !event.getTargetId().equals(enchantment.getAttachedTo())) { return false; } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game); + StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event); if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java index c6948f53828..83006241427 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java @@ -3,15 +3,14 @@ package mage.abilities.common; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.SetTargetPointer; +import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.FilterStackObject; -import mage.game.events.GameEvent; -import mage.constants.Zone; import mage.game.Game; +import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; import mage.target.targetpointer.FixedTarget; -import mage.util.CardUtil; /** * @author xenohedron @@ -63,7 +62,7 @@ public class BecomesTargetControllerTriggeredAbility extends TriggeredAbilityImp return false; } } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game); + StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java index 0f61e332e7d..df3cd8ca8af 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java @@ -10,7 +10,6 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.StackObject; import mage.target.targetpointer.FixedTarget; -import mage.util.CardUtil; /** * @author North @@ -57,7 +56,7 @@ public class BecomesTargetSourceTriggeredAbility extends TriggeredAbilityImpl { if (!event.getTargetId().equals(getSourceId())) { return false; } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game); + StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event); if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java index 01db3372ce5..1a064c0a65c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java @@ -77,7 +77,7 @@ public class WardAbility extends TriggeredAbilityImpl { if (!getSourceId().equals(event.getTargetId())) { return false; } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game); + StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event); if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) { return false; } diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 2df39b9016d..13846b795b8 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -30,6 +30,7 @@ import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.game.stack.SpellStack; +import mage.game.stack.StackObject; import mage.game.turn.Phase; import mage.game.turn.Step; import mage.game.turn.Turn; @@ -310,6 +311,19 @@ public interface Game extends MageItem, Serializable, Copyable { void resetShortLivingLKI(); + /** + * For finding the spell or ability on the stack for "becomes the target" triggers. + * Also ensures that spells/abilities that target the same object twice only trigger each "becomes the target" ability once. + * If this is the first attempt at triggering for a given ability targeting a given object, + * this method temporarily records that in the game state for later checks by this same method, + * to not return the same object again (until short living LKI is cleared) + * + * @param checkingReference must be unique for each usage (this.getId().toString() of the TriggeredAbility, or this.getKey() of the watcher) + * @param event the GameEvent.EventType.TARGETED from checkTrigger() or watch() + * @return the StackObject which targeted the source, or null if already used or not found + */ + StackObject findTargetingStackObject(String checkingReference, GameEvent event); + void setLosingPlayer(Player player); Player getLosingPlayer(); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 06932787520..7c0e2e9c6c4 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -121,6 +121,8 @@ public abstract class GameImpl implements Game { protected Map> lkiExtended = new HashMap<>(); // Used to check if an object was moved by the current effect in resolution (so Wrath like effect can be handled correctly) protected Map> lkiShortLiving = new EnumMap<>(Zone.class); + // For checking "becomes the target" triggers accurately. Cleared on short living LKI reset + protected Map>> targetedMap = new HashMap<>(); // Permanents entering the Battlefield while handling replacement effects before they are added to the battlefield protected Map permanentsEntering = new HashMap<>(); @@ -202,6 +204,7 @@ public abstract class GameImpl implements Game { this.lkiCardState = CardUtil.deepCopyObject(game.lkiCardState); this.lkiExtended = CardUtil.deepCopyObject(game.lkiExtended); this.lkiShortLiving = CardUtil.deepCopyObject(game.lkiShortLiving); + this.targetedMap = CardUtil.deepCopyObject(game.targetedMap); this.permanentsEntering = CardUtil.deepCopyObject(game.permanentsEntering); this.enterWithCounters = CardUtil.deepCopyObject(game.enterWithCounters); @@ -3687,6 +3690,39 @@ public abstract class GameImpl implements Game { @Override public void resetShortLivingLKI() { lkiShortLiving.clear(); + targetedMap.clear(); + } + + @Override + public StackObject findTargetingStackObject(String checkingReference, GameEvent event) { + // In case of multiple simultaneous triggered abilities from the same source, + // need to get the actual one that targeted, see #8026, #8378, rulings for Battle Mammoth + // In case of copied triggered abilities, need to trigger on each independently, see #13498 + // Also avoids triggering on cancelled selections, see #8802 + Map> targetMap = targetedMap.getOrDefault(checkingReference, null); + // targetMap: key - targetId; value - Set of stackObject Ids + if (targetMap == null) { + targetMap = new HashMap<>(); + } else { + targetMap = new HashMap<>(targetMap); // must have new object reference if saved back + } + Set targetingObjects = targetMap.computeIfAbsent(event.getTargetId(), k -> new HashSet<>()); + for (StackObject stackObject : getStack()) { + Ability stackAbility = stackObject.getStackAbility(); + if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId())) { + continue; + } + if (CardUtil.getAllSelectedTargets(stackAbility, this).contains(event.getTargetId())) { + if (!targetingObjects.add(stackObject.getId())) { + continue; // The trigger/watcher already recorded that target of the stack object, check for another + } + // Otherwise, store this combination of trigger/watcher + target + stack object + targetMap.put(event.getTargetId(), targetingObjects); + targetedMap.put(checkingReference, targetMap); + return stackObject; + } + } + return null; } @Override diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 218444683cd..d4c968bd4dd 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -44,7 +44,6 @@ import mage.game.permanent.PermanentMeld; import mage.game.permanent.PermanentToken; import mage.game.permanent.token.Token; import mage.game.stack.Spell; -import mage.game.stack.StackObject; import mage.players.Player; import mage.players.PlayerList; import mage.target.Target; @@ -1133,49 +1132,6 @@ public final class CardUtil { return res; } - /** - * For finding the spell or ability on the stack for "becomes the target" triggers. - * Also ensures that spells/abilities that target the same object twice only trigger each "becomes the target" ability once. - * If this is the first attempt at triggering for a given ability targeting a given object, - * this method records that in the game state for later checks by this same method, to not return the same object again. - * - * @param checkingReference must be unique for each usage (this.getId().toString() of the TriggeredAbility, or this.getKey() of the watcher) - * @param event the GameEvent.EventType.TARGETED from checkTrigger() or watch() - * @param game the Game from checkTrigger() or watch() - * @return the StackObject which targeted the source, or null if already used or not found - */ - public static StackObject findTargetingStackObject(String checkingReference, GameEvent event, Game game) { - // In case of multiple simultaneous triggered abilities from the same source, - // need to get the actual one that targeted, see #8026, #8378, rulings for Battle Mammoth - // In case of copied triggered abilities, need to trigger on each independently, see #13498 - // Also avoids triggering on cancelled selections, see #8802 - String stateKey = "targetedMap" + checkingReference; - Map> targetMap = (Map>) game.getState().getValue(stateKey); - // targetMap: key - targetId; value - Set of stackObject Ids - if (targetMap == null) { - targetMap = new HashMap<>(); - } else { - targetMap = new HashMap<>(targetMap); // must have new object reference if saved back to game state - } - Set targetingObjects = targetMap.computeIfAbsent(event.getTargetId(), k -> new HashSet<>()); - for (StackObject stackObject : game.getStack()) { - Ability stackAbility = stackObject.getStackAbility(); - if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId())) { - continue; - } - if (CardUtil.getAllSelectedTargets(stackAbility, game).contains(event.getTargetId())) { - if (!targetingObjects.add(stackObject.getId())) { - continue; // The trigger/watcher already recorded that target of the stack object, check for another - } - // Otherwise, store this combination of trigger/watcher + target + stack object - targetMap.put(event.getTargetId(), targetingObjects); - game.getState().setValue(stateKey, targetMap); - return stackObject; - } - } - return null; - } - /** * For overriding `canTarget()` with usages such as "any number of target cards with total mana value X or less". * Call this after super.canTarget() returns true. diff --git a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java index b8e031bca49..08e5276cac2 100644 --- a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java @@ -6,7 +6,6 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; -import mage.util.CardUtil; import mage.watchers.Watcher; import java.util.HashMap; @@ -29,7 +28,7 @@ public class NumberOfTimesPermanentTargetedATurnWatcher extends Watcher { if (event.getType() != GameEvent.EventType.TARGETED) { return; } - StackObject targetingObject = CardUtil.findTargetingStackObject(this.getKey(), event, game); + StackObject targetingObject = game.findTargetingStackObject(this.getKey(), event); if (targetingObject == null) { return; }