From 3c3ba6f5a9e6a6d4898d1bfad0672186e1dc65c8 Mon Sep 17 00:00:00 2001 From: RJayz Date: Wed, 31 Aug 2022 03:53:33 +0200 Subject: [PATCH] [CLB] Implemented Gale, Waterdeep Prodigy (#9423) --- .../mage/cards/g/GaleWaterdeepProdigy.java | 190 ++++++++++++++++++ .../CommanderLegendsBattleForBaldursGate.java | 1 + .../single/clb/GaleWaterdeepProdigyTest.java | 61 ++++++ 3 files changed, 252 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java diff --git a/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java b/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java new file mode 100644 index 0000000000..dbeb32e7c7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java @@ -0,0 +1,190 @@ +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.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +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; + +/** + * @author Rjayz + */ +public final class GaleWaterdeepProdigy extends CardImpl { + + public GaleWaterdeepProdigy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Whenever you cast an instant or sorcery spell from your hand, + // 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. + this.addAbility(new GaleWaterdeepProdigyTriggeredAbility()); + + // Choose a Background + this.addAbility(ChooseABackgroundAbility.getInstance()); + } + + private GaleWaterdeepProdigy(final GaleWaterdeepProdigy card) { + super(card); + } + + @Override + public GaleWaterdeepProdigy copy() { + return new GaleWaterdeepProdigy(this); + } +} + +class GaleWaterdeepProdigyTriggeredAbility extends SpellCastControllerTriggeredAbility { + + private static final FilterCard SORCERY_FILTER = new FilterCard("a sorcery card in your graveyard"); + private static final FilterCard INSTANT_FILTER = new FilterCard("an instant card in your graveyard"); + + static { + SORCERY_FILTER.add(CardType.SORCERY.getPredicate()); + INSTANT_FILTER.add(CardType.INSTANT.getPredicate()); + } + + public GaleWaterdeepProdigyTriggeredAbility() { + super(new GaleWaterdeepProdigyEffect(), + new FilterInstantOrSorcerySpell("an instant or sorcery spell from your hand"), + false); + addWatcher(new CastFromHandWatcher()); + } + + public GaleWaterdeepProdigyTriggeredAbility(GaleWaterdeepProdigyTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!super.checkTrigger(event, game)) { + return false; + } + + CastFromHandWatcher watcher = game.getState().getWatcher(CastFromHandWatcher.class); + if (watcher == null || !watcher.spellWasCastFromHand(event.getSourceId())) { + return false; + } + + Spell spell = game.getState().getStack().getSpell(event.getSourceId()); + if (spell == null) { + return false; + } + + FilterCard filterCard; + if (spell.isSorcery()) { + filterCard = INSTANT_FILTER; + } else { + filterCard = SORCERY_FILTER; + } + this.getTargets().clear(); + this.getTargets().add(new TargetCardInYourGraveyard(filterCard)); + return true; + } + + @Override + 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); + } +} diff --git a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java index d3f7475d1b..03b15e431f 100644 --- a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java +++ b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java @@ -250,6 +250,7 @@ public final class CommanderLegendsBattleForBaldursGate extends ExpansionSet { cards.add(new SetCardInfo("Fraying Line", 314, Rarity.RARE, mage.cards.f.FrayingLine.class)); cards.add(new SetCardInfo("From the Catacombs", 671, Rarity.RARE, mage.cards.f.FromTheCatacombs.class)); cards.add(new SetCardInfo("Frontline Medic", 693, Rarity.RARE, mage.cards.f.FrontlineMedic.class)); + cards.add(new SetCardInfo("Gale, Waterdeep Prodigy", 72, Rarity.RARE, mage.cards.g.GaleWaterdeepProdigy.class)); cards.add(new SetCardInfo("Gale's Redirection", 73, Rarity.RARE, mage.cards.g.GalesRedirection.class)); cards.add(new SetCardInfo("Galepowder Mage", 694, Rarity.RARE, mage.cards.g.GalepowderMage.class)); cards.add(new SetCardInfo("Game Trail", 894, Rarity.RARE, mage.cards.g.GameTrail.class)); 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 new file mode 100644 index 0000000000..3991a19fdd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java @@ -0,0 +1,61 @@ +package org.mage.test.cards.single.clb; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Rjayz + */ +public class GaleWaterdeepProdigyTest extends CardTestPlayerBase { + + @Test + public void TestGaleWaterDeepProdigy() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 10); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); + + // Whenever you cast an instant or sorcery spell from your hand, 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. + addCard(Zone.BATTLEFIELD, playerA, "Gale, Waterdeep Prodigy", 1); + + // Draw two cards, sorcery + addCard(Zone.HAND, playerA, "Divination"); + // Deal three damage to any target, instant + addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt"); + + playerA.getLibrary().clear(); + addCard(Zone.LIBRARY, playerA, "Island"); + addCard(Zone.LIBRARY, playerA, "Island"); + skipInitShuffling(); + + setStrictChooseMode(true); + + // Cast Divination from hand, + // this will trigger Gale's ability and let playerA cast an instant from their graveyard + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Divination", true); + // Target Lightning Bolt in graveyard with Gale's ability + addTarget(playerA, "Lightning Bolt"); + // Choose to cast Lightning Bolt when prompted + setChoice(playerA, true); + // Target opponent with Lightning bolt + addTarget(playerA, playerB); + + execute(); + + // Assert Divination was cast from hand + assertHandCount(playerA, 2); + assertHandCount(playerA, "Island", 2); + assertLibraryCount(playerA, 0); + assertGraveyardCount(playerA, 1); + assertGraveyardCount(playerA, "Divination", 1); + + // Assert Lightning Bolt was cast from graveyard, + // and afterwards exiled instead of being put back into the graveyard + assertExileCount(playerA, 1); + assertExileCount(playerA, "Lightning Bolt", 1); + + // Assert opponent was targeted by Lightning Bolt + assertLife(playerB, 17); + } +}