From b233fcf4d8b9f33c49241bfc9338f2c40fd2a910 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sat, 6 Apr 2024 17:37:32 +0200 Subject: [PATCH] Consolidate, cleanup & test a few MayCastTargetCardEffect cards --- .../mage/cards/c/ChancellorOfTheSpires.java | 5 +- .../mage/cards/g/GaleWaterdeepProdigy.java | 101 ++---------------- Mage.Sets/src/mage/cards/g/Galvanoth.java | 30 +++--- Mage.Sets/src/mage/cards/m/MemoryPlunder.java | 54 ++-------- .../src/mage/cards/v/VadrokApexOfThunder.java | 9 +- .../single/clb/GaleWaterdeepProdigyTest.java | 8 +- .../test/cards/single/mbs/GalvanothTest.java | 71 ++++++++++++ .../cards/single/shm/MemoryPlunderTest.java | 57 ++++++++++ .../common/CastTargetForFreeEffect.java | 52 --------- 9 files changed, 173 insertions(+), 214 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/mbs/GalvanothTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/shm/MemoryPlunderTest.java delete mode 100644 Mage/src/main/java/mage/abilities/effects/common/CastTargetForFreeEffect.java diff --git a/Mage.Sets/src/mage/cards/c/ChancellorOfTheSpires.java b/Mage.Sets/src/mage/cards/c/ChancellorOfTheSpires.java index b31654fa237..dbb4de9e61a 100644 --- a/Mage.Sets/src/mage/cards/c/ChancellorOfTheSpires.java +++ b/Mage.Sets/src/mage/cards/c/ChancellorOfTheSpires.java @@ -5,12 +5,13 @@ import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.ChancellorAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.common.CastTargetForFreeEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.MillCardsEachPlayerEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.SubType; import mage.constants.TargetController; import mage.filter.FilterCard; @@ -50,7 +51,7 @@ public final class ChancellorOfTheSpires extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Chancellor of the Spires enters the battlefield, you may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost. - Ability ability = new EntersBattlefieldTriggeredAbility(new CastTargetForFreeEffect(), true); + Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST)); ability.addTarget(new TargetCardInOpponentsGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java b/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java index d384dad0993..c7a50440a70 100644 --- a/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java +++ b/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java @@ -1,26 +1,20 @@ package mage.cards.g; -import mage.ApprovingObject; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.ChooseABackgroundAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.common.FilterInstantOrSorcerySpell; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; import mage.game.stack.Spell; -import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.targetpointer.FixedTarget; import mage.watchers.common.CastFromHandWatcher; import java.util.UUID; @@ -69,9 +63,13 @@ class GaleWaterdeepProdigyTriggeredAbility extends SpellCastControllerTriggeredA } public GaleWaterdeepProdigyTriggeredAbility() { - super(new GaleWaterdeepProdigyEffect(), + super( + new MayCastTargetCardEffect(true) + .setText("you may cast up to one target card of the other type from your graveyard. " + + "If a spell cast from your graveyard this way would be put into your graveyard, exile it instead."), new FilterInstantOrSorcerySpell("an instant or sorcery spell from your hand"), - false); + false + ); addWatcher(new CastFromHandWatcher()); } @@ -110,81 +108,4 @@ class GaleWaterdeepProdigyTriggeredAbility extends SpellCastControllerTriggeredA public GaleWaterdeepProdigyTriggeredAbility copy() { return new GaleWaterdeepProdigyTriggeredAbility(this); } -} - -class GaleWaterdeepProdigyEffect extends OneShotEffect { - - GaleWaterdeepProdigyEffect() { - super(Outcome.PutCardInPlay); - this.staticText = "you may cast up to one of the other type from your graveyard. " + - "If a spell cast from your graveyard this way would be put into your graveyard, exile it instead."; - } - - private GaleWaterdeepProdigyEffect(final GaleWaterdeepProdigyEffect effect) { - super(effect); - } - - @Override - public GaleWaterdeepProdigyEffect copy() { - return new GaleWaterdeepProdigyEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); - if (card != null - && controller.chooseUse(Outcome.Neutral, "Cast " + card.getLogName() + '?', source, game)) { - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - controller.cast(controller.chooseAbilityForCast(card, game, false), - game, false, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - ContinuousEffect effect = new GaleWaterdeepProdigyReplacementEffect(card.getId()); - effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId()))); - game.addEffect(effect, source); - } - return true; - } -} - -class GaleWaterdeepProdigyReplacementEffect extends ReplacementEffectImpl { - - private final UUID cardId; - - GaleWaterdeepProdigyReplacementEffect(UUID cardId) { - super(Duration.EndOfTurn, Outcome.Exile); - this.cardId = cardId; - staticText = "If a spell cast from your graveyard this way would be put into your graveyard, exile it instead."; - } - - private GaleWaterdeepProdigyReplacementEffect(final GaleWaterdeepProdigyReplacementEffect effect) { - super(effect); - this.cardId = effect.cardId; - } - - @Override - public GaleWaterdeepProdigyReplacementEffect copy() { - return new GaleWaterdeepProdigyReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - return zEvent.getToZone() == Zone.GRAVEYARD - && zEvent.getTargetId().equals(this.cardId); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/Galvanoth.java b/Mage.Sets/src/mage/cards/g/Galvanoth.java index 4524cf1fa8e..521576041cc 100644 --- a/Mage.Sets/src/mage/cards/g/Galvanoth.java +++ b/Mage.Sets/src/mage/cards/g/Galvanoth.java @@ -1,18 +1,20 @@ package mage.cards.g; -import java.util.UUID; -import mage.ApprovingObject; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.cards.*; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.TargetController; +import mage.abilities.effects.common.MayCastTargetCardEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; import mage.game.Game; import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** * @author North @@ -45,8 +47,9 @@ class GalvanothEffect extends OneShotEffect { GalvanothEffect() { super(Outcome.PlayForFree); - staticText = "look at the top card of your library. If it's an instant or " - + "sorcery card, you may cast it without paying its mana cost"; + staticText = "look at the top card of your library. " + + "You may cast it without paying its mana cost " + + "if it's an instant or sorcery spell"; } private GalvanothEffect(final GalvanothEffect effect) { @@ -61,12 +64,9 @@ class GalvanothEffect extends OneShotEffect { if (card != null) { controller.lookAtCards(source, null, new CardsImpl(card), game); if (card.isInstantOrSorcery(game)) { - if (controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getName() + " without paying its mana cost?", source, game)) { - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - controller.cast(controller.chooseAbilityForCast(card, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - } + new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST) + .setTargetPointer(new FixedTarget(card.getId())) + .apply(game, source); } } return true; diff --git a/Mage.Sets/src/mage/cards/m/MemoryPlunder.java b/Mage.Sets/src/mage/cards/m/MemoryPlunder.java index a7760a9dd87..5a5e67f686e 100644 --- a/Mage.Sets/src/mage/cards/m/MemoryPlunder.java +++ b/Mage.Sets/src/mage/cards/m/MemoryPlunder.java @@ -1,23 +1,17 @@ package mage.cards.m; -import java.util.UUID; -import mage.ApprovingObject; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.CastManaAdjustment; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCardInOpponentsGraveyard; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class MemoryPlunder extends CardImpl { @@ -34,7 +28,7 @@ public final class MemoryPlunder extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U/B}{U/B}{U/B}{U/B}"); // You may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost. - this.getSpellAbility().addEffect(new MemoryPlunderEffect()); + this.getSpellAbility().addEffect(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST)); this.getSpellAbility().addTarget(new TargetCardInOpponentsGraveyard(filter)); } @@ -47,40 +41,4 @@ public final class MemoryPlunder extends CardImpl { public MemoryPlunder copy() { return new MemoryPlunder(this); } -} - -class MemoryPlunderEffect extends OneShotEffect { - - MemoryPlunderEffect() { - super(Outcome.PlayForFree); - this.staticText = "You may cast target instant or sorcery card from " - + "an opponent's graveyard without paying its mana cost"; - } - - private MemoryPlunderEffect(final MemoryPlunderEffect effect) { - super(effect); - } - - @Override - public MemoryPlunderEffect copy() { - return new MemoryPlunderEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Card card = game.getCard(getTargetPointer().getFirst(game, source)); - if (card != null) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null - && game.getState().getZone(card.getId()) == Zone.GRAVEYARD - && controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getName() + " without paying cost?", source, game)) { - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - Boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - return cardWasCast; - } - } - return false; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/v/VadrokApexOfThunder.java b/Mage.Sets/src/mage/cards/v/VadrokApexOfThunder.java index 74832017430..8983c563218 100644 --- a/Mage.Sets/src/mage/cards/v/VadrokApexOfThunder.java +++ b/Mage.Sets/src/mage/cards/v/VadrokApexOfThunder.java @@ -3,16 +3,13 @@ package mage.cards.v; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.MutatesSourceTriggeredAbility; -import mage.abilities.effects.common.CastTargetForFreeEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.MutateAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.SubType; -import mage.constants.SuperType; +import mage.constants.*; import mage.filter.FilterCard; import mage.filter.common.FilterNoncreatureCard; import mage.filter.predicate.mageobject.ManaValuePredicate; @@ -53,7 +50,7 @@ public final class VadrokApexOfThunder extends CardImpl { this.addAbility(FirstStrikeAbility.getInstance()); // Whenever this creature mutates, you may cast target noncreature card with converted mana cost 3 or less from your graveyard without paying its mana cost. - Ability ability = new MutatesSourceTriggeredAbility(new CastTargetForFreeEffect()); + Ability ability = new MutatesSourceTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST)); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java index 3991a19fdd0..9b346084975 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java @@ -6,12 +6,18 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** + * {@link mage.cards.g.GaleWaterdeepProdigy Gale, Waterdeep Prodigy} {2}{U} + * Legendary Creature — Human Wizard + * Whenever you cast an instant or sorcery spell from your hand, you may cast up to one target card of the other type from your graveyard. If a spell cast from your graveyard this way would be put into your graveyard, exile it instead. + * Choose a Background (You can have a Background as a second commander.) + * 1/3 + * * @author Rjayz */ public class GaleWaterdeepProdigyTest extends CardTestPlayerBase { @Test - public void TestGaleWaterDeepProdigy() { + public void test_GaleWaterDeepProdigy() { addCard(Zone.BATTLEFIELD, playerA, "Island", 10); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mbs/GalvanothTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mbs/GalvanothTest.java new file mode 100644 index 00000000000..303a72339a0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mbs/GalvanothTest.java @@ -0,0 +1,71 @@ +package org.mage.test.cards.single.mbs; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class GalvanothTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.g.Galvanoth Galvanoth} {3}{R}{R} + * Creature — Beast + * At the beginning of your upkeep, you may look at the top card of your library. You may cast it without paying its mana cost if it’s an instant or sorcery spell. + * 3/3 + */ + private static final String galvanoth = "Galvanoth"; + + @Test + public void test_Divination_Cast() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Divination"); + addCard(Zone.BATTLEFIELD, playerA, galvanoth); + + setChoice(playerA, true); // yes to look + setChoice(playerA, true); // yes to cast + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, "Divination", 1); + assertHandCount(playerA, 2); + } + + @Test + public void test_Divination_No_Cast() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Divination"); + addCard(Zone.BATTLEFIELD, playerA, galvanoth); + + setChoice(playerA, true); // yes to look + setChoice(playerA, false); // no to cast + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 0); + } + + @Test + public void test_Creature_NotCastable() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Goblin Piker"); + addCard(Zone.BATTLEFIELD, playerA, galvanoth); + + setChoice(playerA, true); // yes to look + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, galvanoth, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/MemoryPlunderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/MemoryPlunderTest.java new file mode 100644 index 00000000000..798fe998e22 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/MemoryPlunderTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.shm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class MemoryPlunderTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.m.MemoryPlunder Memory Plunder} {U/B}{U/B}{U/B}{U/B} + * Instant + * You may cast target instant or sorcery card from an opponent’s graveyard without paying its mana cost. + */ + private static final String plunder = "Memory Plunder"; + + @Test + public void test_Divination_Cast() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerB, "Divination"); + addCard(Zone.HAND, playerA, plunder); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.UPKEEP, playerA, plunder, "Divination"); + setChoice(playerA, true); // yes to cast + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, plunder, 1); + assertGraveyardCount(playerB, "Divination", 1); // back in graveyard + assertHandCount(playerA, 2); + } + + @Test + public void test_Divination_No_Cast() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerB, "Divination"); + addCard(Zone.HAND, playerA, plunder); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.UPKEEP, playerA, plunder, "Divination"); + setChoice(playerA, false); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, plunder, 1); + assertGraveyardCount(playerB, "Divination", 1); // not moved + assertHandCount(playerA, 0); // no cast so no draw + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/CastTargetForFreeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CastTargetForFreeEffect.java deleted file mode 100644 index 09183a99989..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/CastTargetForFreeEffect.java +++ /dev/null @@ -1,52 +0,0 @@ -package mage.abilities.effects.common; - -import mage.ApprovingObject; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.constants.Outcome; -import mage.game.Game; -import mage.players.Player; - -/** - * @author BetaSteward_at_googlemail.com - */ -public class CastTargetForFreeEffect extends OneShotEffect { - - public CastTargetForFreeEffect() { - super(Outcome.Benefit); - } - - protected CastTargetForFreeEffect(final CastTargetForFreeEffect effect) { - super(effect); - } - - @Override - public CastTargetForFreeEffect copy() { - return new CastTargetForFreeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Card target = (Card) game.getObject(source.getFirstTarget()); - if (controller != null && target != null) { - game.getState().setValue("PlayFromNotOwnHandZone" + target.getId(), Boolean.TRUE); - boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(target, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + target.getId(), null); - return cardWasCast; - } - return false; - } - - @Override - public String getText(Mode mode) { - if (staticText != null && !staticText.isEmpty()) { - return staticText; - } - return "you may cast " + getTargetPointer().describeTargets(mode.getTargets(), "that card") - + " without paying its mana cost"; - } -}