diff --git a/Mage.Sets/src/mage/cards/d/DjinnIlluminatus.java b/Mage.Sets/src/mage/cards/d/DjinnIlluminatus.java index ed616fbec9d..58da044a7e0 100644 --- a/Mage.Sets/src/mage/cards/d/DjinnIlluminatus.java +++ b/Mage.Sets/src/mage/cards/d/DjinnIlluminatus.java @@ -1,4 +1,3 @@ - package mage.cards.d; import mage.MageInt; @@ -29,7 +28,8 @@ public final class DjinnIlluminatus extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new EachSpellYouCastHasReplicateEffect(filter))); + this.addAbility(new SimpleStaticAbility(new EachSpellYouCastHasReplicateEffect(filter, + "When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies."))); } private DjinnIlluminatus(final DjinnIlluminatus card) { diff --git a/Mage.Sets/src/mage/cards/h/HatcherySliver.java b/Mage.Sets/src/mage/cards/h/HatcherySliver.java new file mode 100644 index 00000000000..0f3d77c795b --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HatcherySliver.java @@ -0,0 +1,50 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.EachSpellYouCastHasReplicateEffect; +import mage.abilities.keyword.ReplicateAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterSpell; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class HatcherySliver extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("Sliver spell"); + + static { + filter.add(SubType.SLIVER.getPredicate()); + } + + public HatcherySliver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.SLIVER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Replicate {1}{G} + this.addAbility(new ReplicateAbility("{1}{G}")); + + // Each Sliver spell you cast has replicate. The replicate cost is equal to its mana cost. + this.addAbility(new SimpleStaticAbility(new EachSpellYouCastHasReplicateEffect(filter, + "A copy of a permanent spell becomes a token."))); + + } + + private HatcherySliver(final HatcherySliver card) { + super(card); + } + + @Override + public HatcherySliver copy() { + return new HatcherySliver(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RootSliver.java b/Mage.Sets/src/mage/cards/r/RootSliver.java index 573397f67d5..9ca9a893b90 100644 --- a/Mage.Sets/src/mage/cards/r/RootSliver.java +++ b/Mage.Sets/src/mage/cards/r/RootSliver.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -15,7 +14,6 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterSpell; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; @@ -27,12 +25,6 @@ import mage.game.stack.StackObject; */ public final class RootSliver extends CardImpl { - private static final FilterSpell filter = new FilterSpell("Sliver spells"); - - static { - filter.add(SubType.SLIVER.getPredicate()); - } - public RootSliver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); this.subtype.add(SubType.SLIVER); diff --git a/Mage.Sets/src/mage/cards/t/ThreefoldSignal.java b/Mage.Sets/src/mage/cards/t/ThreefoldSignal.java index 4261df15c33..1341e228070 100644 --- a/Mage.Sets/src/mage/cards/t/ThreefoldSignal.java +++ b/Mage.Sets/src/mage/cards/t/ThreefoldSignal.java @@ -35,7 +35,10 @@ public class ThreefoldSignal extends CardImpl { // (When you cast it, copy it for each time you paid its replicate cost. // You may choose new targets for the copies. // A copy of a permanent spell becomes a token.) - this.addAbility(new SimpleStaticAbility(new EachSpellYouCastHasReplicateEffect(filter, new GenericManaCost(3)))); + this.addAbility(new SimpleStaticAbility(new EachSpellYouCastHasReplicateEffect(filter, + "When you cast it, copy it for each time you paid its replicate cost. " + + "You may choose new targets for the copies. A copy of a permanent spell becomes a token.", + new GenericManaCost(3)))); } private ThreefoldSignal(final ThreefoldSignal card) { diff --git a/Mage.Sets/src/mage/sets/CommanderMasters.java b/Mage.Sets/src/mage/sets/CommanderMasters.java index 39f0e00e3ba..dbc02f13867 100644 --- a/Mage.Sets/src/mage/sets/CommanderMasters.java +++ b/Mage.Sets/src/mage/sets/CommanderMasters.java @@ -289,6 +289,7 @@ public final class CommanderMasters extends ExpansionSet { cards.add(new SetCardInfo("Hanna, Ship's Navigator", 340, Rarity.RARE, mage.cards.h.HannaShipsNavigator.class)); cards.add(new SetCardInfo("Harmonic Sliver", 924, Rarity.UNCOMMON, mage.cards.h.HarmonicSliver.class)); cards.add(new SetCardInfo("Harsh Mercy", 825, Rarity.RARE, mage.cards.h.HarshMercy.class)); + cards.add(new SetCardInfo("Hatchery Sliver", 741, Rarity.RARE, mage.cards.h.HatcherySliver.class)); cards.add(new SetCardInfo("Haunted Cloak", 389, Rarity.COMMON, mage.cards.h.HauntedCloak.class)); cards.add(new SetCardInfo("Havoc Jester", 230, Rarity.UNCOMMON, mage.cards.h.HavocJester.class)); cards.add(new SetCardInfo("Heart-Piercer Bow", 390, Rarity.COMMON, mage.cards.h.HeartPiercerBow.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ReplicateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ReplicateTest.java index 1b9c3c14c49..fe0627fd24a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ReplicateTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ReplicateTest.java @@ -103,6 +103,72 @@ public class ReplicateTest extends CardTestPlayerBase { assertLife(playerB, 20 - 3); // 1 + 2 replicates } + private static final String hatchery = "Hatchery Sliver"; // 1G, Replicate 1G, Sliver spells you cast have replicate. + + @Test + public void testMultipleInstancesReplicate() { + + String metallic = "Metallic Sliver"; // 1 + + addCard(Zone.HAND, playerA, hatchery); + addCard(Zone.HAND, playerA, metallic); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4 + 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hatchery); + setChoice(playerA, true); // pay 1 time replicate + setChoice(playerA, false); // don't pay 2 times replicate + // Now there are two Hatchery Slivers on the battlefield, so Sliver spells have two instances of replicate. + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, metallic); + setChoice(playerA, true); // pay 1 time replicate (first instance) + setChoice(playerA, false); // don't pay 2 times replicate (first instance) + setChoice(playerA, true); // pay 1 time replicate (second instance) + setChoice(playerA, true); // pay 2 times replicate (second instance) + setChoice(playerA, false); // don't pay 3 times replicate (second instance) + setChoice(playerA, "Replicate"); // order triggers (currently appear identical) + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTappedCount("Forest", false, 0); // all mana should have been used to pay costs + assertPermanentCount(playerA, hatchery, 2); + assertPermanentCount(playerA, metallic, 4); + } + + @Test + public void testReplicateProperlyGranted() { + + String diffusion = "Diffusion Sliver"; // 1/1 Sliver for 1U + // "Whenever a Sliver creature you control becomes the target of a spell or ability an opponent controls, + // counter that spell or ability unless its controller pays {2}." + + addCard(Zone.HAND, playerA, hatchery); + addCard(Zone.HAND, playerA, diffusion); + addCard(Zone.HAND, playerB, "Disfigure"); // -2/-2 to kill creature + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 2 + 6); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hatchery); + setChoice(playerA, false); // don't pay 1 time replicate + // Metallic Sliver now has one instance of replicate + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, diffusion); + setChoice(playerA, true); // pay 1 time replicate + setChoice(playerA, true); // pay 2 times replicate + setChoice(playerA, false); // don't pay 3 times replicate + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Disfigure", hatchery); + // Diffusion Slivers not yet on the battlefield, so Disfigure can resolve + // Replicate should still trigger even though the Hatchery Sliver which granted it to Diffusion Sliver has died + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTappedCount("Tropical Island", false, 0); // all mana should have been used to pay costs + assertGraveyardCount(playerA, hatchery, 1); + assertGraveyardCount(playerB, "Disfigure", 1); + assertPermanentCount(playerA, diffusion, 3); + } + @Test @Ignore // TODO: enable test after replicate ability will be supported by AI public void testReplicate_AI() { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/EachSpellYouCastHasReplicateEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/EachSpellYouCastHasReplicateEffect.java index b7f45342e5f..30608f0b740 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/EachSpellYouCastHasReplicateEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/EachSpellYouCastHasReplicateEffect.java @@ -1,9 +1,7 @@ package mage.abilities.effects.common.continuous; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.costs.Cost; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.keyword.ReplicateAbility; import mage.constants.Duration; @@ -30,22 +28,23 @@ public class EachSpellYouCastHasReplicateEffect extends ContinuousEffectImpl { private final Cost fixedNewCost; private final Map replicateAbilities = new HashMap<>(); - public EachSpellYouCastHasReplicateEffect(FilterSpell filter) { - this(filter, null); + public EachSpellYouCastHasReplicateEffect(FilterSpell filter, String reminderText) { + this(filter, reminderText, null); } /** - * * @param filter Filter used for filtering spells + * @param reminderText Reminder text that will be italicized and added (capitalize and include punctuation) * @param fixedNewCost Fixed new cost to pay as the replication cost */ - public EachSpellYouCastHasReplicateEffect(FilterSpell filter, Cost fixedNewCost) { + public EachSpellYouCastHasReplicateEffect(FilterSpell filter, String reminderText, Cost fixedNewCost) { super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); this.filter = filter; this.fixedNewCost = fixedNewCost; - this.staticText = "Each " + this.filter.getMessage() + " you cast has replicate" + + this.staticText = "Each " + this.filter.getMessage() + (this.filter.getMessage().contains("cast") ? "" : " you cast") + + " has replicate" + (this.fixedNewCost == null ? ". The replicate cost is equal to its mana cost" : ' ' + this.fixedNewCost.getText()) - + ". (When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies.)"; + + ((reminderText != null && !reminderText.isEmpty()) ? (". (" + reminderText + ")") : ""); } private EachSpellYouCastHasReplicateEffect(final EachSpellYouCastHasReplicateEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java b/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java index 7d22d9c2a72..501da41f77a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java @@ -18,6 +18,7 @@ import mage.game.stack.StackObject; import mage.players.Player; import java.util.Iterator; +import java.util.UUID; /** * @author LevelX2 @@ -39,7 +40,7 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderTextMana, cost); this.additionalCost.setRepeatable(true); setRuleAtTheTop(true); - addSubAbility(new ReplicateTriggeredAbility()); + addSubAbility(new ReplicateTriggeredAbility(this.getId())); } protected ReplicateAbility(final ReplicateAbility ability) { @@ -131,13 +132,17 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona class ReplicateTriggeredAbility extends TriggeredAbilityImpl { - public ReplicateTriggeredAbility() { + private UUID replicateId; // need to correspond only to own replicate ability, not any other instances of replicate ability + + public ReplicateTriggeredAbility(UUID replicateId) { super(Zone.STACK, new ReplicateCopyEffect()); + this.replicateId = replicateId; this.setRuleVisible(false); } private ReplicateTriggeredAbility(final ReplicateTriggeredAbility ability) { super(ability); + this.replicateId = ability.replicateId; } @Override @@ -164,7 +169,7 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl { return false; } for (Ability ability : card.getAbilities(game)) { - if (!(ability instanceof ReplicateAbility) || !ability.isActivated()) { + if (!(ability instanceof ReplicateAbility) || !ability.isActivated() || ability.getId() != replicateId) { continue; } for (Effect effect : this.getEffects()) {