diff --git a/Mage.Sets/src/mage/cards/s/SmirkingSpelljacker.java b/Mage.Sets/src/mage/cards/s/SmirkingSpelljacker.java new file mode 100644 index 00000000000..137dde27d99 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SmirkingSpelljacker.java @@ -0,0 +1,129 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetSpell; +import mage.target.common.TargetCardInExile; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SmirkingSpelljacker extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("spell an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public SmirkingSpelljacker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.DJINN); + this.subtype.add(SubType.WIZARD); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Smirking Spelljacker enters the battlefield, exile target spell an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect().setToSourceExileZone(true)); + ability.addTarget(new TargetSpell(filter)); + this.addAbility(ability); + + // Whenever Smirking Spelljacker attacks, if a card is exiled with it, you may cast the exiled card without paying its mana cost. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new AttacksTriggeredAbility(new SmirkingSpelljackerEffect()), + SmirkingSpelljackerCondition.instance, + "Whenever Smirking Spelljacker attacks, if a card is exiled with it, " + + "you may cast the exiled card without paying its mana cost." + )); + } + + private SmirkingSpelljacker(final SmirkingSpelljacker card) { + super(card); + } + + @Override + public SmirkingSpelljacker copy() { + return new SmirkingSpelljacker(this); + } +} + +enum SmirkingSpelljackerCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = source.getSourceObject(game); + ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, sourceObject.getId(), sourceObject.getZoneChangeCounter(game))); + return exile != null && !exile.isEmpty(); + } + + @Override + public String toString() { + return "if a card is exiled with it"; + } +} + +class SmirkingSpelljackerEffect extends OneShotEffect { + + SmirkingSpelljackerEffect() { + super(Outcome.PlayForFree); + staticText = "you may cast the exiled card without paying its mana cost"; + } + + private SmirkingSpelljackerEffect(final SmirkingSpelljackerEffect effect) { + super(effect); + } + + @Override + public SmirkingSpelljackerEffect copy() { + return new SmirkingSpelljackerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + FilterCard filter = new FilterCard("card exiled with " + CardUtil.getSourceLogName(game, source)); + TargetCard target = new TargetCardInExile(1, 1, filter, CardUtil.getExileZoneId(game, source)); + target.withNotTarget(true); + controller.choose(Outcome.PlayForFree, target, source, game); + new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST) + .setTargetPointer(new FixedTarget(target.getFirstTarget())) + .apply(game, source); + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java index 99c7045bcbc..639a02b4724 100644 --- a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java @@ -237,6 +237,7 @@ public final class OutlawsOfThunderJunctionCommander extends ExpansionSet { cards.add(new SetCardInfo("Siphon Insight", 242, Rarity.RARE, mage.cards.s.SiphonInsight.class)); cards.add(new SetCardInfo("Skullwinder", 207, Rarity.UNCOMMON, mage.cards.s.Skullwinder.class)); 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("Sol Ring", 267, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); cards.add(new SetCardInfo("Springbloom Druid", 208, Rarity.COMMON, mage.cards.s.SpringbloomDruid.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmirkingSpelljackerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmirkingSpelljackerTest.java new file mode 100644 index 00000000000..cc9de72be79 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmirkingSpelljackerTest.java @@ -0,0 +1,51 @@ +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 SmirkingSpelljackerTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SmirkingSpelljacker Smirking Spelljacker} {4}{U} + * Creature — Djinn Wizard Rogue + * Flash + * Flying + * When Smirking Spelljacker enters the battlefield, exile target spell an opponent controls. + * Whenever Smirking Spelljacker attacks, if a card is exiled with it, you may cast the exiled card without paying its mana cost. + * 3/3 + */ + private static final String spelljacker = "Smirking Spelljacker"; + + @Test + public void test_Simple() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerB, spelljacker); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, spelljacker); + addTarget(playerB, "Lightning Bolt"); + + checkExileCount("Bolt in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", 1); + + attack(2, playerB, spelljacker, playerA); + setChoice(playerB, "Lightning Bolt"); // choosing bolt + setChoice(playerB, true); // yes to cast bolt + addTarget(playerB, playerA); // target for bolt + + setStopAt(2, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerA, 20 - 3 - 3); // 3 from bolt, 3 from spelljacker + assertPermanentCount(playerB, spelljacker, 1); + assertGraveyardCount(playerA, "Lightning Bolt", 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java index 933ee83fd77..4d8d0cee936 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java @@ -34,6 +34,13 @@ public class MayCastTargetCardEffect extends OneShotEffect { this(CastManaAdjustment.NONE, thenExile); } + /** + * Allows to cast the target card immediately, either for its cost or with a modifier (like for free, or mana as any type). + */ + public MayCastTargetCardEffect(CastManaAdjustment manaAdjustment) { + this(manaAdjustment, false); + } + /** * Allows to cast the target card immediately, either for its cost or with a modifier (like for free, or mana as any type). */