rework Ghastly Conscription and Jeskai Infiltrator to common manifest code (#11898)

This commit is contained in:
xenohedron 2024-03-03 21:16:46 -05:00 committed by GitHub
parent 1c38644a5d
commit 9893032a36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 85 additions and 87 deletions

View file

@ -1,18 +1,12 @@
package mage.cards.g; package mage.cards.g;
import java.util.*;
import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.keyword.ManifestEffect;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
@ -20,6 +14,8 @@ import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import java.util.*;
/** /**
* *
* @author LevelX2 * @author LevelX2
@ -48,7 +44,10 @@ class GhastlyConscriptionEffect extends OneShotEffect {
GhastlyConscriptionEffect() { GhastlyConscriptionEffect() {
super(Outcome.PutCreatureInPlay); super(Outcome.PutCreatureInPlay);
this.staticText = "Exile all creature cards from target player's graveyard in a face-down pile, shuffle that pile, then manifest those cards.<i> (To manifest a card, put it onto the battlefield face down as a 2/2 creature. Turn it face up at any time for its mana cost if it's a creature card.)</i>"; this.staticText = "Exile all creature cards from target player's graveyard in a face-down pile, " +
"shuffle that pile, then manifest those cards. " +
"<i>(To manifest a card, put it onto the battlefield face down as a 2/2 creature. " +
"Turn it face up at any time for its mana cost if it's a creature card.)</i>";
} }
private GhastlyConscriptionEffect(final GhastlyConscriptionEffect effect) { private GhastlyConscriptionEffect(final GhastlyConscriptionEffect effect) {
@ -62,39 +61,24 @@ class GhastlyConscriptionEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
// TODO: migrate to shared manifested code
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source));
if (controller != null && targetPlayer != null) { if (controller == null || targetPlayer == null) {
List<Card> cardsToManifest = new ArrayList<>(); return false;
for (Card card : targetPlayer.getGraveyard().getCards(StaticFilters.FILTER_CARD_CREATURE, game)) { }
cardsToManifest.add(card); List<Card> cardsToManifest = new ArrayList<>();
controller.moveCardToExileWithInfo(card, null, "", source, game, Zone.GRAVEYARD, true); for (Card card : targetPlayer.getGraveyard().getCards(StaticFilters.FILTER_CARD_CREATURE, game)) {
} cardsToManifest.add(card);
if (cardsToManifest.isEmpty()) { controller.moveCardToExileWithInfo(card, null, "", source, game, Zone.GRAVEYARD, true);
return true; card.setFaceDown(true, game);
} }
Collections.shuffle(cardsToManifest); if (cardsToManifest.isEmpty()) {
game.informPlayers(controller.getLogName() + " shuffles the face-down pile");
Ability newSource = source.copy();
newSource.setWorksFaceDown(true);
for (Card card : cardsToManifest) {
ManaCosts manaCosts = null;
if (card.isCreature(game)) {
manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null;
if (manaCosts == null) {
manaCosts = new ManaCostsImpl<>("{0}");
}
}
Card battlefieldCard = BecomesFaceDownCreatureEffect.findDefaultCardSideForFaceDown(game, card);
MageObjectReference objectReference = new MageObjectReference(battlefieldCard.getId(), battlefieldCard.getZoneChangeCounter(game) + 1, game);
game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource);
}
Set<Card> toBattlefield = new LinkedHashSet();
toBattlefield.addAll(cardsToManifest);
controller.moveCards(toBattlefield, Zone.BATTLEFIELD, source, game, false, true, false, null);
return true; return true;
} }
return false; Collections.shuffle(cardsToManifest);
game.informPlayers(controller.getLogName() + " shuffles the face-down pile");
game.getState().processAction(game);
ManifestEffect.doManifestCards(game, source, controller, new LinkedHashSet<>(cardsToManifest));
return true;
} }
} }

View file

@ -1,36 +1,25 @@
package mage.cards.j; package mage.cards.j;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.CreatureCountCondition; import mage.abilities.condition.common.CreatureCountCondition;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.decorator.ConditionalRestrictionEffect;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.keyword.ManifestEffect;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.ExileZone;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import java.util.*;
/** /**
* *
* @author emerald000 * @author emerald000
@ -81,38 +70,31 @@ class JeskaiInfiltratorEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
// TODO: migrade to shared manifest code
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null) { if (controller == null) {
Set<Card> cardsToManifest = new HashSet<>(); return false;
cardsToManifest.add(source.getSourcePermanentIfItStillExists(game));
cardsToManifest.add(controller.getLibrary().getFromTop(game));
UUID exileId = UUID.randomUUID();
controller.moveCardsToExile(cardsToManifest, source, game, false, exileId, "");
ExileZone exileZone = game.getExile().getExileZone(exileId);
for (Card card : exileZone.getCards(game)) {
card.setFaceDown(true, game);
}
game.fireUpdatePlayersEvent(); // removes Jeskai Infiltrator from Battlefield, so Jeskai Infiltrator returns as a fresh permanent to the battlefield with new position
Ability newSource = source.copy();
newSource.setWorksFaceDown(true);
//the Set will mimic the Shuffling
exileZone.getCards(game).forEach(card -> {
ManaCosts manaCosts = null;
if (card.isCreature(game)) {
manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null;
if (manaCosts == null) {
manaCosts = new ManaCostsImpl<>("{0}");
}
}
Card battlefieldCard = BecomesFaceDownCreatureEffect.findDefaultCardSideForFaceDown(game, card);
MageObjectReference objectReference = new MageObjectReference(battlefieldCard.getId(), battlefieldCard.getZoneChangeCounter(game) + 1, game);
game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource);
});
controller.moveCards(exileZone.getCards(game), Zone.BATTLEFIELD, source, game, false, true, false, null);
return true;
} }
return false; UUID exileId = UUID.randomUUID();
Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game);
if (sourcePermanent != null) {
controller.moveCardsToExile(sourcePermanent, source, game, false, exileId, "");
}
Card topCard = controller.getLibrary().getFromTop(game);
if (topCard != null) {
controller.moveCardsToExile(topCard, source, game, false, exileId, "");
}
// need to get source permanent as card rather than permanent for next steps, hence this convoluted code
List<Card> cardsToManifest = new ArrayList<>(game.getExile().getExileZone(exileId).getCards(game));
if (cardsToManifest.isEmpty()) {
return false;
}
for (Card card : cardsToManifest) {
card.setFaceDown(true, game);
}
Collections.shuffle(cardsToManifest);
game.informPlayers(controller.getLogName() + " shuffles the face-down pile");
game.getState().processAction(game);
ManifestEffect.doManifestCards(game, source, controller, new LinkedHashSet<>(cardsToManifest));
return true;
} }
} }

View file

@ -151,8 +151,8 @@ public class ManifestTest extends CardTestPlayerBase {
} else { } else {
String realPermanentName = currentGame.getBattlefield().getAllPermanents() String realPermanentName = currentGame.getBattlefield().getAllPermanents()
.stream() .stream()
.filter(p -> p.getName().equals(cardAfterBlink))
.map(MageObject::getName) .map(MageObject::getName)
.filter(name -> name.equals(cardAfterBlink))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
Assert.assertEquals("after blink card must go to battlefield", Assert.assertEquals("after blink card must go to battlefield",
@ -818,4 +818,36 @@ public class ManifestTest extends CardTestPlayerBase {
permanent = findFaceDownPermanent(currentGame, playerB, playerB); permanent = findFaceDownPermanent(currentGame, playerB, playerB);
assertFaceDownManifest("end game: must show for yourself", permanent, "Mountain", true); assertFaceDownManifest("end game: must show for yourself", permanent, "Mountain", true);
} }
@Test
public void testJeskaiInfiltrator() {
// Whenever Jeskai Infiltrator deals combat damage to a player,
// exile it and the top card of your library in a face-down pile, shuffle that pile, then manifest those cards.
String infiltrator = "Jeskai Infiltrator"; // 2/3
String excommunicate = "Excommunicate"; // 2W Sorcery, Put target creature on top of its owner's library
String missionary = "Lone Missionary"; // 2/1 for 1W, ETB gain 4 life
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8);
addCard(Zone.BATTLEFIELD, playerA, infiltrator);
addCard(Zone.BATTLEFIELD, playerA, missionary);
addCard(Zone.HAND, playerA, excommunicate);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, excommunicate, missionary);
attack(1, playerA, infiltrator, playerB);
checkPlayableAbility("missionary manifest",1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}{W}: Turn ", true);
checkPlayableAbility("infiltrator manifest",1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}{U}: Turn ", true);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, excommunicate, 1);
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2);
assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2);
assertLife(playerA, 20);
assertLife(playerB, 18);
}
} }

View file

@ -148,7 +148,7 @@ public class ManifestEffect extends OneShotEffect {
permanent.setManifested(true); permanent.setManifested(true);
} else { } else {
// TODO: looks buggy, card can't be moved to battlefield, but face down effect already active // TODO: looks buggy, card can't be moved to battlefield, but face down effect already active
// or it can be face down on another move to battalefield // or it can be face down on another move to battlefield
} }
} }