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:
jmlundeen 2025-04-04 23:25:53 -05:00
parent 41d3464b5c
commit 8e1805c874
12 changed files with 141 additions and 10 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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())) {

View file

@ -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;
}