diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java index a63f38cbf59..6df3940ec2b 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java @@ -259,7 +259,7 @@ public class MageBook extends JComponent { if (newToken instanceof Token) { ((Token) newToken).setOriginalExpansionSetCode(currentSet); ((Token) newToken).setExpansionSetCodeForImage(currentSet); - ((Token) newToken).setTokenType(token.getType()); + ((Token) newToken).setTokenType(token.getType()); // must be called after set code, so it keep the type res.add(newToken); } } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { 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 d02bcb33943..a00f1a335bd 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 @@ -1,35 +1,333 @@ package org.mage.test.serverside; +import mage.cards.Card; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.game.permanent.PermanentToken; +import mage.util.CardUtil; +import mage.view.CardView; +import mage.view.GameView; +import mage.view.PermanentView; +import mage.view.PlayerView; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + /** + * All tests logic: create a tokens list from specific cards and check a used settins (set code, type) + * * @author JayDi85 */ public class TokenImagesTest extends CardTestPlayerBase { - @Test - public void test_TokenMustGetSameSetCodeAsSourceCard() { - //{3}{W}, {T}, Sacrifice Memorial to Glory: Create two 1/1 white Soldier creature tokens. - addCard(Zone.BATTLEFIELD, playerA, "40K-Memorial to Glory"); - addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + // TODO: add tests for Planes, Dungeons and other command objects (when it gets additional sets) - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}{W}, {T}, Sacrifice"); + private static final Pattern checkPattern = Pattern.compile("(\\w+)([<=>])(\\d+)"); // code=12, code>0 + + private void prepareCards_MemorialToGlory(String... cardsList) { + // {3}{W}, {T}, Sacrifice Memorial to Glory: Create two 1/1 white Soldier creature tokens. + prepareCards_Inner(Zone.BATTLEFIELD, "Memorial to Glory", 4, cardsList); + } + + private void prepareCards_TheHive(String... cardsList) { + // {5}, {T}: Create a 1/1 colorless Insect artifact creature token with flying named Wasp. + // (It can’t be blocked except by creatures with flying or reach.) + prepareCards_Inner(Zone.BATTLEFIELD, "The Hive", 5, cardsList); + } + + private void prepareCards_SacredCat(String... cardsList) { + // Embalm {W} ({W}, Exile this card from your graveyard: Create a token that's a copy of it, + // except it's a white Zombie Cat with no mana cost. Embalm only as a sorcery.) + prepareCards_Inner(Zone.GRAVEYARD, "Sacred Cat", 1, cardsList); + } + + private void prepareCards_Inner(Zone cardZone, String cardName, int plainsAmount, String... cardsList) { + Arrays.stream(cardsList).forEach(list -> { + String[] info = list.split("="); + String needSet = info[0]; + int needAmount = Integer.parseInt(info[1]); + IntStream.range(0, needAmount).forEach(x -> { + addCard(cardZone, playerA, String.format("%s-%s", needSet, cardName)); + if (plainsAmount > 0) { + addCard(Zone.BATTLEFIELD, playerA, "Plains", plainsAmount); + } + }); + }); + } + + private void activate_MemorialToGlory(int amount) { + activate_Inner(amount, "{3}{W}, {T}, Sacrifice"); + } + + private void activate_TheHive(int amount) { + activate_Inner(amount, "{5}, {T}: Create a 1/1 colorless Insect"); + } + + private void activate_SacredCat(int amount) { + activate_Inner(amount, "Embalm {W}"); + } + + private void activate_Inner(int amount, String abilityText) { + IntStream.range(0, amount).forEach(x -> { + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityText); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + }); + } + + private void assert_MemorialToGlory(int cardsAmount, String... tokensList) { + assert_Inner( + "Memorial to Glory", 0, cardsAmount, 0, + "Soldier Token", 2 * cardsAmount, false, tokensList + ); + } + + private void assert_TheHive(int amount, String... tokensList) { + assert_Inner( + "The Hive", 0, 0, amount, + "Wasp", amount, false, tokensList + ); + } + + private void assert_SacredCat(int amount, String... tokensList) { + // token has same name as card + assert_Inner( + "Sacred Cat", amount, 0, amount, + "Sacred Cat", amount, true, tokensList + ); + } + + 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); + + // collect real server stats + Map> realServerStats = new LinkedHashMap<>(); + currentGame.getBattlefield().getAllPermanents() + .stream() + .filter(card -> card.getName().equals(tokenName)) + .sorted(Comparator.comparing(Card::getExpansionSetCode)) + .forEach(card -> { + Assert.assertNotNull("must have set code", card.getExpansionSetCode()); + Assert.assertEquals("must have same set codes in all fields", + card.getExpansionSetCode(), + ((PermanentToken) card).getToken().getOriginalExpansionSetCode() + ); + String realCode = card.getExpansionSetCode(); + realServerStats.computeIfAbsent(realCode, code -> new ArrayList<>()); + realServerStats.get(realCode).add(card); + }); + + // collect real client stats + Map> realClientStats = new LinkedHashMap<>(); + GameView gameView = new GameView(currentGame.getState(), currentGame, playerA.getId(), null); + PlayerView playerView = gameView.getPlayers().stream().filter(p -> p.getName().equals(playerA.getName())).findFirst().orElse(null); + Assert.assertNotNull(playerView); + playerView.getBattlefield().values() + .stream() + .filter(card -> card.getName().equals(tokenName)) + .sorted(Comparator.comparing(CardView::getExpansionSetCode)) + .forEach(permanentView -> { + String realCode = permanentView.getExpansionSetCode(); + realClientStats.computeIfAbsent(realCode, code -> new ArrayList<>()); + realClientStats.get(realCode).add(permanentView); + }); + + // check client data (GameView's objects must get same data as Game's objects) + Assert.assertEquals(realServerStats.size(), realClientStats.size()); + Assert.assertEquals( + realServerStats.values().stream().mapToInt(List::size).sum(), + realClientStats.values().stream().mapToInt(List::size).sum() + ); + String serverDataInfo = realServerStats.entrySet() + .stream() + .map(entry -> String.format("%s-%d", entry.getKey(), entry.getValue().size())) + .collect(Collectors.joining(", ")); + String clientDataInfo = realClientStats.entrySet() + .stream() + .map(entry -> String.format("%s-%d", entry.getKey(), entry.getValue().size())) + .collect(Collectors.joining(", ")); + Assert.assertEquals(serverDataInfo, clientDataInfo); + + // check stats + List checkResults = new ArrayList<>(); + Arrays.stream(checks).forEach(check -> { + Matcher checkMatcher = checkPattern.matcher(check); + if (!checkMatcher.find()) { + throw new IllegalArgumentException("Unknown check operation format: " + check); + } + Assert.assertEquals(3, checkMatcher.groupCount()); + String checkCode = checkMatcher.group(1); + String checkOper = checkMatcher.group(2); + String checkVal = checkMatcher.group(3); + + boolean isFine = true; + List problems = new ArrayList<>(); + + // check: tokens amount + List realList = realServerStats.getOrDefault(checkCode, new ArrayList<>()); + switch (checkOper) { + case "<": + isFine = isFine && (realList.size() < Integer.parseInt(checkVal)); + break; + case "=": + isFine = isFine && (realList.size() == Integer.parseInt(checkVal)); + break; + case ">": + isFine = isFine && (realList.size() > Integer.parseInt(checkVal)); + break; + default: + throw new IllegalArgumentException("Unknown check operation: " + check); + } + if (!isFine) { + problems.add("real amount is " + realList.size()); + } + + // check: tokens store type + // token as card like Embalm ability must have card number, so it will link to card's image instead token's image + boolean hasCardNumbers = !realList.isEmpty() && realList + .stream() + .mapToInt(card -> card.getCardNumber() == null ? 0 : CardUtil.parseCardNumberAsInt(card.getCardNumber())) + .allMatch(x -> x > 0); + if (mustStoreAsCard != hasCardNumbers) { + isFine = false; + problems.add("wrong store type"); + } + + checkResults.add(String.format( + "%s: %s%s", + check, + (isFine ? "OK" : "FAIL"), + (isFine ? "" : ", " + String.join("; ", problems)) + )); + }); + boolean allFine = checkResults.stream().noneMatch(s -> s.contains("FAIL")); + + // assert results + if (!allFine) { + String realInfo = realServerStats.entrySet() + .stream() + .map(entry -> String.format("%s-%s", entry.getKey(), entry.getValue().size())) + .collect(Collectors.joining(", ")); + + System.out.println("Real stats:"); + System.out.println(" - " + realInfo); + System.out.println(); + System.out.println("Check stats:"); + checkResults.forEach(s -> System.out.println(" - " + s)); + Assert.fail("Found wrong real stats, see logs above"); + } + } + + private void assert_TokenTypes(String tokenName, int needTokenTypes) { + Set serverStats = currentGame.getBattlefield().getAllPermanents() + .stream() + .filter(card -> card.getName().equals(tokenName)) + .sorted(Comparator.comparing(Card::getExpansionSetCode)) + .map(card -> (PermanentToken) card) + .map(perm -> perm.getToken().getTokenType()) + .collect(Collectors.toSet()); + + GameView gameView = new GameView(currentGame.getState(), currentGame, playerA.getId(), null); + PlayerView playerView = gameView.getPlayers() + .stream() + .filter(p -> p.getName().equals(playerA.getName())) + .findFirst() + .orElse(null); + Assert.assertNotNull(playerView); + Set clientStats = playerView.getBattlefield().values() + .stream() + .filter(card -> card.getName().equals(tokenName)) + .sorted(Comparator.comparing(CardView::getExpansionSetCode)) + .map(CardView::getType) + .collect(Collectors.toSet()); + + // server and client sides must have same data + Assert.assertEquals(serverStats.size(), clientStats.size()); + Assert.assertEquals(needTokenTypes, serverStats.size()); + Assert.assertEquals(needTokenTypes, clientStats.size()); + } + + @Test + public void test_TokenExists_MustGetSameSetCodeAsSourceCard_Soldier() { + prepareCards_MemorialToGlory("40K=3", "DOM=5"); + activate_MemorialToGlory(3 + 5); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "Soldier Token", 2); - currentGame.getBattlefield().getAllPermanents().stream() - .filter(card -> card.getName().equals("Soldier Token")) - .forEach(card -> { - Assert.assertEquals("40K", card.getExpansionSetCode()); - Assert.assertEquals("40K", ((PermanentToken) card).getToken().getOriginalExpansionSetCode()); - }); + // x2 tokens + assert_MemorialToGlory(3 + 5, "40K=6", "DOM=10"); + } + + @Test + public void test_TokenExists_MustGetSameSetByRandomTypes() { + prepareCards_MemorialToGlory("40K=20"); + activate_MemorialToGlory(20); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + // x2 tokens + assert_MemorialToGlory(20, "40K=40"); + assert_TokenTypes("Soldier Token", 3); // 40K set contains 3 diffrent soldiers + } + + @Test + public void test_TokenExists_MustGetSameSetCodeAsSourceCard_Wasp() { + // Tenth Edition (10E) + // 30th Anniversary Edition (30A) + prepareCards_TheHive("10E=3", "30A=5"); + activate_TheHive(3 + 5); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assert_TheHive(3 + 5, "10E=3", "30A=5"); + } + + @Test + public void test_TokenExists_MustGetRandomSetCodeOnAllUnknownSets() { + // if a source's set don't have tokens then it must be random + // https://github.com/magefree/mage/issues/10139 + + prepareCards_TheHive("5ED=20"); + activate_TheHive(20); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + // 5ED don't have tokens, so must be used another sets (10E, 30A) + assert_TheHive(20, "5ED=0", "10E>0", "30A>0"); + } + + @Test + @Ignore // TODO: implement + public void test_UnknownToken_MustGetDefaultImage() { + } + + @Test + public void test_Abilities_Embalm_MustGenerateSameTokenAsCard() { + prepareCards_SacredCat("AKH=3", "AKR=5", "MB1=1"); + activate_SacredCat(3 + 5 + 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assert_SacredCat(3 + 5 + 1, "AKH=3", "AKR=5", "MB1=1"); } } diff --git a/Mage/src/main/java/mage/game/command/CommandObject.java b/Mage/src/main/java/mage/game/command/CommandObject.java index 8659aba37ba..a2b596e5b24 100644 --- a/Mage/src/main/java/mage/game/command/CommandObject.java +++ b/Mage/src/main/java/mage/game/command/CommandObject.java @@ -18,4 +18,8 @@ public interface CommandObject extends MageObject, Controllable { @Override CommandObject copy(); + + String getExpansionSetCodeForImage(); + + void setExpansionSetCodeForImage(String expansionSetCodeForImage); } diff --git a/Mage/src/main/java/mage/game/command/Commander.java b/Mage/src/main/java/mage/game/command/Commander.java index 47584bd9f1e..2ff8800fce8 100644 --- a/Mage/src/main/java/mage/game/command/Commander.java +++ b/Mage/src/main/java/mage/game/command/Commander.java @@ -282,6 +282,16 @@ public class Commander implements CommandObject { return sourceObject.getImageName(); } + @Override + public String getExpansionSetCodeForImage() { + return sourceObject.getExpansionSetCode(); + } + + @Override + public void setExpansionSetCodeForImage(String expansionSetCodeForImage) { + throw new IllegalStateException("Can't change a set code of the commander, source card already has it"); + } + @Override public int getZoneChangeCounter(Game game) { return sourceObject.getZoneChangeCounter(game); diff --git a/Mage/src/main/java/mage/game/command/Dungeon.java b/Mage/src/main/java/mage/game/command/Dungeon.java index b7987c90ed6..1107e255bc0 100644 --- a/Mage/src/main/java/mage/game/command/Dungeon.java +++ b/Mage/src/main/java/mage/game/command/Dungeon.java @@ -327,10 +327,12 @@ public class Dungeon implements CommandObject { return new Dungeon(this); } + @Override public String getExpansionSetCodeForImage() { return expansionSetCodeForImage; } + @Override public void setExpansionSetCodeForImage(String expansionSetCodeForImage) { this.expansionSetCodeForImage = expansionSetCodeForImage; } diff --git a/Mage/src/main/java/mage/game/command/Emblem.java b/Mage/src/main/java/mage/game/command/Emblem.java index 0f810ce3325..5af9a644698 100644 --- a/Mage/src/main/java/mage/game/command/Emblem.java +++ b/Mage/src/main/java/mage/game/command/Emblem.java @@ -243,14 +243,16 @@ public class Emblem implements CommandObject { return new Emblem(this); } - public void setExpansionSetCodeForImage(String expansionSetCodeForImage) { - this.expansionSetCodeForImage = expansionSetCodeForImage; - } - + @Override public String getExpansionSetCodeForImage() { return expansionSetCodeForImage; } + @Override + public void setExpansionSetCodeForImage(String expansionSetCodeForImage) { + this.expansionSetCodeForImage = expansionSetCodeForImage; + } + @Override public int getZoneChangeCounter(Game game) { return 1; // Emblems can't move zones until now so return always 1 diff --git a/Mage/src/main/java/mage/game/command/Plane.java b/Mage/src/main/java/mage/game/command/Plane.java index 9241735c750..37605ad013a 100644 --- a/Mage/src/main/java/mage/game/command/Plane.java +++ b/Mage/src/main/java/mage/game/command/Plane.java @@ -242,14 +242,16 @@ public class Plane implements CommandObject { return new Plane(this); } - public void setExpansionSetCodeForImage(String expansionSetCodeForImage) { - this.expansionSetCodeForImage = expansionSetCodeForImage; - } - + @Override public String getExpansionSetCodeForImage() { return expansionSetCodeForImage; } + @Override + public void setExpansionSetCodeForImage(String expansionSetCodeForImage) { + this.expansionSetCodeForImage = expansionSetCodeForImage; + } + @Override public int getZoneChangeCounter(Game game) { return 1; // Emblems can't move zones until now so return always 1 diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 85ce4117c30..5f8cf24ba1a 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1703,8 +1703,8 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } @Override - public void setCardNumber(String cid) { - this.cardNumber = cid; + public void setCardNumber(String cardNumber) { + this.cardNumber = cardNumber; } @Override diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index c3e759146a8..3d3b649ce9f 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -121,4 +121,24 @@ public class PermanentToken extends PermanentImpl { // token don't have game card, so return itself return this; } + + @Override + public String getCardNumber() { + return token.getOriginalCardNumber(); + } + + @Override + public void setCardNumber(String cardNumber) { + throw new IllegalArgumentException("Wrong code usage: you can't change a token's card number"); + } + + @Override + public String getExpansionSetCode() { + return token.getOriginalExpansionSetCode(); + } + + @Override + public void setExpansionSetCode(String expansionSetCode) { + throw new IllegalArgumentException("Wrong code usage: you can't change a token's set code"); + } } diff --git a/Mage/src/main/java/mage/game/permanent/token/Token.java b/Mage/src/main/java/mage/game/permanent/token/Token.java index c1203d52d11..6bc9ce7a4ad 100644 --- a/Mage/src/main/java/mage/game/permanent/token/Token.java +++ b/Mage/src/main/java/mage/game/permanent/token/Token.java @@ -58,6 +58,4 @@ public interface Token extends MageObject { void setCopySourceCard(Card copySourceCard); void setExpansionSetCodeForImage(String code); - - boolean updateExpansionSetCode(String setCode); } 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 424eb930ecb..3ed57fef28f 100644 --- a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java +++ b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java @@ -13,6 +13,7 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; +import mage.game.command.CommandObject; import mage.game.events.CreateTokenEvent; import mage.game.events.CreatedTokenEvent; import mage.game.events.ZoneChangeEvent; @@ -34,8 +35,6 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { private int tokenType; private String originalCardNumber; private String originalExpansionSetCode; - private String tokenDescriptor; - private boolean expansionSetCodeChecked; private Card copySourceCard; // the card the Token is a copy from private static final int MAX_TOKENS_PER_GAME = 500; @@ -73,7 +72,6 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { this.lastAddedTokenIds.addAll(token.lastAddedTokenIds); this.originalCardNumber = token.originalCardNumber; this.originalExpansionSetCode = token.originalExpansionSetCode; - this.expansionSetCodeChecked = token.expansionSetCodeChecked; this.copySourceCard = token.copySourceCard; // will never be changed this.availableImageSetCodes = token.availableImageSetCodes; } @@ -81,20 +79,6 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { @Override public abstract Token copy(); - private void setTokenDescriptor() { - this.tokenDescriptor = tokenDescriptor(); - } - - private String tokenDescriptor() { - String strName = this.name.replaceAll("[^a-zA-Z0-9]", ""); - String strColor = this.color.toString().replaceAll("[^a-zA-Z0-9]", ""); - String strSubtype = this.subtype.toString().replaceAll("[^a-zA-Z0-9]", ""); - String strCardType = this.cardType.toString().replaceAll("[^a-zA-Z0-9]", ""); - String descriptor = strName + '.' + strColor + '.' + strSubtype + '.' + strCardType + '.' + this.power + '.' + this.toughness; - descriptor = descriptor.toUpperCase(Locale.ENGLISH); - return descriptor; - } - @Override public String getDescription() { return description; @@ -156,28 +140,41 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { return putOntoBattlefield(amount, game, source, controllerId, tapped, attacking, null); } - private String getSetCode(Game game, UUID sourceId) { - // moved here from CreateTokenEffect because not all cards that create tokens use CreateTokenEffect - // they use putOntoBattlefield directly - // TODO: Check this setCode handling because it makes no sense if token put into play with e.g. "Feldon of the third Path" + public static String generateSetCode(TokenImpl token, Game game, UUID sourceId) { + // Choose a set code's by priority: + // - use source's code + // - use parent source's code (too complicated, so ignore it) + // - use random set code + // - use default set code + // Token type must be set on set's code update + + // source String setCode = null; - if (this.getOriginalExpansionSetCode() != null && !this.getOriginalExpansionSetCode().isEmpty()) { - setCode = this.getOriginalExpansionSetCode(); - } else { - Card source = game.getCard(sourceId); - if (source != null) { - setCode = source.getExpansionSetCode(); - } else { - MageObject object = game.getObject(sourceId); - if (object instanceof PermanentToken) { - setCode = ((PermanentToken) object).getExpansionSetCode(); - } - } + Card sourceCard = game.getCard(sourceId); + if (sourceCard != null) { + setCode = sourceCard.getExpansionSetCode(); + } + MageObject sourceObject = game.getObject(sourceId); + if (sourceObject instanceof CommandObject) { + setCode = ((CommandObject) sourceObject).getExpansionSetCodeForImage(); } - if (!expansionSetCodeChecked) { - expansionSetCodeChecked = this.updateExpansionSetCode(setCode); + // TODO: change to tokens database + if (token.availableImageSetCodes.contains(setCode)) { + return setCode; } + + // random + if (!token.availableImageSetCodes.isEmpty()) { + return token.availableImageSetCodes.get(RandomUtil.nextInt(token.availableImageSetCodes.size())); + } + + // default + // TODO: implement + if (setCode == null) { + setCode = "DEFAULT"; + } + return setCode; } @@ -242,14 +239,19 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { for (Map.Entry entry : event.getTokens().entrySet()) { Token token = entry.getKey(); int amount = entry.getValue(); - String setCode = token instanceof TokenImpl ? ((TokenImpl) token).getSetCode(game, event.getSourceId()) : null; + + // choose token's set code due source + String setCode = TokenImpl.generateSetCode((TokenImpl) token, game, source == null ? null : source.getSourceId()); + token.setOriginalExpansionSetCode(setCode); + token.setExpansionSetCodeForImage(setCode); List needTokens = new ArrayList<>(); List allowedTokens = new ArrayList<>(); // prepare tokens to enter for (int i = 0; i < amount; i++) { - // use event.getPlayerId() as controller cause it can be replaced by replacement effect + // 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(), setCode, game); game.getState().addCard(newPermanent); needTokens.add(newPermanent); @@ -257,7 +259,7 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { newPermanent.setTapped(tapped); ZoneChangeEvent emptyEvent = new ZoneChangeEvent(newPermanent, newPermanent.getControllerId(), Zone.OUTSIDE, Zone.BATTLEFIELD); - // tokens zcc must simulate card's zcc too keep copied card/spell settings + // tokens zcc must simulate card's zcc to keep copied card/spell settings // (example: etb's kicker ability of copied creature spell, see tests with Deathforge Shaman) newPermanent.updateZoneChangeCounter(game, emptyEvent); } @@ -410,10 +412,10 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { @Override public void setOriginalExpansionSetCode(String originalExpansionSetCode) { + // TODO: delete // TODO: remove original set code at all... token image must be takes by card source or by latest set (on null source) // TODO: if set have same tokens then selects it by random this.originalExpansionSetCode = originalExpansionSetCode; - setTokenDescriptor(); } @Override @@ -430,28 +432,7 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { @Override public void setExpansionSetCodeForImage(String code) { - if (!availableImageSetCodes.isEmpty()) { - if (availableImageSetCodes.contains(code)) { - setOriginalExpansionSetCode(code); - } else // we should not set random set if appropriate set is already used - { - if (getOriginalExpansionSetCode() == null || getOriginalExpansionSetCode().isEmpty() - || !availableImageSetCodes.contains(getOriginalExpansionSetCode())) { - setOriginalExpansionSetCode(availableImageSetCodes.get(RandomUtil.nextInt(availableImageSetCodes.size()))); - } - } - } else if (getOriginalExpansionSetCode() == null || getOriginalExpansionSetCode().isEmpty()) { - setOriginalExpansionSetCode(code); - } - setTokenDescriptor(); - } - - @Override - public boolean updateExpansionSetCode(String setCode) { - if (setCode == null || setCode.isEmpty()) { - return false; - } - this.setExpansionSetCodeForImage(setCode); - return true; + // TODO: delete + setOriginalExpansionSetCode(code); } } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index f9bdc931fe5..588cc75153c 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -561,7 +561,7 @@ public final class CardUtil { */ public static int parseCardNumberAsInt(String cardNumber) { - if (cardNumber.isEmpty()) { + if (cardNumber == null || cardNumber.isEmpty()) { throw new IllegalArgumentException("Card number is empty."); } diff --git a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java index c2a06fb9acb..b2035478f0b 100644 --- a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java +++ b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java @@ -36,12 +36,14 @@ public class CopyTokenFunction implements Function { MageObject sourceObj = source; if (source instanceof PermanentToken) { + // create token from another token sourceObj = ((PermanentToken) source).getToken(); // to show the source image, the original values have to be used target.setOriginalExpansionSetCode(((Token) sourceObj).getOriginalExpansionSetCode()); target.setOriginalCardNumber(((Token) sourceObj).getOriginalCardNumber()); target.setCopySourceCard(((PermanentToken) source).getToken().getCopySourceCard()); } else if (source instanceof PermanentCard) { + // create token from non-token permanent if (((PermanentCard) source).isMorphed() || ((PermanentCard) source).isManifested()) { MorphAbility.setPermanentToFaceDownCreature(target, game); return target; @@ -57,6 +59,7 @@ public class CopyTokenFunction implements Function { target.setCopySourceCard((Card) sourceObj); } } else { + // create token from non-permanent object like card (example: Embalm ability) target.setOriginalExpansionSetCode(source.getExpansionSetCode()); target.setOriginalCardNumber(source.getCardNumber()); target.setCopySourceCard(source);