From ac98a3a31ae96372302a71f9dc737ae5e4522c07 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 6 Feb 2021 10:51:41 +0400 Subject: [PATCH] * Commander: fixed duplicated triggers after play (example: Esika, God of the Tree, see #7501, #7503, #7505); --- .../src/mage/cards/e/EsikaGodOfTheTree.java | 4 +- .../ModalDoubleFacesCardsInCommanderTest.java | 75 +++++++++++++++++++ .../java/org/mage/test/player/TestPlayer.java | 25 ++++--- .../base/impl/CardTestPlayerAPIImpl.java | 2 + Mage/src/main/java/mage/game/GameState.java | 4 - .../java/mage/game/command/Commander.java | 17 ++++- 6 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsInCommanderTest.java diff --git a/Mage.Sets/src/mage/cards/e/EsikaGodOfTheTree.java b/Mage.Sets/src/mage/cards/e/EsikaGodOfTheTree.java index c0783491ad0..6b052110ad0 100644 --- a/Mage.Sets/src/mage/cards/e/EsikaGodOfTheTree.java +++ b/Mage.Sets/src/mage/cards/e/EsikaGodOfTheTree.java @@ -57,9 +57,7 @@ public final class EsikaGodOfTheTree extends ModalDoubleFacesCard { // Legendary Enchantment this.getRightHalfCard().addSuperType(SuperType.LEGENDARY); - // At the beginning of your upkeep, reveal cards from the top of your library until you reveal - // a creature or planeswalker card. Put that card onto the battlefield and the rest - // on the bottom of your library in a random order. + // At the beginning of your upkeep, reveal cards from the top of your library until you reveal a creature or planeswalker card. Put that card onto the battlefield and the rest on the bottom of your library in a random order. this.getRightHalfCard().addAbility(new BeginningOfUpkeepTriggeredAbility( Zone.BATTLEFIELD, new PrismaticBridgeEffect(), TargetController.YOU, false, false )); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsInCommanderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsInCommanderTest.java new file mode 100644 index 00000000000..8d0ce5fd6d8 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsInCommanderTest.java @@ -0,0 +1,75 @@ +package org.mage.test.cards.cost.modaldoublefaces; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommanderDuelBase; + +/** + * @author JayDi85 + */ +public class ModalDoubleFacesCardsInCommanderTest extends CardTestCommanderDuelBase { + + @Test + public void test_Triggers_MustAddTriggersOneTimeOnly() { + // possible bug: duplicated triggers from same card + // https://github.com/magefree/mage/issues/7501 + + removeAllCardsFromHand(playerA); + removeAllCardsFromLibrary(playerA); + skipInitShuffling(); + + // Esika, God of the Tree + // creature, 1/4 + // Landfall — Whenever a land enters the battlefield under your control, Kazandu Mammoth gets +2/+2 until end of turn. + // + // The Prismatic Bridge + // Enchantment + // At the beginning of your upkeep, reveal cards from the top of your library until you reveal a creature or + // planeswalker card. Put that card onto the battlefield and the rest on the bottom of your library in a random order. + addCard(Zone.COMMAND, playerA, "Esika, God of the Tree"); // {W}{U}{B}{R}{G} + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + // + // last goes to top library + addCard(Zone.LIBRARY, playerA, "Forest"); + addCard(Zone.LIBRARY, playerA, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerA, "Forest"); + addCard(Zone.LIBRARY, playerA, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerA, "Forest"); + addCard(Zone.LIBRARY, playerA, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerA, "Forest"); + addCard(Zone.LIBRARY, playerA, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerA, "Forest"); + addCard(Zone.LIBRARY, playerA, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerA, "Forest"); + // + // Exile target artifact or enchantment. + addCard(Zone.HAND, playerB, "Ironwright's Cleansing"); // {2}{W} + addCard(Zone.BATTLEFIELD, playerB, "Plains"); + + // prepare mdf + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge", 1); + checkLibraryCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 5); + + // possible bug: you can catch choose dialog for duplicated upkeep triggers + + // turn 3, first upkeep and bear move + checkLibraryCount("after upkeep 1", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 5 - 1); + checkPermanentCount("after upkeep 1", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 1); + + // turn 5, second upkeep and bear move + checkLibraryCount("after upkeep 2", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 5 - 2); + checkPermanentCount("after upkeep 2", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 2); + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 5c3e38558dc..02e48baf357 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -925,7 +925,7 @@ public class TestPlayer implements Player { // show library if (params[0].equals(SHOW_COMMAND_LIBRARY) && params.length == 1) { printStart(action.getActionName()); - printCards(computerPlayer.getLibrary().getCards(game)); + printCards(computerPlayer.getLibrary().getCards(game), false); // do not sort printEnd(); actions.remove(action); wasProccessed = true; @@ -973,7 +973,7 @@ public class TestPlayer implements Player { printStart(action.getActionName()); printCards(game.getExile().getAllCards(game).stream() .filter(card -> card.isOwnedBy(computerPlayer.getId())) - .collect(Collectors.toList())); + .collect(Collectors.toList()), true); printEnd(); actions.remove(action); wasProccessed = true; @@ -1108,16 +1108,23 @@ public class TestPlayer implements Player { } private void printCards(Set cards) { - printCards(new ArrayList<>(cards)); + printCards(new ArrayList<>(cards), true); } - private void printCards(List cards) { + private void printCards(List cards, boolean sorted) { System.out.println("Total cards: " + cards.size()); - List data = cards.stream() - .map(Card::getIdName) - .sorted() - .collect(Collectors.toList()); + List data; + if (sorted) { + data = cards.stream() + .map(Card::getIdName) + .sorted() + .collect(Collectors.toList()); + } else { + data = cards.stream() + .map(Card::getIdName) + .collect(Collectors.toList()); + } for (String s : data) { System.out.println(s); @@ -1365,7 +1372,7 @@ public class TestPlayer implements Player { if (foundCount != count) { printStart("Exile cards"); - printCards(game.getExile().getAllCards(game)); + printCards(game.getExile().getAllCards(game), true); printEnd(); Assert.fail(action.getActionName() + " - exile zone must have " + count + " cards with name " + permanentName + ", but found " + foundCount); } 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 8b7cd2895b1..c8cd5cb4c59 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 @@ -598,6 +598,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement /** * Add a card to specified zone of specified player. + *

+ * Zone.LIBRARY - adding by put on top (e.g. last added card goes to top of the library) * * @param gameZone {@link mage.constants.Zone} to add cards to. * @param player {@link Player} to add cards for. Use either playerA or diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index fc76413697c..c05e519b903 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -630,10 +630,6 @@ public class GameState implements Serializable, Copyable { } } - private void addTrigger(TriggeredAbility ability, MageObject attachedTo) { - addTrigger(ability, null, attachedTo); - } - private void addTrigger(TriggeredAbility ability, UUID sourceId, MageObject attachedTo) { if (sourceId == null) { triggers.add(ability, attachedTo); diff --git a/Mage/src/main/java/mage/game/command/Commander.java b/Mage/src/main/java/mage/game/command/Commander.java index d0e0f42b350..b3a8446792c 100644 --- a/Mage/src/main/java/mage/game/command/Commander.java +++ b/Mage/src/main/java/mage/game/command/Commander.java @@ -73,10 +73,21 @@ public class Commander implements CommandObject { // other abilities for (Ability ability : card.getAbilities()) { - if (!(ability instanceof SpellAbility) && !(ability instanceof PlayLandAbility)) { - Ability newAbility = ability.copy(); - abilities.add(newAbility); + // skip already added above + if (ability instanceof SpellAbility || ability instanceof PlayLandAbility) { + continue; } + + // skip triggers + // workaround to fix double triggers for commanders on battlefield (example: Esika, God of the Tree) + // TODO: is commanders on command zone can have triggers (is there a card with triggered ability in all zones)? + if (ability instanceof TriggeredAbility) { + continue; + } + + // OK, can add it (example: activated, static, alternative cost, etc) + Ability newAbility = ability.copy(); + abilities.add(newAbility); } }