From 6ee10ebc64afff5dd7431c929b79fb9dca761eac Mon Sep 17 00:00:00 2001 From: HT Rajan Date: Sat, 10 Apr 2021 21:43:36 -0700 Subject: [PATCH 1/4] [STX] Implement Ecological Appreciation --- .../mage/cards/e/EcologicalAppreciation.java | 148 ++++++++++++++++++ .../mage/sets/StrixhavenSchoolOfMages.java | 1 + 2 files changed, 149 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java diff --git a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java new file mode 100644 index 000000000000..618b86834da3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java @@ -0,0 +1,148 @@ +package mage.cards.e; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetOpponent; + +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * + * @author htrajan + */ +public final class EcologicalAppreciation extends CardImpl { + + public EcologicalAppreciation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{2}{G}"); + + // Search your library and graveyard for up to four creature cards with different names that each have mana value X or less and reveal them. An opponent chooses two of those cards. Shuffle the chosen cards into your library and put the rest onto the battlefield. Exile Ecological Appreciation. + this.getSpellAbility().addEffect(new EcologicalAppreciationEffect()); + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + } + + private EcologicalAppreciation(final EcologicalAppreciation card) { + super(card); + } + + @Override + public EcologicalAppreciation copy() { + return new EcologicalAppreciation(this); + } +} + +class EcologicalAppreciationEffect extends OneShotEffect { + + EcologicalAppreciationEffect() { + super(Outcome.Benefit); + staticText = "search your library and graveyard for up to four creature cards with different names that each have mana value X or less and reveal them. An opponent chooses two of those cards. Shuffle the chosen cards into your library and put the rest onto the battlefield"; + } + + private EcologicalAppreciationEffect(final EcologicalAppreciationEffect effect) { + super(effect); + } + + @Override + public EcologicalAppreciationEffect copy() { + return new EcologicalAppreciationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int xValue = source.getManaCostsToPay().getX(); + TargetCard targetCardsInLibrary = new EcologicalAppreciationTarget(Zone.LIBRARY, 4, xValue); + boolean searched = player.choose(Outcome.PutCreatureInPlay, new CardsImpl(player.getLibrary().getCards(game)), targetCardsInLibrary, game); + Cards cards = new CardsImpl(targetCardsInLibrary.getTargets()); + + if (cards.isEmpty()) { + if (searched) { + player.shuffleLibrary(source, game); + } + return false; + } + + int remainingCards = 4 - cards.size(); + if (remainingCards > 0) { + TargetCard targetCardsInGY = new EcologicalAppreciationTarget(Zone.GRAVEYARD, remainingCards, xValue); + player.choose(Outcome.PutCreatureInPlay, new CardsImpl(player.getGraveyard().getCards(game)), targetCardsInGY, game); + cards.addAll(targetCardsInGY.getTargets()); + } + + TargetOpponent targetOpponent = new TargetOpponent(); + targetOpponent.setNotTarget(true); + player.choose(outcome, targetOpponent, source.getSourceId(), game); + Player opponent = game.getPlayer(targetOpponent.getFirstTarget()); + if (opponent == null) { + if (searched) { + player.shuffleLibrary(source, game); + } + return false; + } + + TargetCard chosenCards = new TargetCard(2, Zone.ALL, StaticFilters.FILTER_CARD); + chosenCards.setNotTarget(true); + opponent.choose(outcome, cards, chosenCards, game); + Cards toShuffle = new CardsImpl(chosenCards.getTargets().stream() + .map(game::getCard) + .collect(Collectors.toList())); + + player.putCardsOnTopOfLibrary(toShuffle, game, source, false); + player.shuffleLibrary(source, game); + cards.removeAll(toShuffle); + + player.moveCards(cards, Zone.BATTLEFIELD, source, game); + + return true; + } +} + +class EcologicalAppreciationTarget extends TargetCard { + + private static final FilterCard filter = new FilterCreatureCard("creature cards with different names that each have mana value X or less"); + + private final int xValue; + + EcologicalAppreciationTarget(Zone zone, int maxTargets, int xValue) { + super(0, maxTargets, zone, filter); + this.xValue = xValue; + } + + private EcologicalAppreciationTarget(final EcologicalAppreciationTarget target) { + super(target); + this.xValue = target.xValue; + } + + @Override + public EcologicalAppreciationTarget copy() { + return new EcologicalAppreciationTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + Card card = game.getCard(id); + return card != null && card.getConvertedManaCost() <= xValue && + this.getTargets().stream() + .map(game::getCard) + .map(MageObject::getName) + .noneMatch(card.getName()::equals); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java b/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java index 22cf6340b7ea..b1d8a5d8915a 100644 --- a/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java +++ b/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java @@ -97,6 +97,7 @@ private StrixhavenSchoolOfMages() { cards.add(new SetCardInfo("Dream Strix", 42, Rarity.RARE, mage.cards.d.DreamStrix.class)); cards.add(new SetCardInfo("Dueling Coach", 15, Rarity.UNCOMMON, mage.cards.d.DuelingCoach.class)); cards.add(new SetCardInfo("Eager First-Year", 16, Rarity.COMMON, mage.cards.e.EagerFirstYear.class)); + cards.add(new SetCardInfo("Ecological Appreciation", 128, Rarity.MYTHIC, mage.cards.e.EcologicalAppreciation.class)); cards.add(new SetCardInfo("Elemental Expressionist", 181, Rarity.RARE, mage.cards.e.ElementalExpressionist.class)); cards.add(new SetCardInfo("Elemental Masterpiece", 182, Rarity.COMMON, mage.cards.e.ElementalMasterpiece.class)); cards.add(new SetCardInfo("Elemental Summoning", 183, Rarity.COMMON, mage.cards.e.ElementalSummoning.class)); From 58d03c90872e3a7b8df970dc9222b3ed575a8d4e Mon Sep 17 00:00:00 2001 From: HT Rajan Date: Sun, 11 Apr 2021 13:19:52 -0700 Subject: [PATCH 2/4] Additional fixes --- Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java | 2 ++ Mage.Sets/src/mage/cards/e/EmergentUltimatum.java | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java index 618b86834da3..87f9659c6b78 100644 --- a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java +++ b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java @@ -67,6 +67,7 @@ public boolean apply(Game game, Ability source) { } int xValue = source.getManaCostsToPay().getX(); TargetCard targetCardsInLibrary = new EcologicalAppreciationTarget(Zone.LIBRARY, 4, xValue); + targetCardsInLibrary.setNotTarget(true); boolean searched = player.choose(Outcome.PutCreatureInPlay, new CardsImpl(player.getLibrary().getCards(game)), targetCardsInLibrary, game); Cards cards = new CardsImpl(targetCardsInLibrary.getTargets()); @@ -80,6 +81,7 @@ public boolean apply(Game game, Ability source) { int remainingCards = 4 - cards.size(); if (remainingCards > 0) { TargetCard targetCardsInGY = new EcologicalAppreciationTarget(Zone.GRAVEYARD, remainingCards, xValue); + targetCardsInGY.setNotTarget(true); player.choose(Outcome.PutCreatureInPlay, new CardsImpl(player.getGraveyard().getCards(game)), targetCardsInGY, game); cards.addAll(targetCardsInGY.getTargets()); } diff --git a/Mage.Sets/src/mage/cards/e/EmergentUltimatum.java b/Mage.Sets/src/mage/cards/e/EmergentUltimatum.java index 5d5d980c496c..f971b7538e98 100644 --- a/Mage.Sets/src/mage/cards/e/EmergentUltimatum.java +++ b/Mage.Sets/src/mage/cards/e/EmergentUltimatum.java @@ -68,6 +68,7 @@ public boolean apply(Game game, Ability source) { return false; } TargetCardInLibrary targetCardInLibrary = new EmergentUltimatumTarget(); + targetCardInLibrary.setNotTarget(true); boolean searched = player.searchLibrary(targetCardInLibrary, source, game); Cards cards = new CardsImpl(targetCardInLibrary.getTargets()); player.moveCards(cards, Zone.EXILED, source, game); @@ -85,9 +86,10 @@ public boolean apply(Game game, Ability source) { if (searched) { player.shuffleLibrary(source, game); } - return true; + return false; } TargetCardInExile targetCardInExile = new TargetCardInExile(StaticFilters.FILTER_CARD); + targetCardInExile.setNotTarget(true); opponent.choose(outcome, cards, targetCardInExile, game); Card toShuffle = game.getCard(targetCardInExile.getFirstTarget()); if (toShuffle != null) { From d7e702910ee6ede2ca91547943cd83cbabaa7fd2 Mon Sep 17 00:00:00 2001 From: HT Rajan Date: Sun, 11 Apr 2021 22:12:40 -0700 Subject: [PATCH 3/4] Fix PR comments --- .../mage/cards/e/EcologicalAppreciation.java | 137 +++++++++--------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java index 87f9659c6b78..c92eadd8a3b2 100644 --- a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java +++ b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java @@ -1,21 +1,29 @@ package mage.cards.e; +import com.google.common.collect.Sets; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileSpellEffect; import mage.cards.*; import mage.constants.CardType; +import mage.constants.ComparisonType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; import mage.game.Game; import mage.players.Player; import mage.target.TargetCard; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetOpponent; +import mage.util.CardUtil; +import java.util.Objects; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -66,85 +74,84 @@ public boolean apply(Game game, Ability source) { return false; } int xValue = source.getManaCostsToPay().getX(); - TargetCard targetCardsInLibrary = new EcologicalAppreciationTarget(Zone.LIBRARY, 4, xValue); + FilterCard filter = new FilterCreatureCard(); + filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, xValue + 1)); + TargetCardInLibrary targetCardsInLibrary = new TargetCardInLibrary(0, 4, filter) { + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + Card card = game.getCard(id); + Set disallowedCards = this.getTargets().stream() + .map(game::getCard) + .collect(Collectors.toSet()); + return isValidTarget(card, disallowedCards); + } + }; targetCardsInLibrary.setNotTarget(true); - boolean searched = player.choose(Outcome.PutCreatureInPlay, new CardsImpl(player.getLibrary().getCards(game)), targetCardsInLibrary, game); + targetCardsInLibrary.withChooseHint("Step 1 of 2: Search library"); + player.choose(Outcome.PutCreatureInPlay, new CardsImpl(player.getLibrary().getCards(game)), targetCardsInLibrary, game); Cards cards = new CardsImpl(targetCardsInLibrary.getTargets()); - if (cards.isEmpty()) { - if (searched) { - player.shuffleLibrary(source, game); + boolean status = !cards.isEmpty(); + + if (status) { + int remainingCards = 4 - cards.size(); + if (remainingCards > 0) { + TargetCard targetCardsInGY = new TargetCardInGraveyard(0, remainingCards, filter) { + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + Card card = game.getCard(id); + Set disallowedCards = this.getTargets().stream() + .map(game::getCard) + .collect(Collectors.toSet()); + return isValidTarget(card, Sets.union(disallowedCards, cards.getCards(game))); + } + }; + targetCardsInGY.setNotTarget(true); + targetCardsInGY.withChooseHint("Step 2 of 2: Search graveyard"); + player.choose(Outcome.PutCreatureInPlay, new CardsImpl(player.getGraveyard().getCards(game)), targetCardsInGY, game); + cards.addAll(targetCardsInGY.getTargets()); } - return false; - } - int remainingCards = 4 - cards.size(); - if (remainingCards > 0) { - TargetCard targetCardsInGY = new EcologicalAppreciationTarget(Zone.GRAVEYARD, remainingCards, xValue); - targetCardsInGY.setNotTarget(true); - player.choose(Outcome.PutCreatureInPlay, new CardsImpl(player.getGraveyard().getCards(game)), targetCardsInGY, game); - cards.addAll(targetCardsInGY.getTargets()); - } + TargetOpponent targetOpponent = new TargetOpponent(); + targetOpponent.setNotTarget(true); + player.choose(outcome, targetOpponent, source.getSourceId(), game); + Player opponent = game.getPlayer(targetOpponent.getFirstTarget()); - TargetOpponent targetOpponent = new TargetOpponent(); - targetOpponent.setNotTarget(true); - player.choose(outcome, targetOpponent, source.getSourceId(), game); - Player opponent = game.getPlayer(targetOpponent.getFirstTarget()); - if (opponent == null) { - if (searched) { - player.shuffleLibrary(source, game); + if (opponent == null) { + status = false; } - return false; - } - TargetCard chosenCards = new TargetCard(2, Zone.ALL, StaticFilters.FILTER_CARD); - chosenCards.setNotTarget(true); - opponent.choose(outcome, cards, chosenCards, game); - Cards toShuffle = new CardsImpl(chosenCards.getTargets().stream() - .map(game::getCard) - .collect(Collectors.toList())); - - player.putCardsOnTopOfLibrary(toShuffle, game, source, false); - player.shuffleLibrary(source, game); - cards.removeAll(toShuffle); - - player.moveCards(cards, Zone.BATTLEFIELD, source, game); - - return true; - } -} + if (status) { + TargetCard chosenCards = new TargetCard(2, Zone.ALL, StaticFilters.FILTER_CARD); + chosenCards.setNotTarget(true); + opponent.choose(outcome, cards, chosenCards, game); + Cards toShuffle = new CardsImpl(chosenCards.getTargets().stream() + .map(game::getCard) + .collect(Collectors.toList())); -class EcologicalAppreciationTarget extends TargetCard { + player.putCardsOnTopOfLibrary(toShuffle, game, source, false); + cards.removeAll(toShuffle); - private static final FilterCard filter = new FilterCreatureCard("creature cards with different names that each have mana value X or less"); - - private final int xValue; - - EcologicalAppreciationTarget(Zone zone, int maxTargets, int xValue) { - super(0, maxTargets, zone, filter); - this.xValue = xValue; - } + player.moveCards(cards, Zone.BATTLEFIELD, source, game); + } + } - private EcologicalAppreciationTarget(final EcologicalAppreciationTarget target) { - super(target); - this.xValue = target.xValue; - } + player.shuffleLibrary(source, game); - @Override - public EcologicalAppreciationTarget copy() { - return new EcologicalAppreciationTarget(this); + return status; } - @Override - public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { - if (!super.canTarget(playerId, id, source, game)) { - return false; - } - Card card = game.getCard(id); - return card != null && card.getConvertedManaCost() <= xValue && - this.getTargets().stream() - .map(game::getCard) + private boolean isValidTarget(Card target, Set disallowedCards) { + return target != null && + disallowedCards.stream() + .filter(Objects::nonNull) .map(MageObject::getName) - .noneMatch(card.getName()::equals); + .noneMatch(name -> CardUtil.haveSameNames(name, target.getName())); } } \ No newline at end of file From 042b6dcb240f26d842a3b128b5f14a8ddbd658b4 Mon Sep 17 00:00:00 2001 From: HT Rajan Date: Sun, 11 Apr 2021 22:19:17 -0700 Subject: [PATCH 4/4] TargetCardInYOURGraveyard --- Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java index c92eadd8a3b2..03f4504155f1 100644 --- a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java +++ b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java @@ -17,8 +17,8 @@ import mage.game.Game; import mage.players.Player; import mage.target.TargetCard; -import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetOpponent; import mage.util.CardUtil; @@ -76,7 +76,7 @@ public boolean apply(Game game, Ability source) { int xValue = source.getManaCostsToPay().getX(); FilterCard filter = new FilterCreatureCard(); filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, xValue + 1)); - TargetCardInLibrary targetCardsInLibrary = new TargetCardInLibrary(0, 4, filter) { + TargetCard targetCardsInLibrary = new TargetCardInLibrary(0, 4, filter) { @Override public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { if (!super.canTarget(playerId, id, source, game)) { @@ -99,7 +99,7 @@ public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { if (status) { int remainingCards = 4 - cards.size(); if (remainingCards > 0) { - TargetCard targetCardsInGY = new TargetCardInGraveyard(0, remainingCards, filter) { + TargetCard targetCardsInGY = new TargetCardInYourGraveyard(0, remainingCards, filter) { @Override public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { if (!super.canTarget(playerId, id, source, game)) {