From e3e34dae337a55ee3963f7d5e625b3cd9314735e Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Thu, 23 May 2024 19:47:06 +0200 Subject: [PATCH] implement [MH3] Strix Serenade ; refactor similar effects with Pongify effect Slight refactor for Serenade, Swan Song and An Offer You Can't Refuse to use the shared class (formely for Permanents only) for Pongify kind of effects. --- Mage.Sets/src/mage/cards/a/Afterlife.java | 5 +- .../mage/cards/a/AnOfferYouCantRefuse.java | 42 ++--------- .../src/mage/cards/a/AngelicAscension.java | 4 +- Mage.Sets/src/mage/cards/b/BeastWithin.java | 4 +- .../src/mage/cards/b/BovineIntervention.java | 4 +- .../src/mage/cards/b/BuyYourSilence.java | 4 +- .../src/mage/cards/c/CavalierOfDawn.java | 4 +- .../src/mage/cards/c/CityscapeLeveler.java | 5 +- .../mage/cards/c/CommanderSofiaDaguerre.java | 4 +- Mage.Sets/src/mage/cards/c/CribSwap.java | 5 +- .../src/mage/cards/g/GallowsAtWillowHill.java | 4 +- Mage.Sets/src/mage/cards/g/GenerousGift.java | 4 +- Mage.Sets/src/mage/cards/g/GetLost.java | 4 +- Mage.Sets/src/mage/cards/p/Pongify.java | 5 +- Mage.Sets/src/mage/cards/r/Ravenform.java | 4 +- .../src/mage/cards/r/ReduceToMemory.java | 4 +- Mage.Sets/src/mage/cards/r/Resculpt.java | 4 +- .../src/mage/cards/s/SawtuskDemolisher.java | 4 +- .../src/mage/cards/s/SecureTheScene.java | 4 +- Mage.Sets/src/mage/cards/s/StrixSerenade.java | 49 ++++++++++++ .../src/mage/cards/s/StrokeOfMidnight.java | 4 +- Mage.Sets/src/mage/cards/s/SwanSong.java | 58 ++++----------- .../src/mage/cards/t/TransmogrifyingWand.java | 5 +- .../mage/cards/u/UnyieldingGatekeeper.java | 4 +- Mage.Sets/src/mage/sets/ModernHorizons3.java | 1 + .../cards/single/mh3/StrixSerenadeTest.java | 74 +++++++++++++++++++ ...=> CreateTokenControllerTargetEffect.java} | 56 +++++++++++--- 27 files changed, 230 insertions(+), 139 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/StrixSerenade.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/StrixSerenadeTest.java rename Mage/src/main/java/mage/abilities/effects/common/{CreateTokenControllerTargetPermanentEffect.java => CreateTokenControllerTargetEffect.java} (56%) diff --git a/Mage.Sets/src/mage/cards/a/Afterlife.java b/Mage.Sets/src/mage/cards/a/Afterlife.java index 8a22501935e..c42353d06a2 100644 --- a/Mage.Sets/src/mage/cards/a/Afterlife.java +++ b/Mage.Sets/src/mage/cards/a/Afterlife.java @@ -1,6 +1,6 @@ package mage.cards.a; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -11,7 +11,6 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author Backfir3 */ public final class Afterlife extends CardImpl { @@ -23,7 +22,7 @@ public final class Afterlife extends CardImpl { // 1/1 white Spirit creature token with flying. this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addEffect(new DestroyTargetEffect(true)); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new SpiritWhiteToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new SpiritWhiteToken())); } private Afterlife(final Afterlife card) { diff --git a/Mage.Sets/src/mage/cards/a/AnOfferYouCantRefuse.java b/Mage.Sets/src/mage/cards/a/AnOfferYouCantRefuse.java index 37ee35918ea..8bba15096f8 100644 --- a/Mage.Sets/src/mage/cards/a/AnOfferYouCantRefuse.java +++ b/Mage.Sets/src/mage/cards/a/AnOfferYouCantRefuse.java @@ -1,15 +1,12 @@ package mage.cards.a; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.filter.StaticFilters; -import mage.game.Game; import mage.game.permanent.token.TreasureToken; -import mage.game.stack.Spell; import mage.target.TargetSpell; import java.util.UUID; @@ -23,7 +20,10 @@ public final class AnOfferYouCantRefuse extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); // Counter target noncreature spell. Its controller creates two Treasure tokens. - this.getSpellAbility().addEffect(new AnOfferYouCantRefuseEffect()); + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect( + new TreasureToken(), 2, false, CreateTokenControllerTargetEffect.TargetKind.SPELL + )); this.getSpellAbility().addTarget(new TargetSpell(StaticFilters.FILTER_SPELL_NON_CREATURE)); } @@ -35,32 +35,4 @@ public final class AnOfferYouCantRefuse extends CardImpl { public AnOfferYouCantRefuse copy() { return new AnOfferYouCantRefuse(this); } -} - -class AnOfferYouCantRefuseEffect extends OneShotEffect { - - AnOfferYouCantRefuseEffect() { - super(Outcome.Benefit); - staticText = "counter target noncreature spell. Its controller creates two Treasure tokens"; - } - - private AnOfferYouCantRefuseEffect(final AnOfferYouCantRefuseEffect effect) { - super(effect); - } - - @Override - public AnOfferYouCantRefuseEffect copy() { - return new AnOfferYouCantRefuseEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); - if (spell == null) { - return false; - } - game.getStack().counter(spell.getId(), source, game);; - new TreasureToken().putOntoBattlefield(2, game, source, spell.getControllerId()); - return true; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AngelicAscension.java b/Mage.Sets/src/mage/cards/a/AngelicAscension.java index d7daae4e405..a99075ed26c 100644 --- a/Mage.Sets/src/mage/cards/a/AngelicAscension.java +++ b/Mage.Sets/src/mage/cards/a/AngelicAscension.java @@ -1,6 +1,6 @@ package mage.cards.a; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -20,7 +20,7 @@ public final class AngelicAscension extends CardImpl { // Exile target creature or planeswalker. Its controller creates a 4/4 white Angel creature token with flying. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new AngelToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new AngelToken())); this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); } diff --git a/Mage.Sets/src/mage/cards/b/BeastWithin.java b/Mage.Sets/src/mage/cards/b/BeastWithin.java index 375b0b29d48..522002a7157 100644 --- a/Mage.Sets/src/mage/cards/b/BeastWithin.java +++ b/Mage.Sets/src/mage/cards/b/BeastWithin.java @@ -1,6 +1,6 @@ package mage.cards.b; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -21,7 +21,7 @@ public final class BeastWithin extends CardImpl { // Destroy target permanent. Its controller creates a 3/3 green Beast creature token. this.getSpellAbility().addTarget(new TargetPermanent()); this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new BeastToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new BeastToken())); } private BeastWithin(final BeastWithin card) { diff --git a/Mage.Sets/src/mage/cards/b/BovineIntervention.java b/Mage.Sets/src/mage/cards/b/BovineIntervention.java index d51271e1e9b..dadd23f2e86 100644 --- a/Mage.Sets/src/mage/cards/b/BovineIntervention.java +++ b/Mage.Sets/src/mage/cards/b/BovineIntervention.java @@ -1,6 +1,6 @@ package mage.cards.b; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -22,7 +22,7 @@ public final class BovineIntervention extends CardImpl { // Destroy target artifact or creature. Its controller creates a 2/2 white Ox creature token. this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new Ox22Token())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new Ox22Token())); } private BovineIntervention(final BovineIntervention card) { diff --git a/Mage.Sets/src/mage/cards/b/BuyYourSilence.java b/Mage.Sets/src/mage/cards/b/BuyYourSilence.java index 5a0d1b44a0b..fcae2266d01 100644 --- a/Mage.Sets/src/mage/cards/b/BuyYourSilence.java +++ b/Mage.Sets/src/mage/cards/b/BuyYourSilence.java @@ -1,6 +1,6 @@ package mage.cards.b; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -20,7 +20,7 @@ public final class BuyYourSilence extends CardImpl { // Exile target nonland permanent. Its controller creates a Treasure token. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new TreasureToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new TreasureToken())); this.getSpellAbility().addTarget(new TargetNonlandPermanent()); } diff --git a/Mage.Sets/src/mage/cards/c/CavalierOfDawn.java b/Mage.Sets/src/mage/cards/c/CavalierOfDawn.java index 4f0de762380..c11f905fe5e 100644 --- a/Mage.Sets/src/mage/cards/c/CavalierOfDawn.java +++ b/Mage.Sets/src/mage/cards/c/CavalierOfDawn.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; import mage.abilities.keyword.VigilanceAbility; @@ -41,7 +41,7 @@ public final class CavalierOfDawn extends CardImpl { // When Cavalier of Dawn enters the battlefield, destroy up to one target nonland permanent. Its controller creates a 3/3 colorless Golem artifact creature token. Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); - ability.addEffect(new CreateTokenControllerTargetPermanentEffect(new GolemToken())); + ability.addEffect(new CreateTokenControllerTargetEffect(new GolemToken())); ability.addTarget(new TargetNonlandPermanent(0, 1, false)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/CityscapeLeveler.java b/Mage.Sets/src/mage/cards/c/CityscapeLeveler.java index 4bf871910d1..0920ecc99c7 100644 --- a/Mage.Sets/src/mage/cards/c/CityscapeLeveler.java +++ b/Mage.Sets/src/mage/cards/c/CityscapeLeveler.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.UnearthAbility; @@ -21,7 +21,6 @@ import mage.target.common.TargetNonlandPermanent; import java.util.UUID; /** - * * @author weirddan455 */ public final class CityscapeLeveler extends CardImpl { @@ -38,7 +37,7 @@ public final class CityscapeLeveler extends CardImpl { // When you cast this spell and whenever Cityscape Leveler attacks, destroy up to one target nonland permanent. Its controller creates a tapped Powerstone token. Ability ability = new CityscapeLevelerAbility(); - ability.addEffect(new CreateTokenControllerTargetPermanentEffect(new PowerstoneToken(), 1, true)); + ability.addEffect(new CreateTokenControllerTargetEffect(new PowerstoneToken(), 1, true)); ability.addTarget(new TargetNonlandPermanent(0, 1)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/CommanderSofiaDaguerre.java b/Mage.Sets/src/mage/cards/c/CommanderSofiaDaguerre.java index c9068c50703..dab93d77ff8 100644 --- a/Mage.Sets/src/mage/cards/c/CommanderSofiaDaguerre.java +++ b/Mage.Sets/src/mage/cards/c/CommanderSofiaDaguerre.java @@ -5,7 +5,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.constants.SubType; import mage.constants.SuperType; @@ -43,7 +43,7 @@ public final class CommanderSofiaDaguerre extends CardImpl { // Crash Landing -- When Commander Sofia Daguerre enters the battlefield, // destroy up to one target legendary permanent. That permanent's controller creates a Junk token. Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); - ability.addEffect(new CreateTokenControllerTargetPermanentEffect(new JunkToken()) + ability.addEffect(new CreateTokenControllerTargetEffect(new JunkToken()) .setText("that permanent's controller creates a Junk token")); ability.addTarget(new TargetPermanent(0, 1, filter)); this.addAbility(ability.withFlavorWord("Crash Landing")); diff --git a/Mage.Sets/src/mage/cards/c/CribSwap.java b/Mage.Sets/src/mage/cards/c/CribSwap.java index 3c8bfe176c4..cb605543bd7 100644 --- a/Mage.Sets/src/mage/cards/c/CribSwap.java +++ b/Mage.Sets/src/mage/cards/c/CribSwap.java @@ -1,7 +1,7 @@ package mage.cards.c; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.keyword.ChangelingAbility; import mage.cards.CardImpl; @@ -14,7 +14,6 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author LevelX2 */ public final class CribSwap extends CardImpl { @@ -27,7 +26,7 @@ public final class CribSwap extends CardImpl { this.addAbility(new ChangelingAbility()); // Exile target creature. Its controller creates a 1/1 colorless Shapeshifter creature token with changeling. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new CribSwapShapeshifterWhiteToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new CribSwapShapeshifterWhiteToken())); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/g/GallowsAtWillowHill.java b/Mage.Sets/src/mage/cards/g/GallowsAtWillowHill.java index 68a6c79e46f..2b7f676589a 100644 --- a/Mage.Sets/src/mage/cards/g/GallowsAtWillowHill.java +++ b/Mage.Sets/src/mage/cards/g/GallowsAtWillowHill.java @@ -6,7 +6,7 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapTargetCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -38,7 +38,7 @@ public final class GallowsAtWillowHill extends CardImpl { // {3}, {tap}, Tap three untapped Humans you control: Destroy target creature. Its controller creates a 1/1 white Spirit creature token with flying. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new GenericManaCost(3)); - ability.addEffect(new CreateTokenControllerTargetPermanentEffect(new SpiritWhiteToken())); + ability.addEffect(new CreateTokenControllerTargetEffect(new SpiritWhiteToken())); ability.addCost(new TapSourceCost()); ability.addCost(new TapTargetCost(new TargetControlledPermanent(3, 3, humanFilter, false))); ability.addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/g/GenerousGift.java b/Mage.Sets/src/mage/cards/g/GenerousGift.java index 2cc56d5068b..61a6acec94a 100644 --- a/Mage.Sets/src/mage/cards/g/GenerousGift.java +++ b/Mage.Sets/src/mage/cards/g/GenerousGift.java @@ -1,6 +1,6 @@ package mage.cards.g; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -20,7 +20,7 @@ public final class GenerousGift extends CardImpl { // Destroy target permanent. Its controller creates a 3/3 green Elephant creature token. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new ElephantToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new ElephantToken())); this.getSpellAbility().addTarget(new TargetPermanent()); } diff --git a/Mage.Sets/src/mage/cards/g/GetLost.java b/Mage.Sets/src/mage/cards/g/GetLost.java index 35333bd2e07..220c5ea43b9 100644 --- a/Mage.Sets/src/mage/cards/g/GetLost.java +++ b/Mage.Sets/src/mage/cards/g/GetLost.java @@ -1,6 +1,6 @@ package mage.cards.g; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -32,7 +32,7 @@ public final class GetLost extends CardImpl { // Destroy target creature, enchantment, or planeswalker. Its controller creates two Map tokens. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new MapToken(), 2, false)); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new MapToken(), 2, false)); this.getSpellAbility().addTarget(new TargetPermanent(filter)); } diff --git a/Mage.Sets/src/mage/cards/p/Pongify.java b/Mage.Sets/src/mage/cards/p/Pongify.java index 0517107f8cd..c9f484c05a8 100644 --- a/Mage.Sets/src/mage/cards/p/Pongify.java +++ b/Mage.Sets/src/mage/cards/p/Pongify.java @@ -1,7 +1,7 @@ package mage.cards.p; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -12,7 +12,6 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author dustinconrad */ public final class Pongify extends CardImpl { @@ -22,7 +21,7 @@ public final class Pongify extends CardImpl { // Destroy target creature. It can't be regenerated. That creature's controller creates a 3/3 green Ape creature token. this.getSpellAbility().addEffect(new DestroyTargetEffect(true)); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new ApeToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new ApeToken())); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/r/Ravenform.java b/Mage.Sets/src/mage/cards/r/Ravenform.java index 65628c3c304..a2ab3415d5e 100644 --- a/Mage.Sets/src/mage/cards/r/Ravenform.java +++ b/Mage.Sets/src/mage/cards/r/Ravenform.java @@ -1,6 +1,6 @@ package mage.cards.r; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.keyword.ForetellAbility; import mage.cards.CardImpl; @@ -22,7 +22,7 @@ public final class Ravenform extends CardImpl { // Exile target artifact or creature. Its controller creates a 1/1 blue Bird creature token with flying. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new BlueBirdToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new BlueBirdToken())); this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); // Foretell {U} diff --git a/Mage.Sets/src/mage/cards/r/ReduceToMemory.java b/Mage.Sets/src/mage/cards/r/ReduceToMemory.java index 58e2c2222f9..8cf93966b55 100644 --- a/Mage.Sets/src/mage/cards/r/ReduceToMemory.java +++ b/Mage.Sets/src/mage/cards/r/ReduceToMemory.java @@ -1,6 +1,6 @@ package mage.cards.r; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -23,7 +23,7 @@ public final class ReduceToMemory extends CardImpl { // Exile target nonland permanent. Its controller creates a 3/2 red and white spirit creature token. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new Spirit32Token())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new Spirit32Token())); this.getSpellAbility().addTarget(new TargetNonlandPermanent()); } diff --git a/Mage.Sets/src/mage/cards/r/Resculpt.java b/Mage.Sets/src/mage/cards/r/Resculpt.java index bfe25cdf02d..8fbc42e76de 100644 --- a/Mage.Sets/src/mage/cards/r/Resculpt.java +++ b/Mage.Sets/src/mage/cards/r/Resculpt.java @@ -1,6 +1,6 @@ package mage.cards.r; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -21,7 +21,7 @@ public final class Resculpt extends CardImpl { // Exile target artifact or creature. Its controller creates a 4/4 blue and red Elemental creature token. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new Elemental44Token())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new Elemental44Token())); this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); } diff --git a/Mage.Sets/src/mage/cards/s/SawtuskDemolisher.java b/Mage.Sets/src/mage/cards/s/SawtuskDemolisher.java index e8c810d3d52..563da83624b 100644 --- a/Mage.Sets/src/mage/cards/s/SawtuskDemolisher.java +++ b/Mage.Sets/src/mage/cards/s/SawtuskDemolisher.java @@ -5,7 +5,7 @@ import mage.abilities.Ability; import mage.abilities.common.MutatesSourceTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.CreateTokenTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.MutateAbility; @@ -52,7 +52,7 @@ public final class SawtuskDemolisher extends CardImpl { // Whenever this creature mutates, destroy target noncreature permanent. Its controller creates a 3/3 green Beast creature token. Ability ability = new MutatesSourceTriggeredAbility(new DestroyTargetEffect()); - ability.addEffect(new CreateTokenControllerTargetPermanentEffect(new BeastToken())); + ability.addEffect(new CreateTokenControllerTargetEffect(new BeastToken())); ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SecureTheScene.java b/Mage.Sets/src/mage/cards/s/SecureTheScene.java index 3cd4fc6423d..8d8a570ae87 100644 --- a/Mage.Sets/src/mage/cards/s/SecureTheScene.java +++ b/Mage.Sets/src/mage/cards/s/SecureTheScene.java @@ -1,6 +1,6 @@ package mage.cards.s; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -20,7 +20,7 @@ public final class SecureTheScene extends CardImpl { // Exile target nonland permanent. Its controller creates a 1/1 white Soldier creature token. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new SoldierToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new SoldierToken())); this.getSpellAbility().addTarget(new TargetNonlandPermanent()); } diff --git a/Mage.Sets/src/mage/cards/s/StrixSerenade.java b/Mage.Sets/src/mage/cards/s/StrixSerenade.java new file mode 100644 index 00000000000..3749059f10d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StrixSerenade.java @@ -0,0 +1,49 @@ +package mage.cards.s; + +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.game.permanent.token.SwanSongBirdToken; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class StrixSerenade extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("artifact, creature, or planeswalker spell"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate(), + CardType.PLANESWALKER.getPredicate() + )); + } + + public StrixSerenade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // Counter target artifact, creature, or planeswalker spell. Its controller creates a 2/2 blue Bird creature token with flying. + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect( + new SwanSongBirdToken(), CreateTokenControllerTargetEffect.TargetKind.SPELL + )); + this.getSpellAbility().addTarget(new TargetSpell(filter)); + } + + private StrixSerenade(final StrixSerenade card) { + super(card); + } + + @Override + public StrixSerenade copy() { + return new StrixSerenade(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StrokeOfMidnight.java b/Mage.Sets/src/mage/cards/s/StrokeOfMidnight.java index d7990ac218c..84995a90a3d 100644 --- a/Mage.Sets/src/mage/cards/s/StrokeOfMidnight.java +++ b/Mage.Sets/src/mage/cards/s/StrokeOfMidnight.java @@ -1,6 +1,6 @@ package mage.cards.s; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -21,7 +21,7 @@ public final class StrokeOfMidnight extends CardImpl { // Destroy target nonland permanent. Its controller creates a 1/1 white Human creature token. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new HumanToken())); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new HumanToken())); this.getSpellAbility().addTarget(new TargetNonlandPermanent()); } diff --git a/Mage.Sets/src/mage/cards/s/SwanSong.java b/Mage.Sets/src/mage/cards/s/SwanSong.java index cd1bfd569d3..0f8c71b9eef 100644 --- a/Mage.Sets/src/mage/cards/s/SwanSong.java +++ b/Mage.Sets/src/mage/cards/s/SwanSong.java @@ -1,23 +1,19 @@ package mage.cards.s; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.filter.FilterSpell; import mage.filter.predicate.Predicates; -import mage.game.Game; import mage.game.permanent.token.SwanSongBirdToken; -import mage.game.permanent.token.Token; -import mage.game.stack.Spell; import mage.target.TargetSpell; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class SwanSong extends CardImpl { @@ -25,16 +21,21 @@ public final class SwanSong extends CardImpl { private static final FilterSpell filter = new FilterSpell("enchantment, instant, or sorcery spell"); static { - filter.add(Predicates.or(CardType.ENCHANTMENT.getPredicate(), + filter.add(Predicates.or( + CardType.ENCHANTMENT.getPredicate(), CardType.INSTANT.getPredicate(), - CardType.SORCERY.getPredicate())); + CardType.SORCERY.getPredicate() + )); } public SwanSong(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); // Counter target enchantment, instant or sorcery spell. Its controller creates a 2/2 blue Bird creature token with flying. - this.getSpellAbility().addEffect(new SwanSongEffect()); + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect( + new SwanSongBirdToken(), CreateTokenControllerTargetEffect.TargetKind.SPELL + )); this.getSpellAbility().addTarget(new TargetSpell(filter)); } @@ -46,37 +47,4 @@ public final class SwanSong extends CardImpl { public SwanSong copy() { return new SwanSong(this); } -} - -class SwanSongEffect extends OneShotEffect { - - SwanSongEffect() { - super(Outcome.Benefit); - this.staticText = "Counter target enchantment, instant, or sorcery spell. Its controller creates a 2/2 blue Bird creature token with flying"; - } - - private SwanSongEffect(final SwanSongEffect effect) { - super(effect); - } - - @Override - public SwanSongEffect copy() { - return new SwanSongEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - boolean countered = false; - for (UUID targetId : getTargetPointer().getTargets(game, source)) { - Spell spell = game.getStack().getSpell(targetId); - if (game.getStack().counter(targetId, source, game)) { - countered = true; - } - if (spell != null) { - Token token = new SwanSongBirdToken(); - token.putOntoBattlefield(1, game, source, spell.getControllerId()); - } - } - return countered; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TransmogrifyingWand.java b/Mage.Sets/src/mage/cards/t/TransmogrifyingWand.java index d3e045ff22f..a8765f90ee9 100644 --- a/Mage.Sets/src/mage/cards/t/TransmogrifyingWand.java +++ b/Mage.Sets/src/mage/cards/t/TransmogrifyingWand.java @@ -6,7 +6,7 @@ import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; @@ -20,7 +20,6 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author TheElk801 */ public final class TransmogrifyingWand extends CardImpl { @@ -40,7 +39,7 @@ public final class TransmogrifyingWand extends CardImpl { new DestroyTargetEffect(), new GenericManaCost(1) ); - ability.addEffect(new CreateTokenControllerTargetPermanentEffect(new OxToken())); + ability.addEffect(new CreateTokenControllerTargetEffect(new OxToken())); ability.addCost(new TapSourceCost()); ability.addCost(new RemoveCountersSourceCost(CounterType.CHARGE.createInstance())); ability.addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/u/UnyieldingGatekeeper.java b/Mage.Sets/src/mage/cards/u/UnyieldingGatekeeper.java index 139dc896374..6cff9a562a6 100644 --- a/Mage.Sets/src/mage/cards/u/UnyieldingGatekeeper.java +++ b/Mage.Sets/src/mage/cards/u/UnyieldingGatekeeper.java @@ -6,7 +6,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.ExileThenReturnTargetEffect; import mage.constants.Outcome; @@ -89,7 +89,7 @@ class UnyieldingGatekeeperEffect extends OneShotEffect { false, false, PutCards.BATTLEFIELD_TAPPED).apply(game, source); } else { new ExileTargetEffect().apply(game, source); - new CreateTokenControllerTargetPermanentEffect(new DetectiveToken()).apply(game, source); + new CreateTokenControllerTargetEffect(new DetectiveToken()).apply(game, source); } return true; } diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index c13f2da9134..410c1d3970a 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -102,6 +102,7 @@ public final class ModernHorizons3 extends ExpansionSet { cards.add(new SetCardInfo("Sorin, Ravenous Neonate", 245, Rarity.MYTHIC, mage.cards.s.SorinRavenousNeonate.class)); cards.add(new SetCardInfo("Spawn-Gang Commander", 140, Rarity.UNCOMMON, mage.cards.s.SpawnGangCommander.class)); cards.add(new SetCardInfo("Strength of the Harvest", 258, Rarity.UNCOMMON, mage.cards.s.StrengthOfTheHarvest.class)); + cards.add(new SetCardInfo("Strix Serenade", 71, Rarity.RARE, mage.cards.s.StrixSerenade.class)); cards.add(new SetCardInfo("Stump Stomp", 259, Rarity.UNCOMMON, mage.cards.s.StumpStomp.class)); cards.add(new SetCardInfo("Suppression Ray", 260, Rarity.UNCOMMON, mage.cards.s.SuppressionRay.class)); cards.add(new SetCardInfo("Swamp", 306, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/StrixSerenadeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/StrixSerenadeTest.java new file mode 100644 index 00000000000..54205fe4ff7 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/StrixSerenadeTest.java @@ -0,0 +1,74 @@ +package org.mage.test.cards.single.mh3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class StrixSerenadeTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.StrixSerenade Strix Serenade} {U} + * Instant + * Counter target artifact, creature, or planeswalker spell. Its controller creates a 2/2 blue Bird creature token with flying. + */ + private static final String serenade = "Strix Serenade"; + + @Test + public void test_Simple() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.HAND, playerA, serenade); + addCard(Zone.HAND, playerB, "Mox Ruby"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Mox Ruby"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, serenade, "Mox Ruby", "Mox Ruby"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Mox Ruby", 1); + assertPermanentCount(playerB, "Bird Token", 1); + } + + @Test + public void test_Simple_OwnSpellTargetted() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.HAND, playerA, serenade); + addCard(Zone.HAND, playerA, "Mox Ruby"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mox Ruby"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, serenade, "Mox Ruby", "Mox Ruby"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Mox Ruby", 1); + assertPermanentCount(playerA, "Bird Token", 1); + } + + @Test + public void test_CantBeCountered_CreateToken() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.HAND, playerA, serenade); + addCard(Zone.HAND, playerB, "Chandra, Awakened Inferno"); // Can't be countered + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 6); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Chandra, Awakened Inferno"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, serenade, "Chandra, Awakened Inferno", "Chandra, Awakened Inferno"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerB, "Chandra, Awakened Inferno", 1); + assertPermanentCount(playerB, "Bird Token", 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenControllerTargetPermanentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenControllerTargetEffect.java similarity index 56% rename from Mage/src/main/java/mage/abilities/effects/common/CreateTokenControllerTargetPermanentEffect.java rename to Mage/src/main/java/mage/abilities/effects/common/CreateTokenControllerTargetEffect.java index 1aa42c8411d..78953e426b8 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenControllerTargetPermanentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenControllerTargetEffect.java @@ -5,8 +5,8 @@ import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.game.Controllable; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; import mage.players.Player; import mage.util.CardUtil; @@ -14,46 +14,78 @@ import mage.util.CardUtil; /** * @author Susucr *
- * Have the Controller of target permanent (or LKI controller) create Tokens. + * Have the Controller of target object (permanent, or spell) create Tokens. */ -public class CreateTokenControllerTargetPermanentEffect extends OneShotEffect { +public class CreateTokenControllerTargetEffect extends OneShotEffect { private final Token token; private final DynamicValue amount; private final boolean tapped; - public CreateTokenControllerTargetPermanentEffect(Token token) { + /** + * What the target is supposed to be, to retrieve its controller from + */ + public enum TargetKind { + PERMANENT, + SPELL + } + + private final TargetKind targetKind; + + public CreateTokenControllerTargetEffect(Token token) { this(token, 1, false); } - public CreateTokenControllerTargetPermanentEffect(Token token, int amount, boolean tapped) { + public CreateTokenControllerTargetEffect(Token token, TargetKind targetKind) { + this(token, 1, false, targetKind); + } + + public CreateTokenControllerTargetEffect(Token token, int amount, boolean tapped) { this(token, StaticValue.get(amount), tapped); } - public CreateTokenControllerTargetPermanentEffect(Token token, DynamicValue amount, boolean tapped) { + public CreateTokenControllerTargetEffect(Token token, int amount, boolean tapped, TargetKind targetKind) { + this(token, StaticValue.get(amount), tapped, targetKind); + } + + public CreateTokenControllerTargetEffect(Token token, DynamicValue amount, boolean tapped) { + this(token, amount, tapped, TargetKind.PERMANENT); + } + + public CreateTokenControllerTargetEffect(Token token, DynamicValue amount, boolean tapped, TargetKind targetKind) { super(Outcome.Neutral); this.token = token; this.amount = amount.copy(); this.tapped = tapped; this.staticText = makeText(); + this.targetKind = targetKind; } - protected CreateTokenControllerTargetPermanentEffect(final CreateTokenControllerTargetPermanentEffect effect) { + protected CreateTokenControllerTargetEffect(final CreateTokenControllerTargetEffect effect) { super(effect); this.token = effect.token.copy(); this.amount = effect.amount.copy(); this.tapped = effect.tapped; + this.targetKind = effect.targetKind; } @Override - public CreateTokenControllerTargetPermanentEffect copy() { - return new CreateTokenControllerTargetPermanentEffect(this); + public CreateTokenControllerTargetEffect copy() { + return new CreateTokenControllerTargetEffect(this); } @Override public boolean apply(Game game, Ability source) { - Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); - if (permanent != null) { - Player controllerOfTarget = game.getPlayer(permanent.getControllerId()); + Controllable controllable = null; + switch (targetKind) { + case PERMANENT: + controllable = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); + break; + case SPELL: + controllable = game.getSpellOrLKIStack(getTargetPointer().getFirst(game, source)); + break; + } + if (controllable != null) { + Player controllerOfTarget = game.getPlayer(controllable.getControllerId()); if (controllerOfTarget != null) { int value = amount.calculate(game, source, this); return token.putOntoBattlefield(value, game, source, controllerOfTarget.getId(), tapped, false);