diff --git a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java index b79699c7004..282cb292d6b 100644 --- a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java +++ b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java @@ -94,7 +94,7 @@ class AgrusKosEternalSoldierTriggeredAbility extends TriggeredAbilityImpl { if (targetingObject == null || targetingObject instanceof Spell) { return false; } - if (!CardUtil.checkTargetMap(this.id, targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { return false; } Set targets = targetingObject diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java index 0a4917cf4f4..e65f68dd59a 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java @@ -74,7 +74,7 @@ public class BecomesTargetAnyTriggeredAbility extends TriggeredAbilityImpl { if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } - if (!CardUtil.checkTargetMap(this.id, targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { return false; } switch (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java index 264942e928e..b4098303ae0 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java @@ -58,7 +58,7 @@ public class BecomesTargetAttachedTriggeredAbility extends TriggeredAbilityImpl if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } - if (!CardUtil.checkTargetMap(this.id, targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { return false; } switch (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java index b3eb9434c0a..ccb9a6dde2f 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java @@ -67,7 +67,7 @@ public class BecomesTargetControllerTriggeredAbility extends TriggeredAbilityImp if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } - if (!CardUtil.checkTargetMap(this.id, targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { return false; } switch (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java index 51346890811..3e51aa708da 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java @@ -68,7 +68,7 @@ public class BecomesTargetSourceTriggeredAbility extends TriggeredAbilityImpl { if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } - if (!CardUtil.checkTargetMap(this.id, targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { return false; } switch (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java index c80fe36ee9a..604828361f2 100644 --- a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java @@ -81,7 +81,7 @@ public class WardAbility extends TriggeredAbilityImpl { if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) { return false; } - if (!CardUtil.checkTargetMap(this.id, targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { return false; } getEffects().setTargetPointer(new FixedTarget(targetingObject.getId())); diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index d20a4e8c9ee..c5b827b9c3b 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1012,8 +1012,8 @@ public final class CardUtil { /** * For finding the spell or ability on the stack for "becomes the target" triggers. - * @param event the GameEvent.EventType.TARGETED from checkTrigger() - * @param game the Game from checkTrigger() + * @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 not found */ public static StackObject getTargetingStackObject(GameEvent event, Game game) { @@ -1035,31 +1035,36 @@ public final class CardUtil { } /** - * For ensuring that spells/abilities that target the same object twice only trigger "becomes the target" abilities once - * @param triggerId this.id of the TriggeredAbility + * For ensuring 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. + * @param checkingReference must be unique for each usage (this.id.toString() of the TriggeredAbility, or this.getKey() of the watcher) * @param targetingObject from getTargetingStackObject - * @param event the GameEvent.EventType.TARGETED from checkTrigger() - * @param game the Game from checkTrigger() - * @return false if already triggered, true if this is the first/only trigger + * @param event the GameEvent.EventType.TARGETED from checkTrigger() or watch() + * @param game the Game from checkTrigger() or watch() + * @return true if already triggered/watched, false if this is the first/only trigger/watch */ - public static boolean checkTargetMap(UUID triggerId, StackObject targetingObject, GameEvent event, Game game) { + public static boolean checkTargetedEventAlreadyUsed(String checkingReference, StackObject targetingObject, GameEvent event, Game game) { + String stateKey = "targetedMap" + checkingReference; // If a spell or ability an opponent controls targets a single permanent you control more than once, // Battle Mammoth's triggered ability will trigger only once. // However, if a spell or ability an opponent controls targets multiple permanents you control, // Battle Mammoth's triggered ability will trigger once for each of those permanents. (2021-02-05) - Map> targetMap = (Map>) game.getState().getValue("targetMap" + triggerId); + 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<>()); if (!targetingObjects.add(targetingObject.getId())) { - return false; // The triggered ability already recorded that target of the stack object + return true; // The trigger/watcher already recorded that target of the stack object } - // Otherwise, store this combination of triggered ability / target / stack object + // Otherwise, store this combination of trigger/watcher + target + stack object targetMap.put(event.getTargetId(), targetingObjects); - game.getState().setValue("targetMap" + triggerId, targetMap); - return true; + game.getState().setValue(stateKey, targetMap); + return false; } /** diff --git a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java index 55e1b0419e0..ec4149a0ce7 100644 --- a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java @@ -30,7 +30,7 @@ public class NumberOfTimesPermanentTargetedATurnWatcher extends Watcher { return; } StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); - if (targetingObject == null || !CardUtil.checkTargetMap(null, targetingObject, event, game)) { + if (targetingObject == null || CardUtil.checkTargetedEventAlreadyUsed(this.getKey(), targetingObject, event, game)) { return; } Permanent permanent = game.getPermanent(event.getTargetId());