From afdae939c3faae6ceebb58ad8679a105e63d685e Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 17 Nov 2021 16:27:18 +0400 Subject: [PATCH] * Becomes a copy abilities: improved support with MDF cards (#8335); --- .../client/dialog/TestCardRenderDialog.java | 4 +- Mage.Sets/src/mage/cards/z/ZamWesell.java | 3 +- .../ModalDoubleFacesCardsTest.java | 107 ++++++++++++++++++ .../test/serverside/base/MageTestBase.java | 2 +- .../serverside/base/MageTestPlayerBase.java | 4 +- .../base/impl/CardTestPlayerAPIImpl.java | 2 +- .../performance/SerializationTest.java | 6 +- .../abilities/effects/common/CopyEffect.java | 14 ++- Mage/src/main/java/mage/util/CardUtil.java | 17 ++- 9 files changed, 141 insertions(+), 18 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java index 32842b24e8..327285a359 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -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); diff --git a/Mage.Sets/src/mage/cards/z/ZamWesell.java b/Mage.Sets/src/mage/cards/z/ZamWesell.java index 9ef1af0e35..1457b56d6a 100644 --- a/Mage.Sets/src/mage/cards/z/ZamWesell.java +++ b/Mage.Sets/src/mage/cards/z/ZamWesell.java @@ -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()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java index e917db36e1..c15e584363 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java @@ -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); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java index 996948230d..72e129eddf 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java @@ -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); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index 864103cd90..ffd9f92a1b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -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) { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index 0dd3cdc2c8..7147cf4de8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -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); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java index a6bf6033fd..e115acd91d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/performance/SerializationTest.java @@ -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); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java index 0f35025d61..5f4f1dd5cc 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java @@ -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)); diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index ea1bdf5591..d1bd3a5375 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -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; }