Tokens rework:

- added reminder / helper tokens support (example: Copy, Morph, Day // Night, related to #10139);
 - added verify checks for reminder tokens;
 - added images download for reminder tokens;
This commit is contained in:
Oleg Agafonov 2023-04-27 18:45:50 +04:00
parent 0e1e6a0f21
commit f86cf176d7
11 changed files with 242 additions and 40 deletions

View file

@ -1273,12 +1273,12 @@ public class ContinuousEffects implements Serializable {
}
public synchronized void addEffect(ContinuousEffect effect, Ability source) {
if (effect == null) {
logger.error("Effect is null: " + source.toString());
return;
} else if (source == null) {
logger.warn("Adding effect without ability : " + effect);
if (effect == null || source == null) {
// addEffect(effect, source) need a non-null source
throw new IllegalArgumentException("Wrong code usage. Effect and source can't be null here: "
+ source + "; " + effect);
}
switch (effect.getEffectType()) {
case REPLACEMENT:
case REDIRECTION:
@ -1313,9 +1313,12 @@ public class ContinuousEffects implements Serializable {
case CONTINUOUS_RULE_MODIFICATION:
continuousRuleModifyingEffects.addEffect((ContinuousRuleModifyingEffect) effect, source);
break;
default:
case CONTINUOUS:
case ONESHOT:
layeredEffects.addEffect(effect, source);
break;
default:
throw new IllegalArgumentException("Unknown effect type: " + effect.getEffectType());
}
}

View file

@ -7,14 +7,16 @@ package mage.cards.repository;
*/
public class TokenInfo {
private TokenType tokenType;
private String name;
private String setCode;
private Integer imageNumber = 1; // if one set contains diff images with same name
private final TokenType tokenType;
private final String name;
private final String setCode;
private final Integer imageNumber; // if one set contains diff images with same name
private String classFileName;
private final String classFileName;
private String imageFileName;
private final String imageFileName;
private String downloadUrl = "";
public TokenInfo(TokenType tokenType, String name, String setCode, Integer imageNumber) {
this(tokenType, name, setCode, imageNumber, "", "");
@ -31,7 +33,7 @@ public class TokenInfo {
@Override
public String toString() {
return String.format("%s - %s - %d (%s)", this.setCode, this.name, this.imageNumber, this.classFileName);
return String.format("%s - %s - %s - %d (%s)", this.tokenType, this.setCode, this.name, this.imageNumber, this.classFileName);
}
public TokenType getTokenType() {
@ -50,14 +52,19 @@ public class TokenInfo {
return setCode;
}
public String getClassFileName() {
return classFileName;
}
public Integer getImageNumber() {
return imageNumber;
}
public String getDownloadUrl() {
return downloadUrl;
}
public TokenInfo withDownloadUrl(String downloadUrl) {
this.downloadUrl = downloadUrl;
return this;
}
public String getFullClassFileName() {
String simpleName = classFileName.isEmpty() ? name.replaceAll("[^a-zA-Z0-9]", "") : classFileName;
switch (this.tokenType) {
@ -69,6 +76,8 @@ public class TokenInfo {
return "mage.game.command.planes." + simpleName;
case DUNGEON:
return "mage.game.command.dungeons." + simpleName;
case XMAGE:
return classFileName;
default:
throw new IllegalStateException("Unknown token type: " + this.tokenType);
}

View file

@ -15,11 +15,13 @@ public enum TokenRepository {
instance;
public static final String XMAGE_TOKENS_SET_CODE = "XMAGE";
private static final Logger logger = Logger.getLogger(TokenRepository.class);
private ArrayList<TokenInfo> allTokens = new ArrayList<>();
private Map<String, List<TokenInfo>> indexByClassName = new HashMap<>();
private Map<TokenType, List<TokenInfo>> indexByType = new HashMap<>();
private final Map<String, List<TokenInfo>> indexByClassName = new HashMap<>();
private final Map<TokenType, List<TokenInfo>> indexByType = new HashMap<>();
TokenRepository() {
}
@ -29,7 +31,9 @@ public enum TokenRepository {
return;
}
allTokens = loadAllTokens();
// tokens
allTokens = loadMtgTokens();
allTokens.addAll(loadXmageTokens());
// index
allTokens.forEach(token -> {
@ -72,7 +76,7 @@ public enum TokenRepository {
return indexByClassName.getOrDefault(fullClassName, new ArrayList<>());
}
private static ArrayList<TokenInfo> loadAllTokens() throws RuntimeException {
private static ArrayList<TokenInfo> loadMtgTokens() throws RuntimeException {
// Must load tokens data in strict mode (throw exception on any error)
// Try to put verify checks here instead verify tests
String dbSource = "tokens-database.txt";
@ -203,4 +207,72 @@ public enum TokenRepository {
return list;
}
public Map<String, String> prepareScryfallDownloadList() {
init();
Map<String, String> res = new LinkedHashMap<>();
// format example:
// put("ONC/Angel/1", "https://api.scryfall.com/cards/tonc/2/en?format=image");
allTokens.stream()
.filter(token -> token.getTokenType().equals(TokenType.XMAGE))
.forEach(token -> {
String code = String.format("%s/%s/%d", token.getSetCode(), token.getName(), token.getImageNumber());
res.put(code, token.getDownloadUrl());
});
return res;
}
private static TokenInfo createXmageToken(String name, Integer imageNumber, String scryfallDownloadUrl) {
return new TokenInfo(TokenType.XMAGE, name, XMAGE_TOKENS_SET_CODE, imageNumber)
.withDownloadUrl(scryfallDownloadUrl);
}
private static ArrayList<TokenInfo> loadXmageTokens() {
// Create reminder/helper tokens (special images like Copy, Morph, Manifest, etc)
// Search by
// - https://tagger.scryfall.com/tags/card/assistant-cards
// - https://scryfall.com/search?q=otag%3Aassistant-cards&unique=cards&as=grid&order=name
// Must add only unique prints
// TODO: add custom set in download window to download a custom tokens only
// TODO: add custom set in card viewer to view a custom tokens only
ArrayList<TokenInfo> res = new ArrayList<>();
// Copy
// https://scryfall.com/search?q=include%3Aextras+unique%3Aprints+type%3Atoken+copy&unique=cards&as=grid&order=name
res.add(createXmageToken("Copy", 1, "https://api.scryfall.com/cards/tclb/19/en?format=image"));
res.add(createXmageToken("Copy", 2, "https://api.scryfall.com/cards/tsnc/1/en?format=image"));
res.add(createXmageToken("Copy", 3, "https://api.scryfall.com/cards/tvow/19/en?format=image"));
res.add(createXmageToken("Copy", 4, "https://api.scryfall.com/cards/tznr/12/en?format=image"));
// City's Blessing
// https://scryfall.com/search?q=type%3Atoken+include%3Aextras+unique%3Aprints+City%27s+Blessing+&unique=cards&as=grid&order=name
res.add(createXmageToken("City's Blessing", 1, "https://api.scryfall.com/cards/f18/2/en?format=image"));
// Day // Night
// https://scryfall.com/search?q=include%3Aextras+unique%3Aprints+%22Day+%2F%2F+Night%22&unique=cards&as=grid&order=name
res.add(createXmageToken("Day", 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=front"));
res.add(createXmageToken("Night", 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=back"));
// Manifest
// https://scryfall.com/search?q=Manifest+include%3Aextras+unique%3Aprints&unique=cards&as=grid&order=name
res.add(createXmageToken("Manifest", 1, "https://api.scryfall.com/cards/tc19/28/en?format=image"));
res.add(createXmageToken("Manifest", 2, "https://api.scryfall.com/cards/tc18/1/en?format=image"));
res.add(createXmageToken("Manifest", 3, "https://api.scryfall.com/cards/tfrf/4/en?format=image"));
res.add(createXmageToken("Manifest", 4, "https://api.scryfall.com/cards/tncc/3/en?format=image"));
// Morph
// https://scryfall.com/search?q=Morph+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name
res.add(createXmageToken("Morph", 1, "https://api.scryfall.com/cards/tktk/11/en?format=image"));
res.add(createXmageToken("Morph", 2, "https://api.scryfall.com/cards/ta25/15/en?format=image"));
res.add(createXmageToken("Morph", 3, "https://api.scryfall.com/cards/tc19/27/en?format=image"));
// The Monarch
// https://scryfall.com/search?q=Monarch+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name
res.add(createXmageToken("The Monarch", 1, "https://api.scryfall.com/cards/tonc/22/en?format=image"));
res.add(createXmageToken("The Monarch", 2, "https://api.scryfall.com/cards/tcn2/1/en?format=image"));
return res;
}
}

View file

@ -1,6 +1,7 @@
package mage.cards.repository;
/**
* GUI related
* XMage's token types for images
*
* @author JayDi85
@ -10,6 +11,7 @@ public enum TokenType {
TOKEN,
EMBLEM,
PLANE,
DUNGEON
DUNGEON,
XMAGE // custom images for reminder cards like Copy, Manifest, etc
}

View file

@ -1,6 +1,8 @@
package mage.game.permanent.token;
/**
* Token container for copyable characteristics, don't put it to battlefield
*
* @author nantuko
*/
public final class EmptyToken extends TokenImpl {

View file

@ -167,14 +167,13 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
}
}
// by set code
// search by set code
List<TokenInfo> possibleInfo = TokenRepository.instance.getByClassName(token.getClass().getName())
.stream()
.filter(info -> info.getSetCode().equals(setCode))
.collect(Collectors.toList());
// by random set
// search by random set
if (possibleInfo.isEmpty()) {
possibleInfo = new ArrayList<>(TokenRepository.instance.getByClassName(token.getClass().getName()));
}
@ -183,11 +182,13 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
return RandomUtil.randomFromCollection(possibleInfo);
}
// unknown token
// TODO: download default tokens for xmage's set and use random images from it
// example: TOK.zip/Creature.1.full.jpg
// example: TOK.zip/Creature.2.full.jpg
return new TokenInfo(TokenType.TOKEN, "Unknown", "XMAGE", 0);
// TODO: implement auto-generate images for CreatureToken (search public tokens for same characteristics)
// TODO: implement Copy image
// TODO: implement Manifest image
// TODO: implement Morph image
// unknown tokens
return new TokenInfo(TokenType.TOKEN, "Unknown", TokenRepository.XMAGE_TOKENS_SET_CODE, 0);
}
@Override
@ -406,7 +407,7 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
}
/**
* Set token index to search in card-pictures-tok.txt (if set have multiple
* Set token index to search in tokens-database.txt (if set have multiple
* tokens with same name) Default is 1
*/
@Override

View file

@ -10,6 +10,10 @@ import mage.game.permanent.token.TokenImpl;
import java.util.Arrays;
/**
* Token builder for token effects
*
* Use it for custom tokens (tokens without public class and image)
*
* @author JayDi85
*/
public final class CreatureToken extends TokenImpl {

View file

@ -976,7 +976,7 @@ public final class CardUtil {
|| text.startsWith("any ")) {
return text;
}
return vowels.contains(text.substring(0, 1)) ? "an " + text : "a " + text;
return (!text.isEmpty() && vowels.contains(text.substring(0, 1))) ? "an " + text : "a " + text;
}
public static String italicizeWithEmDash(String text) {