mirror of
https://github.com/magefree/mage.git
synced 2025-12-21 19:11:59 -08:00
Refactor CreateTokenEffect to allow multiple tokens at once. (#12704)
* Refactor CreateTokenEffect to allow multiple tokens at once. Partial solution to #10811 - Token copy effects still need to be redone so that mass token copy effects (Ocelot Pride, Mirror Match, other similar effects) can be created in a single batch.
This commit is contained in:
parent
da48821754
commit
543f9f074e
26 changed files with 132 additions and 154 deletions
|
|
@ -59,11 +59,7 @@ public final class AKillerAmongUs extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}");
|
||||
|
||||
// When A Killer Among Us enters the battlefield, create a 1/1 white Human creature token, a 1/1 blue Merfolk creature token, and a 1/1 red Goblin creature token. Then secretly choose Human, Merfolk, or Goblin.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HumanToken()));
|
||||
ability.addEffect(new CreateTokenEffect(new MerfolkToken())
|
||||
.setText(", a 1/1 blue Merfolk creature token"));
|
||||
ability.addEffect(new CreateTokenEffect(new GoblinToken())
|
||||
.setText(", and a 1/1 red Goblin creature token."));
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HumanToken()).withAdditionalTokens(new MerfolkToken(), new GoblinToken()));
|
||||
ability.addEffect(new ChooseHumanMerfolkOrGoblinEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ public final class BestialMenace extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}{G}");
|
||||
|
||||
// Create a 1/1 green Snake creature token, a 2/2 green Wolf creature token, and a 3/3 green Elephant creature token.
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new SnakeToken()));
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new WolfToken()).setText(", a 2/2 green Wolf creature token"));
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new ElephantToken()).setText(", and a 3/3 green Elephant creature token"));
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new SnakeToken()).withAdditionalTokens(new WolfToken(), new ElephantToken()));
|
||||
}
|
||||
|
||||
private BestialMenace(final BestialMenace card) {
|
||||
|
|
|
|||
|
|
@ -64,9 +64,7 @@ public final class DevouringSugarmaw extends AdventureCard {
|
|||
|
||||
// Have for Dinner
|
||||
// Create a 1/1 white Human creature token and a Food token.
|
||||
this.getSpellCard().getSpellAbility().addEffect(new CreateTokenEffect(new HumanToken()));
|
||||
this.getSpellCard().getSpellAbility().addEffect(new CreateTokenEffect(new FoodToken())
|
||||
.setText("and a Food token"));
|
||||
this.getSpellCard().getSpellAbility().addEffect(new CreateTokenEffect(new HumanToken()).withAdditionalTokens(new FoodToken()));
|
||||
|
||||
this.finalizeAdventure();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,16 +31,14 @@ public final class FaeOffering extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
|
||||
|
||||
// At the beginning of each end step, if you've cast both a creature spell and a noncreature spell this turn, create a Clue token, a Food token, and a Treasure token.
|
||||
Ability ability = new ConditionalInterveningIfTriggeredAbility(
|
||||
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
|
||||
new BeginningOfEndStepTriggeredAbility(
|
||||
new CreateTokenEffect(new ClueArtifactToken()), TargetController.ANY, false
|
||||
new CreateTokenEffect(new ClueArtifactToken()).withAdditionalTokens(new FoodToken(), new TreasureToken()),
|
||||
TargetController.ANY, false
|
||||
), FaeOfferingCondition.instance, "At the beginning of each end step, " +
|
||||
"if you've cast both a creature spell and a noncreature spell this turn, " +
|
||||
"create a Clue token, a Food token, and a Treasure token."
|
||||
);
|
||||
ability.addEffect(new CreateTokenEffect(new FoodToken()));
|
||||
ability.addEffect(new CreateTokenEffect(new TreasureToken()));
|
||||
this.addAbility(ability.addHint(FaeOfferingHint.instance));
|
||||
).addHint(FaeOfferingHint.instance));
|
||||
}
|
||||
|
||||
private FaeOffering(final FaeOffering card) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.cards.f;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.TriggeredAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.dynamicvalue.common.GetXValue;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
|
|
@ -31,15 +30,8 @@ public final class FarmerCotton extends CardImpl {
|
|||
this.toughness = new MageInt(1);
|
||||
|
||||
// When Farmer Cotton enters the battlefield, create X 1/1 white Halfling creature tokens and X Food tokens.
|
||||
TriggeredAbility trigger = new EntersBattlefieldTriggeredAbility(
|
||||
new CreateTokenEffect(new HalflingToken(), GetXValue.instance)
|
||||
);
|
||||
trigger.addEffect(
|
||||
new CreateTokenEffect(new FoodToken(), GetXValue.instance)
|
||||
.setText("and X Food tokens")
|
||||
);
|
||||
|
||||
this.addAbility(trigger);
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HalflingToken(),
|
||||
GetXValue.instance).withAdditionalTokens(new FoodToken())));
|
||||
}
|
||||
|
||||
private FarmerCotton(final FarmerCotton card) {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ public final class ForbiddenFriendship extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}");
|
||||
|
||||
// Create a 1/1 red Dinosaur creature token with haste and a 1/1 white Human Soldier creature token.
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new DinosaurHasteToken()));
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new HumanSoldierToken())
|
||||
.setText("and a 1/1 white Human Soldier creature token"));
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new DinosaurHasteToken()).withAdditionalTokens(new HumanSoldierToken()));
|
||||
}
|
||||
|
||||
private ForbiddenFriendship(final ForbiddenFriendship card) {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public final class FrostFairLureFish extends CardImpl {
|
|||
// When Frost Fair Lure Fish enters the battlefield, create two 1/1 blue Fish creature tokens and two tapped Treasure tokens.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new FishNoAbilityToken(), 2));
|
||||
ability.addEffect(new CreateTokenEffect(new TreasureToken(), 2, true)
|
||||
.setText("and two tapped Treasure tokens"));
|
||||
.setText("and create two tapped Treasure tokens"));
|
||||
this.addAbility(ability);
|
||||
|
||||
// Fish you control have haste and can't be blocked by Humans.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.cards.l;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -64,34 +63,34 @@ class LiberatedLivestockEffect extends OneShotEffect {
|
|||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
List<Token> tokens = Arrays.asList(new CatToken2(), new BirdToken(), new OxToken());
|
||||
tokens.forEach(token -> token.putOntoBattlefield(1, game, source, source.getControllerId()));
|
||||
Token firstToken = new CatToken2();
|
||||
firstToken.putOntoBattlefield(1, game, source, source.getControllerId(),
|
||||
false, false, null, null, true,
|
||||
Arrays.asList(firstToken, new BirdToken(), new OxToken()));
|
||||
game.processAction();
|
||||
|
||||
for (Token token : tokens) {
|
||||
for (UUID tokenId : token.getLastAddedTokenIds()) {
|
||||
Permanent tokenPermanent = game.getPermanent(tokenId);
|
||||
if (tokenPermanent == null) {
|
||||
continue;
|
||||
}
|
||||
FilterCard filter = new FilterCard("Aura from your hand or graveyard that can attach to " + tokenPermanent.getName());
|
||||
filter.add(SubType.AURA.getPredicate());
|
||||
filter.add(new AuraCardCanAttachToPermanentId(tokenPermanent.getId()));
|
||||
Cards auraCards = new CardsImpl();
|
||||
auraCards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game));
|
||||
auraCards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game));
|
||||
if (auraCards.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
|
||||
target.withNotTarget(true);
|
||||
controller.chooseTarget(outcome, auraCards, target, source, game);
|
||||
Card auraCard = game.getCard(target.getFirstTarget());
|
||||
if (auraCard != null && !tokenPermanent.cantBeAttachedBy(auraCard, source, game, true)) {
|
||||
game.getState().setValue("attachTo:" + auraCard.getId(), tokenPermanent);
|
||||
controller.moveCards(auraCard, Zone.BATTLEFIELD, source, game);
|
||||
tokenPermanent.addAttachment(auraCard.getId(), source, game);
|
||||
}
|
||||
for (UUID tokenId : firstToken.getLastAddedTokenIds()) {
|
||||
Permanent tokenPermanent = game.getPermanent(tokenId);
|
||||
if (tokenPermanent == null) {
|
||||
continue;
|
||||
}
|
||||
FilterCard filter = new FilterCard("Aura from your hand or graveyard that can attach to " + tokenPermanent.getName());
|
||||
filter.add(SubType.AURA.getPredicate());
|
||||
filter.add(new AuraCardCanAttachToPermanentId(tokenPermanent.getId()));
|
||||
Cards auraCards = new CardsImpl();
|
||||
auraCards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game));
|
||||
auraCards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game));
|
||||
if (auraCards.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
|
||||
target.withNotTarget(true);
|
||||
controller.chooseTarget(outcome, auraCards, target, source, game);
|
||||
Card auraCard = game.getCard(target.getFirstTarget());
|
||||
if (auraCard != null && !tokenPermanent.cantBeAttachedBy(auraCard, source, game, true)) {
|
||||
game.getState().setValue("attachTo:" + auraCard.getId(), tokenPermanent);
|
||||
controller.moveCards(auraCard, Zone.BATTLEFIELD, source, game);
|
||||
tokenPermanent.addAttachment(auraCard.getId(), source, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.cards.m;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.TriggeredAbility;
|
||||
import mage.abilities.common.DealtDamageAndDiedTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
|
|
@ -36,9 +35,7 @@ public final class MadameVastra extends CardImpl {
|
|||
this.addAbility(new SimpleStaticAbility(new MustBeBlockedByAtLeastOneSourceEffect(Duration.WhileOnBattlefield)));
|
||||
|
||||
// Whenever a creature dealt damage by Madame Vastra this turn dies, create a Clue token and a Food token.
|
||||
TriggeredAbility trigger = new DealtDamageAndDiedTriggeredAbility(new CreateTokenEffect(new ClueArtifactToken()));
|
||||
trigger.addEffect(new CreateTokenEffect(new FoodToken()).setText("and a Food token"));
|
||||
this.addAbility(trigger);
|
||||
this.addAbility(new DealtDamageAndDiedTriggeredAbility(new CreateTokenEffect(new ClueArtifactToken()).withAdditionalTokens(new FoodToken())));
|
||||
}
|
||||
|
||||
private MadameVastra(final MadameVastra card) {
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ public final class MascotExhibition extends CardImpl {
|
|||
this.subtype.add(SubType.LESSON);
|
||||
|
||||
// Create a 2/1 white and black Inkling creature token with flying, a 3/2 red and white Spirit creature token, and a 4/4 blue and red Elemental creature token.
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new InklingToken()));
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new Spirit32Token())
|
||||
.setText(", a 3/2 red and white Spirit creature token"));
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new Elemental44Token())
|
||||
.setText(", and a 4/4 blue and red Elemental creature token"));
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new InklingToken()).withAdditionalTokens(new Spirit32Token(), new Elemental44Token()));
|
||||
}
|
||||
|
||||
private MascotExhibition(final MascotExhibition card) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.cards.p;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.DiesThisOrAnotherTriggeredAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.keyword.LifelinkAbility;
|
||||
|
|
@ -50,12 +49,9 @@ public final class PolukranosEngineOfRuin extends CardImpl {
|
|||
this.addAbility(LifelinkAbility.getInstance());
|
||||
|
||||
// Whenever Polukranos, Engine of Ruin or another nontoken Hydra you control dies, create a 3/3 green and white Phyrexian Hydra creature token with reach and a 3/3 green and white Phyrexian Hydra creature token with lifelink.
|
||||
Ability ability = new DiesThisOrAnotherTriggeredAbility(
|
||||
new CreateTokenEffect(new PhyrexianHydraWithReachToken()), false, filter
|
||||
);
|
||||
ability.addEffect(new CreateTokenEffect(new PhyrexianHydraWithLifelinkToken())
|
||||
.setText("and a 3/3 green and white Phyrexian Hydra creature token with lifelink"));
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new DiesThisOrAnotherTriggeredAbility(
|
||||
new CreateTokenEffect(new PhyrexianHydraWithReachToken()).withAdditionalTokens(new PhyrexianHydraWithLifelinkToken()), false, filter
|
||||
));
|
||||
}
|
||||
|
||||
private PolukranosEngineOfRuin(final PolukranosEngineOfRuin card) {
|
||||
|
|
|
|||
|
|
@ -48,12 +48,9 @@ public final class ReckonerBankbuster extends CardImpl {
|
|||
new DrawCardSourceControllerEffect(1), new GenericManaCost(2)
|
||||
);
|
||||
ability.addEffect(new ConditionalOneShotEffect(
|
||||
new CreateTokenEffect(new TreasureToken()), condition,
|
||||
"Then if there are no charge counters on {this}, create a Treasure token"
|
||||
));
|
||||
ability.addEffect(new ConditionalOneShotEffect(
|
||||
new CreateTokenEffect(new PilotToken()), condition, "and a 1/1 colorless Pilot creature token " +
|
||||
"with \"This creature crews Vehicles as though its power were 2 greater.\""
|
||||
new CreateTokenEffect(new TreasureToken()).withAdditionalTokens(new PilotToken()), condition,
|
||||
"Then if there are no charge counters on {this}, create a Treasure token and a 1/1 colorless " +
|
||||
"Pilot creature token with \"This creature crews Vehicles as though its power were 2 greater.\""
|
||||
));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addCost(new RemoveCountersSourceCost(CounterType.CHARGE.createInstance()));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.cards.s;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
|
|
@ -33,10 +32,7 @@ public final class SomberwaldBeastmaster extends CardImpl {
|
|||
this.toughness = new MageInt(1);
|
||||
|
||||
// When Somberwald Beastmaster enters the battlefield, create a 2/2 green Wolf creature token, a 3/3 green Beast creature token, and a 4/4 green Beast creature token.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new WolfToken()));
|
||||
ability.addEffect(new CreateTokenEffect(new BeastToken()).setText(", a 3/3 green Beast creature token"));
|
||||
ability.addEffect(new CreateTokenEffect(new BeastToken2()).setText(", and a 4/4 green Beast creature token"));
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new WolfToken()).withAdditionalTokens(new BeastToken(), new BeastToken2())));
|
||||
|
||||
// Creature tokens you control have deathtouch.
|
||||
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ public final class SongOfEarendil extends CardImpl {
|
|||
// II-- Create a Treasure token and a 2/2 blue Bird creature token with flying.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_II,
|
||||
new CreateTokenEffect(new TreasureToken()),
|
||||
new CreateTokenEffect(new SwanSongBirdToken())
|
||||
.setText("and a 2/2 blue Bird creature token with flying")
|
||||
new CreateTokenEffect(new TreasureToken()).withAdditionalTokens(new SwanSongBirdToken())
|
||||
);
|
||||
|
||||
// III-- Put a flying counter on each creature you control without flying.
|
||||
|
|
|
|||
|
|
@ -39,12 +39,10 @@ public final class SpecimenCollector extends CardImpl {
|
|||
this.toughness = new MageInt(1);
|
||||
|
||||
// When Specimen Collector enters the battlefield, create a 1/1 green Squirrel creature token and a 0/3 blue Crab creature token.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new SquirrelToken()));
|
||||
ability.addEffect(new CreateTokenEffect(new CrabToken()).setText("and a 0/3 blue Crab creature token"));
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new SquirrelToken()).withAdditionalTokens(new CrabToken())));
|
||||
|
||||
// When Specimen Collector dies, create a token that's a copy of target token you control.
|
||||
ability = new DiesSourceTriggeredAbility(new CreateTokenCopyTargetEffect());
|
||||
Ability ability = new DiesSourceTriggeredAbility(new CreateTokenCopyTargetEffect());
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,10 +46,7 @@ public final class TheEleventhHour extends CardImpl {
|
|||
// II -- Create a Food token and a 1/1 white Human creature token with "Doctor spells you cast cost 1 less to cast."
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_II,
|
||||
new CreateTokenEffect(new FoodToken()),
|
||||
new CreateTokenEffect(new TheEleventhHourToken())
|
||||
.setText("and a 1/1 white Human creature token with " +
|
||||
"\"Doctor spells you cast cost {1} less to cast.\"")
|
||||
new CreateTokenEffect(new FoodToken()).withAdditionalTokens(new TheEleventhHourToken())
|
||||
);
|
||||
|
||||
// III -- Create a token that's a copy of target creature, except it's a legendary Alien named Prisoner Zero.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.cards.t;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.DiesSourceTriggeredAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
|
|
@ -39,12 +38,7 @@ public final class TriplicateTitan extends CardImpl {
|
|||
this.addAbility(TrampleAbility.getInstance());
|
||||
|
||||
// When Triplicate Titan dies, create a 3/3 colorless Golem artifact creature token with flying, a 3/3 colorless Golem artifact creature token with vigilance, and a 3/3 colorless Golem artifact creature token with trample.
|
||||
Ability ability = new DiesSourceTriggeredAbility(new CreateTokenEffect(new GolemFlyingToken()));
|
||||
ability.addEffect(new CreateTokenEffect(new GolemVigilanceToken())
|
||||
.setText(", a 3/3 colorless Golem artifact creature token with vigilance"));
|
||||
ability.addEffect(new CreateTokenEffect(new GolemTrampleToken())
|
||||
.setText(", and a 3/3 colorless Golem artifact creature token with trample"));
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new GolemFlyingToken()).withAdditionalTokens(new GolemVigilanceToken(), new GolemTrampleToken())));
|
||||
}
|
||||
|
||||
private TriplicateTitan(final TriplicateTitan card) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package mage.cards.t;
|
|||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.cards.CardImpl;
|
||||
|
|
@ -29,10 +28,7 @@ public final class TrostanisSummoner extends CardImpl {
|
|||
this.toughness = new MageInt(1);
|
||||
|
||||
// When Trostani's Summoner enters the battlefield, create a 2/2 white Knight creature token with vigilance, a 3/3 green Centaur creature token, and a 4/4 green Rhino creature token with trample.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new KnightToken()));
|
||||
ability.addEffect(new CreateTokenEffect(new CentaurToken()).setText(", a 3/3 green Centaur creature token"));
|
||||
ability.addEffect(new CreateTokenEffect(new RhinoToken()).setText(", and a 4/4 green Rhino creature token with trample"));
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new KnightToken()).withAdditionalTokens(new CentaurToken(), new RhinoToken())));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@ public final class Vault101BirthdayParty extends CardImpl {
|
|||
|
||||
// I -- Create a 1/1 white Human Soldier creature token and a Food token.
|
||||
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I,
|
||||
new CreateTokenEffect(new HumanSoldierToken()),
|
||||
new CreateTokenEffect(new FoodToken()).setText("and a Food token"));
|
||||
new CreateTokenEffect(new HumanSoldierToken()).withAdditionalTokens(new FoodToken()));
|
||||
|
||||
// II, III -- You may put an Aura or Equipment card from your hand or graveyard onto the battlefield.
|
||||
// If an Equipment is put onto the battlefield this way, you may attach it to a creature you control.
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ public final class WurmcoilEngine extends CardImpl {
|
|||
this.addAbility(LifelinkAbility.getInstance());
|
||||
|
||||
// When Wurmcoil Engine dies, create a 3/3 colorless Wurm artifact creature token with deathtouch and a 3/3 colorless Wurm artifact creature token with lifelink.
|
||||
Ability ability = new DiesSourceTriggeredAbility(new CreateTokenEffect(new WurmWithDeathtouchToken()), false);
|
||||
ability.addEffect(new CreateTokenEffect(new WurmWithLifelinkToken()).setText("and a 3/3 colorless Phyrexian Wurm artifact creature token with lifelink"));
|
||||
Ability ability = new DiesSourceTriggeredAbility(new CreateTokenEffect(new WurmWithDeathtouchToken()).withAdditionalTokens(new WurmWithLifelinkToken()), false);
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ public final class WurmcoilLarva extends CardImpl {
|
|||
this.addAbility(LifelinkAbility.getInstance());
|
||||
|
||||
// When Wurmcoil Larva dies, create a 1/2 black Phyrexian Wurm artifact creature token with deathtouch and a 2/1 black Phyrexian Wurm artifact creature token with lifelink.
|
||||
Ability ability = new DiesSourceTriggeredAbility(new CreateTokenEffect(new PhyrexianWurm12DeathtouchToken()), false);
|
||||
ability.addEffect(new CreateTokenEffect(new PhyrexianWurm21LifelinkToken())
|
||||
.setText("and a 2/1 black Phyrexian Wurm artifact creature token with lifelink"));
|
||||
Ability ability = new DiesSourceTriggeredAbility(new CreateTokenEffect(new PhyrexianWurm12DeathtouchToken()).withAdditionalTokens(new PhyrexianWurm21LifelinkToken()), false);
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ public class KambalProfiteeringMayorTest extends CardTestPlayerBase {
|
|||
assertPermanentCount(playerA, "Wolf Token", 1);
|
||||
assertPermanentCount(playerA, "Elephant Token", 1);
|
||||
assertPermanentCount(playerB, "Snake Token", 1);
|
||||
assertPermanentCount(playerB, "Wolf Token", 0); // TODO: this is a bug, should be 1, see #10811
|
||||
assertPermanentCount(playerB, "Elephant Token", 0); // TODO: this is a bug, should be 1, see #10811
|
||||
assertPermanentCount(playerB, "Wolf Token", 1);
|
||||
assertPermanentCount(playerB, "Elephant Token", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import mage.target.targetpointer.FixedTarget;
|
|||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ import java.util.UUID;
|
|||
*/
|
||||
public class CreateTokenEffect extends OneShotEffect {
|
||||
|
||||
private final Token token;
|
||||
private final List<Token> tokens = new ArrayList<>();
|
||||
private final DynamicValue amount;
|
||||
private final boolean tapped;
|
||||
private final boolean attacking;
|
||||
|
|
@ -56,7 +57,10 @@ public class CreateTokenEffect extends OneShotEffect {
|
|||
|
||||
public CreateTokenEffect(Token token, DynamicValue amount, boolean tapped, boolean attacking) {
|
||||
super(Outcome.PutCreatureInPlay);
|
||||
this.token = token;
|
||||
if (token == null) {
|
||||
throw new IllegalArgumentException("Wrong code usage. Token provided to CreateTokenEffect must not be null.");
|
||||
}
|
||||
this.tokens.add(token);
|
||||
this.amount = amount.copy();
|
||||
this.tapped = tapped;
|
||||
this.attacking = attacking;
|
||||
|
|
@ -66,7 +70,9 @@ public class CreateTokenEffect extends OneShotEffect {
|
|||
protected CreateTokenEffect(final CreateTokenEffect effect) {
|
||||
super(effect);
|
||||
this.amount = effect.amount.copy();
|
||||
this.token = effect.token.copy();
|
||||
for (Token token : effect.tokens) {
|
||||
this.tokens.add(token.copy());
|
||||
}
|
||||
this.tapped = effect.tapped;
|
||||
this.attacking = effect.attacking;
|
||||
this.lastAddedTokenIds.addAll(effect.lastAddedTokenIds);
|
||||
|
|
@ -82,6 +88,11 @@ public class CreateTokenEffect extends OneShotEffect {
|
|||
return this;
|
||||
}
|
||||
|
||||
public CreateTokenEffect withAdditionalTokens(Token... tokens) {
|
||||
this.tokens.addAll(Arrays.asList(tokens));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CreateTokenEffect copy() {
|
||||
return new CreateTokenEffect(this);
|
||||
|
|
@ -90,8 +101,8 @@ public class CreateTokenEffect extends OneShotEffect {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
int value = amount.calculate(game, source, this);
|
||||
token.putOntoBattlefield(value, game, source, source.getControllerId(), tapped, attacking);
|
||||
this.lastAddedTokenIds = token.getLastAddedTokenIds();
|
||||
tokens.get(0).putOntoBattlefield(value, game, source, source.getControllerId(), tapped, attacking, null, null, true, tokens);
|
||||
this.lastAddedTokenIds = tokens.get(0).getLastAddedTokenIds();
|
||||
// TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786
|
||||
if (counterType != null) {
|
||||
for (UUID tokenId : lastAddedTokenIds) {
|
||||
|
|
@ -150,41 +161,53 @@ public class CreateTokenEffect extends OneShotEffect {
|
|||
}
|
||||
|
||||
private void setText() {
|
||||
String tokenDescription = token.getDescription();
|
||||
boolean singular = amount.toString().equals("1");
|
||||
if (tokenDescription.contains(", a legendary")) {
|
||||
staticText = "create " + tokenDescription;
|
||||
return;
|
||||
}
|
||||
if (oldPhrasing) {
|
||||
tokenDescription = tokenDescription.replace("token with \"",
|
||||
singular ? "token. It has \"" : "tokens. They have \"");
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("create ");
|
||||
if (singular) {
|
||||
if (tapped && !attacking) {
|
||||
sb.append("a tapped ");
|
||||
for (int i = 0; i < tokens.size(); i++) {
|
||||
if (i > 0) {
|
||||
if (tokens.size() > 2) {
|
||||
sb.append(", ");
|
||||
} else {
|
||||
sb.append(" ");
|
||||
}
|
||||
if (i+1 == tokens.size()) {
|
||||
sb.append("and ");
|
||||
}
|
||||
}
|
||||
String tokenDescription = tokens.get(i).getDescription();
|
||||
if (tokenDescription.contains(", a legendary")) {
|
||||
sb.append(tokenDescription);
|
||||
continue;
|
||||
}
|
||||
if (oldPhrasing) {
|
||||
tokenDescription = tokenDescription.replace("token with \"",
|
||||
singular ? "token. It has \"" : "tokens. They have \"");
|
||||
}
|
||||
if (singular) {
|
||||
if (tapped && !attacking) {
|
||||
sb.append("a tapped ");
|
||||
sb.append(tokenDescription);
|
||||
} else {
|
||||
sb.append(CardUtil.addArticle(tokenDescription));
|
||||
}
|
||||
} else {
|
||||
sb.append(CardUtil.addArticle(tokenDescription));
|
||||
}
|
||||
} else {
|
||||
sb.append(CardUtil.numberToText(amount.toString())).append(' ');
|
||||
if (tapped && !attacking) {
|
||||
sb.append("tapped ");
|
||||
}
|
||||
sb.append(tokenDescription);
|
||||
if (tokenDescription.endsWith("token")) {
|
||||
sb.append("s");
|
||||
}
|
||||
int tokenLocation = sb.indexOf("token ");
|
||||
if (tokenLocation != -1) {
|
||||
sb.replace(tokenLocation, tokenLocation + 6, "tokens ");
|
||||
sb.append(CardUtil.numberToText(amount.toString())).append(' ');
|
||||
if (tapped && !attacking) {
|
||||
sb.append("tapped ");
|
||||
}
|
||||
sb.append(tokenDescription);
|
||||
if (tokenDescription.endsWith("token")) {
|
||||
sb.append("s");
|
||||
}
|
||||
int tokenLocation = sb.indexOf("token ");
|
||||
if (tokenLocation != -1) {
|
||||
sb.replace(tokenLocation, tokenLocation + 6, "tokens ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attacking) {
|
||||
if (singular) {
|
||||
if (singular && tokens.size() == 1) {
|
||||
sb.append(" that's");
|
||||
} else {
|
||||
sb.append(" that are");
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import mage.abilities.Ability;
|
|||
import mage.game.permanent.token.Token;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -17,11 +18,15 @@ public class CreateTokenEvent extends GameEvent {
|
|||
* @param source
|
||||
* @param controllerId
|
||||
* @param amount
|
||||
* @param token
|
||||
* @param tokensList
|
||||
*/
|
||||
public CreateTokenEvent(Ability source, UUID controllerId, int amount, Token token) {
|
||||
public CreateTokenEvent(Ability source, UUID controllerId, int amount, List<Token> tokensList) {
|
||||
super(GameEvent.EventType.CREATE_TOKEN, null, source, controllerId, amount, false);
|
||||
tokens.put(token, amount);
|
||||
if (tokensList != null) {
|
||||
for (Token token : tokensList) {
|
||||
tokens.put(token, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Token, Integer> getTokens() {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ public interface Token extends MageObject {
|
|||
|
||||
boolean putOntoBattlefield(int amount, Game game, Ability source, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer, UUID attachedTo, boolean created);
|
||||
|
||||
boolean putOntoBattlefield(int amount, Game game, Ability source, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer, UUID attachedTo, boolean created, List<Token> additionalTokens);
|
||||
|
||||
void setPower(int power);
|
||||
|
||||
void setToughness(int toughness);
|
||||
|
|
|
|||
|
|
@ -218,8 +218,12 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
return putOntoBattlefield(amount, game, source, controllerId, tapped, attacking, attackedPlayer, attachedTo, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putOntoBattlefield(int amount, Game game, Ability source, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer, UUID attachedTo, boolean created) {
|
||||
return putOntoBattlefield(amount, game, source, controllerId, tapped, attacking, attackedPlayer, attachedTo, created, Collections.singletonList(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putOntoBattlefield(int amount, Game game, Ability source, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer, UUID attachedTo, boolean created, List<Token> tokens) {
|
||||
Player controller = game.getPlayer(controllerId);
|
||||
if (controller == null) {
|
||||
return false;
|
||||
|
|
@ -229,7 +233,11 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
}
|
||||
lastAddedTokenIds.clear();
|
||||
|
||||
CreateTokenEvent event = new CreateTokenEvent(source, controllerId, amount, this);
|
||||
if (tokens == null || tokens.get(0) != this) {
|
||||
throw new IllegalArgumentException("Wrong code usage. token.putOntoBattlefield parameter tokens must be initialized to a list of all tokens to be made, with the first element being the token you are calling putOntoBattlefield() on.");
|
||||
}
|
||||
|
||||
CreateTokenEvent event = new CreateTokenEvent(source, controllerId, amount, tokens);
|
||||
if (!created || !game.replaceEvent(event)) {
|
||||
int currentTokens = game.getBattlefield().countTokens(event.getPlayerId());
|
||||
int tokenSlots = Math.max(MAX_TOKENS_PER_GAME - currentTokens, 0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue