* Becomes a copy abilities: improved support with MDF cards (#8335);

This commit is contained in:
Oleg Agafonov 2021-11-17 16:27:18 +04:00
parent 35b257bd8f
commit afdae939c3
9 changed files with 141 additions and 18 deletions

View file

@ -121,7 +121,7 @@ public class TestCardRenderDialog extends MageDialog {
cardsList.add(newCard);
game.loadCards(cardsList, controllerId);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(game, newCard);
if (extraAbilities != null) {
extraAbilities.forEach(ability -> permCard.addAbility(ability));
}
@ -153,7 +153,7 @@ public class TestCardRenderDialog extends MageDialog {
cardsList.add(newCard);
game.loadCards(cardsList, controllerId);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(game, newCard);
PermanentCard perm = new PermanentCard(permCard, controllerId, game);
perm.setFaceDown(true, game);

View file

@ -30,7 +30,8 @@ public final class ZamWesell extends CardImpl {
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// When you cast Zam Wessel, target opponent reveals their hand. You may choose a creature card from it and have Zam Wessel enter the battlefield as a copy of that creature card.
// When you cast Zam Wesell, target opponent reveals their hand. You may choose a creature card from it and have Zam Wesell enter the battlefield as a copy of that creature card.
// TODO: Zam Wesell must be reworked to use on cast + etb abilities
Ability ability = new CastSourceTriggeredAbility(new RevealHandTargetEffect());
ability.addEffect(new ZamWesselEffect());
ability.addTarget(new TargetOpponent());

View file

@ -9,6 +9,7 @@ import mage.game.permanent.PermanentCard;
import mage.util.CardUtil;
import mage.util.ManaUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.player.TestPlayer;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -863,4 +864,110 @@ public class ModalDoubleFacesCardsTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "The Omenkeel", 1);
}
@Test
public void test_Copy_AsSpell() {
addCard(Zone.HAND, playerA, "Akoum Warrior", 1); // {5}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
//
// Copy target creature spell you control, except it isn't legendary if the spell is legendary.
addCard(Zone.HAND, playerA, "Double Major", 1); // {G}{U}
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
// cast mdf card
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 6);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior");
// prepare copy of spell
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Akoum Warrior", "Akoum Warrior");
checkStackSize("before copy spell", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true);
checkStackSize("after copy spell", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 2);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_Copy_AsCloneFromPermanent() {
addCard(Zone.HAND, playerA, "Akoum Warrior", 1); // {5}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
//
// You may have Clone enter the battlefield as a copy of any creature on the battlefield.
addCard(Zone.HAND, playerA, "Clone", 1); // {3}{U}
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// cast mdf card
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 6);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
// copy permanent
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clone");
setChoice(playerA, true); // use copy
setChoice(playerA, "Akoum Warrior"); // copy source
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 2);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
@Ignore // TODO: Zam Wesell must be reworked to use on cast + etb abilities
public void test_Copy_AsCloneFromCard_ZamWesell() {
// When you cast Zam Wesell, target opponent reveals their hand. You may choose a creature card from it
// and have Zam Wesell enter the battlefield as a copy of that creature card.
addCard(Zone.HAND, playerA, "Zam Wesell"); // {2}{U}{U}
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
//
addCard(Zone.HAND, playerB, "Akoum Warrior", 1);
// cast as copy of mdf card
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zam Wesell");
addTarget(playerA, playerB); // target opponent
setChoice(playerA, "Akoum Warrior"); // creature card to copy
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 2);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test
public void test_Copy_AsCloneFromCard_ValkiGodOfLies() {
// When Valki enters the battlefield, each opponent reveals their hand. For each opponent,
// exile a creature card they revealed this way until Valki leaves the battlefield.
// X: Choose a creature card exiled with Valki with converted mana cost X. Valki becomes a copy of that card.
addCard(Zone.HAND, playerA, "Valki, God of Lies"); // {1}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2 + 3); // 3 for X
//
addCard(Zone.HAND, playerB, "Birgi, God of Storytelling", 1); // {2}{R}
// prepare valki
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Valki, God of Lies");
setChoice(playerA, "Birgi, God of Storytelling"); // exile
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
// copy exiled card
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}:");
setChoice(playerA, "X=3");
setChoice(playerA, "Birgi, God of Storytelling");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Valki, God of Lies", 0);
assertPermanentCount(playerA, "Birgi, God of Storytelling", 1);
}
}

View file

