diff --git a/Mage.Sets/src/mage/cards/u/UnstableAmulet.java b/Mage.Sets/src/mage/cards/u/UnstableAmulet.java new file mode 100644 index 00000000000..e17f5d8b5c5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnstableAmulet.java @@ -0,0 +1,164 @@ +package mage.cards.u; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.CastFromZonePredicate; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.Set; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class UnstableAmulet extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a spell from anywhere other than your hand"); + + static { + filter.add(Predicates.not(new CastFromZonePredicate(Zone.HAND))); + } + + public UnstableAmulet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}"); + + // When Unstable Amulet enters the battlefield, you get {E}{E}. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(2))); + + // Whenever you cast a spell from anywhere other than your hand, Unstable Amulet deals 1 damage to each opponent. + this.addAbility(new SpellCastControllerTriggeredAbility( + new DamagePlayersEffect(1, TargetController.OPPONENT), + filter, false + )); + + // {T}, Pay {E}{E}: Exile the top card of your library. You may play it until you exile another card with Unstable Amulet. + Ability ability = new SimpleActivatedAbility( + new UnstableAmuletEffect(), + new TapSourceCost() + ); + ability.addCost(new PayEnergyCost(2)); + this.addAbility(ability); + } + + private UnstableAmulet(final UnstableAmulet card) { + super(card); + } + + @Override + public UnstableAmulet copy() { + return new UnstableAmulet(this); + } +} + +class UnstableAmuletEffect extends OneShotEffect { + + UnstableAmuletEffect() { + super(Outcome.DrawCard); + staticText = "Exile the top card of your library. You may play it until you exile another card with {this}."; + } + + private UnstableAmuletEffect(final UnstableAmuletEffect effect) { + super(effect); + } + + @Override + public UnstableAmuletEffect copy() { + return new UnstableAmuletEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || !controller.getLibrary().hasCards()) { + return false; + } + Card card = controller.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + UUID exileId = CardUtil.getExileZoneId(game, source); + String exileName = CardUtil.getSourceIdName(game, source); + controller.moveCardsToExile(card, source, game, true, exileId, exileName); + game.getState().processAction(game); + if (!Zone.EXILED.equals(game.getState().getZone(card.getId()))) { + return true; + } + // Allow the card to be played until it leaves that exile zone. + ContinuousEffect effect = new UnstableAmuletPlayEffect(exileId); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + // Clean the exile Zone from other cards, that can no longer be played. + ExileZone exileZone = game.getExile().getExileZone(exileId); + if (exileZone == null) { + return true; + } + Set inExileZone = exileZone.getCards(game); + for (Card cardInExile : inExileZone) { + if (cardInExile.getId().equals(card.getId())) { + continue; + } + game.getExile().moveToMainExileZone(cardInExile, game); + } + return true; + } +} + +class UnstableAmuletPlayEffect extends AsThoughEffectImpl { + + // The exile zone the card should be in for the effect to work. + private final UUID exileId; + + UnstableAmuletPlayEffect(UUID exileId) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + this.exileId = exileId; + } + + private UnstableAmuletPlayEffect(final UnstableAmuletPlayEffect effect) { + super(effect); + this.exileId = effect.exileId; + } + + @Override + public UnstableAmuletPlayEffect copy() { + return new UnstableAmuletPlayEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + this.discard(); + return false; + } + ExileZone exileZone = game.getExile().getExileZone(exileId); + if (exileZone == null || !exileZone.contains(cardId)) { + this.discard(); + return false; + } + return cardId.equals(objectId) && affectedControllerId.equals(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index 2d9255bf953..1f9e71c30c7 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -285,6 +285,7 @@ public final class ModernHorizons3 extends ExpansionSet { cards.add(new SetCardInfo("Ugin's Labyrinth", 233, Rarity.MYTHIC, mage.cards.u.UginsLabyrinth.class)); cards.add(new SetCardInfo("Ulamog, the Defiler", 15, Rarity.MYTHIC, mage.cards.u.UlamogTheDefiler.class)); cards.add(new SetCardInfo("Unfathomable Truths", 77, Rarity.COMMON, mage.cards.u.UnfathomableTruths.class)); + cards.add(new SetCardInfo("Unstable Amulet", 142, Rarity.UNCOMMON, mage.cards.u.UnstableAmulet.class)); cards.add(new SetCardInfo("Urza's Cave", 234, Rarity.UNCOMMON, mage.cards.u.UrzasCave.class)); cards.add(new SetCardInfo("Urza's Incubator", 297, Rarity.RARE, mage.cards.u.UrzasIncubator.class)); cards.add(new SetCardInfo("Utter Insignificance", 78, Rarity.COMMON, mage.cards.u.UtterInsignificance.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/UnstableAmuletTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/UnstableAmuletTest.java new file mode 100644 index 00000000000..0d8a20e031f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/UnstableAmuletTest.java @@ -0,0 +1,83 @@ +package org.mage.test.cards.single.mh3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class UnstableAmuletTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.u.UnstableAmulet Unstable Amulet} {1}{R} + * Artifact + * When Unstable Amulet enters the battlefield, you get {E}{E} (two energy counters). + * Whenever you cast a spell from anywhere other than your hand, Unstable Amulet deals 1 damage to each opponent. + * {T}, Pay {E}{E}: Exile the top card of your library. You may play it until you exile another card with Unstable Amulet. + */ + private static final String amulet = "Unstable Amulet"; + + @Test + public void test_Simple() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, "Taiga", 2); + addCard(Zone.HAND, playerA, amulet); + addCard(Zone.LIBRARY, playerA, "Grizzly Bears"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, amulet, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Pay {E}{E}"); + + checkPlayableAbility("No mana to cast Bears", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Grizzly Bears", false); + checkExileCount("Bears in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 1); + + checkPlayableAbility("Can play Bears", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 20 - 1); // 1 damage from Amulet trigger + assertPermanentCount(playerA, "Grizzly Bears", 1); + } + + @Test + public void test_ExileAnother_EndPreviousPlay() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, "Taiga", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.HAND, playerA, amulet); + addCard(Zone.HAND, playerA, "Tune the Narrative"); // Draw a card. You get {E}{E} + addCard(Zone.LIBRARY, playerA, "Balduvian Bears"); + addCard(Zone.LIBRARY, playerA, "Plains", 2); // draw for turn 3 + Tune the Narrative + addCard(Zone.LIBRARY, playerA, "Grizzly Bears"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, amulet, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Pay {E}{E}"); + + checkPlayableAbility("1: No mana to cast Grizzly Bears", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Grizzly Bears", false); + checkExileCount("1: Bears in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 1); + + checkPlayableAbility("2: Can play Grizzly Bears", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Tune the Narrative", true); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Pay {E}{E}"); + checkExileCount("3: Grizzly Bears in exile", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Grizzly Bears", 1); + checkExileCount("3: Balduvian Bears in exile", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears", 1); + checkPlayableAbility("3: Can no longer play Grizzly Bears", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Grizzly Bears", false); + checkPlayableAbility("3: Can play Balduvian Bears", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Balduvian Bears", true); + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Balduvian Bears"); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 1); // 1 damage from Amulet trigger + assertExileCount(playerA, "Grizzly Bears", 1); + assertPermanentCount(playerA, "Balduvian Bears", 1); + } +} diff --git a/Mage/src/main/java/mage/game/Exile.java b/Mage/src/main/java/mage/game/Exile.java index e051b7bc2b1..7d8b7bd5660 100644 --- a/Mage/src/main/java/mage/game/Exile.java +++ b/Mage/src/main/java/mage/game/Exile.java @@ -117,6 +117,10 @@ public class Exile implements Serializable, Copyable { exileZone.add(card); } + public void moveToMainExileZone(Card card, Game game) { + moveToAnotherZone(card, game, getExileZone(PERMANENT)); + } + @Override public Exile copy() { return new Exile(this);