diff --git a/Mage.Sets/src/mage/cards/s/SparkDouble.java b/Mage.Sets/src/mage/cards/s/SparkDouble.java new file mode 100644 index 00000000000..b036665e2fe --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SparkDouble.java @@ -0,0 +1,105 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CopyPermanentEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.functions.ApplyToPermanent; + +import java.util.UUID; + +/** + * @author JayDi85 + */ +public final class SparkDouble extends CardImpl { + + private static FilterPermanent filter = new FilterControlledPermanent("a creature or planeswalker you control"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.CREATURE), + new CardTypePredicate(CardType.PLANESWALKER))); + } + + public SparkDouble(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + this.subtype.add(SubType.ILLUSION); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // You may have Spark Double enter the battlefield as a copy of a creature or planeswalker you control, + // except it enters with an additional +1/+1 counter on it if it’s a creature, + // it enters with an additional loyalty counter on it if it’s a planeswalker, and it isn’t legendary if that permanent is legendary. + Effect effect = new CopyPermanentEffect(filter, new SparkDoubleExceptEffectsApplyerToPermanent()); + effect.setText("as a copy of a creature or planeswalker you control, " + + "except it enters with an additional +1/+1 counter on it if it’s a creature, " + + "it enters with an additional loyalty counter on it if it’s a planeswalker, and it isn’t legendary if that permanent is legendary."); + EntersBattlefieldAbility ability = new EntersBattlefieldAbility(effect, true); + this.addAbility(ability); + } + + public SparkDouble(final SparkDouble card) { + super(card); + } + + @Override + public SparkDouble copy() { + return new SparkDouble(this); + } +} + +class SparkDoubleExceptEffectsApplyerToPermanent extends ApplyToPermanent { + + @Override + public boolean apply(Game game, Permanent copyFromBlueprint, Ability source, UUID copyToObjectId) { + return apply(game, (MageObject) copyFromBlueprint, source, copyToObjectId); + } + + @Override + public boolean apply(Game game, MageObject copyFromBlueprint, Ability source, UUID copyToObjectId) { + Permanent destCard = game.getPermanentEntering(copyToObjectId); + if (destCard == null) { + return false; + } + + // it isn’t legendary if that permanent is legendary + copyFromBlueprint.getSuperType().remove(SuperType.LEGENDARY); + + // TODO: Blood Moon problem, can't apply on type changing effects (same as TeferisTimeTwist) + // see https://magic.wizards.com/en/articles/archive/feature/war-spark-release-notes-2019-04-19 + // If the copied permanent is affected by a type-changing effect, Spark Double may enter the battlefield with + // different permanent types than the copied permanent currently has. Use the characteristics of Spark Double as + // it enters the battlefield, not of the copied permanent, to determine whether it enters with an additional + // counter on it. Notably, if Spark Double copies a Gideon planeswalker that's a creature because its loyalty + // ability caused it to become a planeswalker creature, Spark Double enters as a noncreature planeswalker and + // doesn't get a +1/+1 counter. On the other hand, if Spark Double copies Gideon Blackblade during your turn, + // Spark Double enters as a planeswalker creature and gets both kinds of counters. + + // enters with an additional +1/+1 counter on it if it’s a creature + if (copyFromBlueprint.isCreature()) { + destCard.addCounters(CounterType.P1P1.createInstance(), source, game); + } + + // enters with an additional loyalty counter on it if it’s a planeswalker + if (copyFromBlueprint.isPlaneswalker()) { + destCard.addCounters(CounterType.LOYALTY.createInstance(), source, game); + } + + return true; + } + +} diff --git a/Mage.Sets/src/mage/cards/t/TeferisTimeTwist.java b/Mage.Sets/src/mage/cards/t/TeferisTimeTwist.java index 4405c4832ac..a6395dd0dd3 100644 --- a/Mage.Sets/src/mage/cards/t/TeferisTimeTwist.java +++ b/Mage.Sets/src/mage/cards/t/TeferisTimeTwist.java @@ -113,7 +113,7 @@ class TeferisTimeTwistReturnEffect extends OneShotEffect { } Permanent permanent = game.getPermanent(card.getId()); if (permanent != null && permanent.isCreature()) { - // This is technically wrong as it should enter with the counters, + // TODO: This is technically wrong as it should enter with the counters, // however there's currently no way to know that for sure // this is similar to the blood moon issue permanent.addCounters(CounterType.P1P1.createInstance(), source, game); diff --git a/Mage.Sets/src/mage/sets/WarOfTheSpark.java b/Mage.Sets/src/mage/sets/WarOfTheSpark.java index 5fae5b1c37d..f427ce7a3ce 100644 --- a/Mage.Sets/src/mage/sets/WarOfTheSpark.java +++ b/Mage.Sets/src/mage/sets/WarOfTheSpark.java @@ -245,6 +245,7 @@ public final class WarOfTheSpark extends ExpansionSet { cards.add(new SetCardInfo("Sorin's Thirst", 104, Rarity.COMMON, mage.cards.s.SorinsThirst.class)); cards.add(new SetCardInfo("Sorin, Vengeful Bloodlord", 217, Rarity.RARE, mage.cards.s.SorinVengefulBloodlord.class)); cards.add(new SetCardInfo("Soul Diviner", 218, Rarity.RARE, mage.cards.s.SoulDiviner.class)); + cards.add(new SetCardInfo("Spark Double", 68, Rarity.RARE, mage.cards.s.SparkDouble.class)); cards.add(new SetCardInfo("Spark Harvest", 105, Rarity.COMMON, mage.cards.s.SparkHarvest.class)); cards.add(new SetCardInfo("Spark Reaper", 106, Rarity.COMMON, mage.cards.s.SparkReaper.class)); cards.add(new SetCardInfo("Spellgorger Weird", 145, Rarity.COMMON, mage.cards.s.SpellgorgerWeird.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java new file mode 100644 index 00000000000..b11ff198525 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java @@ -0,0 +1,157 @@ +package org.mage.test.cards.copy; + +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class SparkDoubleTest extends CardTestPlayerBase { + + private Permanent findDoubleSparkPermanent(Game game) { + for (Permanent perm : game.getBattlefield().getAllActivePermanents()) { + if (perm.isCopy()) { + return perm; + } + } + Assert.fail("spark must exist"); + return null; + } + + @Test + public void test_CopyCreatureAndGetOneCounter() { + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 1); // 2/2, fly, vig + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Abbey Griffin"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Abbey Griffin", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 1 counter", 1, spark.getCounters(currentGame).getCount(CounterType.P1P1)); + // + Assert.assertEquals("must copy p/t", 3, spark.getPower().getValue()); + Assert.assertEquals("must copy p/t", 3, spark.getToughness().getValue()); + Assert.assertTrue("must copy ability", spark.getAbilities().contains(VigilanceAbility.getInstance())); + } + + @Test + public void test_CopyPlaneswalkerWithoutLegendaryWithOneCounter() { + addCard(Zone.BATTLEFIELD, playerA, "Ajani, the Greathearted", 1); + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Ajani, the Greathearted"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Ajani, the Greathearted", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 1 loyalty", 5 + 1, spark.getCounters(currentGame).getCount(CounterType.LOYALTY)); + } + + @Test + public void test_CopyCreatureAndGetDoubleCounter() { + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 1); // 2/2, fly, vig + addCard(Zone.BATTLEFIELD, playerA, "Doubling Season", 1); + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Abbey Griffin"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Abbey Griffin", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 2 counter", 2, spark.getCounters(currentGame).getCount(CounterType.P1P1)); + } + + @Test + public void test_CopyPlaneswalkerWithCreatureActivated() { + addCard(Zone.BATTLEFIELD, playerA, "Gideon, Ally of Zendikar", 1); + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + // activate creature ability + checkType("planeswalker not creature", 1, PhaseStep.UPKEEP, playerA, "Gideon, Ally of Zendikar", CardType.CREATURE, false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + checkType("planeswalker is creature", 1, PhaseStep.BEGIN_COMBAT, playerA, "Gideon, Ally of Zendikar", CardType.CREATURE, true); + + // copy + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Gideon, Ally of Zendikar"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Gideon, Ally of Zendikar", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 1 loyalty", 4 + 1, spark.getCounters(currentGame).getCount(CounterType.LOYALTY)); + Assert.assertEquals("must not add creature counter", 0, spark.getCounters(currentGame).getCount(CounterType.P1P1)); + } + + @Test + @Ignore // TODO: enabled after Blood Moon type changing effect will be fixed + public void test_CopyPlaneswalkerWithCreatureTypeChangedEffect() { + addCard(Zone.BATTLEFIELD, playerA, "Gideon Blackblade", 1); + // + addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + // Gideon Blackblade is creature on your turn (by type changing effect) + checkType("planeswalker is creature", 1, PhaseStep.UPKEEP, playerA, "Gideon Blackblade", CardType.CREATURE, true); + + // copy + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Gideon Blackblade"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Gideon Blackblade", 2); + + Permanent spark = findDoubleSparkPermanent(currentGame); + Assert.assertEquals("must add 1 loyalty", 4 + 1, spark.getCounters(currentGame).getCount(CounterType.LOYALTY)); + Assert.assertEquals("must add 1 creature counter", 1, spark.getCounters(currentGame).getCount(CounterType.P1P1)); + } +}