@ -255,7 +255,7 @@ public abstract class MageTestBase {
Card newCard = cardInfo != null ? cardInfo.getCard() : null;
if (newCard != null) {
if (gameZone == Zone.BATTLEFIELD) {
Card permCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard);
PermanentCard p = new PermanentCard(permCard, null, currentGame);
p.setTapped(tapped);
perms.add(p);

View file

@ -269,7 +269,7 @@ public abstract class MageTestPlayerBase {
Card newCard = cardInfo != null ? cardInfo.getCard() : null;
if (newCard != null) {
if (gameZone == Zone.BATTLEFIELD) {
Card permCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard);
PermanentCard p = new PermanentCard(permCard, null, currentGame);
p.setTapped(tapped);
perms.add(p);
@ -422,7 +422,7 @@ public abstract class MageTestPlayerBase {
CardSetInfo testSet = new CardSetInfo(customName, "custom", "123", Rarity.COMMON);
Card newCard = new CustomTestCard(controllerPlayer.getId(), testSet, cardType, spellCost);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard);
PermanentCard permanent = new PermanentCard(permCard, controllerPlayer.getId(), currentGame);
switch (putAtZone) {

View file

@ -685,7 +685,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
if (gameZone == Zone.BATTLEFIELD) {
for (int i = 0; i < count; i++) {
Card newCard = cardInfo.getCard();
Card permCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard);
PermanentCard p = new PermanentCard(permCard, player.getId(), currentGame);
p.setTapped(tapped);

View file

@ -34,7 +34,7 @@ public class SerializationTest extends CardTestPlayerBase {
public void test_PermanentImpl_Simple() {
CardInfo cardInfo = CardRepository.instance.findCard("Balduvian Bears");
Card newCard = cardInfo.getCard();
Card permCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard);
PermanentImpl permanent = new PermanentCard(permCard, playerA.getId(), currentGame);
currentGame.addPermanent(permanent, 0);
@ -48,7 +48,7 @@ public class SerializationTest extends CardTestPlayerBase {
public void test_PermanentImpl_MarkedDamageInfo() {
CardInfo cardInfo = CardRepository.instance.findCard("Balduvian Bears");
Card newCard = cardInfo.getCard();
Card permCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard);
PermanentImpl permanent = new PermanentCard(permCard, playerA.getId(), currentGame);
currentGame.addPermanent(permanent, 0);
@ -73,7 +73,7 @@ public class SerializationTest extends CardTestPlayerBase {
CardUtil.getObjectPartsAsObjects(newCard).stream()
.map(Card.class::cast)
.forEach(card -> {
Card testCard = CardUtil.getDefaultCardSideForBattlefield(newCard);
Card testCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard);
Card testPermanent = null;
if (!testCard.isInstantOrSorcery()) {
testPermanent = new PermanentCard(testCard, playerA.getId(), currentGame);

View file

@ -10,20 +10,20 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.game.permanent.PermanentToken;
import mage.util.CardUtil;
import mage.util.functions.CopyApplier;
import java.util.UUID;
/**
* Make battlefield's permanent as a copy of the source object
* (source can be a card or another permanent)
*
* @author BetaSteward_at_googlemail.com
*/
public class CopyEffect extends ContinuousEffectImpl {
/**
* Object we copy from
*/
protected MageObject copyFromObject;
protected UUID copyToObjectId;
protected CopyApplier applier;
@ -47,9 +47,13 @@ public class CopyEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
// must copy the default side of the card (example: clone with mdf card)
if (!(copyFromObject instanceof Permanent) && (copyFromObject instanceof Card)) {
this.copyFromObject = new PermanentCard((Card) copyFromObject, source.getControllerId(), game);
Card newBluePrint = CardUtil.getDefaultCardSideForBattlefield(game, (Card) copyFromObject);
this.copyFromObject = new PermanentCard(newBluePrint, source.getControllerId(), game);
}
Permanent permanent = game.getPermanent(copyToObjectId);
if (permanent != null) {
affectedObjectList.add(new MageObjectReference(permanent, game));

View file

@ -997,7 +997,7 @@ public final class CardUtil {
// same logic as ZonesHandler->maybeRemoveFromSourceZone
// workaround to put real permanent from one side (example: you call mdf card by cheats)
Card permCard = getDefaultCardSideForBattlefield(newCard);
Card permCard = getDefaultCardSideForBattlefield(game, newCard);
// prepare card and permanent
permCard.setZone(Zone.BATTLEFIELD, game);
@ -1035,12 +1035,17 @@ public final class CardUtil {
/**
* Choose default card's part to put on battlefield (for cheats and tests only)
* or to find a default card side (for copy effect)
*
* @param card
* @return
*/
public static Card getDefaultCardSideForBattlefield(Card card) {
// chose left side all time
public static Card getDefaultCardSideForBattlefield(Game game, Card card) {
if (card instanceof PermanentCard) {
return card;
}
// must choose left side all time
Card permCard;
if (card instanceof SplitCard) {
permCard = card;
@ -1051,6 +1056,12 @@ public final class CardUtil {
} else {
permCard = card;
}
// must be creature/planeswalker (if you catch this error then check targeting/copying code)
if (permCard.isInstantOrSorcery(game)) {
throw new IllegalArgumentException("Card side can't be put to battlefield: " + permCard.getName());
}
return permCard;
}