mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
Fix Ward batch event bug
Fixes #13498 getTargetingStackObject wasn't processing all stackObjects in a batch event added tests for some related cards that also use the method - Agrus Kos, Eternal Soldier - Pawpatch Recruit - Ward Ability
This commit is contained in:
parent
41d3464b5c
commit
8e1805c874
12 changed files with 141 additions and 10 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UUID, Set<UUID>> targetMap = (Map<UUID, Set<UUID>>) 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<UUID> 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())) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue