From be6dd5263d361fa336cceecc6f3990f2260331f1 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 4 Apr 2022 19:27:14 -0400 Subject: [PATCH] [SNC] Implemented Riveteers Charm --- .../src/mage/cards/r/RaffineSchemingSeer.java | 4 +- .../src/mage/cards/r/RiveteersCharm.java | 130 ++++++++++++++++++ Mage.Sets/src/mage/cards/s/SoulShatter.java | 32 +---- .../src/mage/sets/StreetsOfNewCapenna.java | 1 + ...ManaValueControlledPermanentPredicate.java | 37 +++++ Utils/mtg-cards-data.txt | 3 +- 6 files changed, 174 insertions(+), 33 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/r/RiveteersCharm.java create mode 100644 Mage/src/main/java/mage/filter/predicate/permanent/MaxManaValueControlledPermanentPredicate.java diff --git a/Mage.Sets/src/mage/cards/r/RaffineSchemingSeer.java b/Mage.Sets/src/mage/cards/r/RaffineSchemingSeer.java index f7d0244d6cc..2bc15b768b9 100644 --- a/Mage.Sets/src/mage/cards/r/RaffineSchemingSeer.java +++ b/Mage.Sets/src/mage/cards/r/RaffineSchemingSeer.java @@ -16,7 +16,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetAttackingCreature; import java.util.UUID; @@ -45,7 +45,7 @@ public final class RaffineSchemingSeer extends CardImpl { // Whenever you attack, target creature connives X, where X is the number of attacking creatures. Ability ability = new AttacksWithCreaturesTriggeredAbility(new ConniveTargetEffect(xValue), 1); - ability.addTarget(new TargetCreaturePermanent()); + ability.addTarget(new TargetAttackingCreature()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/RiveteersCharm.java b/Mage.Sets/src/mage/cards/r/RiveteersCharm.java new file mode 100644 index 00000000000..f81d402cc3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiveteersCharm.java @@ -0,0 +1,130 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.condition.Condition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileGraveyardAllTargetPlayerEffect; +import mage.abilities.effects.common.SacrificeEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.predicate.permanent.MaxManaValueControlledPermanentPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RiveteersCharm extends CardImpl { + + private static final FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent( + "creature or planeswalker they control with the highest mana value " + + "among creatures and planeswalkers they control" + ); + + static { + filter.add(MaxManaValueControlledPermanentPredicate.instance); + } + + public RiveteersCharm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}{R}{G}"); + + // Choose one — + // • Target opponent sacrifices a creature or planeswalker they control with the highest mana value among creatures and planeswalkers they control. + this.getSpellAbility().addEffect(new SacrificeEffect(filter, 1, "target opponent")); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // • Exile the top three cards of your library. Until your next end step, you may play those cards. + this.getSpellAbility().addMode(new Mode(new RiveteersCharmEffect())); + this.getSpellAbility().addWatcher(new RiveteersCharmWatcher()); + + // • Exile target player's graveyard. + this.getSpellAbility().addMode(new Mode(new ExileGraveyardAllTargetPlayerEffect()).addTarget(new TargetPlayer())); + } + + private RiveteersCharm(final RiveteersCharm card) { + super(card); + } + + @Override + public RiveteersCharm copy() { + return new RiveteersCharm(this); + } +} + +class RiveteersCharmEffect extends OneShotEffect { + + RiveteersCharmEffect() { + super(Outcome.Benefit); + staticText = "exile the top three cards of your library. Until your next end step, you may play those cards"; + } + + private RiveteersCharmEffect(final RiveteersCharmEffect effect) { + super(effect); + } + + @Override + public RiveteersCharmEffect copy() { + return new RiveteersCharmEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 3)); + if (cards.isEmpty()) { + return false; + } + player.moveCards(cards, Zone.EXILED, source, game); + int count = RiveteersCharmWatcher.getCount(game, source); + Condition condition = (g, s) -> RiveteersCharmWatcher.getCount(g, s) == count; + for (Card card : cards.getCards(game)) { + CardUtil.makeCardPlayable(game, source, card, Duration.Custom, false, null, condition); + } + return true; + } +} + +class RiveteersCharmWatcher extends Watcher { + + private final Map playerMap = new HashMap<>(); + + RiveteersCharmWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case END_TURN_STEP_PRE: + playerMap.compute(game.getActivePlayerId(), CardUtil::setOrIncrementValue); + return; + case BEGINNING_PHASE_PRE: + if (game.getTurnNum() == 1) { + playerMap.clear(); + } + } + } + + static int getCount(Game game, Ability source) { + return game + .getState() + .getWatcher(RiveteersCharmWatcher.class) + .playerMap + .getOrDefault(source.getControllerId(), 0); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SoulShatter.java b/Mage.Sets/src/mage/cards/s/SoulShatter.java index b690afb3380..77030b36cd0 100644 --- a/Mage.Sets/src/mage/cards/s/SoulShatter.java +++ b/Mage.Sets/src/mage/cards/s/SoulShatter.java @@ -1,19 +1,13 @@ package mage.cards.s; -import mage.MageObject; import mage.abilities.effects.common.SacrificeOpponentsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; -import mage.filter.predicate.ObjectSourcePlayer; -import mage.filter.predicate.ObjectSourcePlayerPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.filter.predicate.permanent.MaxManaValueControlledPermanentPredicate; -import java.util.Objects; import java.util.UUID; /** @@ -27,7 +21,7 @@ public final class SoulShatter extends CardImpl { ); static { - filter.add(SoulShatterPredicate.instance); + filter.add(MaxManaValueControlledPermanentPredicate.instance); } public SoulShatter(UUID ownerId, CardSetInfo setInfo) { @@ -46,25 +40,3 @@ public final class SoulShatter extends CardImpl { return new SoulShatter(this); } } - -enum SoulShatterPredicate implements ObjectSourcePlayerPredicate { - instance; - - private static final FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent(); - - static { - filter.add(TargetController.YOU.getControllerPredicate()); - } - - @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - int cmc = game.getBattlefield() - .getActivePermanents(filter, input.getPlayerId(), game) - .stream() - .filter(Objects::nonNull) - .mapToInt(MageObject::getManaValue) - .max() - .orElse(0); - return input.getObject().getManaValue() >= cmc; - } -} diff --git a/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java b/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java index 00effc60eaa..ea8271c388e 100644 --- a/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java +++ b/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java @@ -34,6 +34,7 @@ public final class StreetsOfNewCapenna extends ExpansionSet { cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Raffine's Tower", 254, Rarity.RARE, mage.cards.r.RaffinesTower.class)); cards.add(new SetCardInfo("Raffine, Scheming Seer", 213, Rarity.MYTHIC, mage.cards.r.RaffineSchemingSeer.class)); + cards.add(new SetCardInfo("Riveteers Charm", 217, Rarity.UNCOMMON, mage.cards.r.RiveteersCharm.class)); cards.add(new SetCardInfo("Spara's Headquarters", 257, Rarity.RARE, mage.cards.s.SparasHeadquarters.class)); cards.add(new SetCardInfo("Swamp", 276, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Xander's Lounge", 260, Rarity.RARE, mage.cards.x.XandersLounge.class)); diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/MaxManaValueControlledPermanentPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/MaxManaValueControlledPermanentPredicate.java new file mode 100644 index 00000000000..bcb91d8a465 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/MaxManaValueControlledPermanentPredicate.java @@ -0,0 +1,37 @@ +package mage.filter.predicate.permanent; + +import mage.MageObject; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.Objects; + +/** + * @author TheElk801 + */ +public enum MaxManaValueControlledPermanentPredicate implements ObjectSourcePlayerPredicate { + instance; + + private static final FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent(); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + int cmc = game.getBattlefield() + .getActivePermanents(filter, input.getPlayerId(), input.getSource(), game) + .stream() + .filter(Objects::nonNull) + .mapToInt(MageObject::getManaValue) + .max() + .orElse(0); + return input.getObject().getManaValue() >= cmc; + } +} diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 326699f2b10..c417bce53cb 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -43923,7 +43923,8 @@ Jetmir, Nexus of Revels|Streets of New Capenna|193|M|{1}{R}{G}{W}|Legendary Crea Lord Xander, the Collector|Streets of New Capenna|197|M|{4}{U}{B}{R}|Legendary Creature - Vampire Demon Noble|6|6|When Lord Xander, the Collector enters the battlefield, target opponent discards half the cards in their hand, rounded down.$Whenever Lord Xander attacks, defending player mills half their library, rounded down.$When Lord Xander dies, target opponent sacrifices half the nonland permanents they control, rounded down.| Maestros Charm|Streets of New Capenna|199|U|{U}{B}{R}|Instant|||Choose one —$• Look at the top five cards of your library. Put one of those cards into your hand and the rest into your graveyard.$• Each opponent loses 3 life and you gain 3 life.$• Maestros Charm deals 5 damage to target creature or planeswalker.| Obscura Charm|Streets of New Capenna|208|U|{W}{U}{B}|Instant|||Choose one —$• Return target multicolored permanent card with mana value 3 or less from your graveyard to the battlefield tapped.$• Counter target instant or sorcery spell.$• Destroy target creature or planeswalker with mana value 3 or less.| -Raffine, Scheming Seer|Streets of New Capenna|213|M|{W}{U}{B}|Legendary Creature - Sphinx Demon|1|4|Flying, ward {1}$Whenever you attack, target creature connives X, where X is the number of attacking creatures.| +Raffine, Scheming Seer|Streets of New Capenna|213|M|{W}{U}{B}|Legendary Creature - Sphinx Demon|1|4|Flying, ward {1}$Whenever you attack, target attacking creature connives X, where X is the number of attacking creatures.| +Riveteers Charm|Streets of New Capenna|217|U|{B}{R}{G}|Instant|||Choose one —$• Target opponent sacrifices a creature or planeswalker they control with the highest mana value among creatures and planeswalkers they control.$• Exile the top three cards of your library. Until your next end step, you may play those cards.$• Exile target player's graveyard.| Jetmir's Garden|Streets of New Capenna|250|R||Land - Mountain Forest Plains|||({T}: Add {R}, {G}, or {W}.)$Jetmir's Garden enters the battlefield tapped.$Cycling {3}| Raffine's Tower|Streets of New Capenna|254|R||Land - Plains Island Swamp|||({T}: Add {W}, {U}, or {B}.)$Raffine's Tower enters the battlefield tapped.$Cycling {3}| Spara's Headquarters|Streets of New Capenna|257|R||Land - Forest Plains Island|||({T}: Add {G}, {W}, or {U}.)$Spara's Headquarters enters the battlefield tapped.$Cycling {3}|