forked from External/mage
Manifest abilities - improved combo support for MDF, split and other cards (related to #10803, part of #11873)
This commit is contained in:
parent
6cd8359fbd
commit
11ddfa0087
5 changed files with 136 additions and 9 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
package org.mage.test.cards.abilities.keywords;
|
package org.mage.test.cards.abilities.keywords;
|
||||||
|
|
||||||
|
import mage.MageObject;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.repository.TokenRepository;
|
import mage.cards.repository.TokenRepository;
|
||||||
import mage.constants.EmptyNames;
|
import mage.constants.EmptyNames;
|
||||||
|
|
@ -118,6 +119,102 @@ public class ManifestTest extends CardTestPlayerBase {
|
||||||
Assert.assertEquals("manifested cards must be taken from opponent's library", 2, playerA.getLibrary().size() - playerB.getLibrary().size());
|
Assert.assertEquals("manifested cards must be taken from opponent's library", 2, playerA.getLibrary().size() - playerB.getLibrary().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runManifestThenBlink(String cardToManifest, String cardAfterBlink) {
|
||||||
|
// split, mdfc and other cards must be able to manifested
|
||||||
|
// bug: https://github.com/magefree/mage/issues/10608
|
||||||
|
skipInitShuffling();
|
||||||
|
|
||||||
|
// Manifest the top card of your library.
|
||||||
|
addCard(Zone.HAND, playerA, "Soul Summons", 1); // {1}{W}, sorcery
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
|
||||||
|
//
|
||||||
|
// Exile target creature you control, then return that card to the battlefield under your control.
|
||||||
|
addCard(Zone.HAND, playerA, "Cloudshift", 1); // {W}, instant
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
|
||||||
|
//
|
||||||
|
addCard(Zone.LIBRARY, playerA, cardToManifest, 1);
|
||||||
|
|
||||||
|
// manifest
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Summons");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkPermanentCount("need face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
|
||||||
|
|
||||||
|
// blink
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.toString());
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkPermanentCount("need no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0);
|
||||||
|
|
||||||
|
runCode("after blink", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
if (cardAfterBlink == null) {
|
||||||
|
Assert.assertEquals("after blink card must keep in exile",
|
||||||
|
1, currentGame.getExile().getAllCardsByRange(currentGame, playerA.getId()).size());
|
||||||
|
} else {
|
||||||
|
String realPermanentName = currentGame.getBattlefield().getAllPermanents()
|
||||||
|
.stream()
|
||||||
|
.filter(p -> p.getName().equals(cardAfterBlink))
|
||||||
|
.map(MageObject::getName)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
Assert.assertEquals("after blink card must go to battlefield",
|
||||||
|
cardAfterBlink, realPermanentName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_Creature() {
|
||||||
|
runManifestThenBlink("Grizzly Bears", "Grizzly Bears");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_Instant() {
|
||||||
|
runManifestThenBlink("Lightning Bolt", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_MDFC_Creature() {
|
||||||
|
runManifestThenBlink("Akoum Warrior // Akoum Teeth", "Akoum Warrior");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_MDFC_LandOnMainSide() {
|
||||||
|
runManifestThenBlink("Barkchannel Pathway // Tidechannel Pathway", "Barkchannel Pathway");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_MDFC_LandOnSecondSide() {
|
||||||
|
runManifestThenBlink("Bala Ged Recovery // Bala Ged Sanctuary", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_Split_Normal() {
|
||||||
|
runManifestThenBlink("Assault // Battery", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_Split_Fused() {
|
||||||
|
runManifestThenBlink("Alive // Well", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_Split_Aftermath() {
|
||||||
|
runManifestThenBlink("Dusk // Dawn", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_Meld() {
|
||||||
|
runManifestThenBlink("Graf Rats", "Graf Rats");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManifestThenBlink_Adventure() {
|
||||||
|
runManifestThenBlink("Ardenvale Tactician // Dizzying Swoop", "Ardenvale Tactician");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests that ETB triggered abilities did not trigger for manifested cards
|
* Tests that ETB triggered abilities did not trigger for manifested cards
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1219,6 +1219,7 @@ public class TestPlayer implements Player {
|
||||||
.map(c -> (((c instanceof PermanentToken) ? "[T] " : "[C] ")
|
.map(c -> (((c instanceof PermanentToken) ? "[T] " : "[C] ")
|
||||||
+ c.getIdName()
|
+ c.getIdName()
|
||||||
+ (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "")
|
+ (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "")
|
||||||
|
+ " class " + c.getMainCard().getClass().getSimpleName() + ""
|
||||||
+ " - " + c.getPower().getValue() + "/" + c.getToughness().getValue()
|
+ " - " + c.getPower().getValue() + "/" + c.getToughness().getValue()
|
||||||
+ (c.isPlaneswalker(game) ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "")
|
+ (c.isPlaneswalker(game) ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "")
|
||||||
+ ", " + (c.isTapped() ? "Tapped" : "Untapped")
|
+ ", " + (c.isTapped() ? "Tapped" : "Untapped")
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import mage.abilities.effects.common.InfoEffect;
|
||||||
import mage.abilities.keyword.WardAbility;
|
import mage.abilities.keyword.WardAbility;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.ModalDoubleFacedCard;
|
||||||
import mage.cards.repository.TokenInfo;
|
import mage.cards.repository.TokenInfo;
|
||||||
import mage.cards.repository.TokenRepository;
|
import mage.cards.repository.TokenRepository;
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
|
|
@ -31,6 +32,9 @@ import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support different face down types: morph/manifest and disguise/cloak
|
* Support different face down types: morph/manifest and disguise/cloak
|
||||||
|
* <p>
|
||||||
|
* WARNING, if you use it custom effect then must create it for left side card,
|
||||||
|
* not main (see findDefaultCardSideForFaceDown)
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This effect lets the card be a 2/2 face-down creature, with no text, no name,
|
* This effect lets the card be a 2/2 face-down creature, with no text, no name,
|
||||||
|
|
@ -334,4 +338,21 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are cards with multiple sides like MDFC, but face down must use only main/left side all the time.
|
||||||
|
* So try to find that side.
|
||||||
|
*/
|
||||||
|
public static Card findDefaultCardSideForFaceDown(Game game, Card card) {
|
||||||
|
// rules example:
|
||||||
|
// If a double-faced card is manifested, it will be put onto the battlefield face down. While face down,
|
||||||
|
// it can't transform. If the front face of the card is a creature card, you can turn it face up by paying
|
||||||
|
// its mana cost. If you do, its front face will be up.
|
||||||
|
|
||||||
|
if (card instanceof ModalDoubleFacedCard) {
|
||||||
|
// only MDFC uses independent card sides on 2024
|
||||||
|
return ((ModalDoubleFacedCard) card).getLeftHalfCard();
|
||||||
|
} else {
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,6 @@ import java.util.Set;
|
||||||
* 701.34g TODO: need support it
|
* 701.34g TODO: need support it
|
||||||
* If a manifested permanent that’s represented by an instant or sorcery card would turn face up, its controller
|
* If a manifested permanent that’s represented by an instant or sorcery card would turn face up, its controller
|
||||||
* reveals it and leaves it face down. Abilities that trigger whenever a permanent is turned face up won’t trigger.
|
* reveals it and leaves it face down. Abilities that trigger whenever a permanent is turned face up won’t trigger.
|
||||||
* <p>
|
|
||||||
* 701.34g
|
|
||||||
* If a manifested permanent that’s represented by an instant or sorcery card would turn face up, its controller
|
|
||||||
* reveals it and leaves it face down. Abilities that trigger whenever a permanent is turned face up won’t trigger.
|
|
||||||
*
|
*
|
||||||
* @author LevelX2, JayDi85
|
* @author LevelX2, JayDi85
|
||||||
*/
|
*/
|
||||||
|
|
@ -123,16 +119,19 @@ public class ManifestEffect extends OneShotEffect {
|
||||||
// prepare face down effect for battlefield permanents
|
// prepare face down effect for battlefield permanents
|
||||||
// TODO: need research - why it add effect before move?!
|
// TODO: need research - why it add effect before move?!
|
||||||
for (Card card : cardsToManifest) {
|
for (Card card : cardsToManifest) {
|
||||||
|
Card battlefieldCard = BecomesFaceDownCreatureEffect.findDefaultCardSideForFaceDown(game, card);
|
||||||
|
|
||||||
// search mana cost for a face up ability (look at face side of the double side card)
|
// search mana cost for a face up ability (look at face side of the double side card)
|
||||||
ManaCosts manaCosts = null;
|
ManaCosts manaCosts = null;
|
||||||
if (card.isCreature(game)) {
|
if (battlefieldCard.isCreature(game)) {
|
||||||
manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null;
|
manaCosts = battlefieldCard.getSpellAbility() != null ? battlefieldCard.getSpellAbility().getManaCosts() : null;
|
||||||
if (manaCosts == null) {
|
if (manaCosts == null) {
|
||||||
manaCosts = new ManaCostsImpl<>("{0}");
|
manaCosts = new ManaCostsImpl<>("{0}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// zcc + 1 for use case with Rally the Ancestors (see related test)
|
// zcc + 1 for use case with Rally the Ancestors (see related test)
|
||||||
MageObjectReference objectReference = new MageObjectReference(card.getId(), card.getZoneChangeCounter(game) + 1, game);
|
MageObjectReference objectReference = new MageObjectReference(battlefieldCard.getId(), battlefieldCard.getZoneChangeCounter(game) + 1, game);
|
||||||
game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource);
|
game.addEffect(new BecomesFaceDownCreatureEffect(manaCosts, objectReference, Duration.Custom, FaceDownType.MANIFESTED), newSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,13 +139,16 @@ public class ManifestEffect extends OneShotEffect {
|
||||||
// TODO: possible buggy for multiple cards, see rule 701.34e - it require manifest one by one (card to check: Omarthis, Ghostfire Initiate)
|
// TODO: possible buggy for multiple cards, see rule 701.34e - it require manifest one by one (card to check: Omarthis, Ghostfire Initiate)
|
||||||
manifestPlayer.moveCards(cardsToManifest, Zone.BATTLEFIELD, source, game, false, true, false, null);
|
manifestPlayer.moveCards(cardsToManifest, Zone.BATTLEFIELD, source, game, false, true, false, null);
|
||||||
for (Card card : cardsToManifest) {
|
for (Card card : cardsToManifest) {
|
||||||
Permanent permanent = game.getPermanent(card.getId());
|
Card battlefieldCard = BecomesFaceDownCreatureEffect.findDefaultCardSideForFaceDown(game, card);
|
||||||
|
|
||||||
|
Permanent permanent = game.getPermanent(battlefieldCard.getId());
|
||||||
if (permanent != null) {
|
if (permanent != null) {
|
||||||
// TODO: why it set manifested here (face down effect doesn't work?!)
|
// TODO: permanent already has manifested status, so code can be deleted later
|
||||||
// TODO: add test with battlefield trigger/watcher (must not see normal card, must not see face down status without manifest)
|
// TODO: add test with battlefield trigger/watcher (must not see normal card, must not see face down status without manifest)
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,12 @@ public class PermanentCard extends PermanentImpl {
|
||||||
goodForBattlefield = false;
|
goodForBattlefield = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// face down cards allows in any forms (only face up restricted for non-permanents)
|
||||||
|
if (card.isFaceDown(game)) {
|
||||||
|
goodForBattlefield = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!goodForBattlefield) {
|
if (!goodForBattlefield) {
|
||||||
throw new IllegalArgumentException("Wrong code usage: can't create permanent card from split or mdf: " + card.getName());
|
throw new IllegalArgumentException("Wrong code usage: can't create permanent card from split or mdf: " + card.getName());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue