diff --git a/Mage.Sets/src/mage/cards/m/MnemonicBetrayal.java b/Mage.Sets/src/mage/cards/m/MnemonicBetrayal.java new file mode 100644 index 00000000000..b9a5f94619c --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MnemonicBetrayal.java @@ -0,0 +1,219 @@ +package mage.cards.m; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * + * @author TheElk801 + */ +public final class MnemonicBetrayal extends CardImpl { + + public MnemonicBetrayal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{U}{B}"); + + // Exile all cards from all opponents' graveyards. You may cast those cards this turn, and you may spend mana as though it were mana of any type to cast those spells. At the beginning of the next end step, if any of those cards remain exiled, return them to their owner's graveyards. + this.getSpellAbility().addEffect(new MnemonicBetrayalExileEffect()); + + // Exile Mnemonic Betrayal. + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + } + + public MnemonicBetrayal(final MnemonicBetrayal card) { + super(card); + } + + @Override + public MnemonicBetrayal copy() { + return new MnemonicBetrayal(this); + } +} + +class MnemonicBetrayalExileEffect extends OneShotEffect { + + public MnemonicBetrayalExileEffect() { + super(Outcome.Benefit); + this.staticText = "Exile all cards from all opponents' graveyards. " + + "You may cast those cards this turn, " + + "and you may spend mana as though it were mana of any type " + + "to cast those spells. At the beginning of the next end step, " + + "if any of those cards remain exiled, " + + "return them to their owner's graveyards."; + } + + public MnemonicBetrayalExileEffect(final MnemonicBetrayalExileEffect effect) { + super(effect); + } + + @Override + public MnemonicBetrayalExileEffect copy() { + return new MnemonicBetrayalExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Cards cards = new CardsImpl(); + Map cardMap = new HashMap(); + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(playerId); + if (player != null) { + cards.addAll(player.getGraveyard()); + } + } + for (Card card : cards.getCards(game)) { + cardMap.put(card.getId(), card.getZoneChangeCounter(game)); + game.addEffect(new MnemonicBetrayalCastFromExileEffect(card, game), source); + } + controller.moveCardsToExile(cards.getCards(game), source, game, true, source.getSourceId(), source.getSourceObjectIfItStillExists(game).getName()); + game.addDelayedTriggeredAbility(new MnemonicBetrayalDelayedTriggeredAbility(cards, cardMap)); + return true; + } +} + +class MnemonicBetrayalCastFromExileEffect extends AsThoughEffectImpl { + + private final Card card; + private final int zoneCounter; + + public MnemonicBetrayalCastFromExileEffect(Card card, Game game) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + this.card = card; + this.zoneCounter = card.getZoneChangeCounter(game) + 1; + } + + public MnemonicBetrayalCastFromExileEffect(final MnemonicBetrayalCastFromExileEffect effect) { + super(effect); + this.card = effect.card; + this.zoneCounter = effect.zoneCounter; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public MnemonicBetrayalCastFromExileEffect copy() { + return new MnemonicBetrayalCastFromExileEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (card.getZoneChangeCounter(game) != zoneCounter) { + this.discard(); + return false; + } + return objectId.equals(card.getId()) + && card.getZoneChangeCounter(game) == zoneCounter + && affectedControllerId.equals(source.getControllerId()); + } +} + +class MnemonicBetrayalDelayedTriggeredAbility extends DelayedTriggeredAbility { + + private final Cards cards; + private final Map cardMap = new HashMap(); + + public MnemonicBetrayalDelayedTriggeredAbility(Cards cards, Map cardMap) { + super(new MnemonicBetrayalReturnEffect(cards, cardMap)); + this.triggerOnlyOnce = true; + this.cards = cards; + this.cardMap.putAll(cardMap); + } + + public MnemonicBetrayalDelayedTriggeredAbility(final MnemonicBetrayalDelayedTriggeredAbility ability) { + super(ability); + this.cards = ability.cards.copy(); + this.cardMap.putAll(ability.cardMap); + } + + @Override + public MnemonicBetrayalDelayedTriggeredAbility copy() { + return new MnemonicBetrayalDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.END_TURN_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return true; + } + + @Override + public boolean checkInterveningIfClause(Game game) { + return cards.stream().anyMatch((cardId) -> (game.getState().getZone(cardId) == Zone.EXILED + && game.getState().getZoneChangeCounter(cardId) == cardMap.getOrDefault(cardId, -5) + 1)); + } + + @Override + public String getRule() { + return "At the beginning of the next end step, " + + "if any of those cards remain exiled, " + + "return them to their owner's graveyards."; + } +} + +class MnemonicBetrayalReturnEffect extends OneShotEffect { + + private final Cards cards; + private final Map cardMap = new HashMap(); + + public MnemonicBetrayalReturnEffect(Cards cards, Map cardMap) { + super(Outcome.Benefit); + this.cards = cards; + this.cardMap.putAll(cardMap); + } + + public MnemonicBetrayalReturnEffect(final MnemonicBetrayalReturnEffect effect) { + super(effect); + this.cards = effect.cards.copy(); + this.cardMap.putAll(effect.cardMap); + } + + @Override + public MnemonicBetrayalReturnEffect copy() { + return new MnemonicBetrayalReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cardsToReturn = new CardsImpl(); + for (Card card : cards.getCards(game)) { + if (game.getState().getZone(card.getId()) == Zone.EXILED + && card.getZoneChangeCounter(game) == cardMap.getOrDefault(card.getId(), -5) + 1) { + cardsToReturn.add(card); + } + } + return player.moveCards(cardsToReturn, Zone.GRAVEYARD, source, game); + } +} diff --git a/Mage.Sets/src/mage/sets/GuildsOfRavnica.java b/Mage.Sets/src/mage/sets/GuildsOfRavnica.java index e8438b3573a..97ffae582bd 100644 --- a/Mage.Sets/src/mage/sets/GuildsOfRavnica.java +++ b/Mage.Sets/src/mage/sets/GuildsOfRavnica.java @@ -180,6 +180,7 @@ public final class GuildsOfRavnica extends ExpansionSet { cards.add(new SetCardInfo("Midnight Reaper", 77, Rarity.RARE, mage.cards.m.MidnightReaper.class)); cards.add(new SetCardInfo("Might of the Masses", 137, Rarity.UNCOMMON, mage.cards.m.MightOfTheMasses.class)); cards.add(new SetCardInfo("Mission Briefing", 44, Rarity.RARE, mage.cards.m.MissionBriefing.class)); + cards.add(new SetCardInfo("Mnemonic Betrayal", 189, Rarity.MYTHIC, mage.cards.m.MnemonicBetrayal.class)); cards.add(new SetCardInfo("Molderhulk", 190, Rarity.UNCOMMON, mage.cards.m.Molderhulk.class)); cards.add(new SetCardInfo("Moodmark Painter", 78, Rarity.COMMON, mage.cards.m.MoodmarkPainter.class)); cards.add(new SetCardInfo("Mountain", 263, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage/src/main/java/mage/abilities/DelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/DelayedTriggeredAbility.java index 8748e25d3fb..739915cc4c2 100644 --- a/Mage/src/main/java/mage/abilities/DelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/DelayedTriggeredAbility.java @@ -13,7 +13,7 @@ import mage.game.Game; public abstract class DelayedTriggeredAbility extends TriggeredAbilityImpl { private Duration duration; - private boolean triggerOnlyOnce; + protected boolean triggerOnlyOnce; public DelayedTriggeredAbility(Effect effect) { this(effect, Duration.EndOfGame);