diff --git a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java index 3789ce55f66..7253374c3df 100644 --- a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java +++ b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java @@ -90,7 +90,7 @@ class AgrusKosEternalSoldierTriggeredAbility extends TriggeredAbilityImpl { if (!event.getTargetId().equals(getSourceId())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); 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 f6ea0a2222a..0a72089951c 100644 --- a/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java +++ b/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java @@ -96,7 +96,7 @@ class PawpatchRecruitTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } 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 b200bb76bfb..323d9b4cdd3 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 @@ -30,4 +30,54 @@ public class WardTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Solitude", 1); assertPermanentCount(playerB, "Waterfall Aerialist", 1); } + + @Test + public void wardPanharmonicon() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Young Red Dragon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.BATTLEFIELD, playerB, "Roaming Throne"); + addCard(Zone.HAND, playerA, "Scourge of Valkas"); + + setChoice(playerB, "Dragon"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Valkas"); + setChoice(playerA, "Whenever {this} or another Dragon"); + addTarget(playerA, "Roaming Throne"); + addTarget(playerA, "Roaming Throne"); + setChoice(playerB, "ward {2}"); + setChoice(playerA, "Yes"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerB, "Roaming Throne", 1); + assertDamageReceived(playerB, "Roaming Throne", 2); + } + + @Test + public void wardPanharmoniconCounter() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Young Red Dragon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerB, "Roaming Throne"); + addCard(Zone.HAND, playerA, "Scourge of Valkas"); + + setChoice(playerB, "Dragon"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Valkas"); + setChoice(playerA, "Whenever {this} or another Dragon"); + addTarget(playerA, "Roaming Throne"); + addTarget(playerA, "Roaming Throne"); + setChoice(playerB, "ward {2}"); + setChoice(playerA, "No"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerB, "Roaming Throne", 1); + assertDamageReceived(playerB, "Roaming Throne", 0); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java new file mode 100644 index 00000000000..343d8267800 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java @@ -0,0 +1,41 @@ +package org.mage.test.cards.single.blb; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class PawpatchRecruitTest extends CardTestPlayerBase { + + private static final String paw = "Pawpatch Recruit"; + private static final String cub = "Bear Cub"; + private static final String panharm = "Panharmonicon"; + private static final String prowler = "Chrome Prowler"; + + @Test + public void testCopiedTriggerAbility() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, paw); + addCard(Zone.BATTLEFIELD, playerB, cub); + addCard(Zone.BATTLEFIELD, playerA, panharm); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.HAND, playerA, prowler); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, prowler); + setChoice(playerA, "When {this} enters"); + addTarget(playerA, paw); + addTarget(playerA, cub); + setChoice(playerB, "Whenever a creature"); + addTarget(playerB, paw); + addTarget(playerB, cub); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertTapped(paw, true); + assertTapped(cub, true); + assertCounterCount(playerB, paw, CounterType.P1P1, 1); + assertCounterCount(playerB, cub, CounterType.P1P1, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java index 0df627d9194..349dda8af12 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java @@ -67,4 +67,35 @@ public class AgrusKosEternalSoldierTest extends CardTestPlayerBase { assertLife(playerB, 20); } + @Test + public void testCopiedTriggerAbility() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, agrus); + addCard(Zone.BATTLEFIELD, playerB, turtle); + addCard(Zone.BATTLEFIELD, playerB, firewalker); + addCard(Zone.BATTLEFIELD, playerB, "Plateau", 4); + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.HAND, playerA, "Smoldering Werewolf"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Smoldering Werewolf"); + setChoice(playerA, "When {this} enters, it deals"); + addTarget(playerA, agrus); + addTarget(playerA, agrus); + setChoice(playerB, true); // gain life + setChoice(playerB, "Whenever {this} becomes"); + setChoice(playerB, true); // pay to copy + setChoice(playerB, true); // pay to copy + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerB, agrus, 2); + assertDamageReceived(playerB, turtle, 2); + assertDamageReceived(playerB, firewalker, 0); + assertLife(playerA, 20); + assertLife(playerB, 21); + } + } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java index 9e5a7490e98..13377578b94 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java @@ -70,7 +70,7 @@ public class BecomesTargetAnyTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); 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 2995cacb11b..2c4c8d13a85 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java @@ -54,7 +54,7 @@ public class BecomesTargetAttachedTriggeredAbility extends TriggeredAbilityImpl if (enchantment == null || enchantment.getAttachedTo() == null || !event.getTargetId().equals(enchantment.getAttachedTo())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); 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 c31b39b22d2..9b60880a21d 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java @@ -63,7 +63,7 @@ public class BecomesTargetControllerTriggeredAbility extends TriggeredAbilityImp return false; } } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); 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 e1fcc98a772..0b410c7e078 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java @@ -57,7 +57,7 @@ public class BecomesTargetSourceTriggeredAbility extends TriggeredAbilityImpl { if (!event.getTargetId().equals(getSourceId())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); 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 b6bebf87769..c68ff475567 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.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) { return false; } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 7ba17e4036f..b55d2a33da6 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1086,13 +1086,22 @@ public final class CardUtil { * @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) { + public static StackObject getTargetingStackObject(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 // 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())) { + if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId()) || targetingObjects.contains(stackObject.getId())) { continue; } if (CardUtil.getAllSelectedTargets(stackAbility, game).contains(event.getTargetId())) { diff --git a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java index c97cf9436f3..accc6b4628c 100644 --- a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java @@ -29,7 +29,7 @@ public class NumberOfTimesPermanentTargetedATurnWatcher extends Watcher { if (event.getType() != GameEvent.EventType.TARGETED) { return; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getKey(), event, game); if (targetingObject == null || CardUtil.checkTargetedEventAlreadyUsed(this.getKey(), targetingObject, event, game)) { return; }