[NEO] Implemented Myojin of Cryptic Dreams and Myojin of Grim Betrayal (#8680)

* - Implemented Myojin of Cryptic Dreams and Myojin of Grim Betrayal.
- Updated CardsPutIntoGraveyardWatcher to keep track of all cards that entered the graveyard.
- Added documentation to CardsPutIntoGraveyardWatcher.

* Fixed add indestructible counter ability
This commit is contained in:
Alex Vasile 2022-02-10 09:23:13 -05:00 committed by GitHub
parent 8010ce50e4
commit 3709b5c098
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 297 additions and 28 deletions

View file

@ -64,15 +64,14 @@ class CryOfTheCarnariumExileEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { 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); CardsPutIntoGraveyardWatcher watcher = game.getState().getWatcher(CardsPutIntoGraveyardWatcher.class);
if (player == null || watcher == null) { if (controller == null || watcher == null) { return false; }
return false;
} Cards cards = new CardsImpl(watcher.getCardsPutIntoGraveyardFromBattlefield(game));
Cards cards = new CardsImpl(watcher.getCardsPutToGraveyardFromBattlefield(game));
cards.removeIf(uuid -> !game.getCard(uuid).isCreature(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);
} }
} }

View file

@ -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. <i>(The copies become tokens.)</i>"),
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<StackObject> {
instance;
@Override
public boolean apply(StackObject input, Game game) {
return input.isPermanent(game);
}
}

View file

@ -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); }
}

View file

@ -67,17 +67,12 @@ class ThrillingEncoreEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller == null) { CardsPutIntoGraveyardWatcher watcher = game.getState().getWatcher(CardsPutIntoGraveyardWatcher.class);
return false; if (controller == null || watcher == null) { return false; }
}
Cards cards = new CardsImpl(); Cards cards = new CardsImpl(watcher.getCardsPutIntoGraveyardFromBattlefield(game));
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { cards.removeIf(uuid -> !game.getCard(uuid).isCreature(game));
Player player = game.getPlayer(playerId);
if (player == null) {
continue;
}
cards.addAll(player.getGraveyard().getCards(filter, source.getSourceId(), playerId, game));
}
return controller.moveCards(cards, Zone.BATTLEFIELD, source, game); return controller.moveCards(cards, Zone.BATTLEFIELD, source, game);
} }
} }

View file

@ -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("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("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 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 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("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)); cards.add(new SetCardInfo("Myrsmith", 86, Rarity.UNCOMMON, mage.cards.m.Myrsmith.class));

View file

@ -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<Card> {
instance;
@Override
public boolean apply(Card input, Game game) {
CardsPutIntoGraveyardWatcher watcher = game.getState().getWatcher(CardsPutIntoGraveyardWatcher.class);
return watcher != null && watcher.checkCardFromAnywhere(input, game);
}
}

View file

@ -13,16 +13,20 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* Counts amount of cards put into graveyards of players during the current * Counts how many cards are put into each player's graveyard this turn.
* turn. Also the UUIDs of cards that went to graveyard from Battlefield this * Keeps track of the UUIDs of the cards that went to graveyard this turn.
* turn. * from the battlefield, from anywhere other both from anywhere and from only the battlefield.
* *
* @author LevelX2 * @author LevelX2
*/ */
public class CardsPutIntoGraveyardWatcher extends Watcher { public class CardsPutIntoGraveyardWatcher extends Watcher {
// Number of cards that have entered each players graveyards
private final Map<UUID, Integer> amountOfCardsThisTurn = new HashMap<>(); private final Map<UUID, Integer> amountOfCardsThisTurn = new HashMap<>();
private final Set<MageObjectReference> cardsPutToGraveyardFromBattlefield = new HashSet<>(); // UUID of cards that entered the graveyard from the battlefield
private final Set<MageObjectReference> cardsPutIntoGraveyardFromBattlefield = new HashSet<>();
// UUID of cards that entered the graveyard from everywhere other than the battlefield
private final Set<MageObjectReference> cardsPutIntoGraveyardFromEverywhereElse = new HashSet<>();
public CardsPutIntoGraveyardWatcher() { public CardsPutIntoGraveyardWatcher() {
super(WatcherScope.GAME); super(WatcherScope.GAME);
@ -34,33 +38,102 @@ public class CardsPutIntoGraveyardWatcher extends Watcher {
|| ((ZoneChangeEvent) event).getToZone() != Zone.GRAVEYARD) { || ((ZoneChangeEvent) event).getToZone() != Zone.GRAVEYARD) {
return; return;
} }
UUID playerId = event.getPlayerId(); UUID playerId = event.getPlayerId();
if (playerId == null || game.getCard(event.getTargetId()) == null) { if (playerId == null || game.getCard(event.getTargetId()) == null) {
return; return;
} }
amountOfCardsThisTurn.compute(playerId, (k, amount) -> amount == null ? 1 : Integer.sum(amount, 1)); amountOfCardsThisTurn.compute(playerId, (k, amount) -> amount == null ? 1 : Integer.sum(amount, 1));
if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) { 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) { public int getAmountCardsPutToGraveyard(UUID playerId) {
return amountOfCardsThisTurn.getOrDefault(playerId, 0); return amountOfCardsThisTurn.getOrDefault(playerId, 0);
} }
public Set<Card> 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<Card> 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<Card> 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<Card> getCardsPutIntoGraveyardFromAnywhere(Game game) {
Set<Card> 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) { 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 @Override
public void reset() { public void reset() {
super.reset(); super.reset();
amountOfCardsThisTurn.clear(); amountOfCardsThisTurn.clear();
cardsPutToGraveyardFromBattlefield.clear(); cardsPutIntoGraveyardFromBattlefield.clear();
cardsPutIntoGraveyardFromEverywhereElse.clear();
} }
} }