diff --git a/Mage.Sets/src/mage/cards/c/CryOfTheCarnarium.java b/Mage.Sets/src/mage/cards/c/CryOfTheCarnarium.java index ba9584e3488..0079b741b7d 100644 --- a/Mage.Sets/src/mage/cards/c/CryOfTheCarnarium.java +++ b/Mage.Sets/src/mage/cards/c/CryOfTheCarnarium.java @@ -64,15 +64,14 @@ class CryOfTheCarnariumExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); + Player controller = game.getPlayer(source.getControllerId()); CardsPutIntoGraveyardWatcher watcher = game.getState().getWatcher(CardsPutIntoGraveyardWatcher.class); - if (player == null || watcher == null) { - return false; - } - Cards cards = new CardsImpl(watcher.getCardsPutToGraveyardFromBattlefield(game)); + if (controller == null || watcher == null) { return false; } + + Cards cards = new CardsImpl(watcher.getCardsPutIntoGraveyardFromBattlefield(game)); cards.removeIf(uuid -> !game.getCard(uuid).isCreature(game)); - player.moveCards(cards, Zone.EXILED, source, game); - return true; + + return controller.moveCards(cards, Zone.EXILED, source, game); } } diff --git a/Mage.Sets/src/mage/cards/m/MyojinOfCrypticDreams.java b/Mage.Sets/src/mage/cards/m/MyojinOfCrypticDreams.java new file mode 100644 index 00000000000..da089a8c8c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MyojinOfCrypticDreams.java @@ -0,0 +1,79 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.CastFromHandSourcePermanentCondition; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.stack.StackObject; +import mage.target.TargetSpell; +import mage.watchers.common.CastFromHandWatcher; + +import java.util.UUID; + +/** + * @author Alex-Vasile + */ +public class MyojinOfCrypticDreams extends CardImpl { + + private static final FilterSpell permanentSpellFilter = new FilterSpell("permanent spell you control"); + static { + permanentSpellFilter.add(TargetController.YOU.getControllerPredicate()); + permanentSpellFilter.add(MyojinOfCrypticDreamsPredicate.instance); + } + + public MyojinOfCrypticDreams(UUID ownderId, CardSetInfo setInfo) { + super(ownderId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Myojin of Cryptic Dreams enters the battlefield with an indestructible counter on it if you cast it from your hand. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.INDESTRUCTIBLE.createInstance()), + CastFromHandSourcePermanentCondition.instance, null, + "with an indestructible counter on it if you cast it from your hand" + ), new CastFromHandWatcher()); + + // Remove an indestructible counter from Myojin of Cryptic Dreams: + // Copy target permanent spell you control three times. (The copies become tokens.) + Ability ability = new SimpleActivatedAbility( + new CopyTargetSpellEffect(false, false, false) + .setText("Copy target permanent spell you control three times. (The copies become tokens.)"), + new RemoveCountersSourceCost(CounterType.INDESTRUCTIBLE.createInstance()) + ); + ability.addEffect(new CopyTargetSpellEffect(false, false, false).setText(" ")); + ability.addEffect(new CopyTargetSpellEffect(false, false, false).setText(" ")); + ability.addTarget(new TargetSpell(permanentSpellFilter)); + this.addAbility(ability); + } + + private MyojinOfCrypticDreams(final MyojinOfCrypticDreams card) { super(card); } + + @Override + public MyojinOfCrypticDreams copy() { return new MyojinOfCrypticDreams(this); } +} + +enum MyojinOfCrypticDreamsPredicate implements Predicate { + instance; + + @Override + public boolean apply(StackObject input, Game game) { + return input.isPermanent(game); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MyojinOfGrimBetrayal.java b/Mage.Sets/src/mage/cards/m/MyojinOfGrimBetrayal.java new file mode 100644 index 00000000000..21cc2a0feb6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MyojinOfGrimBetrayal.java @@ -0,0 +1,101 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.CastFromHandSourcePermanentCondition; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInAllGraveyardsCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.card.PutIntoGraveFromAnywhereThisTurnPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.watchers.common.CardsPutIntoGraveyardWatcher; +import mage.watchers.common.CastFromHandWatcher; + +import java.util.UUID; + +/** +* @author Alex-Vasile +*/ +public class MyojinOfGrimBetrayal extends CardImpl { + + private static final FilterCreatureCard filter = new FilterCreatureCard(); + static { filter.add(PutIntoGraveFromAnywhereThisTurnPredicate.instance); } + private static final DynamicValue xValue = new CardsInAllGraveyardsCount(filter); + private static final Hint hint = new ValueHint("Permanents put into the graveyard this turn", xValue); + + public MyojinOfGrimBetrayal(UUID ownderId, CardSetInfo setInfo) { + super(ownderId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}{B}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(5); + this.toughness = new MageInt(2); + + // Myojin of Grim Betrayal enters the battlefield with an indestructible counter on it if you cast it from your hand. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.INDESTRUCTIBLE.createInstance()), + CastFromHandSourcePermanentCondition.instance, null, + "with an indestructible counter on it if you cast it from your hand" + ), new CastFromHandWatcher()); + + // Remove an indestructible counter from Myojin of Grim Betrayal: + // Put onto the battlefield under your control all creature cards in all graveyards that were put there from anywhere this turn. + Ability ability = new SimpleActivatedAbility( + new MyojinOfGrimBetrayalEffect(filter), + new RemoveCountersSourceCost(CounterType.INDESTRUCTIBLE.createInstance()) + ).addHint(hint); + ability.addWatcher(new CardsPutIntoGraveyardWatcher()); + this.addAbility(ability); + } + + private MyojinOfGrimBetrayal(final MyojinOfGrimBetrayal card) { super(card); } + + @Override + public MyojinOfGrimBetrayal copy() {return new MyojinOfGrimBetrayal(this); } +} + +class MyojinOfGrimBetrayalEffect extends OneShotEffect { + + private final FilterCreatureCard filter; + + MyojinOfGrimBetrayalEffect(FilterCreatureCard filter) { + super(Outcome.PutCardInPlay); + this.filter = filter; + this.staticText = "Put onto the battlefield under your control all creature cards in all graveyards " + + "that were put there from anywhere this turn"; + } + + private MyojinOfGrimBetrayalEffect(final MyojinOfGrimBetrayalEffect effect) { + super(effect); + this.filter = effect.filter; + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + CardsPutIntoGraveyardWatcher watcher = game.getState().getWatcher(CardsPutIntoGraveyardWatcher.class); + if (controller == null || watcher == null) { return false; } + + Cards cards = new CardsImpl(watcher.getCardsPutIntoGraveyardFromBattlefield(game)); + cards.removeIf(uuid -> !game.getCard(uuid).isCreature(game)); + + return controller.moveCards(cards, Zone.BATTLEFIELD, source, game); + } + + @Override + public MyojinOfGrimBetrayalEffect copy() { return new MyojinOfGrimBetrayalEffect(this); } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/ThrillingEncore.java b/Mage.Sets/src/mage/cards/t/ThrillingEncore.java index 865b21b9ea5..81e837ea137 100644 --- a/Mage.Sets/src/mage/cards/t/ThrillingEncore.java +++ b/Mage.Sets/src/mage/cards/t/ThrillingEncore.java @@ -67,17 +67,12 @@ class ThrillingEncoreEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - Cards cards = new CardsImpl(); - for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { - Player player = game.getPlayer(playerId); - if (player == null) { - continue; - } - cards.addAll(player.getGraveyard().getCards(filter, source.getSourceId(), playerId, game)); - } + CardsPutIntoGraveyardWatcher watcher = game.getState().getWatcher(CardsPutIntoGraveyardWatcher.class); + if (controller == null || watcher == null) { return false; } + + Cards cards = new CardsImpl(watcher.getCardsPutIntoGraveyardFromBattlefield(game)); + cards.removeIf(uuid -> !game.getCard(uuid).isCreature(game)); + return controller.moveCards(cards, Zone.BATTLEFIELD, source, game); } } diff --git a/Mage.Sets/src/mage/sets/NeonDynastyCommander.java b/Mage.Sets/src/mage/sets/NeonDynastyCommander.java index 0a569ce25a3..33a97a80050 100644 --- a/Mage.Sets/src/mage/sets/NeonDynastyCommander.java +++ b/Mage.Sets/src/mage/sets/NeonDynastyCommander.java @@ -77,6 +77,8 @@ public final class NeonDynastyCommander extends ExpansionSet { cards.add(new SetCardInfo("Mirage Mirror", 154, Rarity.RARE, mage.cards.m.MirageMirror.class)); cards.add(new SetCardInfo("Mossfire Valley", 171, Rarity.RARE, mage.cards.m.MossfireValley.class)); cards.add(new SetCardInfo("Myojin of Blooming Dawn", 31, Rarity.RARE, mage.cards.m.MyojinOfBloomingDawn.class)); + cards.add(new SetCardInfo("Myojin of Cryptic Dreams", 33, Rarity.RARE, mage.cards.m.MyojinOfCrypticDreams.class)); + cards.add(new SetCardInfo("Myojin of Grim Betrayal", 34, Rarity.RARE, mage.cards.m.MyojinOfGrimBetrayal.class)); cards.add(new SetCardInfo("Myojin of Roaring Blades", 36, Rarity.RARE, mage.cards.m.MyojinOfRoaringBlades.class)); cards.add(new SetCardInfo("Myojin of Towering Might", 38, Rarity.RARE, mage.cards.m.MyojinOfToweringMight.class)); cards.add(new SetCardInfo("Myrsmith", 86, Rarity.UNCOMMON, mage.cards.m.Myrsmith.class)); diff --git a/Mage/src/main/java/mage/filter/predicate/card/PutIntoGraveFromAnywhereThisTurnPredicate.java b/Mage/src/main/java/mage/filter/predicate/card/PutIntoGraveFromAnywhereThisTurnPredicate.java new file mode 100644 index 00000000000..a6fdaab53d7 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/card/PutIntoGraveFromAnywhereThisTurnPredicate.java @@ -0,0 +1,20 @@ +package mage.filter.predicate.card; + +import mage.cards.Card; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.watchers.common.CardsPutIntoGraveyardWatcher; + +/** + * @author Alex-Vasile + */ +public enum PutIntoGraveFromAnywhereThisTurnPredicate implements Predicate { + instance; + + @Override + public boolean apply(Card input, Game game) { + CardsPutIntoGraveyardWatcher watcher = game.getState().getWatcher(CardsPutIntoGraveyardWatcher.class); + + return watcher != null && watcher.checkCardFromAnywhere(input, game); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/CardsPutIntoGraveyardWatcher.java b/Mage/src/main/java/mage/watchers/common/CardsPutIntoGraveyardWatcher.java index 75ed33cdb59..2c3211c1daa 100644 --- a/Mage/src/main/java/mage/watchers/common/CardsPutIntoGraveyardWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CardsPutIntoGraveyardWatcher.java @@ -13,16 +13,20 @@ import java.util.*; import java.util.stream.Collectors; /** - * Counts amount of cards put into graveyards of players during the current - * turn. Also the UUIDs of cards that went to graveyard from Battlefield this - * turn. + * Counts how many cards are put into each player's graveyard this turn. + * Keeps track of the UUIDs of the cards that went to graveyard this turn. + * from the battlefield, from anywhere other both from anywhere and from only the battlefield. * * @author LevelX2 */ public class CardsPutIntoGraveyardWatcher extends Watcher { + // Number of cards that have entered each players graveyards private final Map amountOfCardsThisTurn = new HashMap<>(); - private final Set cardsPutToGraveyardFromBattlefield = new HashSet<>(); + // UUID of cards that entered the graveyard from the battlefield + private final Set cardsPutIntoGraveyardFromBattlefield = new HashSet<>(); + // UUID of cards that entered the graveyard from everywhere other than the battlefield + private final Set cardsPutIntoGraveyardFromEverywhereElse = new HashSet<>(); public CardsPutIntoGraveyardWatcher() { super(WatcherScope.GAME); @@ -34,33 +38,102 @@ public class CardsPutIntoGraveyardWatcher extends Watcher { || ((ZoneChangeEvent) event).getToZone() != Zone.GRAVEYARD) { return; } + UUID playerId = event.getPlayerId(); if (playerId == null || game.getCard(event.getTargetId()) == null) { return; } + amountOfCardsThisTurn.compute(playerId, (k, amount) -> amount == null ? 1 : Integer.sum(amount, 1)); + if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) { - cardsPutToGraveyardFromBattlefield.add(new MageObjectReference(((ZoneChangeEvent) event).getTarget(), game, 1)); + cardsPutIntoGraveyardFromBattlefield.add(new MageObjectReference(((ZoneChangeEvent) event).getTarget(), game, 1)); + } else { + cardsPutIntoGraveyardFromEverywhereElse.add(new MageObjectReference(((ZoneChangeEvent) event).getTarget(), game, 1)); } } + /** + * The number of cards that were put into a specific player's graveyard this turn. + * + * @param playerId The player's UUID. + * @return The number of cards. + */ public int getAmountCardsPutToGraveyard(UUID playerId) { return amountOfCardsThisTurn.getOrDefault(playerId, 0); } - public Set getCardsPutToGraveyardFromBattlefield(Game game) { - return cardsPutToGraveyardFromBattlefield.stream().map(mor -> mor.getCard(game)).filter(Objects::nonNull).collect(Collectors.toSet()); + /** + * The cards put into any graveyard from the battelfield this turn. + * + * @param game The game to check for. + * @return A set containing the card objects. + */ + public Set getCardsPutIntoGraveyardFromBattlefield(Game game) { + return cardsPutIntoGraveyardFromBattlefield.stream().map(mor -> mor.getCard(game)).filter(Objects::nonNull).collect(Collectors.toSet()); } + /** + * The cards put into any graveyard from anywhere other than the battelfield this turn. + * + * @param game The game to check for. + * @return A set containing the card objects. + */ + public Set getCardsPutIntoGraveyardNotFromBattlefield(Game game) { + return cardsPutIntoGraveyardFromEverywhereElse.stream().map(mor -> mor.getCard(game)).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + /** + * The cards put into any graveyard from anywhere this turn. + * + * @param game The game to check for. + * @return A set containing the card objects. + */ + public Set getCardsPutIntoGraveyardFromAnywhere(Game game) { + Set cardsPutIntoGraveyardFromAnywhere = getCardsPutIntoGraveyardFromBattlefield(game); + cardsPutIntoGraveyardFromAnywhere.addAll(getCardsPutIntoGraveyardNotFromBattlefield(game)); + + return cardsPutIntoGraveyardFromAnywhere; + } + + /** + * Check if the passed card was put into the graveyard from the battlefield this turn. + * + * @param card The card to check. + * @param game The game to check for. + * @return Boolean indicating if the card was put into the graveyard from the battlefield this turn. + */ public boolean checkCardFromBattlefield(Card card, Game game) { - return cardsPutToGraveyardFromBattlefield.stream().anyMatch(mor -> mor.refersTo(card, game)); + return cardsPutIntoGraveyardFromBattlefield.stream().anyMatch(mor -> mor.refersTo(card, game)); + } + + /** + * Check if the passed card was put into the graveyard from anywhere other than the battlefield this turn. + * + * @param card The card to check. + * @param game The game to check for. + * @return Boolean indicating if the card was put into the graveyard from anywhere other than the battlefield this turn. + */ + public boolean checkCardNotFromBattlefield(Card card, Game game) { + return cardsPutIntoGraveyardFromEverywhereElse.stream().anyMatch(mor -> mor.refersTo(card, game)); + } + + /** + * Check if the passed card was put into the graveyard from anywhere this turn. + * + * @param card The card to check. + * @param game The game to check for. + * @return Boolean indicating if the card was put into the graveyard from anywhere this turn. + */ + public boolean checkCardFromAnywhere(Card card, Game game) { + return checkCardFromBattlefield(card, game) || checkCardNotFromBattlefield(card, game); } @Override public void reset() { super.reset(); amountOfCardsThisTurn.clear(); - cardsPutToGraveyardFromBattlefield.clear(); + cardsPutIntoGraveyardFromBattlefield.clear(); + cardsPutIntoGraveyardFromEverywhereElse.clear(); } - }