refactor: consistent logic for delayed triggers that sacrifice/exile tokens

single trigger for multiple tokens
This commit is contained in:
xenohedron 2025-09-07 00:15:01 -04:00
parent 457a5303f3
commit f92af2b9cd
7 changed files with 50 additions and 72 deletions

View file

@ -9,10 +9,7 @@ import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.keyword.HexproofAbility; import mage.abilities.keyword.HexproofAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SuperType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.token.AngelToken; import mage.game.permanent.token.AngelToken;
import mage.players.Player; import mage.players.Player;
@ -65,7 +62,7 @@ class GeistOfSaintTraftEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null && effect.apply(game, source)) { if (controller != null && effect.apply(game, source)) {
effect.exileTokensCreatedAtEndOfCombat(game, source); effect.removeTokensCreatedAt(game, source, true, PhaseStep.END_COMBAT, TargetController.ANY);
return true; return true;
} }
return false; return false;

View file

@ -12,11 +12,7 @@ import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.EnchantAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AttachmentType; import mage.constants.*;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.token.AngelToken; import mage.game.permanent.token.AngelToken;
import mage.players.Player; import mage.players.Player;
@ -72,7 +68,7 @@ class InvocationOfSaintTraftEffect extends OneShotEffect {
CreateTokenEffect effect = new CreateTokenEffect(new AngelToken(), 1, true, true); CreateTokenEffect effect = new CreateTokenEffect(new AngelToken(), 1, true, true);
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null && (effect.apply(game, source))) { if (controller != null && (effect.apply(game, source))) {
effect.exileTokensCreatedAtEndOfCombat(game, source); effect.removeTokensCreatedAt(game, source, true, PhaseStep.END_COMBAT, TargetController.ANY);
return true; return true;
} }
return false; return false;

View file

@ -11,10 +11,7 @@ import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.MenaceAbility; import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SuperType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.token.RagavanToken; import mage.game.permanent.token.RagavanToken;
import mage.players.Player; import mage.players.Player;
@ -70,7 +67,7 @@ class KariZevSkyshipRaiderEffect extends OneShotEffect {
CreateTokenEffect effect = new CreateTokenEffect(new RagavanToken(), 1, true, true); CreateTokenEffect effect = new CreateTokenEffect(new RagavanToken(), 1, true, true);
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null && effect.apply(game, source)) { if (controller != null && effect.apply(game, source)) {
effect.exileTokensCreatedAtEndOfCombat(game, source); effect.removeTokensCreatedAt(game, source, true, PhaseStep.END_COMBAT, TargetController.ANY);
return true; return true;
} }
return false; return false;

View file

@ -82,7 +82,7 @@ class MiragePhalanxEffect extends OneShotEffect {
// Create the token(s) // Create the token(s)
tokenCopyEffect.apply(game, source); tokenCopyEffect.apply(game, source);
// Exile it at the end of combat // Exile it at the end of combat
tokenCopyEffect.exileTokensCreatedAtEndOfCombat(game, source); tokenCopyEffect.removeTokensCreatedAt(game, source, true, PhaseStep.END_COMBAT, TargetController.ANY);
return !tokenCopyEffect.getAddedPermanents().isEmpty(); return !tokenCopyEffect.getAddedPermanents().isEmpty();
} }

View file

@ -414,41 +414,29 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
} }
public void sacrificeTokensCreatedAtNextEndStep(Game game, Ability source) { public void sacrificeTokensCreatedAtNextEndStep(Game game, Ability source) {
this.removeTokensCreatedAtEndOf(game, source, PhaseStep.END_TURN, false); this.removeTokensCreatedAt(game, source, false, PhaseStep.END_TURN, TargetController.ANY);
} }
public void exileTokensCreatedAtNextEndStep(Game game, Ability source) { public void exileTokensCreatedAtNextEndStep(Game game, Ability source) {
this.removeTokensCreatedAtEndOf(game, source, PhaseStep.END_TURN, true); this.removeTokensCreatedAt(game, source, true, PhaseStep.END_TURN, TargetController.ANY);
} }
public void sacrificeTokensCreatedAtEndOfCombat(Game game, Ability source) { public void removeTokensCreatedAt(Game game, Ability source, boolean exile, PhaseStep phaseStep, TargetController targetController) {
this.removeTokensCreatedAtEndOf(game, source, PhaseStep.END_COMBAT, false); Effect effect = exile
} ? new ExileTargetEffect(null, "", Zone.BATTLEFIELD).setText("exile the token copies")
: new SacrificeTargetEffect("sacrifice the token copies", source.getControllerId());
public void exileTokensCreatedAtEndOfCombat(Game game, Ability source) {
this.removeTokensCreatedAtEndOf(game, source, PhaseStep.END_COMBAT, true);
}
private void removeTokensCreatedAtEndOf(Game game, Ability source, PhaseStep phaseStepToExileCards, boolean exile) {
Effect effect;
if (exile) {
effect = new ExileTargetEffect(null, "", Zone.BATTLEFIELD).setText("exile the token copies");
} else {
effect = new SacrificeTargetEffect("sacrifice the token copies", source.getControllerId());
}
effect.setTargetPointer(new FixedTargets(new ArrayList<>(addedTokenPermanents), game)); effect.setTargetPointer(new FixedTargets(new ArrayList<>(addedTokenPermanents), game));
DelayedTriggeredAbility exileAbility; DelayedTriggeredAbility exileAbility;
switch (phaseStep) {
switch (phaseStepToExileCards) {
case END_TURN: case END_TURN:
exileAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); exileAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect, targetController);
break; break;
case END_COMBAT: case END_COMBAT:
exileAbility = new AtTheEndOfCombatDelayedTriggeredAbility(effect); exileAbility = new AtTheEndOfCombatDelayedTriggeredAbility(effect);
break; break;
default: default:
return; throw new UnsupportedOperationException("Unsupported PhaseStep in CreateTokenCopyTargetEffect::removeTokensCreatedAt");
} }
game.addDelayedTriggeredAbility(exileAbility, source); game.addDelayedTriggeredAbility(exileAbility, source);

