From b8eb58edbe30d3e6e1fd6f34a711bf1e75823587 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Wed, 5 Jul 2023 02:39:20 +0200 Subject: [PATCH] [LTR] Implement Shadow of the Enemy (#10556) There is room to refactor some of the cards that allow casting from an exile zone (for instance Gonti, Lord of Luxury, Dead Man's Chest or Thief of Sanity), but that felt complex enough I did not want to risk it. Each one bring some additional complexity to the effect. Tested that Dryad Arbor can not be cast from the exiled zone, did not attempt morphed Zoetic Cavern. --- .../src/mage/cards/s/ShadowOfTheEnemy.java | 186 ++++++++++++++++++ .../TheLordOfTheRingsTalesOfMiddleEarth.java | 1 + 2 files changed, 187 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/ShadowOfTheEnemy.java diff --git a/Mage.Sets/src/mage/cards/s/ShadowOfTheEnemy.java b/Mage.Sets/src/mage/cards/s/ShadowOfTheEnemy.java new file mode 100644 index 00000000000..6768e034e0a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShadowOfTheEnemy.java @@ -0,0 +1,186 @@ +package mage.cards.s; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.ManaPoolItem; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.Set; +import java.util.UUID; + +/** + * + * @author Susucr + */ +public final class ShadowOfTheEnemy extends CardImpl { + + public ShadowOfTheEnemy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}{B}"); + + // Exile all creature cards from target player's graveyard. + // You may cast spells from among those cards for as long as + // they remain exiled, and mana of any type can be spent to cast them. + this.getSpellAbility().addEffect(new ShadowOfTheEnemyEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + private ShadowOfTheEnemy(final ShadowOfTheEnemy card) { + super(card); + } + + @Override + public ShadowOfTheEnemy copy() { + return new ShadowOfTheEnemy(this); + } +} + +class ShadowOfTheEnemyEffect extends OneShotEffect { + + ShadowOfTheEnemyEffect() { + super(Outcome.Detriment); + staticText = "Exile all creature cards from target player's graveyard. " + + "You may cast spells from among those cards for as long as " + + "they remain exiled, and mana of any type can be spent to cast them."; + } + + private ShadowOfTheEnemyEffect(final ShadowOfTheEnemyEffect effect) { + super(effect); + } + + @Override + public ShadowOfTheEnemyEffect copy() { + return new ShadowOfTheEnemyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + if(player == null || targetPlayer == null || sourceObject == null){ + return false; + } + + Set cards = + targetPlayer + .getGraveyard() + .getCards(StaticFilters.FILTER_CARD_CREATURE, game); + + if(cards.isEmpty()){ + return true; + } + + player.moveCardsToExile(cards, source, game, true, source.getSourceId(), sourceObject.getName()); + for (Card card : cards) { + // allow to cast the card + ContinuousEffect effect = new ShadowOfTheEnemyCastFromExileEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + // and you may spend mana as though it were mana of any color to cast it + effect = new ShadowOfTheEnemySpendManaEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + } + return true; + } +} + +class ShadowOfTheEnemyCastFromExileEffect extends AsThoughEffectImpl { + + public ShadowOfTheEnemyCastFromExileEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + staticText = "You may cast spells from among them as long as they remain exiled"; + } + + public ShadowOfTheEnemyCastFromExileEffect(final ShadowOfTheEnemyCastFromExileEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public ShadowOfTheEnemyCastFromExileEffect copy() { + return new ShadowOfTheEnemyCastFromExileEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + this.discard(); // card is no longer in the origin zone, effect can be discarded + return false; + } + Card theCard = game.getCard(objectId); + if (theCard == null || theCard.isLand(game)) { + return false; + } + objectId = theCard.getMainCard().getId(); // for split cards + + if (objectId.equals(cardId) + && affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + return card != null; + } + return false; + } +} + +class ShadowOfTheEnemySpendManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + + public ShadowOfTheEnemySpendManaEffect() { + super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.Custom, Outcome.Benefit); + staticText = "and you may spend mana as though it were mana of any type to cast those spells"; + } + + public ShadowOfTheEnemySpendManaEffect(final ShadowOfTheEnemySpendManaEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public ShadowOfTheEnemySpendManaEffect copy() { + return new ShadowOfTheEnemySpendManaEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = CardUtil.getMainCardId(game, objectId); // for split cards + if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) + && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { + // if the card moved from exile to spell the zone change counter is increased by 1 + // (effect must applies before and on stack, use isCheckPlayableMode?) + return source.isControlledBy(affectedControllerId); + } else { + if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { + // object has moved zone so effect can be discarded + this.discard(); + } + } + return false; + } + + @Override + public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { + return mana.getFirstAvailable(); + } +} diff --git a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java index 72cea2806a7..6bc71797215 100644 --- a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java +++ b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java @@ -227,6 +227,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Scroll of Isildur", 69, Rarity.RARE, mage.cards.s.ScrollOfIsildur.class)); cards.add(new SetCardInfo("Second Breakfast", 29, Rarity.COMMON, mage.cards.s.SecondBreakfast.class)); cards.add(new SetCardInfo("Shadow Summoning", 226, Rarity.UNCOMMON, mage.cards.s.ShadowSummoning.class)); + cards.add(new SetCardInfo("Shadow of the Enemy", 107, Rarity.MYTHIC, mage.cards.s.ShadowOfTheEnemy.class)); cards.add(new SetCardInfo("Shadowfax, Lord of Horses", 227, Rarity.UNCOMMON, mage.cards.s.ShadowfaxLordOfHorses.class)); cards.add(new SetCardInfo("Shagrat, Loot Bearer", 228, Rarity.RARE, mage.cards.s.ShagratLootBearer.class)); cards.add(new SetCardInfo("Shelob's Ambush", 108, Rarity.COMMON, mage.cards.s.ShelobsAmbush.class));