From 4bb2b2df28e8a398f9dc81b0d73cbda4dd62e22e Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Wed, 26 Apr 2023 01:39:52 +0100 Subject: [PATCH] [CLB] Implement Intellect Devourer --- .../src/mage/cards/i/IntellectDevourer.java | 280 ++++++++++++++++++ .../CommanderLegendsBattleForBaldursGate.java | 2 + 2 files changed, 282 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/i/IntellectDevourer.java diff --git a/Mage.Sets/src/mage/cards/i/IntellectDevourer.java b/Mage.Sets/src/mage/cards/i/IntellectDevourer.java new file mode 100644 index 0000000000..ae6e106e9e --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IntellectDevourer.java @@ -0,0 +1,280 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.ManaPoolItem; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class IntellectDevourer extends CardImpl { + + public IntellectDevourer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.HORROR); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Devour Intellect — When Intellect Devourer enters the battlefield, each opponent exiles a card from their hand until Intellect Devourer leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new IntellectDevourerExileEffect()); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new IntellectDevourerReturnCardsAbility())); + this.addAbility(ability.withFlavorWord("Devour Intellect")); + + + // Body Thief — You may play lands and cast spells from among cards exiled with Intellect Devourer. + // If you cast a spell this way, you may spend mana as though it were mana of any color to cast it. + ability = new SimpleStaticAbility(new IntellectDevourerPlayFromExileEffect()); + ability.addEffect(new IntellectDevourerManaEffect()); + this.addAbility(ability.withFlavorWord("Body Thief")); + } + + private IntellectDevourer(final IntellectDevourer card) { + super(card); + } + + @Override + public IntellectDevourer copy() { + return new IntellectDevourer(this); + } +} + +class IntellectDevourerExileEffect extends OneShotEffect { + + IntellectDevourerExileEffect() { + super(Outcome.Exile); + this.staticText = "each opponent exiles a card from their hand until {this} leaves the battlefield"; + } + + private IntellectDevourerExileEffect(final IntellectDevourerExileEffect effect) {super(effect);} + + @Override + public IntellectDevourerExileEffect copy() {return new IntellectDevourerExileEffect(this);} + + @Override + public boolean apply(Game game, Ability source) { + // if Intellect Devourer dies before this ability resolves, it fizzles with nothing being exiled + if (game.getState().getZone(source.getSourceId()) != Zone.BATTLEFIELD) { + return false; + } + + Boolean applied = false; + // for storing each card to exile + Map cardsToExile = new HashMap<>(); + + // Each player chooses a card to exile + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent == null) { + continue; + } + if (opponent.getHand().size() > 0) { + Target target = new TargetCardInHand(1, new FilterCard()); + target.setRequired(true); + if (opponent.chooseTarget(Outcome.Exile, target, source, game)) { + Cards cards = new CardsImpl(target.getTargets()); + cardsToExile.put(opponentId, cards); + } + } else { + cardsToExile.put(opponentId, new CardsImpl()); + } + } + + // Exile all chosen cards at the same time + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null|| sourceObject == null) { + return false; + } + UUID exileZoneId = CardUtil.getExileZoneId(game, sourceObject.getId(), sourceObject.getZoneChangeCounter(game)); + + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Cards cardsOpponentsChoseToExile = new CardsImpl(); + Player opponent = game.getPlayer(opponentId); + if (opponent == null || !cardsToExile.containsKey(opponentId)) { + continue; + } + cardsOpponentsChoseToExile.addAll(cardsToExile.get(opponentId)); + opponent.moveCardsToExile(cardsOpponentsChoseToExile.getCards(game), source, game, false, exileZoneId, sourceObject.getIdName()); + Card thisCard = cardsOpponentsChoseToExile.getCards(game).iterator().next(); + game.getState().setValue(thisCard.getId().toString() + game.getState().getZoneChangeCounter(thisCard.getId()), exileZoneId); + applied = true; + } + + return applied; + } +} + +class IntellectDevourerPlayFromExileEffect extends AsThoughEffectImpl { + + IntellectDevourerPlayFromExileEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "You may play lands and cast spells from among cards exiled with {this}"; + } + + IntellectDevourerPlayFromExileEffect(final IntellectDevourerPlayFromExileEffect effect) {super(effect);} + + @Override + public boolean apply(Game game, Ability source) {return true;} + + @Override + public IntellectDevourerPlayFromExileEffect copy() {return new IntellectDevourerPlayFromExileEffect(this);} + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + MageObject sourceObject = source.getSourceObject(game); + Card theCard = game.getCard(objectId); + if (theCard == null) { + return false; + } + objectId = theCard.getMainCard().getId(); // for split cards + + UUID exileZoneId = CardUtil.getExileZoneId(game, sourceObject.getId(), sourceObject.getZoneChangeCounter(game)); + ExileZone exileZone = game.getExile().getExileZone(exileZoneId); + if (exileZone == null) { + return false; + } + // this check happens while the chosen card is in the exile zone + if (exileZone.contains(objectId) && affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + return card != null; + } + return false; + } +} + +class IntellectDevourerManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + + IntellectDevourerManaEffect() { + super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.WhileOnBattlefield, Outcome.Benefit); + this.staticText = "If you cast a spell this way, you may spend mana as though it were mana of any color to cast it"; + } + + private IntellectDevourerManaEffect(final IntellectDevourerManaEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public IntellectDevourerManaEffect copy() { + return new IntellectDevourerManaEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + // this check occurs when the chosen card is outside of the exile zone, so the exileId must be retrieved from history + MageObject sourceObject = source.getSourceObject(game); + Card theCard = game.getCard(objectId); + if (theCard == null) { + return false; + } + objectId = theCard.getMainCard().getId(); // for split cards + + // get the current zcc of the chosen exiled card + int zcc = game.getState().getZoneChangeCounter(theCard.getId()); + // retrieve the exileId of this source card + UUID exileId = CardUtil.getExileZoneId(game, sourceObject.getId(), sourceObject.getZoneChangeCounter(game)); + // retrieve the exileId stored on the chosen exiled card (note that we subtract 1 from it due to it being moved from the exile zone to the stack + UUID storedExileIdOfTheCard = (UUID) game.getState().getValue(theCard.getId().toString() + (zcc - 1)); + + if (objectId != null + && game.getState().getZone(objectId) == Zone.STACK + && exileId == storedExileIdOfTheCard + && affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + return card != null; + } + return false; + } + + @Override + public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { + return mana.getFirstAvailable(); + } +} + +class IntellectDevourerReturnCardsAbility extends DelayedTriggeredAbility { + + public IntellectDevourerReturnCardsAbility() { + super(new IntellectDevourerReturnExiledCardEffect(), Duration.OneUse); + this.usesStack = false; + this.setRuleVisible(false); + } + + public IntellectDevourerReturnCardsAbility(final IntellectDevourerReturnCardsAbility ability) {super(ability);} + + @Override + public IntellectDevourerReturnCardsAbility copy() {return new IntellectDevourerReturnCardsAbility(this);} + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getTargetId().equals(this.getSourceId())) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getFromZone() == Zone.BATTLEFIELD) { + return true; + } + } + return false; + } +} + +class IntellectDevourerReturnExiledCardEffect extends OneShotEffect { + + public IntellectDevourerReturnExiledCardEffect() { + super(Outcome.Benefit); + this.staticText = "Return exiled cards to their owners' hands"; + } + + public IntellectDevourerReturnExiledCardEffect(final IntellectDevourerReturnExiledCardEffect effect) {super(effect);} + + @Override + public IntellectDevourerReturnExiledCardEffect copy() {return new IntellectDevourerReturnExiledCardEffect(this);} + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (sourceObject != null && controller != null) { + ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (exile != null && sourcePermanent != null) { + controller.moveCards(exile, Zone.HAND, source, game); + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java index cab58fbf1c..51663abaa2 100644 --- a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java +++ b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java @@ -317,6 +317,8 @@ public final class CommanderLegendsBattleForBaldursGate extends ExpansionSet { cards.add(new SetCardInfo("Inspired Tinkering", 183, Rarity.UNCOMMON, mage.cards.i.InspiredTinkering.class)); cards.add(new SetCardInfo("Inspiring Leader", 28, Rarity.UNCOMMON, mage.cards.i.InspiringLeader.class)); cards.add(new SetCardInfo("Insufferable Balladeer", 184, Rarity.COMMON, mage.cards.i.InsufferableBalladeer.class)); + cards.add(new SetCardInfo("Intellect Devourer", 132, Rarity.RARE, mage.cards.i.IntellectDevourer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Intellect Devourer", 575, Rarity.RARE, mage.cards.i.IntellectDevourer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Irenicus's Vile Duplication", 78, Rarity.UNCOMMON, mage.cards.i.IrenicussVileDuplication.class)); cards.add(new SetCardInfo("Iron Mastiff", 317, Rarity.UNCOMMON, mage.cards.i.IronMastiff.class)); cards.add(new SetCardInfo("Irregular Cohort", 696, Rarity.COMMON, mage.cards.i.IrregularCohort.class));