View file

@ -1,24 +1,26 @@
package mage.abilities.effects.common; package mage.abilities.effects.common;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.PhaseStep;
import mage.constants.TargetController;
import mage.constants.Zone; import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.permanent.token.Token; import mage.game.permanent.token.Token;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays; import java.util.stream.Collectors;
import java.util.List;
import java.util.UUID;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
@ -121,37 +123,34 @@ public class CreateTokenEffect extends OneShotEffect {
return lastAddedTokenIds; return lastAddedTokenIds;
} }
public void sacrificeTokensCreatedAtNextEndStep(Game game, Ability source) {
for (UUID tokenId : this.getLastAddedTokenIds()) {
Permanent tokenPermanent = game.getPermanent(tokenId);
if (tokenPermanent != null) {
SacrificeTargetEffect sacrificeEffect = new SacrificeTargetEffect();
sacrificeEffect.setTargetPointer(new FixedTarget(tokenPermanent, game));
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(sacrificeEffect), source);
}
}
}
public void exileTokensCreatedAtNextEndStep(Game game, Ability source) { public void exileTokensCreatedAtNextEndStep(Game game, Ability source) {
for (UUID tokenId : this.getLastAddedTokenIds()) { removeTokensCreatedAt(game, source, true, PhaseStep.END_TURN, TargetController.ANY);
Permanent tokenPermanent = game.getPermanent(tokenId);
if (tokenPermanent != null) {
ExileTargetEffect exileEffect = new ExileTargetEffect(null, "", Zone.BATTLEFIELD);
exileEffect.setTargetPointer(new FixedTarget(tokenPermanent, game));
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(exileEffect), source);
}
}
} }
public void exileTokensCreatedAtEndOfCombat(Game game, Ability source) { public void removeTokensCreatedAt(Game game, Ability source, boolean exile, PhaseStep phaseStep, TargetController targetController) {
for (UUID tokenId : this.getLastAddedTokenIds()) { List<Permanent> permanents = this.getLastAddedTokenIds()
Permanent tokenPermanent = game.getPermanent(tokenId); .stream()
if (tokenPermanent != null) { .map(game::getPermanent)
ExileTargetEffect exileEffect = new ExileTargetEffect(null, "", Zone.BATTLEFIELD); .filter(Objects::nonNull)
exileEffect.setTargetPointer(new FixedTarget(tokenPermanent, game)); .collect(Collectors.toList());
game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility(exileEffect), source); Effect effect = exile
} ? new ExileTargetEffect(null, "", Zone.BATTLEFIELD).setText("exile those tokens")
: new SacrificeTargetEffect("sacrifice those tokens", source.getControllerId());
effect.setTargetPointer(new FixedTargets(permanents, game));
DelayedTriggeredAbility exileAbility;
switch (phaseStep) {
case END_TURN:
exileAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect, targetController);
break;
case END_COMBAT:
exileAbility = new AtTheEndOfCombatDelayedTriggeredAbility(effect);
break;
default:
throw new UnsupportedOperationException("Unsupported PhaseStep in CreateTokenEffect::removeTokensCreatedAt");
} }
game.addDelayedTriggeredAbility(exileAbility, source);
} }
/** /**

View file

@ -7,6 +7,8 @@ import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.PhaseStep;
import mage.constants.TargetController;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.token.RedWarriorToken; import mage.game.permanent.token.RedWarriorToken;
import mage.util.CardUtil; import mage.util.CardUtil;
@ -34,7 +36,6 @@ public class MobilizeAbility extends AttacksTriggeredAbility {
} }
private static String makeText(DynamicValue amount) { private static String makeText(DynamicValue amount) {
StringBuilder sb = new StringBuilder();
String message; String message;
String numToText; String numToText;
boolean plural; boolean plural;
@ -77,7 +78,7 @@ class MobilizeEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
CreateTokenEffect effect = new CreateTokenEffect(new RedWarriorToken(), this.count, true, true); CreateTokenEffect effect = new CreateTokenEffect(new RedWarriorToken(), this.count, true, true);
effect.apply(game, source); effect.apply(game, source);
effect.sacrificeTokensCreatedAtNextEndStep(game, source); effect.removeTokensCreatedAt(game, source, false, PhaseStep.END_TURN, TargetController.ANY);
return true; return true;
} }
} }