From de4afab118ed5704399f454f189fffe062c7f474 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 22 Sep 2021 08:08:14 -0400 Subject: [PATCH] [MID] Implemented Siphon Insight --- .../src/mage/cards/g/GontiLordOfLuxury.java | 79 +++--- Mage.Sets/src/mage/cards/s/SiphonInsight.java | 244 ++++++++++++++++++ .../src/mage/sets/InnistradMidnightHunt.java | 1 + 3 files changed, 284 insertions(+), 40 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/SiphonInsight.java diff --git a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java index ffd0b7369a2..669690eb3ee 100644 --- a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java +++ b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java @@ -29,8 +29,6 @@ import java.util.UUID; */ public final class GontiLordOfLuxury extends CardImpl { - protected static final String VALUE_PREFIX = "ExileZones"; - public GontiLordOfLuxury(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); addSuperType(SuperType.LEGENDARY); @@ -62,6 +60,8 @@ public final class GontiLordOfLuxury extends CardImpl { class GontiLordOfLuxuryEffect extends OneShotEffect { + private static final String VALUE_PREFIX = "ExileZones"; + public GontiLordOfLuxuryEffect() { super(Outcome.Benefit); this.staticText = "look at the top four cards of target opponent's library, exile one of them face down, then put the rest on the bottom of that library in a random order. You may look at and cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any type to cast that spell"; @@ -81,48 +81,47 @@ class GontiLordOfLuxuryEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); - if (controller != null && opponent != null && sourceObject != null) { - Cards topCards = new CardsImpl(); - topCards.addAll(opponent.getLibrary().getTopCards(game, 4)); - TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to exile")); - if (controller.choose(outcome, topCards, target, game)) { - Card card = game.getCard(target.getFirstTarget()); - if (card != null) { - topCards.remove(card); - // move card to exile - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - card.setFaceDown(true, game); - if (controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName())) { - card.setFaceDown(true, game); - Set exileZones = (Set) game.getState().getValue(GontiLordOfLuxury.VALUE_PREFIX + source.getSourceId().toString()); - if (exileZones == null) { - exileZones = new HashSet<>(); - game.getState().setValue(GontiLordOfLuxury.VALUE_PREFIX + source.getSourceId().toString(), exileZones); - } - exileZones.add(exileZoneId); - // allow to cast the card - ContinuousEffect effect = new GontiLordOfLuxuryCastFromExileEffect(); - 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 GontiLordOfLuxurySpendAnyManaEffect(); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); - // For as long as that card remains exiled, you may look at it - effect = new GontiLordOfLuxuryLookEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); - } - } - } - // then put the rest on the bottom of that library in a random order + if (controller == null || opponent == null || sourceObject == null) { + return false; + } + Cards topCards = new CardsImpl(); + topCards.addAll(opponent.getLibrary().getTopCards(game, 4)); + TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to exile")); + controller.choose(outcome, topCards, target, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { controller.putCardsOnBottomOfLibrary(topCards, game, source, false); return true; } - - return false; + topCards.remove(card); + // move card to exile + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + card.setFaceDown(true, game); + if (controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName())) { + card.setFaceDown(true, game); + Set exileZones = (Set) game.getState().getValue(VALUE_PREFIX + source.getSourceId().toString()); + if (exileZones == null) { + exileZones = new HashSet<>(); + game.getState().setValue(VALUE_PREFIX + source.getSourceId().toString(), exileZones); + } + exileZones.add(exileZoneId); + // allow to cast the card + ContinuousEffect effect = new GontiLordOfLuxuryCastFromExileEffect(); + 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 GontiLordOfLuxurySpendAnyManaEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + // For as long as that card remains exiled, you may look at it + effect = new GontiLordOfLuxuryLookEffect(controller.getId()); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + } + // then put the rest on the bottom of that library in a random order + controller.putCardsOnBottomOfLibrary(topCards, game, source, false); + return true; } - } class GontiLordOfLuxuryCastFromExileEffect extends AsThoughEffectImpl { diff --git a/Mage.Sets/src/mage/cards/s/SiphonInsight.java b/Mage.Sets/src/mage/cards/s/SiphonInsight.java new file mode 100644 index 00000000000..b3603389046 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SiphonInsight.java @@ -0,0 +1,244 @@ +package mage.cards.s; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.ManaPoolItem; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 plus everyone who worked on Gonti + */ +public final class SiphonInsight extends CardImpl { + + public SiphonInsight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{B}"); + + // Look at the top two cards of target opponent's library. Exile one of them face down and put the other on the bottom of that library. You may look at and play the exiled card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell. + this.getSpellAbility().addEffect(new SiphonInsightEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // Flashback {1}{U}{B} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{U}{B}"))); + } + + private SiphonInsight(final SiphonInsight card) { + super(card); + } + + @Override + public SiphonInsight copy() { + return new SiphonInsight(this); + } +} + +class SiphonInsightEffect extends OneShotEffect { + + private static final String VALUE_PREFIX = "ExileZones"; + + public SiphonInsightEffect() { + super(Outcome.Benefit); + this.staticText = "look at the top two cards of target opponent's library. " + + "Exile one of them face down and put the other on the bottom of that library. " + + "You may look at and play the exiled card for as long as it remains exiled, " + + "and you may spend mana as though it were mana of any color to cast that spell"; + } + + private SiphonInsightEffect(final SiphonInsightEffect effect) { + super(effect); + } + + @Override + public SiphonInsightEffect copy() { + return new SiphonInsightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || opponent == null || sourceObject == null) { + return false; + } + Cards topCards = new CardsImpl(); + topCards.addAll(opponent.getLibrary().getTopCards(game, 2)); + TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to exile")); + controller.choose(outcome, topCards, target, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + controller.putCardsOnBottomOfLibrary(topCards, game, source, false); + return true; + } + topCards.remove(card); + // move card to exile + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + card.setFaceDown(true, game); + if (controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName())) { + card.setFaceDown(true, game); + Set exileZones = (Set) game.getState().getValue(VALUE_PREFIX + source.getSourceId().toString()); + if (exileZones == null) { + exileZones = new HashSet<>(); + game.getState().setValue(VALUE_PREFIX + source.getSourceId().toString(), exileZones); + } + exileZones.add(exileZoneId); + // allow to cast the card + ContinuousEffect effect = new SiphonInsightCastFromExileEffect(); + 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 SiphonInsightSpendAnyManaEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + // For as long as that card remains exiled, you may look at it + effect = new SiphonInsightLookEffect(controller.getId()); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + } + // then put the rest on the bottom of that library in a random order + controller.putCardsOnBottomOfLibrary(topCards, game, source, false); + return true; + } +} + +class SiphonInsightCastFromExileEffect extends AsThoughEffectImpl { + + public SiphonInsightCastFromExileEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + staticText = "You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell"; + } + + private SiphonInsightCastFromExileEffect(final SiphonInsightCastFromExileEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public SiphonInsightCastFromExileEffect copy() { + return new SiphonInsightCastFromExileEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID targetId = getTargetPointer().getFirst(game, source); + if (targetId == 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(targetId) + && affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + // TODO: Allow to cast Zoetic Cavern face down + return card != null; + } + return false; + } +} + +class SiphonInsightSpendAnyManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + + public SiphonInsightSpendAnyManaEffect() { + super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.Custom, Outcome.Benefit); + staticText = "you may spend mana as though it were mana of any color to cast it"; + } + + private SiphonInsightSpendAnyManaEffect(final SiphonInsightSpendAnyManaEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public SiphonInsightSpendAnyManaEffect copy() { + return new SiphonInsightSpendAnyManaEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + Card theCard = game.getCard(objectId); + if (theCard == null) { + return false; + } + objectId = theCard.getMainCard().getId(); // 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(); + } +} + +class SiphonInsightLookEffect extends AsThoughEffectImpl { + + private final UUID authorizedPlayerId; + + public SiphonInsightLookEffect(UUID authorizedPlayerId) { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + this.authorizedPlayerId = authorizedPlayerId; + staticText = "You may look at the cards exiled with {this}"; + } + + private SiphonInsightLookEffect(final SiphonInsightLookEffect effect) { + super(effect); + this.authorizedPlayerId = effect.authorizedPlayerId; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public SiphonInsightLookEffect copy() { + return new SiphonInsightLookEffect(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 affectedControllerId.equals(authorizedPlayerId) + && objectId.equals(cardId); + } +} diff --git a/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java b/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java index 43457a77152..e8484c9a1ab 100644 --- a/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java +++ b/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java @@ -276,6 +276,7 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Sigarda, Champion of Light", 240, Rarity.MYTHIC, mage.cards.s.SigardaChampionOfLight.class)); cards.add(new SetCardInfo("Sigardian Savior", 34, Rarity.MYTHIC, mage.cards.s.SigardianSavior.class)); cards.add(new SetCardInfo("Silver Bolt", 258, Rarity.COMMON, mage.cards.s.SilverBolt.class)); + cards.add(new SetCardInfo("Siphon Insight", 241, Rarity.RARE, mage.cards.s.SiphonInsight.class)); cards.add(new SetCardInfo("Skaab Wrangler", 75, Rarity.UNCOMMON, mage.cards.s.SkaabWrangler.class)); cards.add(new SetCardInfo("Slaughter Specialist", 122, Rarity.RARE, mage.cards.s.SlaughterSpecialist.class)); cards.add(new SetCardInfo("Slogurk, the Overslime", 242, Rarity.RARE, mage.cards.s.SlogurkTheOverslime.class));