PaperToken: add CollectorNumber and update Downloader

This commit is contained in:
Hans Mackowiak
2025-05-02 21:31:40 +02:00
committed by Chris H
parent 67f8f4760e
commit b57a5e9ad1
11 changed files with 134 additions and 131 deletions

View File

@@ -103,7 +103,13 @@ public final class ImageKeys {
final String dir;
final String filename;
if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
filename = key.substring(ImageKeys.TOKEN_PREFIX.length());
String[] tempdata = key.substring(ImageKeys.TOKEN_PREFIX.length()).split("\\|");
String tokenname = tempdata[0] + "_" + tempdata[1];
if (tempdata.length > 2) {
tokenname += "_" + tempdata[2];
}
filename = tokenname;
dir = CACHE_TOKEN_PICS_DIR;
} else if (key.startsWith(ImageKeys.ICON_PREFIX)) {
filename = key.substring(ImageKeys.ICON_PREFIX.length());
@@ -226,37 +232,28 @@ public final class ImageKeys {
}
}
if (dir.equals(CACHE_TOKEN_PICS_DIR)) {
int index = filename.lastIndexOf('_');
if (index != -1) {
String setlessFilename = filename.substring(0, index);
String setCode = filename.substring(index + 1);
// try with upper case set
file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
String[] tempdata = key.substring(ImageKeys.TOKEN_PREFIX.length()).split("\\|");
String setlessFilename = tempdata[0];
String setCode = tempdata[1];
String collectorNumber = tempdata.length > 2 ? tempdata[2] : "";
if (!collectorNumber.isEmpty()) {
file = findFile(dir, setlessFilename + "_" + setCode + "_" + collectorNumber);
if (file != null) {
cachedCards.put(filename, file);
return file;
}
// try with lower case set
file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase());
}
file = findFile(dir, setlessFilename + "_" + setCode);
if (file != null) {
cachedCards.put(filename, file);
return file;
}
// try without set name
file = findFile(dir, setlessFilename);
if (file != null) {
cachedCards.put(filename, file);
return file;
}
// if there's an art variant try without it
if (setlessFilename.matches(".*[0-9]*$")) {
file = findFile(dir, setlessFilename.replaceAll("[0-9]*$", ""));
if (file != null) {
cachedCards.put(filename, file);
return file;
}
}
}
} else if (filename.contains("/")) {
String setlessFilename = filename.substring(filename.indexOf('/') + 1);
file = findFile(dir, setlessFilename);

View File

@@ -1043,7 +1043,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
public static final Predicate<PaperCard> EDITION_NON_PROMO = paperCard -> {
String code = paperCard.getEdition();
CardEdition edition = StaticData.instance().getCardEdition(code);
if(edition == null && code.equals("???"))
if(edition == null && code.equals(CardEdition.UNKNOWN_CODE))
return true;
return edition != null && edition.getType() != Type.PROMO;
};
@@ -1051,7 +1051,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
public static final Predicate<PaperCard> EDITION_NON_REPRINT = paperCard -> {
String code = paperCard.getEdition();
CardEdition edition = StaticData.instance().getCardEdition(code);
if(edition == null && code.equals("???"))
if(edition == null && code.equals(CardEdition.UNKNOWN_CODE))
return true;
return edition != null && Type.REPRINT_SET_TYPES.contains(edition.getType());
};

View File

@@ -870,7 +870,7 @@ public final class CardEdition implements Comparable<CardEdition> {
public CardEdition getEditionByCodeOrThrow(final String code) {
final CardEdition set = this.get(code);
if (null == set && code.equals("???")) //Hardcoded set ??? is not with the others, needs special check.
if (null == set && code.equals(UNKNOWN_CODE)) //Hardcoded set ??? is not with the others, needs special check.
return UNKNOWN;
if (null == set) {
throw new RuntimeException("Edition with code '" + code + "' not found");

View File

@@ -11,6 +11,8 @@ import java.util.Locale;
public class PaperToken implements InventoryItemFromSet, IPaperCard {
private static final long serialVersionUID = 1L;
private String name;
private String collectorNumber;
private String artist;
private transient CardEdition edition;
private ArrayList<String> imageFileName = new ArrayList<>();
private transient CardRules cardRules;
@@ -53,75 +55,36 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
return makeTokenFileName(fileName);
}
public static String makeTokenFileName(final CardRules rules, CardEdition edition) {
ArrayList<String> build = new ArrayList<>();
String subtypes = StringUtils.join(rules.getType().getSubtypes(), " ");
if (!rules.getName().equals(subtypes)) {
return makeTokenFileName(rules.getName());
}
ColorSet colors = rules.getColor();
if (colors.isColorless()) {
build.add("C");
} else {
String color = "";
if (colors.hasWhite()) color += "W";
if (colors.hasBlue()) color += "U";
if (colors.hasBlack()) color += "B";
if (colors.hasRed()) color += "R";
if (colors.hasGreen()) color += "G";
build.add(color);
}
if (rules.getPower() != null && rules.getToughness() != null) {
build.add(rules.getPower());
build.add(rules.getToughness());
}
String cardTypes = "";
if (rules.getType().isArtifact()) cardTypes += "A";
if (rules.getType().isEnchantment()) cardTypes += "E";
if (!cardTypes.isEmpty()) {
build.add(cardTypes);
}
build.add(subtypes);
// Are these keywords sorted?
for (String keyword : rules.getMainPart().getKeywords()) {
build.add(keyword);
}
if (edition != null) {
build.add(edition.getCode());
}
return StringUtils.join(build, "_").replace('*', 'x').toLowerCase();
}
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName) {
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName, String collectorNumber, String artist) {
this.cardRules = c;
this.name = c.getName();
this.edition = edition0;
this.collectorNumber = collectorNumber;
this.artist = artist;
if (edition != null && edition.getTokens().containsKey(imageFileName)) {
this.artIndex = edition.getTokens().get(imageFileName).size();
if (collectorNumber != null && !collectorNumber.isEmpty()) {
int idx = 0;
// count the one with the same collectorNumber
for (CardEdition.TokenInSet t : edition.getTokens().get(imageFileName)) {
++idx;
if (!t.collectorNumber.equals(collectorNumber)) {
continue;
}
if (imageFileName == null) {
// This shouldn't really happen. We can just use the normalized name again for the base image name
this.imageFileName.add(makeTokenFileName(c, edition0));
} else {
// TODO make better image file names when collector number is known
// for the right index, we need to count the ones with wrong collector number too
this.imageFileName.add(String.format("%s|%s|%s|%d", imageFileName, edition.getCode().toLowerCase(), collectorNumber, idx));
}
this.artIndex = this.imageFileName.size();
}
} else if (imageFileName != null) {
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : "_" + edition.getCode().toLowerCase();
this.imageFileName.add(String.format("%s%s", imageFileName, formatEdition));
for (int idx = 2; idx <= this.artIndex; idx++) {
this.imageFileName.add(String.format("%s%d%s", imageFileName, idx, formatEdition));
if (null == edition || CardEdition.UNKNOWN == edition) {
this.imageFileName.add(imageFileName);
}
// Fallback for if the Edition doesn't know about the Token
this.imageFileName.add(String.format("%s|%s", imageFileName, formatEdition));
}
}
@@ -137,12 +100,14 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
@Override
public String getEdition() {
return edition != null ? edition.getCode() : "???";
return edition != null ? edition.getCode() : CardEdition.UNKNOWN_CODE;
}
@Override
public String getCollectorNumber() {
if (collectorNumber.isEmpty())
return IPaperCard.NO_COLLECTOR_NUMBER;
return collectorNumber;
}
@Override
@@ -177,13 +142,8 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
}
@Override
public String getArtist() { /*TODO*/
return "";
}
// Unfortunately this is a property of token, cannot move it outside of class
public String getImageFilename() {
return getImageFilename(1);
public String getArtist() {
return artist;
}
public String getImageFilename(int idx) {
@@ -261,11 +221,11 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
if (hasBackFace()) {
String edCode = edition != null ? "_" + edition.getCode().toLowerCase() : "";
if (altState) {
String name = ImageKeys.TOKEN_PREFIX + cardRules.getOtherPart().getName().toLowerCase().replace(" token", "");
String name = ImageKeys.getTokenKey(cardRules.getOtherPart().getName().toLowerCase().replace(" token", ""));
name.replace(" ", "_");
return name + edCode;
} else {
String name = ImageKeys.TOKEN_PREFIX + cardRules.getMainPart().getName().toLowerCase().replace(" token", "");
String name = ImageKeys.getTokenKey(cardRules.getMainPart().getName().toLowerCase().replace(" token", ""));
name.replace(" ", "_");
return name + edCode;
}
@@ -275,7 +235,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
}
public String getImageKey(int artIndex) {
return ImageKeys.TOKEN_PREFIX + imageFileName.get(artIndex).replace(" ", "_");
return ImageKeys.getTokenKey(imageFileName.get(artIndex).replace(" ", "_"));
}
public boolean isRebalanced() {

View File

@@ -1,10 +1,15 @@
package forge.token;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import forge.card.CardDb;
import forge.card.CardEdition;
import forge.card.CardRules;
import forge.item.IPaperCard;
import forge.item.PaperToken;
import forge.util.Aggregates;
import java.util.*;
import java.util.function.Predicate;
@@ -23,8 +28,8 @@ public class TokenDb implements ITokenDatabase {
// The image names should be the same as the script name + _set
// If that isn't found, consider falling back to the original token
private final Map<String, PaperToken> tokensByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Multimap<String, PaperToken> allTokenByName = HashMultimap.create();
private final Map<String, PaperToken> extraTokensByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final CardEdition.Collection editions;
private final Map<String, CardRules> rulesByName;
@@ -38,38 +43,64 @@ public class TokenDb implements ITokenDatabase {
return this.rulesByName.containsKey(rule);
}
@Override
public PaperToken getToken(String tokenName) {
return getToken(tokenName, CardEdition.UNKNOWN.getName());
}
public void preloadTokens() {
for (CardEdition edition : this.editions) {
for (String name : edition.getTokens().keySet()) {
try {
getToken(name, edition.getCode());
} catch(Exception e) {
System.out.println(name + "_" + edition.getCode() + " defined in Edition file, but not defined as a token script.");
for (Map.Entry<String, Collection<CardEdition.TokenInSet>> inSet : edition.getTokens().asMap().entrySet()) {
String name = inSet.getKey();
String fullName = String.format("%s_%s", name, edition.getCode().toLowerCase());
for (CardEdition.TokenInSet t : inSet.getValue()) {
allTokenByName.put(fullName, addTokenInSet(edition, name, t));
}
}
}
}
protected boolean loadTokenFromSet(CardEdition edition, String name) {
String fullName = String.format("%s_%s", name, edition.getCode().toLowerCase());
if (allTokenByName.containsKey(fullName)) {
return true;
}
if (!edition.getTokens().containsKey(name)) {
return false;
}
for (CardEdition.TokenInSet t : edition.getTokens().get(name)) {
allTokenByName.put(fullName, addTokenInSet(edition, name, t));
}
return true;
}
protected PaperToken addTokenInSet(CardEdition edition, String name, CardEdition.TokenInSet t) {
return new PaperToken(rulesByName.get(name), edition, name, t.collectorNumber, t.artistName);
}
@Override
public PaperToken getToken(String tokenName) {
return getToken(tokenName, CardEdition.UNKNOWN.getCode());
}
@Override
public PaperToken getToken(String tokenName, String edition) {
String fullName = String.format("%s_%s", tokenName, edition.toLowerCase());
CardEdition realEdition = editions.getEditionByCodeOrThrow(edition);
String fullName = String.format("%s_%s", tokenName, realEdition.getCode().toLowerCase());
if (!tokensByName.containsKey(fullName)) {
// token exist in Set, return one at random
if (loadTokenFromSet(realEdition, tokenName)) {
return Aggregates.random(allTokenByName.get(fullName));
}
if (!extraTokensByName.containsKey(fullName)) {
try {
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition), tokenName);
tokensByName.put(fullName, pt);
PaperToken pt = new PaperToken(rulesByName.get(tokenName), realEdition, tokenName, "", IPaperCard.NO_ARTIST_NAME);
extraTokensByName.put(fullName, pt);
return pt;
} catch(Exception e) {
throw e;
}
}
return tokensByName.get(fullName);
return extraTokensByName.get(fullName);
}
@Override
@@ -119,7 +150,7 @@ public class TokenDb implements ITokenDatabase {
@Override
public List<PaperToken> getAllTokens() {
return new ArrayList<>(tokensByName.values());
return new ArrayList<>(allTokenByName.values());
}
@Override
@@ -139,6 +170,6 @@ public class TokenDb implements ITokenDatabase {
@Override
public Iterator<PaperToken> iterator() {
return tokensByName.values().iterator();
return allTokenByName.values().iterator();
}
}

View File

@@ -36,7 +36,7 @@ public class SwingImageFetcher extends ImageFetcher {
String newdespath = urlToDownload.contains(".fullborder.jpg") || urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD) ?
TextUtil.fastReplace(destPath, ".full.jpg", ".fullborder.jpg") : destPath;
if (!newdespath.contains(".full") && urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD))
if (!newdespath.contains(".full") && urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD) && !destPath.startsWith(ForgeConstants.CACHE_TOKEN_PICS_DIR))
newdespath = newdespath.replace(".jpg", ".fullborder.jpg"); //fix planes/phenomenon for round border options
URL url = new URL(urlToDownload);
System.out.println("Attempting to fetch: " + url);

View File

@@ -37,7 +37,7 @@ public class LibGDXImageFetcher extends ImageFetcher {
String newdespath = urlToDownload.contains(".fullborder.") || urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD) ?
TextUtil.fastReplace(destPath, ".full.", ".fullborder.") : destPath;
if (!newdespath.contains(".full") && urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD))
if (!newdespath.contains(".full") && urlToDownload.startsWith(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD) && !destPath.startsWith(ForgeConstants.CACHE_TOKEN_PICS_DIR))
newdespath = newdespath.replace(".jpg", ".fullborder.jpg"); //fix planes/phenomenon for round border options
URL url = new URL(urlToDownload);
System.out.println("Attempting to fetch: " + url);

View File

@@ -156,7 +156,7 @@ public class QuestUtil {
script.add("Types:" + properties[5].replace(';', ' '));
script.add("Oracle:"); // tokens don't have texts yet
final String fileName = PaperToken.makeTokenFileName(properties[1], properties[2], properties[3], properties[4]);
return new PaperToken(CardRules.fromScript(script), CardEdition.UNKNOWN, fileName);
return new PaperToken(CardRules.fromScript(script), CardEdition.UNKNOWN, fileName, "", IPaperCard.NO_ARTIST_NAME);
}
/**

View File

@@ -8,6 +8,7 @@ import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import forge.card.CardEdition;
import forge.card.CardRules;
import forge.item.IPaperCard;
import forge.item.PaperToken;
import forge.localinstance.properties.ForgeConstants;
import forge.util.FileUtil;
@@ -59,7 +60,7 @@ public class QuestPetStats {
if (null == petCard) {
List<String> cardLines = FileUtil.readFile(new File(ForgeConstants.BAZAAR_DIR, cardFile));
CardRules rules = CardRules.fromScript(cardLines);
petCard = new PaperToken(rules, CardEdition.UNKNOWN, picture);
petCard = new PaperToken(rules, CardEdition.UNKNOWN, picture, "", IPaperCard.NO_ARTIST_NAME);
}
return petCard;
}

View File

@@ -17,6 +17,7 @@
*/
package forge.gui.download;
import forge.card.CardEdition;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgeConstants;
import forge.model.FModel;
@@ -93,7 +94,7 @@ public class GuiDownloadPicturesHQ extends GuiDownloadService {
cardname = cardname.replace(" ", "+");
cardname = cardname.replace("'", "");
String scryfallurl = ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD + "named?fuzzy=" + cardname;
if(!setCode.equals("???")) scryfallurl += "&set=" + setCode.toLowerCase();
if(!setCode.equals(CardEdition.UNKNOWN_CODE)) scryfallurl += "&set=" + setCode.toLowerCase();
if(face.equals("back")) scryfallurl += "&face=back";
scryfallurl += "&format=image";

View File

@@ -228,21 +228,29 @@ public abstract class ImageFetcher {
this.getScryfallDownloadURL(paperCard, face, useArtCrop, hasSetLookup, filename, downloadUrls);
}
} else if (prefix.equals(ImageKeys.TOKEN_PREFIX)) {
final String filename = imageKey.substring(2) + ".jpg";
String[] tempdata = imageKey.substring(2).split("\\|"); //We want to check the edition first.
String tokenName = tempdata[0];
String setCode = tempdata[1];
StringBuilder sb = new StringBuilder(tokenName);
sb.append("_").append(setCode);
if (tempdata.length > 2) {
sb.append("_").append(tempdata[2]);
}
sb.append(".jpg");
final String filename = sb.toString();
if (ImageKeys.missingCards.contains(filename))
return;
if (filename.equalsIgnoreCase("null.jpg"))
return;
String[] tempdata = imageKey.substring(2).split("[_](?=[^_]*$)"); //We want to check the edition first.
if (tempdata.length < 2) {
System.err.println("Token image key is malformed: " + imageKey);
return;
}
String tokenName = tempdata[0];
String setCode = tempdata[1];
// Load the paper token from filename + edition
CardEdition edition = StaticData.instance().getEditions().get(setCode);
@@ -251,7 +259,12 @@ public abstract class ImageFetcher {
//PaperToken pt = StaticData.instance().getAllTokens().getToken(tokenName, setCode);
Collection<CardEdition.EditionEntry> allTokens = edition.getTokens().get(tokenName);
if (!allTokens.isEmpty()) {
if (tempdata.length > 2) {
String tokenCode = edition.getTokensCode();
String langCode = edition.getCardsLangCode();
// just assume the CNr from the token image is valid
downloadUrls.add(ForgeConstants.URL_PIC_SCRYFALL_DOWNLOAD + ImageUtil.getScryfallTokenDownloadUrl(tempdata[2], tokenCode, langCode));
} else if (!allTokens.isEmpty()) {
// This loop is going to try to download all the arts until it finds one
// This is a bit wrong since it _should_ just be trying to get the one with the appropriate collector number
// Since we don't have that for tokens, we'll just take the first one