mirror of
https://github.com/magefree/mage.git
synced 2025-12-23 12:02:01 -08:00
refactor: find targeting stack object (#13534)
* refactor: simplify finding targeting stack object
related to #11185, 8e1805c
* clarify docs
---------
Co-authored-by: xenohedron <12538125+xenohedron@users.noreply.github.com>
This commit is contained in:
parent
0f0026c375
commit
74d2265c12
10 changed files with 27 additions and 73 deletions
|
|
@ -90,13 +90,10 @@ class AgrusKosEternalSoldierTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
if (!event.getTargetId().equals(getSourceId())) {
|
if (!event.getTargetId().equals(getSourceId())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
|
||||||
if (targetingObject == null || targetingObject instanceof Spell) {
|
if (targetingObject == null || targetingObject instanceof Spell) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Set<UUID> targets = targetingObject
|
Set<UUID> targets = targetingObject
|
||||||
.getStackAbility()
|
.getStackAbility()
|
||||||
.getTargets()
|
.getTargets()
|
||||||
|
|
|
||||||
|
|
@ -96,13 +96,10 @@ class PawpatchRecruitTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) {
|
if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
|
||||||
if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) {
|
if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.getTargets().clear();
|
this.getTargets().clear();
|
||||||
FilterControlledPermanent filter = new FilterControlledCreaturePermanent();
|
FilterControlledPermanent filter = new FilterControlledCreaturePermanent();
|
||||||
filter.add(Predicates.not(new MageObjectReferencePredicate(event.getTargetId(), game)));
|
filter.add(Predicates.not(new MageObjectReferencePredicate(event.getTargetId(), game)));
|
||||||
|
|
|
||||||
|
|
@ -90,9 +90,7 @@ class SurrakElusiveHunterTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
if (!checkTargeted(event.getTargetId(), game)) {
|
if (!checkTargeted(event.getTargetId(), game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
|
||||||
return targetingObject != null
|
return targetingObject != null && game.getOpponents(getControllerId()).contains(targetingObject.getControllerId());
|
||||||
&& game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())
|
|
||||||
&& !CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,13 +70,10 @@ public class BecomesTargetAnyTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) {
|
if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
|
||||||
if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) {
|
if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (setTargetPointer) {
|
switch (setTargetPointer) {
|
||||||
case PERMANENT:
|
case PERMANENT:
|
||||||
this.getAllEffects().setTargetPointer(new FixedTarget(permanent.getId(), game));
|
this.getAllEffects().setTargetPointer(new FixedTarget(permanent.getId(), game));
|
||||||
|
|
|
||||||
|
|
@ -54,13 +54,10 @@ public class BecomesTargetAttachedTriggeredAbility extends TriggeredAbilityImpl
|
||||||
if (enchantment == null || enchantment.getAttachedTo() == null || !event.getTargetId().equals(enchantment.getAttachedTo())) {
|
if (enchantment == null || enchantment.getAttachedTo() == null || !event.getTargetId().equals(enchantment.getAttachedTo())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
|
||||||
if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) {
|
if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (setTargetPointer) {
|
switch (setTargetPointer) {
|
||||||
case PLAYER:
|
case PLAYER:
|
||||||
this.getAllEffects().setTargetPointer(new FixedTarget(targetingObject.getControllerId(), game));
|
this.getAllEffects().setTargetPointer(new FixedTarget(targetingObject.getControllerId(), game));
|
||||||
|
|
|
||||||
|
|
@ -63,13 +63,10 @@ public class BecomesTargetControllerTriggeredAbility extends TriggeredAbilityImp
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
|
||||||
if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) {
|
if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (setTargetPointer) {
|
switch (setTargetPointer) {
|
||||||
case SPELL:
|
case SPELL:
|
||||||
this.getAllEffects().setTargetPointer(new FixedTarget(targetingObject.getId()));
|
this.getAllEffects().setTargetPointer(new FixedTarget(targetingObject.getId()));
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,10 @@ public class BecomesTargetSourceTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
if (!event.getTargetId().equals(getSourceId())) {
|
if (!event.getTargetId().equals(getSourceId())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
|
||||||
if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) {
|
if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (setTargetPointer) {
|
switch (setTargetPointer) {
|
||||||
case PLAYER:
|
case PLAYER:
|
||||||
this.getAllEffects().setTargetPointer(new FixedTarget(targetingObject.getControllerId(), game));
|
this.getAllEffects().setTargetPointer(new FixedTarget(targetingObject.getControllerId(), game));
|
||||||
|
|
|
||||||
|
|
@ -77,13 +77,10 @@ public class WardAbility extends TriggeredAbilityImpl {
|
||||||
if (!getSourceId().equals(event.getTargetId())) {
|
if (!getSourceId().equals(event.getTargetId())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
|
||||||
if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) {
|
if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
getEffects().setTargetPointer(new FixedTarget(targetingObject.getId()));
|
getEffects().setTargetPointer(new FixedTarget(targetingObject.getId()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1128,14 +1128,19 @@ public final class CardUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For finding the spell or ability on the stack for "becomes the target" triggers.
|
* 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 event the GameEvent.EventType.TARGETED from checkTrigger() or watch()
|
||||||
* @param game the Game from checkTrigger() or watch()
|
* @param game the Game from checkTrigger() or watch()
|
||||||
* @return the StackObject which targeted the source, or null if not found
|
* @return the StackObject which targeted the source, or null if already used or not found
|
||||||
*/
|
*/
|
||||||
public static StackObject getTargetingStackObject(String checkingReference, GameEvent event, Game game) {
|
public static StackObject findTargetingStackObject(String checkingReference, GameEvent event, Game game) {
|
||||||
// In case of multiple simultaneous triggered abilities from the same source,
|
// In case of multiple simultaneous triggered abilities from the same source,
|
||||||
// need to get the actual one that targeted, see #8026, #8378
|
// 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
|
// Also avoids triggering on cancelled selections, see #8802
|
||||||
String stateKey = "targetedMap" + checkingReference;
|
String stateKey = "targetedMap" + checkingReference;
|
||||||
Map<UUID, Set<UUID>> targetMap = (Map<UUID, Set<UUID>>) game.getState().getValue(stateKey);
|
Map<UUID, Set<UUID>> targetMap = (Map<UUID, Set<UUID>>) game.getState().getValue(stateKey);
|
||||||
|
|
@ -1148,48 +1153,20 @@ public final class CardUtil {
|
||||||
Set<UUID> targetingObjects = targetMap.computeIfAbsent(event.getTargetId(), k -> new HashSet<>());
|
Set<UUID> targetingObjects = targetMap.computeIfAbsent(event.getTargetId(), k -> new HashSet<>());
|
||||||
for (StackObject stackObject : game.getStack()) {
|
for (StackObject stackObject : game.getStack()) {
|
||||||
Ability stackAbility = stackObject.getStackAbility();
|
Ability stackAbility = stackObject.getStackAbility();
|
||||||
if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId()) || targetingObjects.contains(stackObject.getId())) {
|
if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (CardUtil.getAllSelectedTargets(stackAbility, game).contains(event.getTargetId())) {
|
if (CardUtil.getAllSelectedTargets(stackAbility, game).contains(event.getTargetId())) {
|
||||||
return stackObject;
|
if (!targetingObjects.add(stackObject.getId())) {
|
||||||
}
|
continue; // The trigger/watcher already recorded that target of the stack object, check for another
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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() 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 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<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<>());
|
|
||||||
if (!targetingObjects.add(targetingObject.getId())) {
|
|
||||||
return true; // The trigger/watcher already recorded that target of the stack object
|
|
||||||
}
|
}
|
||||||
// Otherwise, store this combination of trigger/watcher + target + stack object
|
// Otherwise, store this combination of trigger/watcher + target + stack object
|
||||||
targetMap.put(event.getTargetId(), targetingObjects);
|
targetMap.put(event.getTargetId(), targetingObjects);
|
||||||
game.getState().setValue(stateKey, targetMap);
|
game.getState().setValue(stateKey, targetMap);
|
||||||
return false;
|
return stackObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ public class NumberOfTimesPermanentTargetedATurnWatcher extends Watcher {
|
||||||
if (event.getType() != GameEvent.EventType.TARGETED) {
|
if (event.getType() != GameEvent.EventType.TARGETED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StackObject targetingObject = CardUtil.getTargetingStackObject(this.getKey(), event, game);
|
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getKey(), event, game);
|
||||||
if (targetingObject == null || CardUtil.checkTargetedEventAlreadyUsed(this.getKey(), targetingObject, event, game)) {
|
if (targetingObject == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Permanent permanent = game.getPermanent(event.getTargetId());
|
Permanent permanent = game.getPermanent(event.getTargetId());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue