From e196dbd5304d4e1e07e62296fbcfa63f53891adf Mon Sep 17 00:00:00 2001 From: DominionSpy Date: Tue, 20 Feb 2024 09:12:15 +0200 Subject: [PATCH 1/3] [MKM] Implement Intrude on the Mind --- .../src/mage/cards/i/IntrudeOnTheMind.java | 159 ++++++++++++++++++ .../src/mage/sets/MurdersAtKarlovManor.java | 1 + .../token/ThopterColorlessToken2.java | 29 ++++ 3 files changed, 189 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/ThopterColorlessToken2.java diff --git a/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java b/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java new file mode 100644 index 000000000000..4949aff5878f --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java @@ -0,0 +1,159 @@ +package mage.cards.i; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.ThopterColorlessToken2; +import mage.game.permanent.token.Token; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetCard; +import mage.target.common.TargetOpponent; + +/** + * + * @author DominionSpy + */ +public final class IntrudeOnTheMind extends CardImpl { + + public IntrudeOnTheMind(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}{U}"); + + // Reveal the top five cards of your library and separate them into two piles. An opponent chooses one of those piles. Put that pile into your hand and the other into your graveyard. + // Create a 0/0 colorless Thopter artifact creature token with flying, then put a +1/+1 counter on it for each card put into your graveyard this way. + this.getSpellAbility().addEffect(new IntrudeOnTheMindEffect()); + } + + private IntrudeOnTheMind(final IntrudeOnTheMind card) { + super(card); + } + + @Override + public IntrudeOnTheMind copy() { + return new IntrudeOnTheMind(this); + } +} + +class IntrudeOnTheMindEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard("cards to put in the first pile"); + + IntrudeOnTheMindEffect() { + super(Outcome.DrawCard); + staticText = "Reveal the top five cards of your library and separate them into two piles. " + + "An opponent chooses one of those piles. Put that pile into your hand and the other into your graveyard. " + + "Create a 0/0 colorless Thopter artifact creature token with flying, " + + "then put a +1/+1 counter on it for each card put into your graveyard this way."; + } + + private IntrudeOnTheMindEffect(final IntrudeOnTheMindEffect effect) { + super(effect); + } + + @Override + public IntrudeOnTheMindEffect copy() { + return new IntrudeOnTheMindEffect(this); + } + + /** + * Pile-choosing logic based on {@link mage.abilities.effects.common.RevealAndSeparatePilesEffect}. + */ + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + Cards allCards = new CardsImpl(controller.getLibrary().getTopCards(game, 5)); + Cards cards = allCards.copy(); + controller.revealCards(source, cards, game); + + TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY, filter); + List pile1 = new ArrayList<>(); + controller.choose(Outcome.Neutral, cards, target, source, game); + target.getTargets() + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .forEach(pile1::add); + cards.removeIf(target.getTargets()::contains); + List pile2 = new ArrayList<>(); + pile2.addAll(cards.getCards(game)); + + Player opponent = getOpponent(controller, game, source); + boolean choice = opponent.choosePile(outcome, "Choose a pile to put into hand.", pile1, pile2, game); + + Zone pile1Zone = choice ? Zone.HAND : Zone.GRAVEYARD; + Zone pile2Zone = choice ? Zone.GRAVEYARD : Zone.HAND; + + game.informPlayers("Pile 1, going to " + pile1Zone + ": " + (pile1.isEmpty() ? " (none)" : + pile1.stream().map(MageObject::getName).collect(Collectors.joining(", ")))); + cards.clear(); + cards.addAllCards(pile1); + controller.moveCards(cards, pile1Zone, source, game); + + game.informPlayers("Pile 2, going to " + pile2Zone + ": " + (pile2.isEmpty() ? " (none)" : + pile2.stream().map(MageObject::getName).collect(Collectors.joining(", ")))); + cards.clear(); + cards.addAllCards(pile2); + controller.moveCards(cards, pile2Zone, source, game); + + Token token = new ThopterColorlessToken2(); + token.putOntoBattlefield(1, game, source); + + allCards.retainZone(Zone.GRAVEYARD, game); + int count = allCards.size(); + if (count <= 0) { + return true; + } + for (UUID tokenId : token.getLastAddedTokenIds()) { + Permanent permanent = game.getPermanent(tokenId); + if (permanent == null) { + continue; + } + permanent.addCounters(CounterType.P1P1.createInstance(count), source.getControllerId(), source, game); + } + return true; + } + + private static Player getOpponent(Player controller, Game game, Ability source) { + Player opponent; + Set opponents = game.getOpponents(source.getControllerId()); + if (opponents.isEmpty()) { + return null; + } + if (opponents.size() == 1) { + opponent = game.getPlayer(opponents.iterator().next()); + } else { + Target targetOpponent = new TargetOpponent(true); + controller.chooseTarget(Outcome.Neutral, targetOpponent, source, game); + opponent = game.getPlayer(targetOpponent.getFirstTarget()); + if (opponent != null) { + game.informPlayers(controller.getLogName() + " chose " + opponent.getLogName() + " to choose the piles"); + } else { + game.informPlayers(controller.getLogName() + " chose nothing" + " to choose the piles"); + } + } + return opponent; + } +} diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java index ee9548ee5cf9..26fb9e14ea8f 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java @@ -133,6 +133,7 @@ private MurdersAtKarlovManor() { cards.add(new SetCardInfo("Innocent Bystander", 133, Rarity.COMMON, mage.cards.i.InnocentBystander.class)); cards.add(new SetCardInfo("Inside Source", 19, Rarity.COMMON, mage.cards.i.InsideSource.class)); cards.add(new SetCardInfo("Insidious Roots", 208, Rarity.UNCOMMON, mage.cards.i.InsidiousRoots.class)); + cards.add(new SetCardInfo("Intrude on the Mind", 61, Rarity.MYTHIC, mage.cards.i.IntrudeOnTheMind.class)); cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("It Doesn't Add Up", 89, Rarity.UNCOMMON, mage.cards.i.ItDoesntAddUp.class)); cards.add(new SetCardInfo("Izoni, Center of the Web", 209, Rarity.RARE, mage.cards.i.IzoniCenterOfTheWeb.class)); diff --git a/Mage/src/main/java/mage/game/permanent/token/ThopterColorlessToken2.java b/Mage/src/main/java/mage/game/permanent/token/ThopterColorlessToken2.java new file mode 100644 index 000000000000..175a807d4cd0 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/ThopterColorlessToken2.java @@ -0,0 +1,29 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +public class ThopterColorlessToken2 extends TokenImpl { + + public ThopterColorlessToken2() { + super("Thopter Token", "0/0 colorless Thopter artifact creature token with flying"); + cardType.add(CardType.ARTIFACT); + cardType.add(CardType.CREATURE); + subtype.add(SubType.THOPTER); + power = new MageInt(0); + toughness = new MageInt(0); + + addAbility(FlyingAbility.getInstance()); + } + + private ThopterColorlessToken2(final ThopterColorlessToken2 token) { + super(token); + } + + @Override + public ThopterColorlessToken2 copy() { + return new ThopterColorlessToken2(this); + } +} From dc613ca7086a0fe4792bfa0edb24599f25cf1a5f Mon Sep 17 00:00:00 2001 From: DominionSpy Date: Wed, 21 Feb 2024 10:12:38 +0200 Subject: [PATCH 2/3] Address comments --- Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java | 13 ++++++------- ...lessToken2.java => Thopter00ColorlessToken.java} | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) rename Mage/src/main/java/mage/game/permanent/token/{ThopterColorlessToken2.java => Thopter00ColorlessToken.java} (67%) diff --git a/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java b/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java index 4949aff5878f..b1d49bfdf421 100644 --- a/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java +++ b/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java @@ -22,7 +22,7 @@ import mage.filter.FilterCard; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.ThopterColorlessToken2; +import mage.game.permanent.token.Thopter00ColorlessToken; import mage.game.permanent.token.Token; import mage.players.Player; import mage.target.Target; @@ -101,6 +101,9 @@ public boolean apply(Game game, Ability source) { pile2.addAll(cards.getCards(game)); Player opponent = getOpponent(controller, game, source); + if (opponent == null) { + return false; + } boolean choice = opponent.choosePile(outcome, "Choose a pile to put into hand.", pile1, pile2, game); Zone pile1Zone = choice ? Zone.HAND : Zone.GRAVEYARD; @@ -118,7 +121,7 @@ public boolean apply(Game game, Ability source) { cards.addAllCards(pile2); controller.moveCards(cards, pile2Zone, source, game); - Token token = new ThopterColorlessToken2(); + Token token = new Thopter00ColorlessToken(); token.putOntoBattlefield(1, game, source); allCards.retainZone(Zone.GRAVEYARD, game); @@ -148,11 +151,7 @@ private static Player getOpponent(Player controller, Game game, Ability source) Target targetOpponent = new TargetOpponent(true); controller.chooseTarget(Outcome.Neutral, targetOpponent, source, game); opponent = game.getPlayer(targetOpponent.getFirstTarget()); - if (opponent != null) { - game.informPlayers(controller.getLogName() + " chose " + opponent.getLogName() + " to choose the piles"); - } else { - game.informPlayers(controller.getLogName() + " chose nothing" + " to choose the piles"); - } + game.informPlayers(controller.getLogName() + " chose " + opponent.getLogName() + " to choose the piles"); } return opponent; } diff --git a/Mage/src/main/java/mage/game/permanent/token/ThopterColorlessToken2.java b/Mage/src/main/java/mage/game/permanent/token/Thopter00ColorlessToken.java similarity index 67% rename from Mage/src/main/java/mage/game/permanent/token/ThopterColorlessToken2.java rename to Mage/src/main/java/mage/game/permanent/token/Thopter00ColorlessToken.java index 175a807d4cd0..dd5d8a3a99aa 100644 --- a/Mage/src/main/java/mage/game/permanent/token/ThopterColorlessToken2.java +++ b/Mage/src/main/java/mage/game/permanent/token/Thopter00ColorlessToken.java @@ -5,9 +5,9 @@ import mage.constants.CardType; import mage.constants.SubType; -public class ThopterColorlessToken2 extends TokenImpl { +public class Thopter00ColorlessToken extends TokenImpl { - public ThopterColorlessToken2() { + public Thopter00ColorlessToken() { super("Thopter Token", "0/0 colorless Thopter artifact creature token with flying"); cardType.add(CardType.ARTIFACT); cardType.add(CardType.CREATURE); @@ -18,12 +18,12 @@ public ThopterColorlessToken2() { addAbility(FlyingAbility.getInstance()); } - private ThopterColorlessToken2(final ThopterColorlessToken2 token) { + private Thopter00ColorlessToken(final Thopter00ColorlessToken token) { super(token); } @Override - public ThopterColorlessToken2 copy() { - return new ThopterColorlessToken2(this); + public Thopter00ColorlessToken copy() { + return new Thopter00ColorlessToken(this); } } From f01be3a1fec9304c6b65316989c4cf03586f9202 Mon Sep 17 00:00:00 2001 From: DominionSpy Date: Thu, 22 Feb 2024 08:06:14 +0200 Subject: [PATCH 3/3] Address comment --- Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java b/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java index b1d49bfdf421..bf55022d1409 100644 --- a/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java +++ b/Mage.Sets/src/mage/cards/i/IntrudeOnTheMind.java @@ -151,6 +151,9 @@ private static Player getOpponent(Player controller, Game game, Ability source) Target targetOpponent = new TargetOpponent(true); controller.chooseTarget(Outcome.Neutral, targetOpponent, source, game); opponent = game.getPlayer(targetOpponent.getFirstTarget()); + if (opponent == null) { + return null; + } game.informPlayers(controller.getLogName() + " chose " + opponent.getLogName() + " to choose the piles"); } return opponent;