From f92af2b9cd1ab3288a466430737d9ac128f71dcb Mon Sep 17 00:00:00 2001 From: xenohedron <12538125+xenohedron@users.noreply.github.com> Date: Sun, 7 Sep 2025 00:15:01 -0400 Subject: [PATCH] refactor: consistent logic for delayed triggers that sacrifice/exile tokens single trigger for multiple tokens --- .../src/mage/cards/g/GeistOfSaintTraft.java | 7 +-- .../mage/cards/i/InvocationOfSaintTraft.java | 8 +-- .../mage/cards/k/KariZevSkyshipRaider.java | 7 +-- Mage.Sets/src/mage/cards/m/MiragePhalanx.java | 2 +- .../common/CreateTokenCopyTargetEffect.java | 30 +++------ .../effects/common/CreateTokenEffect.java | 63 +++++++++---------- .../abilities/keyword/MobilizeAbility.java | 5 +- 7 files changed, 50 insertions(+), 72 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GeistOfSaintTraft.java b/Mage.Sets/src/mage/cards/g/GeistOfSaintTraft.java index a372e6f28d8..958c5c9ef1e 100644 --- a/Mage.Sets/src/mage/cards/g/GeistOfSaintTraft.java +++ b/Mage.Sets/src/mage/cards/g/GeistOfSaintTraft.java @@ -9,10 +9,7 @@ import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.keyword.HexproofAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.SuperType; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.token.AngelToken; import mage.players.Player; @@ -65,7 +62,7 @@ class GeistOfSaintTraftEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && effect.apply(game, source)) { - effect.exileTokensCreatedAtEndOfCombat(game, source); + effect.removeTokensCreatedAt(game, source, true, PhaseStep.END_COMBAT, TargetController.ANY); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/i/InvocationOfSaintTraft.java b/Mage.Sets/src/mage/cards/i/InvocationOfSaintTraft.java index d5742a4bff4..fb67d5b61eb 100644 --- a/Mage.Sets/src/mage/cards/i/InvocationOfSaintTraft.java +++ b/Mage.Sets/src/mage/cards/i/InvocationOfSaintTraft.java @@ -12,11 +12,7 @@ import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.token.AngelToken; import mage.players.Player; @@ -72,7 +68,7 @@ class InvocationOfSaintTraftEffect extends OneShotEffect { CreateTokenEffect effect = new CreateTokenEffect(new AngelToken(), 1, true, true); Player controller = game.getPlayer(source.getControllerId()); if (controller != null && (effect.apply(game, source))) { - effect.exileTokensCreatedAtEndOfCombat(game, source); + effect.removeTokensCreatedAt(game, source, true, PhaseStep.END_COMBAT, TargetController.ANY); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/k/KariZevSkyshipRaider.java b/Mage.Sets/src/mage/cards/k/KariZevSkyshipRaider.java index 93d407ce393..b864a33b8ac 100644 --- a/Mage.Sets/src/mage/cards/k/KariZevSkyshipRaider.java +++ b/Mage.Sets/src/mage/cards/k/KariZevSkyshipRaider.java @@ -11,10 +11,7 @@ import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.MenaceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.SuperType; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.token.RagavanToken; import mage.players.Player; @@ -70,7 +67,7 @@ class KariZevSkyshipRaiderEffect extends OneShotEffect { CreateTokenEffect effect = new CreateTokenEffect(new RagavanToken(), 1, true, true); Player controller = game.getPlayer(source.getControllerId()); if (controller != null && effect.apply(game, source)) { - effect.exileTokensCreatedAtEndOfCombat(game, source); + effect.removeTokensCreatedAt(game, source, true, PhaseStep.END_COMBAT, TargetController.ANY); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/m/MiragePhalanx.java b/Mage.Sets/src/mage/cards/m/MiragePhalanx.java index aa74aa821be..6649a8e3a3d 100644 --- a/Mage.Sets/src/mage/cards/m/MiragePhalanx.java +++ b/Mage.Sets/src/mage/cards/m/MiragePhalanx.java @@ -82,7 +82,7 @@ class MiragePhalanxEffect extends OneShotEffect { // Create the token(s) tokenCopyEffect.apply(game, source); // 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(); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java index ca0a32efa39..6f02329d0b3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java @@ -414,41 +414,29 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { } 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) { - 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) { - this.removeTokensCreatedAtEndOf(game, source, PhaseStep.END_COMBAT, false); - } - - 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()); - } + public void removeTokensCreatedAt(Game game, Ability source, boolean exile, PhaseStep phaseStep, TargetController targetController) { + Effect effect = exile + ? new ExileTargetEffect(null, "", Zone.BATTLEFIELD).setText("exile the token copies") + : new SacrificeTargetEffect("sacrifice the token copies", source.getControllerId()); effect.setTargetPointer(new FixedTargets(new ArrayList<>(addedTokenPermanents), game)); DelayedTriggeredAbility exileAbility; - - switch (phaseStepToExileCards) { + switch (phaseStep) { case END_TURN: - exileAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); + exileAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect, targetController); break; case END_COMBAT: exileAbility = new AtTheEndOfCombatDelayedTriggeredAbility(effect); break; default: - return; + throw new UnsupportedOperationException("Unsupported PhaseStep in CreateTokenCopyTargetEffect::removeTokensCreatedAt"); } game.addDelayedTriggeredAbility(exileAbility, source); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenEffect.java index 7d05603eeda..51b21230ae7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenEffect.java @@ -1,24 +1,26 @@ package mage.abilities.effects.common; import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.TargetController; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com @@ -121,37 +123,34 @@ public class CreateTokenEffect extends OneShotEffect { 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) { - for (UUID tokenId : this.getLastAddedTokenIds()) { - 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); - } - } + removeTokensCreatedAt(game, source, true, PhaseStep.END_TURN, TargetController.ANY); } - public void exileTokensCreatedAtEndOfCombat(Game game, Ability source) { - for (UUID tokenId : this.getLastAddedTokenIds()) { - Permanent tokenPermanent = game.getPermanent(tokenId); - if (tokenPermanent != null) { - ExileTargetEffect exileEffect = new ExileTargetEffect(null, "", Zone.BATTLEFIELD); - exileEffect.setTargetPointer(new FixedTarget(tokenPermanent, game)); - game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility(exileEffect), source); - } + public void removeTokensCreatedAt(Game game, Ability source, boolean exile, PhaseStep phaseStep, TargetController targetController) { + List permanents = this.getLastAddedTokenIds() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + 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); } /** diff --git a/Mage/src/main/java/mage/abilities/keyword/MobilizeAbility.java b/Mage/src/main/java/mage/abilities/keyword/MobilizeAbility.java index 41878053fd8..9549ead76dc 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MobilizeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MobilizeAbility.java @@ -7,6 +7,8 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.TargetController; import mage.game.Game; import mage.game.permanent.token.RedWarriorToken; import mage.util.CardUtil; @@ -34,7 +36,6 @@ public class MobilizeAbility extends AttacksTriggeredAbility { } private static String makeText(DynamicValue amount) { - StringBuilder sb = new StringBuilder(); String message; String numToText; boolean plural; @@ -77,7 +78,7 @@ class MobilizeEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { CreateTokenEffect effect = new CreateTokenEffect(new RedWarriorToken(), this.count, true, true); effect.apply(game, source); - effect.sacrificeTokensCreatedAtNextEndStep(game, source); + effect.removeTokensCreatedAt(game, source, false, PhaseStep.END_TURN, TargetController.ANY); return true; } }