From 94819ff91c526679556839c32938a11ac4dfc3ea Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 1 May 2023 17:27:26 +0400 Subject: [PATCH] Tokens rework: - tests: added support of set code in custom ability (see addCustomCardWithAbility); - tests: added additional tests for token images (related to #10139, #10222); - fixed wrong token images for copied cards/tokens (#10222); --- .../mage/test/serverside/TokenImagesTest.java | 100 +++++++++++++++++- .../test/serverside/base/CardTestAPI.java | 9 ++ .../serverside/base/MageTestPlayerBase.java | 18 +++- .../base/impl/CardTestPlayerAPIImpl.java | 19 +++- .../mage/game/permanent/token/TokenImpl.java | 15 ++- 5 files changed, 149 insertions(+), 12 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java index 84513322e1..e7330ea81d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java @@ -9,6 +9,7 @@ import mage.cards.repository.TokenRepository; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.game.permanent.PermanentToken; +import mage.game.permanent.token.SoldierToken; import mage.game.permanent.token.Token; import mage.game.permanent.token.TokenImpl; import mage.game.permanent.token.custom.CreatureToken; @@ -129,16 +130,19 @@ public class TokenImagesTest extends CardTestPlayerBase { private void assert_Inner(String cardName, int cardAmountInExile, int cardAmountInGrave, int cardAmountInBattlefield, String tokenName, int tokenAmount, boolean mustStoreAsCard, String... checks) { - assertExileCount(playerA, cardName, cardAmountInExile); - assertGraveyardCount(playerA, cardName, cardAmountInGrave); - assertPermanentCount(playerA, cardName, cardAmountInBattlefield); - assertPermanentCount(playerA, tokenName, tokenAmount); + if (!cardName.isEmpty()) { + assertExileCount(playerA, cardName, cardAmountInExile); + assertGraveyardCount(playerA, cardName, cardAmountInGrave); + assertPermanentCount(playerA, cardName, cardAmountInBattlefield); + } + assertTokenCount(playerA, tokenName, tokenAmount); // collect real server stats Map> realServerStats = new LinkedHashMap<>(); currentGame.getBattlefield().getAllPermanents() .stream() .filter(card -> card.getName().equals(tokenName)) + .filter(card -> card instanceof PermanentToken) .sorted(Comparator.comparing(Card::getExpansionSetCode)) .forEach(card -> { Assert.assertNotNull("must have set code", card.getExpansionSetCode()); @@ -159,6 +163,7 @@ public class TokenImagesTest extends CardTestPlayerBase { playerView.getBattlefield().values() .stream() .filter(card -> card.getName().equals(tokenName)) + .filter(CardView::isToken) .sorted(Comparator.comparing(CardView::getExpansionSetCode)) .forEach(permanentView -> { String realCode = permanentView.getExpansionSetCode(); @@ -338,6 +343,93 @@ public class TokenImagesTest extends CardTestPlayerBase { assert_TheHive(20, "5ED=0", "10E>0", "30A>0"); } + @Test + public void test_TokenExists_MustGetSameImageForAllTokenInstances() { + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new CreateTokenEffect(new SoldierToken(), 10), + new ManaCostsImpl<>("") + ); + addCustomCardWithAbility("40K-test", playerA, ability); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "create ten"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, 1 + 10); // 1 test card + 10 tokens + assert_TokenTypes("Soldier Token", 1); // one ability's call must generate tokens with same image + assert_Inner("test", 0, 0, 1, + "Soldier Token", 10, false, "40K=10"); + } + + @Test + public void test_TokenExists_CopyMustGetSameImageAsCopiedCard() { + // copied cards + // https://github.com/magefree/mage/issues/10222 + + addCard(Zone.BATTLEFIELD, playerA, "NEC-Silver Myr", 3); + addCard(Zone.BATTLEFIELD, playerA, "MM2-Alloy Myr", 3); + // + // Choose target artifact creature you control. For each creature chosen this way, create a token that's a copy of it. + // Overload {6}{U} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.") + addCard(Zone.HAND, playerA, "BRC-March of Progress", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "March of Progress with overload"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // +3 new tokens for each + assertPermanentCount(playerA, "Silver Myr", 3 + 3); + assertPermanentCount(playerA, "Alloy Myr", 3 + 3); + + // tokens must use same set code as copied card + assert_Inner("Silver Myr", 0, 0, 3 + 3, + "Silver Myr", 3, true, "NEC=3"); + assert_Inner("Alloy Myr", 0, 0, 3 + 3, + "Alloy Myr", 3, true, "MM2=3"); + } + + @Test + public void test_TokenExists_CopyMustGetSameImageAsCopiedToken() { + // copied tokens + // https://github.com/magefree/mage/issues/10222 + + // -2: Create a 0/0 colorless Construct artifact creature token with "This creature gets +1/+1 for each artifact you control." + addCard(Zone.BATTLEFIELD, playerA, "MED-Karn, Scion of Urza", 1); + // + // Choose target artifact creature you control. For each creature chosen this way, create a token that's a copy of it. + // Overload {6}{U} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.") + addCard(Zone.HAND, playerA, "BRC-March of Progress", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + + // prepare token + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2:"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Construct Token", 1); + + // copy token + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "March of Progress with overload"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // +1 new token + assertPermanentCount(playerA, "Construct Token", 1 + 1); + + // tokens must use same set code as copied token + assert_Inner("", 0, 0, 0, + "Construct Token", 2, false, "MED=2"); + } + @Test @Ignore // TODO: implement auto-generate creature token images from public tokens (by name, type, color, PT, abilities) diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java index 8dde02167a..ccb7f87858 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestAPI.java @@ -171,6 +171,15 @@ public interface CardTestAPI { */ void assertPermanentCount(Player player, String cardName, int count) throws AssertionError; + /** + * Assert token count under player's control. + * + * @param player {@link Player} which permanents should be counted. + * @param tokenName Name of the tokens that should be counted. + * @param count Expected count. + */ + void assertTokenCount(Player player, String tokenName, int count) throws AssertionError; + /** * Assert command zone object count in player's command zone * 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 73ae45eef5..85ef29888b 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 @@ -35,6 +35,7 @@ import mage.server.managers.ConfigSettings; import mage.server.util.ConfigFactory; import mage.server.util.ConfigWrapper; import mage.server.util.PluginClassLoader; +import mage.server.util.SystemUtil; import mage.server.util.config.GamePlugin; import mage.server.util.config.Plugin; import mage.target.TargetPermanent; @@ -425,12 +426,19 @@ public abstract class MageTestPlayerBase { protected void addCustomCardWithAbility(String customName, TestPlayer controllerPlayer, Ability ability, SpellAbility spellAbility, CardType cardType, String spellCost, Zone putAtZone, SubType... additionalSubTypes) { - CustomTestCard.clearCustomAbilities(customName); - CustomTestCard.addCustomAbility(customName, spellAbility, ability); - CustomTestCard.clearAdditionalSubtypes(customName); - CustomTestCard.addAdditionalSubtypes(customName, additionalSubTypes); + List cardCommand = SystemUtil.parseSetAndCardNameCommand(customName); + String needSetCode = cardCommand.get(0); + String needCardName = cardCommand.get(1); + if (needSetCode.isEmpty()) { + needSetCode = "custom"; + } - CardSetInfo testSet = new CardSetInfo(customName, "custom", "123", Rarity.COMMON); + CustomTestCard.clearCustomAbilities(needCardName); + CustomTestCard.addCustomAbility(needCardName, spellAbility, ability); + CustomTestCard.clearAdditionalSubtypes(needCardName); + CustomTestCard.addAdditionalSubtypes(needCardName, additionalSubTypes); + + CardSetInfo testSet = new CardSetInfo(needCardName, needSetCode, "123", Rarity.COMMON); Card newCard = new CustomTestCard(controllerPlayer.getId(), testSet, cardType, spellCost); Card permCard = CardUtil.getDefaultCardSideForBattlefield(currentGame, newCard); PermanentCard permanent = new PermanentCard(permCard, controllerPlayer.getId(), currentGame); 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 d894913f31..ff37a55fc1 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 @@ -21,6 +21,7 @@ import mage.game.command.CommandObject; import mage.game.match.MatchOptions; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; +import mage.game.permanent.PermanentToken; import mage.player.ai.ComputerPlayer7; import mage.player.ai.ComputerPlayerMCTS; import mage.players.ManaPool; @@ -623,7 +624,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } // set code for card - String setCode = ""; + String setCode; List cardCommand = SystemUtil.parseSetAndCardNameCommand(cardName); setCode = cardCommand.get(0); cardName = cardCommand.get(1); @@ -966,6 +967,22 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement Assert.assertEquals("(Battlefield) Permanents counts for " + player.getName() + " are not equal (" + cardName + ')', count, actualCount); } + @Override + public void assertTokenCount(Player player, String tokenName, int count) throws AssertionError { + //Assert.assertNotEquals("", tokenName); + int actualCount = 0; + for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { + if (permanent instanceof PermanentToken) { + if (permanent.getControllerId().equals(player.getId())) { + if (isObjectHaveTargetNameOrAlias(player, permanent, tokenName)) { + actualCount++; + } + } + } + } + Assert.assertEquals("(Battlefield) Tokens counts for " + player.getName() + " are not equal (" + tokenName + ')', count, actualCount); + } + @Override public void assertCommandZoneCount(Player player, String commandZoneObjectName, int count) throws AssertionError { //Assert.assertNotEquals("", commandZoneObjectName); diff --git a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java index e45f12bf00..434f051d0e 100644 --- a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java +++ b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java @@ -156,11 +156,22 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { // - use random set code // - use default set code + // token from a card - must use card image instead (example: Embalm ability) if (token.getOriginalCardNumber() != null) { - // token from a card, so must use card image instead (example: Embalm ability) return new TokenInfo(TokenType.TOKEN, token.getName(), token.getOriginalExpansionSetCode(), 0); } + // token from another token + if (token instanceof EmptyToken) { + if (token.getOriginalExpansionSetCode() == null) { + // possible reason: miss call of CardUtil.copySetAndCardNumber in copying method + throw new IllegalArgumentException("Wrong code usage: can't copy token without set code"); + } + return new TokenInfo(TokenType.TOKEN, token.getName(), token.getOriginalExpansionSetCode(), token.getTokenType()); + } + + // token as is + // source final String setCode; Card sourceCard = game.getCard(sourceId); @@ -272,8 +283,8 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { List allowedTokens = new ArrayList<>(); // prepare tokens to enter + // must use same image for all tokens for (int i = 0; i < amount; i++) { - // TODO: add random setTokenType here? // use event.getPlayerId() as controller because it can be replaced by replacement effect PermanentToken newPermanent = new PermanentToken(token, event.getPlayerId(), game); game.getState().addCard(newPermanent);