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);
This commit is contained in:
Oleg Agafonov 2023-05-01 17:27:26 +04:00
parent 5c6de9815f
commit 94819ff91c
5 changed files with 149 additions and 12 deletions

View file

@ -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<String, List<Card>> 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)

View file

@ -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
*

View file

@ -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<String> 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);

View file

@ -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<String> 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);

View file

@ -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<Permanent> 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);