From db5dc897764b6d17d6a76730d7a665c94026faa3 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:46:34 +0200 Subject: [PATCH] [OTC] Implement Smoldering Stagecoach --- .../mage/cards/s/SmolderingStagecoach.java | 71 +++++++++++++++++++ .../OutlawsOfThunderJunctionCommander.java | 1 + .../single/otc/SmolderingStagecoachTest.java | 52 ++++++++++++++ .../NextSpellCastHasAbilityEffect.java | 23 +++--- 4 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/SmolderingStagecoach.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmolderingStagecoachTest.java diff --git a/Mage.Sets/src/mage/cards/s/SmolderingStagecoach.java b/Mage.Sets/src/mage/cards/s/SmolderingStagecoach.java new file mode 100644 index 00000000000..95b301359af --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SmolderingStagecoach.java @@ -0,0 +1,71 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.continuous.NextSpellCastHasAbilityEffect; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.CascadeAbility; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SmolderingStagecoach extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY); + private static final Hint hint = new ValueHint("Number of instant and sorcery cards in your graveyard", xValue); + + private static final FilterCard filterInstant = new FilterCard("instant spell"); + private static final FilterCard filterSorcery = new FilterCard("sorcery spell"); + + static { + filterInstant.add(CardType.INSTANT.getPredicate()); + filterSorcery.add(CardType.SORCERY.getPredicate()); + } + + public SmolderingStagecoach(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{R}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(0); + this.toughness = new MageInt(5); + + // Smoldering Stagecoach's power is equal to the number of instant and sorcery cards in your graveyard. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(xValue)).addHint(hint)); + + // Whenever Smoldering Stagecoach attacks, the next instant spell and the next sorcery spell you cast this turn each have cascade. + Ability ability = new AttacksTriggeredAbility(null); + ability.addEffect(new NextSpellCastHasAbilityEffect(new CascadeAbility(), filterInstant) + .setText("the next instant spell")); + ability.addEffect(new NextSpellCastHasAbilityEffect(new CascadeAbility(), filterSorcery) + .setText("and the next sorcery spell you cast this turn each have cascade")); + this.addAbility(ability); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + } + + private SmolderingStagecoach(final SmolderingStagecoach card) { + super(card); + } + + @Override + public SmolderingStagecoach copy() { + return new SmolderingStagecoach(this); + } +} diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java index 2e10cd9189e..ef935b9ea8e 100644 --- a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java @@ -247,6 +247,7 @@ public final class OutlawsOfThunderJunctionCommander extends ExpansionSet { cards.add(new SetCardInfo("Slither Blade", 114, Rarity.COMMON, mage.cards.s.SlitherBlade.class)); cards.add(new SetCardInfo("Smirking Spelljacker", 16, Rarity.RARE, mage.cards.s.SmirkingSpelljacker.class)); cards.add(new SetCardInfo("Smoldering Marsh", 321, Rarity.RARE, mage.cards.s.SmolderingMarsh.class)); + cards.add(new SetCardInfo("Smoldering Stagecoach", 30, Rarity.RARE, mage.cards.s.SmolderingStagecoach.class)); cards.add(new SetCardInfo("Sol Ring", 267, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); cards.add(new SetCardInfo("Springbloom Druid", 208, Rarity.COMMON, mage.cards.s.SpringbloomDruid.class)); cards.add(new SetCardInfo("Stella Lee, Wild Card", 3, Rarity.MYTHIC, mage.cards.s.StellaLeeWildCard.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmolderingStagecoachTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmolderingStagecoachTest.java new file mode 100644 index 00000000000..b50cb773d5a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmolderingStagecoachTest.java @@ -0,0 +1,52 @@ +package org.mage.test.cards.single.otc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SmolderingStagecoachTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SmolderingStagecoach Smoldering Stagecoach} {3}{R} + * Artifact — Vehicle + * Smoldering Stagecoach’s power is equal to the number of instant and sorcery cards in your graveyard. + * Whenever Smoldering Stagecoach attacks, the next instant spell and the next sorcery spell you cast this turn each have cascade. + * Crew 2 + * 3/3 + */ + private static final String stagecoach = "Smoldering Stagecoach"; + + @Test + public void test_Cascade_Into_Cascade() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, stagecoach); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard"); // 2 power for crew + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7); + addCard(Zone.HAND, playerA, "Absorb Vis"); // {6}{B} Sorcery - Target player loses 4 life and you gain 4 life. + + addCard(Zone.LIBRARY, playerA, "Raging Goblin"); // {R}, won't be cascaded into + addCard(Zone.LIBRARY, playerA, "Goblin Piker"); // {1}{R}, will be be cascaded into by Mercy + addCard(Zone.LIBRARY, playerA, "Angel's Mercy"); // {2}{W}{W} Instant - You gain 7 life, will be cascaded into by Absorb Vis + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Crew"); + setChoice(playerA, "Elite Vanguard"); // for crew + attack(1, playerA, stagecoach, playerB); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Absorb Vis", playerB); + setChoice(playerA, true, 2); // yes to both cascade trigger + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Goblin Piker", 1); + assertPermanentCount(playerA, "Raging Goblin", 0); + assertLife(playerB, 20 - 4); + assertLife(playerA, 20 + 4 + 7); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/NextSpellCastHasAbilityEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/NextSpellCastHasAbilityEffect.java index 88f02aaff83..df157da49ef 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/NextSpellCastHasAbilityEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/NextSpellCastHasAbilityEffect.java @@ -68,7 +68,7 @@ public class NextSpellCastHasAbilityEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { UUID playerId; - switch (targetController){ + switch (targetController) { case SOURCE_TARGETS: playerId = source.getFirstTarget(); break; @@ -83,35 +83,40 @@ public class NextSpellCastHasAbilityEffect extends ContinuousEffectImpl { if (player == null || watcher == null) { return false; } - //check if a spell was cast before - if (watcher.getCount(playerId) > spellsCast) { + //check if a spell matching the filter was cast before + if (watcher + .getSpellsCastThisTurn(playerId) + .stream() + .skip(spellsCast) + .anyMatch(s -> filter.match(s, playerId, source, game)) + ) { discard(); // only one use return false; } for (Card card : game.getExile().getAllCardsByRange(game, playerId)) { - if (filter.match(card, game)) { + if (filter.match(card, playerId, source, game)) { game.getState().addOtherAbility(card, ability); } } for (Card card : player.getLibrary().getCards(game)) { - if (filter.match(card, game)) { + if (filter.match(card, playerId, source, game)) { game.getState().addOtherAbility(card, ability); } } for (Card card : player.getHand().getCards(game)) { - if (filter.match(card, game)) { + if (filter.match(card, playerId, source, game)) { game.getState().addOtherAbility(card, ability); } } for (Card card : player.getGraveyard().getCards(game)) { - if (filter.match(card, game)) { + if (filter.match(card, playerId, source, game)) { game.getState().addOtherAbility(card, ability); } } // workaround to gain cost reduction abilities to commanders before cast (make it playable) game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY) .stream() - .filter(card -> filter.match(card, game)) + .filter(card -> filter.match(card, playerId, source, game)) .forEach(card -> game.getState().addOtherAbility(card, ability)); for (StackObject stackObject : game.getStack()) { @@ -120,7 +125,7 @@ public class NextSpellCastHasAbilityEffect extends ContinuousEffectImpl { } // TODO: Distinguish "you cast" to exclude copies Card card = game.getCard(stackObject.getSourceId()); - if (card != null && filter.match((Spell) stackObject, game)) { + if (card != null && filter.match((Spell) stackObject, playerId, source, game)) { game.getState().addOtherAbility(card, ability); } }