Flavor Names (#8849)

* Make getAllFaces return nonnull list

* Optimize Predicates

* CardDB and script syntax changes

* Apply syntax changes

* In-game support for flavor names

* Add display names to PaperCards

* Support searching by flavor names

* Remove some WIP stuff

* Update PaperCard translation key.

* Update capitalization

* Auto-map to variants when edition entry uses a flavor name

* Consolidate display name logic.

* Added syntax for generating flavor named variants in edition files.

* Some examples of new syntax.

* Ignore flavored oracle text when searching rules text

* add hasFlavorName

* Add image key

* Get correct variant from card requests with flavor names.
This commit is contained in:
Jetz72
2025-10-21 09:00:59 -04:00
committed by GitHub
parent 1188b6889a
commit f9b6652c2a
90 changed files with 898 additions and 626 deletions

View File

@@ -68,10 +68,6 @@ public class StaticData {
this(cardReader, null, customCardReader, null, editionFolder, customEditionsFolder, blockDataFolder, "", cardArtPreference, enableUnknownCards, loadNonLegalCards, false, false);
}
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, CardStorageReader customCardReader, CardStorageReader customTokenReader, String editionFolder, String customEditionsFolder, String blockDataFolder, String setLookupFolder, String cardArtPreference, boolean enableUnknownCards, boolean loadNonLegalCards, boolean allowCustomCardsInDecksConformance){
this(cardReader, tokenReader, customCardReader, customTokenReader, editionFolder, customEditionsFolder, blockDataFolder, setLookupFolder, cardArtPreference, enableUnknownCards, loadNonLegalCards, allowCustomCardsInDecksConformance, false);
}
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, CardStorageReader customCardReader, CardStorageReader customTokenReader, String editionFolder, String customEditionsFolder, String blockDataFolder, String setLookupFolder, String cardArtPreference, boolean enableUnknownCards, boolean loadNonLegalCards, boolean allowCustomCardsInDecksConformance, boolean enableSmartCardArtSelection) {
this.cardReader = cardReader;
this.tokenReader = tokenReader;
@@ -81,8 +77,9 @@ public class StaticData {
this.enableSmartCardArtSelection = enableSmartCardArtSelection;
this.loadNonLegalCards = loadNonLegalCards;
lastInstance = this;
List<String> funnyCards = new ArrayList<>();
List<String> filtered = new ArrayList<>();
Set<String> funnyCards = new HashSet<>();
Set<String> filtered = new HashSet<>();
editions.append(new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder), true)));
{
@@ -108,7 +105,7 @@ public class StaticData {
final String cardName = card.getName();
if (!loadNonLegalCards && !card.getType().isLand() && funnyCards.contains(cardName))
if (!loadNonLegalCards && funnyCards.contains(cardName) && !card.getType().isBasicLand())
filtered.add(cardName);
if (card.isVariant()) {
@@ -131,12 +128,11 @@ public class StaticData {
}
}
if (!filtered.isEmpty()) {
Collections.sort(filtered);
}
commonCards = new CardDb(regularCards, editions, filtered);
variantCards = new CardDb(variantsCards, editions, filtered);
commonCards = new CardDb(regularCards, editions, filtered, cardArtPreference);
variantCards = new CardDb(variantsCards, editions, filtered, cardArtPreference);
commonCards.setCardArtPreference(cardArtPreference);
variantCards.setCardArtPreference(cardArtPreference);
//must initialize after establish field values for the sake of card image logic
commonCards.initialize(false, false, enableUnknownCards);
@@ -561,7 +557,6 @@ public class StaticData {
* @param allowedSetCodes The list of the allowed set codes to consider when looking for alternative card art
* candidates. If the list is not null and not empty, will be used in combination with the
* <code>isLegal</code> predicate.
* @see CardDb#isLegal(List<String>)
* @return an instance of <code>PaperCard</code> that is the selected alternative candidate, or <code>null</code>
* if None could be found.
*/

View File

@@ -17,10 +17,7 @@
*/
package forge.card;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.*;
import forge.ImageKeys;
import forge.StaticData;
import forge.card.CardEdition.EditionEntry;
@@ -30,7 +27,6 @@ import forge.item.IPaperCard;
import forge.item.PaperCard;
import forge.util.Lang;
import forge.util.TextUtil;
import forge.util.lang.LangEnglish;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -53,12 +49,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
private final Map<String, ICardFace> facesByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, String> normalizedNames = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private static Map<String, String> artPrefs = Maps.newHashMap();
/**
* Map of flavor names to the identifier of the functional variant on which they appear in their respective card rules.
*/
private final Map<String, String> flavorNameMappings = Maps.newHashMap();
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, Integer> artIds = Maps.newHashMap();
private final CardEdition.Collection editions;
private List<String> filtered;
private final Set<String> filtered;
private Map<String, Boolean> nonLegendaryCreatureNames = Maps.newHashMap();
@@ -294,11 +293,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
}
public CardDb(Map<String, CardRules> rules, CardEdition.Collection editions0, List<String> filteredCards, String cardArtPreference) {
public CardDb(Map<String, CardRules> rules, CardEdition.Collection editions0, Set<String> filteredCards) {
this.filtered = filteredCards;
this.rulesByName = rules;
this.editions = editions0;
//Collects additional mappings used for flavor names
//Need an extra map for these to avoid ConcurrentModificationException
Map<String, CardRules> extraRuleMappings = new HashMap<>();
// create faces list from rules
for (final CardRules rule : rules.values()) {
if (filteredCards.contains(rule.getName()))
@@ -306,8 +309,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
for (ICardFace face : rule.getAllFaces()) {
addFaceToDbNames(face);
}
if (rule.hasFunctionalVariants()){
cacheFlavorNames(rule, extraRuleMappings);
}
setCardArtPreference(cardArtPreference);
}
rulesByName.putAll(extraRuleMappings);
}
private void addFaceToDbNames(ICardFace face) {
@@ -321,14 +328,40 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
normalizedNames.put(normalName, name);
}
final String altName = face.getAltName();
if (altName != null) {
alternateName.put(altName, face.getName());
if(face.hasFunctionalVariants()) {
for(ICardFace varFace : face.getFunctionalVariants().values())
cacheFlavorName(varFace);
}
if (face.getFlavorName() != null) //Probably shouldn't be putting a flavor name on the main print?
cacheFlavorName(face);
}
private void cacheFlavorName(ICardFace face) {
String altName = face.getFlavorName();
if(altName == null)
return;
facesByName.putIfAbsent(altName, face);
final String normalAltName = StringUtils.stripAccents(altName);
if (!normalAltName.equals(altName)) {
normalizedNames.put(normalAltName, altName);
}
}
private void cacheFlavorNames(CardRules rules, Map<String, CardRules> map) {
if(rules.getSupportedFunctionalVariants() == null)
return;
boolean hasFlavorName = false;
String baseName = rules.getName();
for(String variantName : rules.getSupportedFunctionalVariants()) {
String name = rules.getDisplayNameForVariant(variantName);
if(baseName.equals(name))
continue;
hasFlavorName = true;
map.put(name, rules);
flavorNameMappings.put(name, variantName);
}
if(hasFlavorName)
flavorNameMappings.put(baseName, IPaperCard.NO_FUNCTIONAL_VARIANT);
}
private void addSetCard(CardEdition e, EditionEntry cis, CardRules cr) {
@@ -337,16 +370,40 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
if (artIds.containsKey(key)) {
artIdx = artIds.get(key) + 1;
}
artIds.put(key, artIdx);
addCard(new PaperCard(cr, e.getCode(), cis.rarity(), artIdx, false, cis.collectorNumber(), cis.artistName(), cis.functionalVariantName()));
String variantName = cis.getFunctionalVariantName();
String flavorName = cis.getFlavorName();
assert(variantName == null || flavorName == null); //Can't currently assign both this way.
if(variantName == null && !cr.getName().equals(cis.name())) {
//If an edition entry uses a known flavor name without specifying the variant, swap to that variant.
variantName = flavorNameMappings.get(cis.name());
//System.out.printf("Auto-mapping flavor name \"%s\" -> \"%s\" $%s\n", cis.name(), cr.getName(), variantName);
}
if(flavorName != null) {
String suggestedFlavorName = e.getCode().startsWith("OM") ? "Alchemy"
: e.getCode().equals("SLX") ? "UniversesWithin"
: null;
variantName = cr.findOrCreateVariantForFlavorName(flavorName, suggestedFlavorName);
String normalizedFlavorName = cr.getDisplayNameForVariant(variantName);
if(!flavorNameMappings.containsKey(normalizedFlavorName)) {
flavorNameMappings.put(normalizedFlavorName, variantName);
rulesByName.put(normalizedFlavorName, cr);
cacheFlavorName(cr.getMainPart().getFunctionalVariant(variantName));
if(cr.getOtherPart() != null)
cacheFlavorName(cr.getOtherPart().getFunctionalVariant(variantName));
}
}
addCard(new PaperCard(cr, e.getCode(), cis.rarity(), artIdx, false, cis.collectorNumber(), cis.artistName(), variantName));
}
private boolean addFromSetByName(String cardName, CardEdition ed, CardRules cr) {
List<EditionEntry> cardsInSet = ed.getCardInSet(cardName); // empty collection if not present
if (cr.hasFunctionalVariants()) {
cardsInSet = cardsInSet.stream().filter(c -> StringUtils.isEmpty(c.functionalVariantName())
|| cr.getSupportedFunctionalVariants().contains(c.functionalVariantName())
cardsInSet = cardsInSet.stream().filter(c -> StringUtils.isEmpty(c.getFunctionalVariantName())
|| cr.getSupportedFunctionalVariants().contains(c.getFunctionalVariantName())
).collect(Collectors.toList());
}
if (cardsInSet.isEmpty())
@@ -383,9 +440,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
CardEdition upcomingSet = null;
Date today = new Date();
// do this first so they're not considered missing
buildRenamedCards();
for (CardEdition e : editions.getOrderedEditions()) {
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
@@ -403,8 +457,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
continue;
}
if (cr.hasFunctionalVariants()) {
if (StringUtils.isNotEmpty(cis.functionalVariantName())
&& !cr.getSupportedFunctionalVariants().contains(cis.functionalVariantName())) {
if (StringUtils.isNotEmpty(cis.getFunctionalVariantName())
&& !cr.getSupportedFunctionalVariants().contains(cis.getFunctionalVariantName())) {
//Supported card, unsupported variant.
//Could note the card as missing but since these are often un-cards,
//it's likely absent because it does something out of scope.
@@ -455,71 +509,28 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
reIndex();
}
private void buildRenamedCards() {
Lang lang = Lang.getInstance();
if (lang == null) {
// for some tests
lang = new LangEnglish();
}
// for now just check Universes Within
for (EditionEntry cis : editions.get("SLX").getCards()) {
String orgName = alternateName.get(cis.name());
if (orgName != null) {
// found original (beyond) print
CardRules org = getRules(orgName);
CardFace renamedMain = (CardFace) ((CardFace) org.getMainPart()).clone();
renamedMain.setName(renamedMain.getAltName());
renamedMain.setAltName(null);
// TODO this could mess up some "named ..." cardname literals but there's no printing like that currently
renamedMain.setOracleText(renamedMain.getOracleText()
.replace(orgName, renamedMain.getName())
.replace(lang.getNickName(orgName), lang.getNickName(renamedMain.getName()))
);
facesByName.put(renamedMain.getName(), renamedMain);
CardFace renamedOther = null;
if (org.getOtherPart() != null) {
renamedOther = (CardFace) ((CardFace) org.getOtherPart()).clone();
orgName = renamedOther.getName();
renamedOther.setName(renamedOther.getAltName());
renamedOther.setAltName(null);
renamedOther.setOracleText(renamedOther.getOracleText()
.replace(orgName, renamedOther.getName())
.replace(lang.getNickName(orgName), lang.getNickName(renamedOther.getName()))
);
facesByName.put(renamedOther.getName(), renamedOther);
}
CardRules within = new CardRules(new ICardFace[] { renamedMain, renamedOther, null, null, null, null, null }, org.getSplitType(), org.getAiHints());
// so workshop can edit same script
within.setNormalizedName(org.getNormalizedName());
rulesByName.put(cis.name(), within);
}
}
}
public void addCard(PaperCard paperCard) {
if (filtered.contains(paperCard.getName())) {
return;
}
allCardsByName.put(paperCard.getName(), paperCard);
String mainName = paperCard.getName();
allCardsByName.put(mainName, paperCard);
if (paperCard.getRules().getSplitType() == CardSplitType.None) {
CardRules rules = paperCard.getRules();
if (rules.getSplitType() == CardSplitType.None && !rules.hasFunctionalVariants()) {
return;
}
if (paperCard.getRules().getOtherPart() != null) {
//allow looking up card by the name of other faces
allCardsByName.put(paperCard.getRules().getOtherPart().getName(), paperCard);
}
if (paperCard.getRules().getSplitType() == CardSplitType.Split) {
//also include main part for split cards
allCardsByName.put(paperCard.getRules().getMainPart().getName(), paperCard);
} else if (paperCard.getRules().getSplitType() == CardSplitType.Specialize) {
//also include specialize faces
for (ICardFace face : paperCard.getRules().getSpecializeParts().values()) allCardsByName.put(face.getName(), paperCard);
}
//Card may have multiple names. Add it under all of them.
List<ICardFace> allFaces = paperCard.getAllFaces();
Set<String> namesToAdd = new HashSet<>();
allFaces.stream().map(ICardCharacteristics::getName).forEach(namesToAdd::add);
allFaces.stream().map(ICardFace::getFlavorName).filter(Objects::nonNull).forEach(namesToAdd::add);
namesToAdd.remove(mainName);
for(String name : namesToAdd)
allCardsByName.put(name, paperCard);
}
private void reIndex() {
@@ -560,11 +571,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
public CardRules getRules(String cardName) {
CardRules result = rulesByName.get(cardName);
if (result != null) {
return result;
} else {
return CardRules.getUnsupportedCardNamed(cardName);
}
return Objects.requireNonNullElseGet(result, () -> CardRules.getUnsupportedCardNamed(cardName));
}
public CardArtPreference getCardArtPreference(){ return this.defaultCardArtPreference; }
@@ -869,10 +876,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
cardQueryFilter = card -> card.getArtIndex() == cr.artIndex;
cardQueryFilter = cardQueryFilter.and(filter);
cards = getAllCards(cr.cardName, cardQueryFilter);
// Note: No need to check whether "cards" is empty; the next for loop will validate condition at L699
if (cards.isEmpty())
return null;
if (cards.size() == 1) // if only one candidate, there much else we should do
return cr.isFoil ? cards.get(0).getFoiled() : cards.get(0);
if (flavorNameMappings.containsKey(cr.cardName)) {
Collection<PaperCard> matchingNames = cards.stream().filter(c -> c.getDisplayName().equals(cr.cardName)).collect(Collectors.toSet());
if(!matchingNames.isEmpty())
cards.retainAll(matchingNames);
}
/* 2. Retrieve cards based of [Frame]Set Preference
================================================ */
// Collect the list of all editions found for target card
@@ -967,11 +981,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
public List<PaperCard> getUniqueCardsNoAlt(String cardName) {
return Lists.newArrayList(Maps.filterEntries(uniqueCardsByName, entry -> entry.getKey().equals(entry.getValue().getName())).get(getName(cardName)));
return Lists.newArrayList(Maps.filterEntries(uniqueCardsByName, entry -> entry.getKey().equals(entry.getValue().getName())).get(getNormalizedName(cardName)));
}
public PaperCard getUniqueByName(final String name) {
return uniqueCardsByName.get(getName(name));
return uniqueCardsByName.get(getNormalizedName(name));
}
public Collection<ICardFace> getAllFaces() {
@@ -979,7 +993,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
public ICardFace getFaceByName(final String name) {
return facesByName.get(getName(name));
return facesByName.get(getNormalizedName(name));
}
public boolean isNonLegendaryCreatureName(final String name) {
@@ -1050,26 +1064,20 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return streamAllCardsNoAlt().filter(EDITION_NON_REPRINT).collect(Collectors.toList());
}
public String getName(final String cardName) {
return getName(cardName, false);
}
public String getName(String cardName, boolean engine) {
public String getNormalizedName(final String cardName) {
String cardName1 = cardName;
// normalize Names first
cardName = normalizedNames.getOrDefault(cardName, cardName);
if (alternateName.containsKey(cardName) && engine) {
// TODO might want to implement GUI option so it always fetches the Within version
return alternateName.get(cardName);
}
return cardName;
cardName1 = normalizedNames.getOrDefault(cardName1, cardName1);
return cardName1;
}
@Override
public List<PaperCard> getAllCards(String cardName) {
return allCardsByName.get(getName(cardName));
return allCardsByName.get(getNormalizedName(cardName));
}
public List<PaperCard> getAllCardsNoAlt(String cardName) {
return Lists.newArrayList(Multimaps.filterEntries(allCardsByName, entry -> entry.getKey().equals(entry.getValue().getName())).get(getName(cardName)));
return Lists.newArrayList(Multimaps.filterEntries(allCardsByName, entry -> entry.getKey().equals(entry.getValue().getName())).get(getNormalizedName(cardName)));
}
/**
@@ -1111,7 +1119,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override
public boolean contains(String name) {
return allCardsByName.containsKey(getName(name));
return allCardsByName.containsKey(getNormalizedName(name));
}
@Override
@@ -1221,7 +1229,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
int artIdx = IPaperCard.DEFAULT_ART_INDEX;
for (EditionEntry cis : e.getCardInSet(cardName))
paperCards.add(new PaperCard(rules, e.getCode(), cis.rarity(), artIdx++, false,
cis.collectorNumber(), cis.artistName(), cis.functionalVariantName()));
cis.collectorNumber(), cis.artistName(), cis.getFunctionalVariantName()));
}
} else {
String lastEdition = null;
@@ -1241,7 +1249,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
int cardInSetIndex = Math.max(artIdx-1, 0); // make sure doesn't go below zero
EditionEntry cds = cardsInSet.get(cardInSetIndex); // use ArtIndex to get the right Coll. Number
paperCards.add(new PaperCard(rules, lastEdition, tuple.getValue(), artIdx++, false,
cds.collectorNumber(), cds.artistName(), cds.functionalVariantName()));
cds.collectorNumber(), cds.artistName(), cds.getFunctionalVariantName()));
}
}
if (paperCards.isEmpty()) {

View File

@@ -82,23 +82,17 @@ public final class CardEdition implements Comparable<CardEdition> {
public static final EnumSet<Type> REPRINT_SET_TYPES = EnumSet.of(REPRINT, PROMO, COLLECTOR_EDITION);
public String getBoosterBoxDefault() {
switch (this) {
case CORE:
case EXPANSION:
return "36";
default:
return "0";
}
return switch (this) {
case CORE, EXPANSION -> "36";
default -> "0";
};
}
public String getFatPackDefault() {
switch (this) {
case CORE:
case EXPANSION:
return "10";
default:
return "0";
}
return switch (this) {
case CORE, EXPANSION -> "10";
default -> "0";
};
}
public String toString(){
@@ -215,7 +209,7 @@ public final class CardEdition implements Comparable<CardEdition> {
return sortableCollNr;
}
public record EditionEntry(String name, String collectorNumber, CardRarity rarity, String artistName, String functionalVariantName) implements Comparable<EditionEntry> {
public record EditionEntry(String name, String collectorNumber, CardRarity rarity, String artistName, Map<String, String> extraParams) implements Comparable<EditionEntry> {
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -232,9 +226,9 @@ public final class CardEdition implements Comparable<CardEdition> {
sb.append(" @");
sb.append(artistName);
}
if (functionalVariantName != null) {
if (extraParams != null) {
sb.append(" $");
sb.append(functionalVariantName);
sb.append(extraParams.entrySet().stream().map(e -> String.format("\"%s\"=\"%s\"", e.getKey(), e.getValue())).collect(Collectors.joining(", ")));
}
return sb.toString();
}
@@ -253,6 +247,24 @@ public final class CardEdition implements Comparable<CardEdition> {
}
return rarity.compareTo(o.rarity);
}
public String getFlavorName() {
if(extraParams == null)
return null;
return extraParams.get("flavorname");
}
public String getFunctionalVariantName() {
if(extraParams == null)
return null;
return extraParams.get("variant");
}
public String getScriptOverride() {
if(extraParams == null)
return null;
return extraParams.get("script");
}
}
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
@@ -589,6 +601,31 @@ public final class CardEdition implements Comparable<CardEdition> {
}
public static class Reader extends StorageReaderFolder<CardEdition> {
public static final Pattern CARD_PATTERN = Pattern.compile(
/*
The following pattern will match the WAR Japanese art entries,
it should also match the Un-set and older alternate art cards
like Merseine from FEM.
*/
// Collector numbers now should allow hyphens for Planeswalker Championship Promos
"(?:^(?<cnum>.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:(?<rarity>[SCURML])\\s)?(?<name>[^@$]*)(?: @(?<artist>[^$]*))?(?: \\$\\{(?<params>.+)})?$"
);
public static final Pattern TOKEN_PATTERN = Pattern.compile(
/*
* cnum - grouping #2
* name - grouping #3
* artist name - grouping #5
*/
"(?:^(?<cnum>.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?(?<name>[^@]*)(?: @(?<artist>.*))?$"
);
public static final Pattern EXTRA_PARAMS_PATTERN = Pattern.compile(
//Simple JSON string map parser - "key": "value". No support for escaping quotation marks or anything fancy.
"\"([^\"]+)\"\\s*:\\s*\"([^\"]+)\",?"
);
private final boolean isCustomEditions;
public Reader(File path) {
@@ -610,38 +647,6 @@ public final class CardEdition implements Comparable<CardEdition> {
protected CardEdition read(File file) {
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
final Pattern pattern = Pattern.compile(
/*
The following pattern will match the WAR Japanese art entries,
it should also match the Un-set and older alternate art cards
like Merseine from FEM.
*/
// Collector numbers now should allow hyphens for Planeswalker Championship Promos
//"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
/* Ideally we'd use the named group above, but Android 6 and
earlier don't appear to support named groups.
So, untill support for those devices is officially dropped,
we'll have to suffice with numbered groups.
We are looking for:
* cnum - grouping #2
* rarity - grouping #4
* name - grouping #5
* artist name - grouping #7
* functional variant name - grouping #9
*/
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
"(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
);
final Pattern tokenPattern = Pattern.compile(
/*
* cnum - grouping #2
* name - grouping #3
* artist name - grouping #5
*/
"(^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]*)( @(.*))?$"
);
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
List<BoosterSlot> boosterSlots = null;
Map<String, List<String>> customPrintSheetsToParse = new HashMap<>();
@@ -668,18 +673,35 @@ public final class CardEdition implements Comparable<CardEdition> {
// parse sections of the format "<collector number> <rarity> <name>"
if (editionSectionsWithCollectorNumbers.contains(sectionName)) {
for(String line : contents.get(sectionName)) {
Matcher matcher = pattern.matcher(line);
Matcher matcher = CARD_PATTERN.matcher(line);
if (!matcher.matches()) {
continue;
}
String collectorNumber = matcher.group(2);
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
String cardName = matcher.group(5);
String artistName = matcher.group(7);
String functionalVariantName = matcher.group(9);
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
String collectorNumber = matcher.group("cnum");
CardRarity r = CardRarity.smartValueOf(matcher.group("rarity"));
String cardName = matcher.group("name");
String artistName = matcher.group("artist");
String extraParamText = matcher.group("params");
Map<String, String> extraParams = null;
if(!StringUtils.isBlank(extraParamText)) {
Matcher paramMatcher = EXTRA_PARAMS_PATTERN.matcher(extraParamText);
if(!paramMatcher.lookingAt())
System.err.println("Ignoring malformed parameter text: " + extraParamText);
else {
extraParams = new HashMap<>(2);
do {
String k = paramMatcher.group(1).trim().toLowerCase();
String v = paramMatcher.group(2).trim();
if(k.isEmpty() || v.isEmpty())
continue;
extraParams.put(k, v);
} while(paramMatcher.find());
}
}
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, extraParams);
cardMap.put(sectionName, cis);
}
@@ -701,15 +723,15 @@ public final class CardEdition implements Comparable<CardEdition> {
for (String line : contents.get("tokens")) {
if (StringUtils.isBlank(line))
continue;
Matcher matcher = tokenPattern.matcher(line);
Matcher matcher = TOKEN_PATTERN.matcher(line);
if (!matcher.matches()) {
continue;
}
String collectorNumber = matcher.group(2);
String cardName = matcher.group(3);
String artistName = matcher.group(5);
String collectorNumber = matcher.group("cnum");
String cardName = matcher.group("name");
String artistName = matcher.group("artist");
// rarity isn't used for this anyway
EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Token, artistName, null);
tokenMap.put(cardName, tis);
@@ -719,14 +741,14 @@ public final class CardEdition implements Comparable<CardEdition> {
for (String line : contents.get("other")) {
if (StringUtils.isBlank(line))
continue;
Matcher matcher = tokenPattern.matcher(line);
Matcher matcher = TOKEN_PATTERN.matcher(line);
if (!matcher.matches()) {
continue;
}
String collectorNumber = matcher.group(2);
String cardName = matcher.group(3);
String artistName = matcher.group(5);
String collectorNumber = matcher.group("cnum");
String cardName = matcher.group("name");
String artistName = matcher.group("artist");
EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Unknown, artistName, null);
otherMap.put(cardName, tis);
}
@@ -931,7 +953,7 @@ public final class CardEdition implements Comparable<CardEdition> {
public final Comparator<PaperCard> CARD_EDITION_COMPARATOR = Comparator.comparing(c -> Collection.this.get(c.getEdition()));
public IItemReader<SealedTemplate> getBoosterGenerator() {
return new StorageReaderBase<SealedTemplate>(null) {
return new StorageReaderBase<>(null) {
@Override
public Map<String, SealedTemplate> readAll() {
Map<String, SealedTemplate> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

View File

@@ -17,20 +17,12 @@ import java.util.stream.Collectors;
* <i>Do not use reference to class except for card parsing.<br>Always use reference to interface type outside of package.</i>
*/
final class CardFace implements ICardFace, Cloneable {
public enum FaceSelectionMethod { //
USE_ACTIVE_FACE,
USE_PRIMARY_FACE,
COMBINE
}
private final static List<String> emptyList = Collections.unmodifiableList(new ArrayList<>());
private final static Map<String, String> emptyMap = Collections.unmodifiableMap(new TreeMap<>());
private final static Set<Integer> emptySet = Collections.unmodifiableSet(new HashSet<>());
private String name;
private String altName = null;
private String flavorName = null;
private CardType type = null;
private ManaCost manaCost = null;
private ColorSet color = null;
@@ -85,7 +77,7 @@ final class CardFace implements ICardFace, Cloneable {
return variables.entrySet();
}
@Override public String getAltName() { return this.altName; }
@Override public String getFlavorName() { return this.flavorName; }
public CardFace(String name0) {
this.name = name0;
@@ -94,7 +86,7 @@ final class CardFace implements ICardFace, Cloneable {
}
// Here come setters to allow parser supply values
void setName(String name) { this.name = name; }
void setAltName(String name) { this.altName = name; }
void setFlavorName(String name) { this.flavorName = name; }
void setType(CardType type0) { this.type = type0; }
void setManaCost(ManaCost manaCost0) { this.manaCost = manaCost0; }
void setColor(ColorSet color0) { this.color = color0; }
@@ -187,6 +179,12 @@ final class CardFace implements ICardFace, Cloneable {
if(this.functionalVariants != null) {
//Copy fields to undefined ones in functional variants
for (CardFace variant : this.functionalVariants.values()) {
assignMissingFieldsToVariant(variant);
}
}
}
void assignMissingFieldsToVariant(CardFace variant) {
if(variant.oracleText == null) variant.oracleText = this.oracleText;
if(variant.manaCost == null) variant.manaCost = this.manaCost;
if(variant.color == null) variant.color = ColorSet.fromManaCost(variant.manaCost);
@@ -205,7 +203,6 @@ final class CardFace implements ICardFace, Cloneable {
if("".equals(variant.initialLoyalty)) variant.initialLoyalty = this.initialLoyalty;
if("".equals(variant.defense)) variant.defense = this.defense;
//variant.assignMissingFields();
if(variant.keywords == null) variant.keywords = this.keywords;
else variant.keywords.addAll(0, this.keywords);
@@ -227,9 +224,7 @@ final class CardFace implements ICardFace, Cloneable {
if(variant.nonAbilityText == null) variant.nonAbilityText = this.nonAbilityText;
if(variant.draftActions == null) variant.draftActions = this.draftActions;
if(variant.attractionLights == null) variant.attractionLights = this.attractionLights;
if(variant.altName == null) variant.altName = this.altName;
}
}
//if(variant.flavorName == null) variant.flavorName = this.flavorName; //Probably shouldn't be setting this on the main variant to begin with?
}

View File

@@ -27,6 +27,7 @@ import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
import static forge.card.MagicColor.Constant.BASIC_LANDS;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
@@ -43,8 +44,9 @@ public final class CardRules implements ICardCharacteristics {
private CardSplitType splitType;
private ICardFace mainPart;
private ICardFace otherPart;
private Map<CardStateName, ICardFace> specializedParts = Maps.newHashMap();
private List<ICardFace> allFaces;
private CardAiHints aiHints;
private ColorSet colorIdentity;
private ColorSet deckbuildingColors;
@@ -69,6 +71,8 @@ public final class CardRules implements ICardCharacteristics {
specializedParts.put(CardStateName.SpecializeG, faces[6]);
}
allFaces = Arrays.stream(faces).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList());
aiHints = cah;
meldWith = "";
partnerWith = "";
@@ -93,6 +97,7 @@ public final class CardRules implements ICardCharacteristics {
mainPart = newRules.mainPart;
otherPart = newRules.otherPart;
specializedParts = Maps.newHashMap(newRules.specializedParts);
allFaces = newRules.allFaces;
aiHints = newRules.aiHints;
colorIdentity = newRules.colorIdentity;
meldWith = newRules.meldWith;
@@ -163,8 +168,8 @@ public final class CardRules implements ICardCharacteristics {
return specializedParts;
}
public Iterable<ICardFace> getAllFaces() {
return Iterables.concat(Arrays.asList(mainPart, otherPart), specializedParts.values());
public List<ICardFace> getAllFaces() {
return allFaces;
}
public boolean isTransformable() {
@@ -464,6 +469,58 @@ public final class CardRules implements ICardCharacteristics {
return this.supportedFunctionalVariants;
}
public String getDisplayNameForVariant(String variantName) {
if(supportedFunctionalVariants == null || !supportedFunctionalVariants.contains(variantName))
return getName();
ICardFace mainFace = Objects.requireNonNullElse(mainPart.getFunctionalVariant(variantName), mainPart);
String mainPartName = Objects.requireNonNullElse(mainFace.getFlavorName(), mainFace.getName());
if(splitType.getAggregationMethod() == CardSplitType.FaceSelectionMethod.COMBINE) {
ICardFace otherFace = Objects.requireNonNullElse(otherPart.getFunctionalVariant(variantName), otherPart);
String otherPartName = Objects.requireNonNullElse(otherFace.getFlavorName(), otherFace.getName());
return mainPartName + " // " + otherPartName;
}
else
return mainPartName;
}
/* package */ String findOrCreateVariantForFlavorName(String flavorName, String suggestedVariantName) {
Objects.requireNonNull(flavorName);
String[] nameParts = flavorName.trim().split("\\s*//\\s*");
flavorName = String.join(" // ", nameParts); //Normalize this just in case.
if(otherPart != null && nameParts.length < 2)
throw new IllegalArgumentException("Tried to assign a single flavor name to a multi-faced card. Use ' // ' as a separator in the flavorName parameter.");
if(supportedFunctionalVariants == null)
supportedFunctionalVariants = new HashSet<>();
for(String variantName : this.supportedFunctionalVariants) {
if(getDisplayNameForVariant(variantName).equals(flavorName))
return variantName;
}
String variantName = suggestedVariantName != null ? suggestedVariantName : "FlavorName" + flavorName.hashCode();
if(supportedFunctionalVariants.contains(variantName))
variantName = variantName + flavorName.hashCode();
CardFace variantMain = ((CardFace) mainPart).getOrCreateFunctionalVariant(variantName);
variantMain.setFlavorName(nameParts[0]);
//Rudimentary name replacement. Can't do nicknames, pronouns, ability words, or flavored keywords. Need to define variants manually for that.
if(mainPart.getOracleText().contains(mainPart.getName()))
variantMain.setOracleText(mainPart.getOracleText().replace(mainPart.getName(), nameParts[0]));
((CardFace) mainPart).assignMissingFieldsToVariant(variantMain);
if(otherPart != null) {
CardFace variantOther = ((CardFace) otherPart).getOrCreateFunctionalVariant(variantName);
variantOther.setFlavorName(nameParts[1]);
if(otherPart.getOracleText().contains(otherPart.getName()))
variantMain.setOracleText(otherPart.getOracleText().replace(otherPart.getName(), nameParts[1]));
((CardFace) otherPart).assignMissingFieldsToVariant(variantOther);
}
supportedFunctionalVariants.add(variantName);
return variantName;
}
public ColorSet getColorIdentity() {
return colorIdentity;
}
@@ -622,8 +679,6 @@ public final class CardRules implements ICardCharacteristics {
this.altMode = CardSplitType.smartValueOf(value);
} else if ("ALTERNATE".equals(key)) {
this.curFace = 1;
} else if ("AltName".equals(key)) {
face.setAltName(value);
}
break;
@@ -648,6 +703,11 @@ public final class CardRules implements ICardCharacteristics {
}
break;
case 'F':
if("FlavorName".equals(key)) {
face.setFlavorName(value);
}
case 'H':
if ("HandLifeModifier".equals(key)) {
handLife = value;

View File

@@ -141,7 +141,7 @@ public final class CardRulesPredicates {
* @return the predicate
*/
public static Predicate<CardRules> hasKeyword(final String keyword) {
return card -> IterableUtil.any(card.getAllFaces(), cf -> cf != null && card.hasStartOfKeyword(keyword, cf));
return card -> IterableUtil.any(card.getAllFaces(), cf -> card.hasStartOfKeyword(keyword, cf));
}
/**
@@ -325,12 +325,19 @@ public final class CardRulesPredicates {
return false;
}
if (face.hasFunctionalVariants()) {
//Couple quirks here - an ICardFace doesn't have a specific variant, so they all need to be checked.
//This means text matching the rules of one variant will match prints with any variant. In the case of
//flavor names though, we exclude their oracle modified text from matching, so that searching a flavor
//name will return only the card matching that name.
//TODO: Fix all that someday by doing rules searches by the PaperCard rather than the CardRules.
for (Map.Entry<String, ? extends ICardFace> v : face.getFunctionalVariants().entrySet()) {
//Not a very pretty implementation, but an ICardFace doesn't have a specific variant, so they all need to be checked.
String origOracle = v.getValue().getOracleText();
ICardFace vFace = v.getValue();
if(vFace.getFlavorName() != null)
continue;
String origOracle = vFace.getOracleText();
if(op(origOracle, operand))
return true;
String name = v.getValue().getName() + " $" + v.getKey();
String name = vFace.getFlavorName() != null ? vFace.getFlavorName() : vFace.getName() + " $" + v.getKey();
if(op(CardTranslation.getTranslatedOracle(name), operand))
return true;
}
@@ -346,10 +353,11 @@ public final class CardRulesPredicates {
}
if (face.hasFunctionalVariants()) {
for (Map.Entry<String, ? extends ICardFace> v : face.getFunctionalVariants().entrySet()) {
String origType = v.getValue().getType().toString();
ICardFace vFace = v.getValue();
String origType = vFace.getType().toString();
if(op(origType, operand))
return true;
String name = v.getValue().getName() + " $" + v.getKey();
String name = vFace.getFlavorName() != null ? vFace.getFlavorName() : vFace.getName() + " $" + v.getKey();
if(op(CardTranslation.getTranslatedType(name, origType), operand))
return true;
}
@@ -363,7 +371,7 @@ public final class CardRulesPredicates {
switch (this.field) {
case NAME:
for (ICardFace face : card.getAllFaces()) {
if (face != null && checkName(face.getName())) {
if (checkName(face.getName())) {
return true;
}
}

View File

@@ -1,7 +1,5 @@
package forge.card;
import forge.card.CardFace.FaceSelectionMethod;
import java.util.EnumSet;
public enum CardSplitType
@@ -41,4 +39,10 @@ public enum CardSplitType
public CardStateName getChangedStateName() {
return changedStateName;
}
public enum FaceSelectionMethod {
USE_ACTIVE_FACE,
USE_PRIMARY_FACE,
COMBINE
}
}

View File

@@ -7,7 +7,7 @@ import java.util.Map;
*
*/
public interface ICardFace extends ICardCharacteristics, ICardRawAbilites, Comparable<ICardFace> {
String getAltName();
String getFlavorName();
boolean hasFunctionalVariants();
ICardFace getFunctionalVariant(String variant);

View File

@@ -406,7 +406,7 @@ public enum DeckFormat {
final CardPool allCards = deck.getAllCardsInASinglePool(hasCommander());
// Should group all cards by name, so that different editions of same card are really counted as the same card
for (final Entry<String, Integer> cp : Aggregates.groupSumBy(allCards, pc -> StaticData.instance().getCommonCards().getName(pc.getName(), true))) {
for (final Entry<String, Integer> cp : Aggregates.groupSumBy(allCards, pc -> StaticData.instance().getCommonCards().getNormalizedName(pc.getName()))) {
IPaperCard simpleCard = StaticData.instance().getCommonCards().getCard(cp.getKey());
if (simpleCard != null && simpleCard.getRules().isCustom() && !allowCustomCards())
return TextUtil.concatWithSpace("contains a Custom Card:", cp.getKey(), "\nPlease Enable Custom Cards in Forge Preferences to use this deck.");

View File

@@ -6,6 +6,7 @@ import forge.card.ColorSet;
import forge.card.ICardFace;
import java.io.Serializable;
import java.util.List;
public interface IPaperCard extends InventoryItem, Serializable {
@@ -31,6 +32,7 @@ public interface IPaperCard extends InventoryItem, Serializable {
boolean hasBackFace();
ICardFace getMainFace();
ICardFace getOtherFace();
List<ICardFace> getAllFaces();
String getCardImageKey();
String getCardAltImageKey();
String getCardWSpecImageKey();
@@ -43,9 +45,10 @@ public interface IPaperCard extends InventoryItem, Serializable {
@Override
default String getTranslationKey() {
if(!NO_FUNCTIONAL_VARIANT.equals(getFunctionalVariant()))
//Cards with flavor names will use that flavor name as their translation key. Other variants are just appended as a suffix.
if(!NO_FUNCTIONAL_VARIANT.equals(getFunctionalVariant()) && getAllFaces().stream().noneMatch(pc -> pc.getFlavorName() != null))
return getName() + " $" + getFunctionalVariant();
return getName();
return getDisplayName();
}
@Override

View File

@@ -27,6 +27,21 @@ public interface InventoryItem extends ITranslatable {
String getItemType();
String getImageKey(boolean altState);
/**
* Supplies the user-facing name of this item. Usually the same as `getName()`, but may be overwritten in cases such
* as flavor names for cards.
*/
default String getDisplayName() {
return getName();
}
/**
* @return true if this item's display name is different from its actual name. False otherwise.
*/
default boolean hasFlavorName() {
return false;
}
@Override
default String getUntranslatedType() {
return getItemType();

View File

@@ -20,10 +20,7 @@ package forge.item;
import forge.ImageKeys;
import forge.StaticData;
import forge.card.*;
import forge.util.CardTranslation;
import forge.util.ImageUtil;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.*;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
@@ -56,7 +53,6 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
private final int artIndex;
private final boolean foil;
private final PaperCardFlags flags;
private final String sortableName;
private final String functionalVariant;
// Calculated fields are below:
@@ -64,6 +60,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
// Reference to a new instance of Self, but foiled!
private transient PaperCard foiledVersion, noSellVersion, flaglessVersion;
private transient Boolean hasImage;
private transient String displayName;
private transient String sortableName;
private transient boolean hasFlavorName;
@Override
public String getName() {
@@ -252,10 +251,12 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
this.rarity = rarity;
this.artist = artist;
this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER;
this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT;
this.displayName = rules.getDisplayNameForVariant(functionalVariant);
this.hasFlavorName = !name.equals(displayName);
// If the user changes the language this will make cards sort by the old language until they restart the game.
// This is a good tradeoff
sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules.getName()));
this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT;
this.sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(displayName));
if(flags == null || flags.equals(PaperCardFlags.IDENTITY_FLAGS))
this.flags = PaperCardFlags.IDENTITY_FLAGS;
@@ -313,6 +314,67 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
return name;
}
@Override
public String getDisplayName() {
return this.displayName;
}
@Override
public boolean hasFlavorName() {
return this.hasFlavorName;
}
private transient Set<String> searchableNames = null;
private transient String searchableNameLang = null;
public Set<String> getAllSearchableNames() {
if(this.searchableNames != null && CardTranslation.getLanguageSelected().equals(searchableNameLang))
return searchableNames;
if(searchableNameLang != null) //Changed the language. May as well update this.
sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(displayName));
searchableNameLang = CardTranslation.getLanguageSelected();
searchableNames = computeSearchableNames(searchableNameLang);
return searchableNames;
}
private Set<String> computeSearchableNames(String language) {
ICardFace otherFace = this.getOtherFace();
if(otherFace == null && NO_FUNCTIONAL_VARIANT.equals(this.functionalVariant))
{
//99% of cases will land here. This could possibly be optimized further by computing and storing this on
//the CardRules instead, but flavor names will still need to work per-print, or at least per-variant.
if("en-US".equals(language))
return Set.of(this.name);
else {
String translatedName = CardTranslation.getTranslatedName(this.name);
return Set.of(this.name, translatedName, StringUtils.stripAccents(translatedName));
}
}
Set<String> names = new HashSet<>();
ICardFace mainFace = this.getMainFace();
names.add(mainFace.getName());
String mainFlavor = mainFace.getFlavorName();
if(mainFlavor != null)
names.add(mainFlavor);
if(otherFace != null) {
names.add(otherFace.getName());
String otherFlavor = otherFace.getFlavorName();
if(otherFlavor != null)
names.add(otherFlavor);
names.add(mainFace.getName() + " // " + otherFace.getName());
if(mainFlavor != null && otherFlavor != null)
names.add(mainFlavor + " // " + otherFlavor);
}
if(!"en-US".equals(language)) {
Set<String> translated = names.stream().map(CardTranslation::getTranslatedName).filter(Objects::nonNull).collect(Collectors.toSet());
names.addAll(translated);
}
Set<String> noAccents = names.stream().map(StringUtils::stripAccents).collect(Collectors.toSet());
names.addAll(noAccents);
return names;
}
/*
* This (utility) method transform a collectorNumber String into a key string for sorting.
* This method proxies the same strategy implemented in CardEdition.CardInSet class from which the
@@ -383,6 +445,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
}
rules = pc.getRules();
rarity = pc.getRarity();
displayName = pc.getDisplayName();
hasFlavorName = pc.hasFlavorName();
sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(displayName));
}
private IPaperCard readObjectAlternate(String name, String edition) throws ClassNotFoundException, IOException {
@@ -516,9 +581,16 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
@Override
public ICardFace getOtherFace() {
ICardFace face = this.rules.getOtherPart();
if(face == null)
return null;
return this.getVariantForFace(face);
}
@Override
public List<ICardFace> getAllFaces() {
return StreamUtil.stream(this.rules.getAllFaces()).map(this::getVariantForFace).collect(Collectors.toList());
}
private ICardFace getVariantForFace(ICardFace face) {
if(!face.hasFunctionalVariants() || this.functionalVariant.equals(NO_FUNCTIONAL_VARIANT))
return face;

View File

@@ -42,6 +42,10 @@ public abstract class PaperCardPredicates {
return new PredicatePrintedWithRarity(rarity);
}
public static Predicate<PaperCard> searchableName(final PredicateString.StringOp op, final String what) {
return new PredicateSearchableName(op, what);
}
public static Predicate<PaperCard> name(final String what) {
return new PredicateName(what);
}
@@ -184,6 +188,20 @@ public abstract class PaperCardPredicates {
}
}
private static final class PredicateSearchableName extends PredicateString<PaperCard> {
private final String operand;
PredicateSearchableName(final StringOp operator, final String operand) {
super(operator);
this.operand = operand;
}
@Override
public boolean test(PaperCard paperCard) {
return paperCard.getAllSearchableNames().stream().anyMatch(name -> this.op(name, this.operand));
}
}
private static final class PredicateName extends PredicateString<PaperCard> {
private final String operand;

View File

@@ -6,6 +6,7 @@ import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class PaperToken implements InventoryItemFromSet, IPaperCard {
@@ -88,6 +89,11 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
return name;
}
@Override
public String getDisplayName() {
return name;
}
@Override
public String toString() {
return name;
@@ -169,6 +175,11 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
return this.getRules().getOtherPart();
}
@Override
public List<ICardFace> getAllFaces() {
return this.cardRules.getAllFaces();
}
@Override
public boolean isToken() {
return true;

View File

@@ -258,6 +258,10 @@ public class CardTranslation {
return translations;
}
public static String getLanguageSelected() {
return languageSelected;
}
private static boolean needsTranslation() {
return !languageSelected.equals("en-US");
}

View File

@@ -185,6 +185,8 @@ public class ImageUtil {
}
} else if (CardSplitType.Split == cp.getRules().getSplitType()) {
return card.getMainPart().getName() + card.getOtherPart().getName();
} else if (cp.hasFlavorName()) {
return cp.getDisplayName();
} else if (!IPaperCard.NO_FUNCTIONAL_VARIANT.equals(cp.getFunctionalVariant())) {
return cp.getName() + " " + cp.getFunctionalVariant();
}

View File

@@ -1,6 +1,7 @@
package forge.util;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -19,6 +20,8 @@ public class IterableUtil {
* which requires the subject to match each of the component predicates.
*/
public static <T> Predicate<T> and(Iterable<? extends Predicate<? super T>> components) {
if(components instanceof List && ((List<?>) components).size() == 1)
return ((List<? extends Predicate<? super T>>) components).get(0)::test;
return x -> all(components, i -> i.test(x));
}
@@ -27,6 +30,8 @@ public class IterableUtil {
* which requires the subject to match at least one of the component predicates.
*/
public static <T> Predicate<T> or(Iterable<? extends Predicate<? super T>> components) {
if(components instanceof List && ((List<?>) components).size() == 1)
return ((List<? extends Predicate<? super T>>) components).get(0)::test;
return x -> any(components, i -> i.test(x));
}

View File

@@ -271,7 +271,7 @@ public class GameFormat implements Comparable<GameFormat> {
if (erroneousCI.size() > 0) {
final StringBuilder sb = new StringBuilder("contains the following illegal cards:\n");
for (final PaperCard cp : erroneousCI) {
sb.append("\n").append(cp.getName());
sb.append("\n").append(cp.getDisplayName());
}
return sb.toString();
}
@@ -290,7 +290,7 @@ public class GameFormat implements Comparable<GameFormat> {
if (erroneousRestricted.size() > 0) {
final StringBuilder sb = new StringBuilder("contains more than one copy of the following restricted cards:\n");
for (final PaperCard cp : erroneousRestricted) {
sb.append("\n").append(cp.getName());
sb.append("\n").append(cp.getDisplayName());
}
return sb.toString();
}

View File

@@ -91,10 +91,10 @@ public class AlterAttributeEffect extends SpellAbilityEffect {
}
p.addCommander(gameCard);
//Seems important enough to mention in the game log.
gameCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, String.format("%s is now %s's commander.", gameCard.getPaperCard().getName(), p));
gameCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, String.format("%s is now %s's commander.", gameCard.getPaperCard().getDisplayName(), p));
} else {
p.removeCommander(gameCard);
gameCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, String.format("%s is no longer %s's commander.", gameCard.getPaperCard().getName(), p));
gameCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, String.format("%s is no longer %s's commander.", gameCard.getPaperCard().getDisplayName(), p));
}
altered = true;
break;

View File

@@ -952,23 +952,28 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
@Override
public final String getName() {
return getName(currentState, false);
return getName(currentState);
}
public final String getName(boolean alt) {
return getName(currentState, alt);
}
public final String getName(CardStateName stateName) {
return getName(getState(stateName), false);
}
public final String getName(CardState state, boolean alt) {
public final String getName(CardState state) {
String name = state.getName();
for (CardChangedName change : this.changedCardNames.values()) {
if (change.isOverwrite()) {
name = change.newName();
}
}
return alt ? StaticData.instance().getCommonCards().getName(name, true) : name;
return name;
}
public final String getDisplayName() {
return getDisplayName(currentState);
}
public final String getDisplayName(CardState state) {
//If this card has a changed name, don't use flavor names.
if(state.getFlavorName() == null || hasNameOverwrite())
return getName(state);
return state.getFlavorName();
}
public final boolean hasNameOverwrite() {
@@ -6058,7 +6063,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return true;
}
}
return sharesNameWith(c1.getName(true));
return sharesNameWith(c1.getName());
}
public final boolean sharesNameWith(final String name) {
@@ -6067,7 +6072,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return false;
}
boolean shares = getName(true).equals(name);
boolean shares = getName().equals(name);
// Split cards has extra logic to check if it does share a name with
if (!shares && !hasNameOverwrite()) {
@@ -7844,7 +7849,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
}
@Override
public String getUntranslatedName() {
return this.getName();
return this.getDisplayName();
}
@Override
public String getUntranslatedType() {

View File

@@ -402,6 +402,8 @@ public class CardFactory {
c.getCurrentState().setOracleText(face.getOracleText());
c.getCurrentState().setFlavorName(face.getFlavorName());
// Super and 'middle' types should use enums.
c.setType(new CardType(face.getType()));

View File

@@ -63,6 +63,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
private ColorSet color = ColorSet.C;
private String oracleText = "";
private String functionalVariantName = null;
private String flavorName = null;
private int basePower = 0;
private int baseToughness = 0;
private String basePowerString = null;
@@ -221,6 +222,15 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
view.setFunctionalVariantName(functionalVariantName);
}
public String getFlavorName() {
return flavorName;
}
public void setFlavorName(String flavorName) {
this.flavorName = flavorName;
view.updateName(this);
}
public final int getBasePower() {
return basePower;
}
@@ -716,6 +726,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
setBaseLoyalty(source.getBaseLoyalty());
setBaseDefense(source.getBaseDefense());
setAttractionLights(source.getAttractionLights());
setFlavorName(source.getFlavorName());
setSVars(source.getSVars());
abilities.clear();
@@ -930,9 +941,10 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
@Override
public String getTranslationKey() {
String displayName = flavorName == null ? name : flavorName;
if(StringUtils.isNotEmpty(functionalVariantName))
return name + " $" + functionalVariantName;
return name;
return displayName + " $" + functionalVariantName;
return displayName;
}
@Override

View File

@@ -7,10 +7,7 @@ import forge.ImageKeys;
import forge.StaticData;
import forge.card.*;
import forge.card.mana.ManaCost;
import forge.game.Direction;
import forge.game.EvenOdd;
import forge.game.GameEntityView;
import forge.game.GameType;
import forge.game.*;
import forge.game.combat.Combat;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
@@ -100,6 +97,25 @@ public class CardView extends GameEntityView {
set(TrackableProperty.Controller, ownerAndController);
set(TrackableProperty.ImageKey, imageKey);
}
@Override
protected void updateName(GameEntity e) {
//Name reflects the current display name, as modified by any flavor names.
//OracleName can be used to find the true name of a card.
if (e instanceof Card c) {
set(TrackableProperty.Name, c.getDisplayName());
set(TrackableProperty.OracleName, c.getName());
}
else {
super.updateName(e);
set(TrackableProperty.OracleName, e.getName());
}
}
public String getOracleName() {
return get(TrackableProperty.OracleName);
}
public PlayerView getOwner() {
return get(TrackableProperty.Owner);
}
@@ -1280,16 +1296,26 @@ public class CardView extends GameEntityView {
}
void updateName(CardState c) {
Card card = c.getCard();
setName(card.getName(c, false));
setName(card.getDisplayName(c));
setOracleName(card.getName(c));
if (CardView.this.getCurrentState() == this) {
if (card != null) {
CardView.this.updateName(card);
}
}
private void setName(String name) {
set(TrackableProperty.Name, name);
}
private void setName(String name0) {
set(TrackableProperty.Name, name0);
/**
* @return The name of the card, unaltered by flavor names.
*/
public String getOracleName() {
return get(TrackableProperty.OracleName);
}
private void setOracleName(String name) {
set(TrackableProperty.OracleName, name);
}
public ColorSet getColors() {

View File

@@ -131,6 +131,7 @@ public enum TrackableProperty {
FunctionalVariant(TrackableTypes.StringType),
OracleText(TrackableTypes.StringType),
RulesText(TrackableTypes.StringType),
OracleName(TrackableTypes.StringType),
Power(TrackableTypes.IntegerType),
Toughness(TrackableTypes.IntegerType),
Loyalty(TrackableTypes.StringType),

View File

@@ -142,7 +142,7 @@ public class CardDetailPanel extends SkinnedPanel {
}
public final void setItem(final InventoryItemFromSet item) {
nameCostLabel.setText(item.getName());
nameCostLabel.setText(item.getDisplayName());
typeLabel.setVisible(false);
powerToughnessLabel.setVisible(false);
idLabel.setText("");

View File

@@ -90,7 +90,7 @@ public class CardListChooser extends FDialog {
public void windowClosing(final WindowEvent e) {
//CardTranslation.getTranslatedName
if (FOptionPane.showConfirmDialog(
Localizer.getInstance().getMessage("lblAreYouSureWantPickCard", CardTranslation.getTranslatedName(jList.getSelectedValue().getName())),
Localizer.getInstance().getMessage("lblAreYouSureWantPickCard", CardTranslation.getTranslatedName(jList.getSelectedValue().getDisplayName())),
Localizer.getInstance().getMessage("lblSelectThisCardConfirm"), false)
) {
dispose();

View File

@@ -1215,7 +1215,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
g.setColor(Color.white);
Shape clip = g.getClip();
g.setClip(bounds);
g.drawString(item.getName(), bounds.x + 10, bounds.y + 20);
g.drawString(item.getDisplayName(), bounds.x + 10, bounds.y + 20);
g.setClip(clip);
}

View File

@@ -102,11 +102,11 @@ public class StarRenderer extends ItemCellRenderer {
return;
}
if (favorite == 0) {
this.setToolTipText(localizer.getMessage("lblClickToAddTargetToFavorites", CardTranslation.getTranslatedName(card.getName())));
this.setToolTipText(localizer.getMessage("lblClickToAddTargetToFavorites", CardTranslation.getTranslatedName(card.getDisplayName())));
skinImage = FSkin.getImage(FSkinProp.IMG_STAR_OUTLINE);
}
else { //TODO: consider supporting more than 1 star
this.setToolTipText(localizer.getMessage("lblClickToRemoveTargetToFavorites", CardTranslation.getTranslatedName(card.getName())));
this.setToolTipText(localizer.getMessage("lblClickToRemoveTargetToFavorites", CardTranslation.getTranslatedName(card.getDisplayName())));
skinImage = FSkin.getImage(FSkinProp.IMG_STAR_FILLED);
}
}

View File

@@ -622,7 +622,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
cardPreviewLabel.setText(String.format("<html>%s %s<br>%s</html>", STYLESHEET, editionLbl, statusLbl));
// set tooltip
String tooltip = String.format("%s [%s] #%s", card.getName(), card.getEdition(),
String tooltip = String.format("%s [%s] #%s", card.getDisplayName(), card.getEdition(),
card.getCollectorNumber());
cardImagePreview.setToolTipText(tooltip);
}

View File

@@ -524,7 +524,7 @@ public class VLobby implements ILobbyView {
PaperCard vanguardAvatar = null;
final Deck deck = decks[playerIndex];
if (selected instanceof PaperCard) {
pp.setVanguardButtonText(((PaperCard) selected).getName());
pp.setVanguardButtonText(((PaperCard) selected).getDisplayName());
cdp.setCard(CardView.getCardForUi((PaperCard) selected));
cdp.setVisible(true);
refreshPanels(false, true);

View File

@@ -1438,8 +1438,8 @@ public final class CMatchUI
private int getRotation(CardView cardView) {
final int rotation;
if (cardView.isSplitCard()) {
String cardName = cardView.getName();
if (cardName.isEmpty()) { cardName = cardView.getAlternateState().getName(); }
String cardName = cardView.getOracleName();
if (cardName.isEmpty()) { cardName = cardView.getAlternateState().getOracleName(); }
PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName);
boolean hasKeywordAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH);

View File

@@ -797,7 +797,7 @@ public class FCardImageRenderer {
if (state.isBasicLand()) {
//draw icons for basic lands
String imageKey;
switch (state.getName().replaceFirst("^Snow-Covered ", "")) {
switch (state.getOracleName().replaceFirst("^Snow-Covered ", "")) {
case "Plains":
imageKey = "W";
break;

View File

@@ -230,8 +230,8 @@ public enum CardZoomer {
return 0;
}
if (thisCard.getCard().isSplitCard()) {
String cardName = thisCard.getCard().getName();
if (cardName.isEmpty()) { cardName = thisCard.getCard().getAlternateState().getName(); }
String cardName = thisCard.getCard().getOracleName();
if (cardName.isEmpty()) { cardName = thisCard.getCard().getAlternateState().getOracleName(); }
PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName);
boolean isAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH);

View File

@@ -504,8 +504,8 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} else {
if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested)
PaperCard pc = null;
if (!card.getName().isEmpty()) {
pc = StaticData.instance().getCommonCards().getCard(card.getName());
if (!card.getOracleName().isEmpty()) {
pc = StaticData.instance().getCommonCards().getCard(card.getOracleName());
}
int ofs = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH) ? -12 : 12;

View File

@@ -107,7 +107,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
for (int i = 0, n = allLands.size(); i < n; i++) {
final CardStack stack = allLands.get(i);
final CardPanel firstPanel = stack.get(0);
if (firstPanel.getCard().getCurrentState().getName().equals(state.getName())) {
if (firstPanel.getCard().getCurrentState().getOracleName().equals(state.getOracleName())) {
if (!firstPanel.getAttachedPanels().isEmpty() || firstPanel.getCard().hasCardAttachments()) {
// Put this land to the left of lands with the same name
// and attachments.
@@ -162,7 +162,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
final CardPanel firstPanel = stack.get(0);
final CardView firstCard = firstPanel.getCard();
if (firstPanel.getCard().getCurrentState().getName().equals(state.getName())) {
if (firstPanel.getCard().getCurrentState().getOracleName().equals(state.getOracleName())) {
if (!firstPanel.getAttachedPanels().isEmpty()) {
// Put this token to the left of tokens with the same
// name and attachments.
@@ -220,7 +220,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
final CardStack stack = allCreatures.get(i);
final CardPanel firstPanel = stack.get(0);
final CardView firstCard = firstPanel.getCard();
if (firstCard.getName().equals(card.getName())) {
if (firstCard.getOracleName().equals(card.getOracleName())) {
if (!firstPanel.getAttachedPanels().isEmpty()) {
// Put this creature to the left of creatures with the same
// name and attachments.
@@ -335,7 +335,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
final CardStateView otherState = otherCard.getCurrentState();
final CardView thisCard = panel.getCard();
final CardStateView thisState = thisCard.getCurrentState();
if (otherState.getName().equals(thisState.getName()) && s.size() < STACK_MAX_OTHERS) {
if (otherState.getOracleName().equals(thisState.getOracleName()) && s.size() < STACK_MAX_OTHERS) {
if (panel.getAttachedPanels().isEmpty()
&& thisCard.hasSameCounters(otherCard)
&& (thisCard.isSick() == otherCard.isSick())

View File

@@ -993,7 +993,7 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb
Reward.Type rewardType = reward.getType();
switch (rewardType) {
case Card:
display = reward.getCard() != null ? CardTranslation.getTranslatedName(reward.getCard().getName()) : "";
display = reward.getCard() != null ? CardTranslation.getTranslatedName(reward.getCard().getDisplayName()) : "";
//alignment = Align.topLeft;
labelStyle = "dialog";
break;

View File

@@ -219,7 +219,7 @@ public class CardZoom extends FOverlay {
}
if (item instanceof InventoryItem) {
InventoryItem ii = (InventoryItem)item;
return new CardView(-1, null, ii.getName(), null, ii.getImageKey(false));
return new CardView(-1, null, ii.getDisplayName(), null, ii.getImageKey(false));
}
return new CardView(-1, null, item.toString());
}

View File

@@ -1866,7 +1866,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
sortedOptions.add(option);
}
}
GuiChoose.oneOrNone(Forge.getLocalizer().getMessage("lblSelectPreferredArt") + " " + card.getName(), sortedOptions, result -> {
GuiChoose.oneOrNone(Forge.getLocalizer().getMessage("lblSelectPreferredArt") + " " + card.getDisplayName(), sortedOptions, result -> {
if (result != null) {
if (result != card) {
cardManager.replaceAll(card, result);
@@ -2104,7 +2104,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
}
final Localizer localizer = Forge.getLocalizer();
String lblReplaceCard = localizer.getMessage("lblReplace");
String prompt = localizer.getMessage("lblSelectReplacementCard") + " " + card.getName();
String prompt = localizer.getMessage("lblSelectReplacementCard") + " " + card.getDisplayName();
String promptQuantity = String.format("%s - %s %s", card, lblReplaceCard, localizer.getMessage("lblHowMany"));
//First have the player choose which card to swap in.
GuiChoose.oneOrNone(prompt, sortedOptions, replacement -> {

View File

@@ -1251,9 +1251,9 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
g.drawImage(Forge.getAssets().getTexture(getDefaultSkinFile("cover.png"), false), x + (w - w * scale) / 2, y + (h - h * scale) / 1.5f, w * scale, h * scale);
}
//fake labelname shadow
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.BLACK, (x + PADDING) - 1f, (y + PADDING * 2) + 1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
g.drawText(item.getDisplayName(), GROUP_HEADER_FONT, Color.BLACK, (x + PADDING) - 1f, (y + PADDING * 2) + 1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
//labelname
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING * 2, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
g.drawText(item.getDisplayName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING * 2, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
} else {
if (!deckProxy.isGeneratedDeck()) {
if (deckProxy.getDeck().isEmpty()) {
@@ -1326,13 +1326,13 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
String key = item.getImageKey(false);
if (key.startsWith(ImageKeys.PRECON_PREFIX) || key.startsWith(ImageKeys.FATPACK_PREFIX)
|| key.startsWith(ImageKeys.BOOSTERBOX_PREFIX) || key.startsWith(ImageKeys.BOOSTER_PREFIX) || key.startsWith(ImageKeys.TOURNAMENTPACK_PREFIX)) {
CardView cv = new CardView(-1, null, item.getName(), null, item.getImageKey(false));
CardView cv = new CardView(-1, null, item.getDisplayName(), null, item.getImageKey(false));
CardImageRenderer.drawCardImage(g, cv, false, x, y, w, h, CardStackPosition.Top, false, false);
return;
}
}
g.fillRect(Color.BLACK, x, y, w, h);
g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
g.drawText(item.getDisplayName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false);
}
}

View File

@@ -777,7 +777,7 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
CardPool avatarPool = new CardPool();
avatarPool.add(playerPanel.getVanguardAvatar());
playerDeck.putSection(DeckSection.Avatar, avatarPool);
VanguardAvatar = Forge.getLocalizer().getMessage("lblVanguard") + ": " + playerPanel.getVanguardAvatar().getName();
VanguardAvatar = Forge.getLocalizer().getMessage("lblVanguard") + ": " + playerPanel.getVanguardAvatar().getDisplayName();
playerPanel.setVanguarAvatarName(VanguardAvatar);
}

View File

@@ -204,7 +204,7 @@ public class PlayerPanel extends FContainer {
});
lstVanguardAvatars = new FVanguardChooser(isAi, e -> {
btnVanguardAvatar.setText(Forge.getLocalizer().getMessage("lblVanguard")
+ ":" + (Forge.isLandscapeMode() ? " " : "\n") + ((CardManager)e.getSource()).getSelectedItem().getName());
+ ":" + (Forge.isLandscapeMode() ? " " : "\n") + ((CardManager)e.getSource()).getSelectedItem().getDisplayName());
if (allowNetworking && btnVanguardAvatar.isEnabled() && humanAiSwitch.isToggled()) {
screen.updateMyDeck(index);
}

View File

@@ -128,12 +128,12 @@ public class VField extends FContainer {
if (!this.stackNonTokenCreatures && cardState.isCreature() && !card.isToken()) {
return false;
}
final String cardName = cardState.getName();
final String cardName = cardState.getOracleName();
for (CardView c : cardsOfType) {
CardStateView cState = c.getCurrentState();
if (cState.isCreature()) {
if (!c.hasCardAttachments() &&
cardName.equals(cState.getName()) &&
cardName.equals(cState.getOracleName()) &&
card.hasSameCounters(c) &&
card.hasSamePT(c) && //don't stack token with different PT
cardState.getKeywordKey().equals(cState.getKeywordKey()) &&
@@ -145,7 +145,7 @@ public class VField extends FContainer {
}
} else {
if (!c.hasCardAttachments() &&
cardName.equals(cState.getName()) &&
cardName.equals(cState.getOracleName()) &&
card.hasSameCounters(c) &&
cardState.getKeywordKey().equals(cState.getKeywordKey()) &&
cardState.getColors() == cState.getColors() &&

View File

@@ -130,7 +130,7 @@ public class VReveal extends FDropDown {
private RevealEntryDisplay(PaperCard pc, boolean isAltRow) {
paperCard = pc;
altRow = isAltRow;
text = CardTranslation.getTranslatedName(pc.getName()) + "\n" + formatType();
text = CardTranslation.getTranslatedName(pc.getDisplayName()) + "\n" + formatType();
}
public float getMinHeight(float width) {

View File

@@ -202,7 +202,7 @@ public class ConquestCommandersScreen extends FScreen {
float imageSize = CardRenderer.MANA_SYMBOL_SIZE;
ColorSet cardColor = card.getRules().getColorIdentity();
float availableWidth = w - cardArtWidth - CardFaceSymbols.getWidth(cardColor, imageSize) - FList.PADDING;
g.drawText(card.getName(), font, foreColor, x, y, availableWidth, imageSize, false, Align.left, true);
g.drawText(card.getDisplayName(), font, foreColor, x, y, availableWidth, imageSize, false, Align.left, true);
CardFaceSymbols.drawColorSet(g, cardColor, x + availableWidth + FList.PADDING, y, imageSize);
if (compactModeHandler.isCompactMode()) {

View File

@@ -684,10 +684,10 @@ public class ConquestMultiverseScreen extends FScreen {
float labelHeight = playerAvatar.getTop();
if (playerAvatar.card != null) {
g.drawText(playerAvatar.card.getName(), AVATAR_NAME_FONT, Color.WHITE, PADDING, 0, labelWidth, labelHeight, false, Align.left, true);
g.drawText(playerAvatar.card.getDisplayName(), AVATAR_NAME_FONT, Color.WHITE, PADDING, 0, labelWidth, labelHeight, false, Align.left, true);
}
if (opponentAvatar.card != null) {
g.drawText(opponentAvatar.card.getName(), AVATAR_NAME_FONT, Color.WHITE, getWidth() - labelWidth - PADDING, getHeight() - labelHeight, labelWidth, labelHeight, false, Align.right, true);
g.drawText(opponentAvatar.card.getDisplayName(), AVATAR_NAME_FONT, Color.WHITE, getWidth() - labelWidth - PADDING, getHeight() - labelHeight, labelWidth, labelHeight, false, Align.right, true);
}
}

View File

@@ -282,7 +282,7 @@ public class LoadConquestScreen extends LaunchScreen {
font = FSkinFont.get(12);
float cardsWidth = font.getBounds(cards).width + iconSize + SettingsScreen.SETTING_PADDING;
float shardsWidth = font.getBounds(shards).width + iconSize + SettingsScreen.SETTING_PADDING;
g.drawText(value.getPlaneswalker().getName() + " - " + value.getCurrentPlane().getName().replace("_", " "), font, SettingsScreen.DESC_COLOR, x, y, w - shardsWidth - cardsWidth, h, false, Align.left, false);
g.drawText(value.getPlaneswalker().getDisplayName() + " - " + value.getCurrentPlane().getName().replace("_", " "), font, SettingsScreen.DESC_COLOR, x, y, w - shardsWidth - cardsWidth, h, false, Align.left, false);
g.drawImage(FSkinImage.SPELLBOOK, x + w - shardsWidth - cardsWidth + iconOffset, y - SettingsScreen.SETTING_PADDING, iconSize, iconSize);
g.drawText(cards, font, SettingsScreen.DESC_COLOR, x + w - shardsWidth - cardsWidth + iconSize + SettingsScreen.SETTING_PADDING, y, w, h, false, Align.left, false);
g.drawImage(FSkinImage.AETHER_SHARD, x + w - shardsWidth + iconOffset, y - SettingsScreen.SETTING_PADDING, iconSize, iconSize);

View File

@@ -9,7 +9,7 @@ import forge.screens.match.MatchController;
public class CardRendererUtils {
public static boolean needsRotation(final CardView card) {
return needsRotation(card.isSplitCard() ? ForgePreferences.FPref.UI_ROTATE_SPLIT_CARDS
: ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON, card, canShowAlternate(card, card.getName()));
: ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON, card, canShowAlternate(card, card.getOracleName()));
}
public static boolean needsRotation(final CardView card, final boolean altState) {
return needsRotation(card.isSplitCard() ? ForgePreferences.FPref.UI_ROTATE_SPLIT_CARDS
@@ -52,7 +52,7 @@ public class CardRendererUtils {
showAlt = card.getAlternateState().getOracleText().contains(reference.trim());
else {
if (card.isRoom()) // special case for room cards
showAlt = card.getAlternateState().getName().equalsIgnoreCase(reference);
showAlt = card.getAlternateState().getOracleName().equalsIgnoreCase(reference);
else
showAlt = reference.contains(card.getAlternateState().getAbilityText());
}

View File

@@ -1,5 +1,5 @@
Name:Blanka, Ferocious Friend
AltName:The Howling Abomination
Variant:UniversesWithin:FlavorName:The Howling Abomination
ManaCost:3 R G
Types:Legendary Creature Human Beast Warrior
PT:5/5

View File

@@ -1,5 +1,5 @@
Name:Chief Jim Hopper
AltName:Sophina, Spearsage Deserter
Variant:UniversesWithin:FlavorName:Sophina, Spearsage Deserter
ManaCost:2 R W
Types:Legendary Creature Human Soldier
PT:4/4

View File

@@ -1,5 +1,5 @@
Name:Chun-Li, Countless Kicks
AltName:Zethi, Arcane Blademaster
Variant:UniversesWithin:FlavorName:Zethi, Arcane Blademaster
ManaCost:1 W U
Types:Legendary Creature Human Soldier
PT:3/3

View File

@@ -1,5 +1,5 @@
Name:Daryl, Hunter of Walkers
AltName:Hansk, Slayer Zealot
Variant:UniversesWithin:FlavorName:Hansk, Slayer Zealot
ManaCost:2 R G
Types:Legendary Creature Human Archer
PT:4/4

View File

@@ -1,5 +1,5 @@
Name:Dhalsim, Pliable Pacifist
AltName:Tadeas, Juniper Ascendant
Variant:UniversesWithin:FlavorName:Tadeas, Juniper Ascendant
ManaCost:2 G W
Types:Legendary Creature Human Monk
PT:1/3

View File

@@ -1,5 +1,5 @@
Name:Doric, Nature's Warden
AltName:Casal, Lurkwood Pathfinder
Variant:UniversesWithin:FlavorName:Casal, Lurkwood Pathfinder
ManaCost:3 G
Types:Legendary Creature Tiefling Druid
PT:3/3
@@ -15,7 +15,7 @@ Oracle:Vigilance\nWhen Doric, Nature's Warden enters, search your library for a
ALTERNATE
Name:Doric, Owlbear Avenger
AltName:Casal, Pathbreaker Owlbear
Variant:UniversesWithin:FlavorName:Casal, Pathbreaker Owlbear
ManaCost:no cost
Colors:green
Types:Legendary Creature Bird Bear

View File

@@ -1,5 +1,5 @@
Name:Dustin, Gadget Genius
AltName:Hargilde, Kindly Runechanter
Variant:UniversesWithin:FlavorName:Hargilde, Kindly Runechanter
ManaCost:2 W U
Types:Legendary Creature Human
PT:2/3

View File

@@ -1,5 +1,5 @@
Name:E. Honda, Sumo Champion
AltName:Baldin, Century Herdmaster
Variant:UniversesWithin:FlavorName:Baldin, Century Herdmaster
ManaCost:4 W W
Types:Legendary Creature Human Warrior
PT:0/7

View File

@@ -1,5 +1,5 @@
Name:Edgin, Larcenous Lutenist
AltName:Bohn, Beguiling Balladeer
Variant:UniversesWithin:FlavorName:Bohn, Beguiling Balladeer
ManaCost:1 U R
Types:Legendary Creature Human Bard
PT:3/3

View File

@@ -1,5 +1,5 @@
Name:Eleven, the Mage
AltName:Cecily, Haunted Mage
Variant:UniversesWithin:FlavorName:Cecily, Haunted Mage
ManaCost:1 U B R
Types:Legendary Creature Human Wizard
PT:3/5

View File

@@ -1,5 +1,5 @@
Name:Forge, Neverwinter Charlatan
AltName:Evin, Waterdeep Opportunist
Variant:UniversesWithin:FlavorName:Evin, Waterdeep Opportunist
ManaCost:3 B
Types:Legendary Creature Human Rogue
PT:2/4

View File

@@ -1,5 +1,5 @@
Name:Glenn, the Voice of Calm
AltName:Gregor, Shrewd Magistrate
Variant:UniversesWithin:FlavorName:Gregor, Shrewd Magistrate
ManaCost:1 W U
Types:Legendary Creature Human Advisor
PT:1/3

View File

@@ -1,5 +1,5 @@
Name:Guile, Sonic Soldier
AltName:Immard, the Stormcleaver
Variant:UniversesWithin:FlavorName:Immard, the Stormcleaver
ManaCost:1 U R W
Types:Legendary Creature Human Soldier
PT:4/4

View File

@@ -1,5 +1,5 @@
Name:Hawkins National Laboratory
AltName:Havengul Laboratory
Variant:UniversesWithin:FlavorName:Havengul Laboratory
ManaCost:no cost
Types:Legendary Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
@@ -15,7 +15,7 @@ Oracle:{T}: Add {C}.\n{4}, {T}: Investigate.\nAt the beginning of your end step,
ALTERNATE
Name:The Upside Down
AltName:Havengul Mystery
Variant:UniversesWithin:FlavorName:Havengul Mystery
ManaCost:no cost
Types:Legendary Land
T:Mode$ Transformed | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When this land transforms into CARDNAME, return target creature card from your graveyard to the battlefield.

View File

@@ -1,5 +1,5 @@
Name:Holga, Relentless Rager
AltName:Jurin, Leading the Charge
Variant:UniversesWithin:FlavorName:Jurin, Leading the Charge
ManaCost:4 R R
Types:Legendary Creature Human Barbarian
PT:4/6

View File

@@ -1,5 +1,5 @@
Name:Ken, Burning Brawler
AltName:Aisha of Sparks and Smoke
Variant:UniversesWithin:FlavorName:Aisha of Sparks and Smoke
ManaCost:1 R R
Types:Legendary Creature Human Warrior
PT:4/2

View File

@@ -1,5 +1,5 @@
Name:Lucas, the Sharpshooter
AltName:Bjorna, Nightfall Alchemist
Variant:UniversesWithin:FlavorName:Bjorna, Nightfall Alchemist
ManaCost:U R
Types:Legendary Creature Human
PT:1/3

View File

@@ -1,5 +1,5 @@
Name:Lucille
AltName:Gisa's Favorite Shovel
Variant:UniversesWithin:FlavorName:Gisa's Favorite Shovel
ManaCost:1 B
Types:Legendary Artifact Equipment
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 2 | AddKeyword$ Menace | Description$ Equipped creature gets +2/+0 and has menace.

View File

@@ -1,5 +1,5 @@
Name:Max, the Daredevil
AltName:Elmar, Ulvenwald Informant
Variant:UniversesWithin:FlavorName:Elmar, Ulvenwald Informant
ManaCost:1 R G
Types:Legendary Creature Human
PT:3/2

View File

@@ -1,5 +1,5 @@
Name:Michonne, Ruthless Survivor
AltName:Enkira, Hostile Scavenger
Variant:UniversesWithin:FlavorName:Enkira, Hostile Scavenger
ManaCost:3 B G
Types:Legendary Creature Human Warrior
PT:3/3

View File

@@ -1,5 +1,5 @@
Name:Mike, the Dungeon Master
AltName:Othelm, Sigardian Outcast
Variant:UniversesWithin:FlavorName:Othelm, Sigardian Outcast
ManaCost:1 G W
Types:Legendary Creature Human
PT:2/2

View File

@@ -1,5 +1,5 @@
Name:Mind Flayer, the Shadow
AltName:Arvinox, the Mind Flail
Variant:UniversesWithin:FlavorName:Arvinox, the Mind Flail
ManaCost:4 B B B
Types:Legendary Enchantment Creature Horror
PT:9/9

View File

@@ -1,5 +1,5 @@
Name:Negan, the Cold-Blooded
AltName:Malik, Grim Manipulator
Variant:UniversesWithin:FlavorName:Malik, Grim Manipulator
ManaCost:2 R W B
Types:Legendary Creature Human Rogue
PT:4/3

View File

@@ -1,4 +1,4 @@
Name:No Secret Is Hidden From Me
Name:No Secret Is Hidden from Me
ManaCost:no cost
Types:Scheme
T:Mode$ SetInMotion | ValidCard$ Card.Self | Execute$ TrigDigUntil | TriggerZones$ Command | TriggerDescription$ When you set this scheme in motion, exile cards from the top of your library until you exile a nonland card. You may cast that card without paying its mana cost. Then if you control six or more lands, repeat this process once.

View File

@@ -1,5 +1,5 @@
Name:Rick, Steadfast Leader
AltName:Greymond, Avacyn's Stalwart
Variant:UniversesWithin:FlavorName:Greymond, Avacyn's Stalwart
ManaCost:2 W W
Types:Legendary Creature Human Soldier
PT:3/4

View File

@@ -1,5 +1,5 @@
Name:Ryu, World Warrior
AltName:Vikya, Scorching Stalwart
Variant:UniversesWithin:FlavorName:Vikya, Scorching Stalwart
ManaCost:2 W
Types:Legendary Creature Human Warrior
PT:2/4

View File

@@ -1,5 +1,5 @@
Name:Simon, Wild Magic Sorcerer
AltName:Mathise, Surge Channeler
Variant:UniversesWithin:FlavorName:Mathise, Surge Channeler
ManaCost:2 U
Types:Legendary Creature Human Elf Shaman
PT:1/1

View File

@@ -1,5 +1,5 @@
Name:Will the Wise
AltName:Wernog, Rider's Chaplain
Variant:UniversesWithin:FlavorName:Wernog, Rider's Chaplain
ManaCost:W B
Types:Legendary Creature Human
PT:1/2

View File

@@ -1,5 +1,5 @@
Name:Xenk, Paladin Unbroken
AltName:Rashel, Fist of Torm
Variant:UniversesWithin:FlavorName:Rashel, Fist of Torm
ManaCost:2 W W
Types:Legendary Creature Human Knight
PT:2/4

View File

@@ -1,5 +1,5 @@
Name:Zangief, the Red Cyclone
AltName:Maarika, Brutal Gladiator
Variant:UniversesWithin:FlavorName:Maarika, Brutal Gladiator
ManaCost:2 B R G
Types:Legendary Creature Human Warrior
PT:7/4

View File

@@ -686,7 +686,7 @@ TokensCode=SLD
707 R Knight Exemplar @Victor Adame Minguez
708 R Fellwar Stone @Dan Frazier
709 R Dragon's Hoard @Pedro Potier
710 R Command Tower @Joana LaFuente
710 R Command Tower @Joana LaFuente ${"flavorName": "Cybertron"}
711 R Tireless Tracker @Nils Hamm
713 R Swords to Plowshares @Justin Hernandez & Alexis Hernandez
714 R Merfolk of the Pearl Trident @Dani Pendergast
@@ -1577,7 +1577,7 @@ F1540 M Rainbow Dash @John Thacker
1609 R Beastmaster Ascension @Greg Staples
1610 R Howl of the Night Pack @Johannes Voss
1611 R Second Harvest @Miranda Meeks
1612 R Tovolar, Dire Overlord @Zoltan Boros
1612 R Tovolar, Dire Overlord @Zoltan Boros ${"flavorName": "NOT A WOLF // KING OF WOLVES"}
1614 R Brash Taunter @Mark Behm & Scott Okumura
1615 R Goblin Chieftain @Jakub Kasper & Scott Okumura
1616 R Goblin Ringleader @Svetlin Velinov & Scott Okumura
@@ -1902,7 +1902,7 @@ F1540 M Rainbow Dash @John Thacker
1929 M Toxrill, the Corrosive @Gregg Schigiel
1930 M Toski, Bearer of Secrets @Caleb Meurer
1931 R Barktooth Warbeard @Caleb Meurer
1932 M Jodah, the Unifier @Caleb Meurer
1932 M Jodah, the Unifier @Caleb Meurer ${"flavorName": "SpongeBob SquarePants"}
1933 R Counterspell @Tyler Walpole
1934 R Daze @Tyler Walpole
1935 R Inevitable Betrayal @Tyler Walpole
@@ -2113,7 +2113,7 @@ F1540 M Rainbow Dash @John Thacker
2176 R Descent into Avernus @Stephen Andrade
2177 R Reckless Endeavor @Stephen Andrade
2178 M Sneak Attack @Stephen Andrade
2179 R Abrade @Stephen Andrade
2179 R Abrade @Stephen Andrade ${"flavorName": "You're Gonna Need a Bigger Boat"}
2181 M Bruvac the Grandiloquent @Akirant
2182 R Windfall @Dan Mumford
2183 M Captain N'ghathrod @Akirant
@@ -2212,7 +2212,7 @@ F1540 M Rainbow Dash @John Thacker
7027 R Tibalt's Trickery @Babs Webb
7028 R Minds Aglow @Evan Geltosky
7029 R Command Tower @Dan Black
7030 R Command Tower @Sylvain Sarrailh
7030 R Command Tower @Sylvain Sarrailh ${"flavorName": "Master Emerald Shrine"}
7031 M Lotus Petal @Mike Burns
7032 M Lotus Petal @Mike Burns
7033 M Lotus Petal @Mike Burns

View File

@@ -4185,7 +4185,7 @@ SHM-258 C Pili-Pala @Ron Spencer
SHM-260 R Reaper King @Jim Murray
SHM-263 C Scuttlemutt @Jeremy Jarvis
SHM-267 U Umbral Mantle @Richard Sardinha
SLX-4 R Elmar, Ulvenwald Informant @Eliz Roxs
SLX-4 R Elmar, Ulvenwald Informant @Eliz Roxs ${"variant": "UniversesWithin"}
SNC-14 R Giada, Font of Hope @Eric Deschamps
SNC-15 M Halo Fountain @Anastasia Ovchinnikova
SNC-18 C Inspiring Overseer @Irina Nordsol

View File

@@ -102,54 +102,54 @@ F192 R Souvenir T-Shirt @Michael Phillippi
F197 U The Big Top @Kirsten Zirngibl
F198 C Nearby Planet @Bruce Brenneise
F199 R Urza's Fun House @Dmitry Burmak
F203a R Centrifuge @Greg Staples $A
F203b R Centrifuge @Greg Staples $B
F207a C Cover the Spot @Jeff Miracola $A
F207b C Cover the Spot @Jeff Miracola $B
F207c C Cover the Spot @Jeff Miracola $C
F207d C Cover the Spot @Jeff Miracola $D
F208a C Dart Throw @Gaboleps $A
F208b C Dart Throw @Gaboleps $B
F208c C Dart Throw @Gaboleps $C
F208d C Dart Throw @Gaboleps $D
F213a R Gallery of Legends @Jakub Kasper $A
F213b R Gallery of Legends @Jakub Kasper $B
F214a R Gift Shop @Matt Gaser $A
F214b R Gift Shop @Matt Gaser $B
F215a U Guess Your Fate @Bruce Brenneise $A
F215b U Guess Your Fate @Bruce Brenneise $B
F215c U Guess Your Fate @Bruce Brenneise $C
F215d U Guess Your Fate @Bruce Brenneise $D
F220a R Log Flume @Marco Bucci $A
F220b R Log Flume @Marco Bucci $B
F221a R Memory Test @Setor Fiadzigbey $A
F221b R Memory Test @Setor Fiadzigbey $B
F224a R Push Your Luck @Sebastian Giacobino $A
F224b R Push Your Luck @Sebastian Giacobino $B
F226a U Scavenger Hunt @Jamroz Gary $A
F226b U Scavenger Hunt @Jamroz Gary $B
F226c U Scavenger Hunt @Jamroz Gary $C
F226d U Scavenger Hunt @Jamroz Gary $D
F226e U Scavenger Hunt @Jamroz Gary $E
F226f U Scavenger Hunt @Jamroz Gary $F
F228a U Squirrel Stack @Andrea Radeck $A
F228b U Squirrel Stack @Andrea Radeck $B
F228c U Squirrel Stack @Andrea Radeck $C
F228d U Squirrel Stack @Andrea Radeck $D
F228e U Squirrel Stack @Andrea Radeck $E
F228f U Squirrel Stack @Andrea Radeck $F
F230a U The Superlatorium @Simon Dominic $A
F230b U The Superlatorium @Simon Dominic $B
F230c U The Superlatorium @Simon Dominic $C
F230d U The Superlatorium @Simon Dominic $D
F230e U The Superlatorium @Simon Dominic $E
F230f U The Superlatorium @Simon Dominic $F
F233a U Trivia Contest @Caroline Gariba $A
F233b U Trivia Contest @Caroline Gariba $B
F233c U Trivia Contest @Caroline Gariba $C
F233d U Trivia Contest @Caroline Gariba $D
F233e U Trivia Contest @Caroline Gariba $E
F233f U Trivia Contest @Caroline Gariba $F
F203a R Centrifuge @Greg Staples ${"variant": "A"}
F203b R Centrifuge @Greg Staples ${"variant": "B"}
F207a C Cover the Spot @Jeff Miracola ${"variant": "A"}
F207b C Cover the Spot @Jeff Miracola ${"variant": "B"}
F207c C Cover the Spot @Jeff Miracola ${"variant": "C"}
F207d C Cover the Spot @Jeff Miracola ${"variant": "D"}
F208a C Dart Throw @Gaboleps ${"variant": "A"}
F208b C Dart Throw @Gaboleps ${"variant": "B"}
F208c C Dart Throw @Gaboleps ${"variant": "C"}
F208d C Dart Throw @Gaboleps ${"variant": "D"}
F213a R Gallery of Legends @Jakub Kasper ${"variant": "A"}
F213b R Gallery of Legends @Jakub Kasper ${"variant": "B"}
F214a R Gift Shop @Matt Gaser ${"variant": "A"}
F214b R Gift Shop @Matt Gaser ${"variant": "B"}
F215a U Guess Your Fate @Bruce Brenneise ${"variant": "A"}
F215b U Guess Your Fate @Bruce Brenneise ${"variant": "B"}
F215c U Guess Your Fate @Bruce Brenneise ${"variant": "C"}
F215d U Guess Your Fate @Bruce Brenneise ${"variant": "D"}
F220a R Log Flume @Marco Bucci ${"variant": "A"}
F220b R Log Flume @Marco Bucci ${"variant": "B"}
F221a R Memory Test @Setor Fiadzigbey ${"variant": "A"}
F221b R Memory Test @Setor Fiadzigbey ${"variant": "B"}
F224a R Push Your Luck @Sebastian Giacobino ${"variant": "A"}
F224b R Push Your Luck @Sebastian Giacobino ${"variant": "B"}
F226a U Scavenger Hunt @Jamroz Gary ${"variant": "A"}
F226b U Scavenger Hunt @Jamroz Gary ${"variant": "B"}
F226c U Scavenger Hunt @Jamroz Gary ${"variant": "C"}
F226d U Scavenger Hunt @Jamroz Gary ${"variant": "D"}
F226e U Scavenger Hunt @Jamroz Gary ${"variant": "E"}
F226f U Scavenger Hunt @Jamroz Gary ${"variant": "F"}
F228a U Squirrel Stack @Andrea Radeck ${"variant": "A"}
F228b U Squirrel Stack @Andrea Radeck ${"variant": "B"}
F228c U Squirrel Stack @Andrea Radeck ${"variant": "C"}
F228d U Squirrel Stack @Andrea Radeck ${"variant": "D"}
F228e U Squirrel Stack @Andrea Radeck ${"variant": "E"}
F228f U Squirrel Stack @Andrea Radeck ${"variant": "F"}
F230a U The Superlatorium @Simon Dominic ${"variant": "A"}
F230b U The Superlatorium @Simon Dominic ${"variant": "B"}
F230c U The Superlatorium @Simon Dominic ${"variant": "C"}
F230d U The Superlatorium @Simon Dominic ${"variant": "D"}
F230e U The Superlatorium @Simon Dominic ${"variant": "E"}
F230f U The Superlatorium @Simon Dominic ${"variant": "F"}
F233a U Trivia Contest @Caroline Gariba ${"variant": "A"}
F233b U Trivia Contest @Caroline Gariba ${"variant": "B"}
F233c U Trivia Contest @Caroline Gariba ${"variant": "C"}
F233d U Trivia Contest @Caroline Gariba ${"variant": "D"}
F233e U Trivia Contest @Caroline Gariba ${"variant": "E"}
F233f U Trivia Contest @Caroline Gariba ${"variant": "F"}
F245 R Katerina of Myra's Marvels @David Semple
F246 R Solaflora, Intergalactic Icon @Scooter
F247 R Fluros of Myra's Marvels @David Semple
@@ -391,93 +391,93 @@ F538 R Water Gun Balloon Game @Ralph Horsley
194 C Ticket Turbotubes @Matt Gaser
195 C Ticketomaton @Michael Phillippi
196 U Wicker Picker @Igor Grechanyi
200a U Balloon Stand @Jakub Kasper $A
200b U Balloon Stand @Jakub Kasper $B
200c U Balloon Stand @Jakub Kasper $C
200d U Balloon Stand @Jakub Kasper $D
201a U Bounce Chamber @Dmitry Burmak $A
201b U Bounce Chamber @Dmitry Burmak $B
201c U Bounce Chamber @Dmitry Burmak $C
201d U Bounce Chamber @Dmitry Burmak $D
202a U Bumper Cars @Gabor Szikszai $A
202b U Bumper Cars @Gabor Szikszai $B
202c U Bumper Cars @Gabor Szikszai $C
202d U Bumper Cars @Gabor Szikszai $D
202e U Bumper Cars @Gabor Szikszai $E
202f U Bumper Cars @Gabor Szikszai $F
204a C Clown Extruder @Marco Bucci $A
204b C Clown Extruder @Marco Bucci $B
204c C Clown Extruder @Marco Bucci $C
204d C Clown Extruder @Marco Bucci $D
205a U Concession Stand @David Sladek $A
205b U Concession Stand @David Sladek $B
205c U Concession Stand @David Sladek $C
205d U Concession Stand @David Sladek $D
206a C Costume Shop @Raluca Marinescu $A
206b C Costume Shop @Raluca Marinescu $B
206c C Costume Shop @Raluca Marinescu $C
206d C Costume Shop @Raluca Marinescu $D
206e C Costume Shop @Raluca Marinescu $E
206f C Costume Shop @Raluca Marinescu $F
209a C Drop Tower @Dmitry Burmak $A
209b C Drop Tower @Dmitry Burmak $B
209c C Drop Tower @Dmitry Burmak $C
209d C Drop Tower @Dmitry Burmak $D
209e C Drop Tower @Dmitry Burmak $E
209f C Drop Tower @Dmitry Burmak $F
200a U Balloon Stand @Jakub Kasper ${"variant": "A"}
200b U Balloon Stand @Jakub Kasper ${"variant": "B"}
200c U Balloon Stand @Jakub Kasper ${"variant": "C"}
200d U Balloon Stand @Jakub Kasper ${"variant": "D"}
201a U Bounce Chamber @Dmitry Burmak ${"variant": "A"}
201b U Bounce Chamber @Dmitry Burmak ${"variant": "B"}
201c U Bounce Chamber @Dmitry Burmak ${"variant": "C"}
201d U Bounce Chamber @Dmitry Burmak ${"variant": "D"}
202a U Bumper Cars @Gabor Szikszai ${"variant": "A"}
202b U Bumper Cars @Gabor Szikszai ${"variant": "B"}
202c U Bumper Cars @Gabor Szikszai ${"variant": "C"}
202d U Bumper Cars @Gabor Szikszai ${"variant": "D"}
202e U Bumper Cars @Gabor Szikszai ${"variant": "E"}
202f U Bumper Cars @Gabor Szikszai ${"variant": "F"}
204a C Clown Extruder @Marco Bucci ${"variant": "A"}
204b C Clown Extruder @Marco Bucci ${"variant": "B"}
204c C Clown Extruder @Marco Bucci ${"variant": "C"}
204d C Clown Extruder @Marco Bucci ${"variant": "D"}
205a U Concession Stand @David Sladek ${"variant": "A"}
205b U Concession Stand @David Sladek ${"variant": "B"}
205c U Concession Stand @David Sladek ${"variant": "C"}
205d U Concession Stand @David Sladek ${"variant": "D"}
206a C Costume Shop @Raluca Marinescu ${"variant": "A"}
206b C Costume Shop @Raluca Marinescu ${"variant": "B"}
206c C Costume Shop @Raluca Marinescu ${"variant": "C"}
206d C Costume Shop @Raluca Marinescu ${"variant": "D"}
206e C Costume Shop @Raluca Marinescu ${"variant": "E"}
206f C Costume Shop @Raluca Marinescu ${"variant": "F"}
209a C Drop Tower @Dmitry Burmak ${"variant": "A"}
209b C Drop Tower @Dmitry Burmak ${"variant": "B"}
209c C Drop Tower @Dmitry Burmak ${"variant": "C"}
209d C Drop Tower @Dmitry Burmak ${"variant": "D"}
209e C Drop Tower @Dmitry Burmak ${"variant": "E"}
209f C Drop Tower @Dmitry Burmak ${"variant": "F"}
210 R Ferris Wheel @Kirsten Zirngibl
211a C Foam Weapons Kiosk @Matt Gaser $A
211b C Foam Weapons Kiosk @Matt Gaser $B
211c C Foam Weapons Kiosk @Matt Gaser $C
211d C Foam Weapons Kiosk @Matt Gaser $D
212a C Fortune Teller @Jamroz Gary $A
212b C Fortune Teller @Jamroz Gary $B
212c C Fortune Teller @Jamroz Gary $C
212d C Fortune Teller @Jamroz Gary $D
212e C Fortune Teller @Jamroz Gary $E
212f C Fortune Teller @Jamroz Gary $F
216a R Hall of Mirrors @Vincent Christiaens $A
216b R Hall of Mirrors @Vincent Christiaens $B
217a R Haunted House @Dmitry Burmak $A
217b R Haunted House @Dmitry Burmak $B
218a U Information Booth @Gaboleps $A
218b U Information Booth @Gaboleps $B
218c U Information Booth @Gaboleps $C
218d U Information Booth @Gaboleps $D
219a C Kiddie Coaster @Marco Bucci $A
219b C Kiddie Coaster @Marco Bucci $B
219c C Kiddie Coaster @Marco Bucci $C
219d C Kiddie Coaster @Marco Bucci $D
219e C Kiddie Coaster @Marco Bucci $E
219f C Kiddie Coaster @Marco Bucci $F
222a R Merry-Go-Round @Carl Critchlow $A
222b R Merry-Go-Round @Carl Critchlow $B
223a C Pick-a-Beeble @Dave Greco $A
223b C Pick-a-Beeble @Dave Greco $B
223c C Pick-a-Beeble @Dave Greco $C
223d C Pick-a-Beeble @Dave Greco $D
223e C Pick-a-Beeble @Dave Greco $E
223f C Pick-a-Beeble @Dave Greco $F
225a U Roller Coaster @Gabor Szikszai $A
225b U Roller Coaster @Gabor Szikszai $B
225c U Roller Coaster @Gabor Szikszai $C
225d U Roller Coaster @Gabor Szikszai $D
227a C Spinny Ride @Aaron J. Riley $A
227b C Spinny Ride @Aaron J. Riley $B
227c C Spinny Ride @Aaron J. Riley $C
227d C Spinny Ride @Aaron J. Riley $D
227e C Spinny Ride @Aaron J. Riley $E
227f C Spinny Ride @Aaron J. Riley $F
229a R Storybook Ride @Dmitry Burmak $A
229b R Storybook Ride @Dmitry Burmak $B
231a R Swinging Ship @Mike Burns $A
231b R Swinging Ship @Mike Burns $B
232a U Trash Bin @Greg Bobrowski $A
232b U Trash Bin @Greg Bobrowski $B
232c U Trash Bin @Greg Bobrowski $C
232d U Trash Bin @Greg Bobrowski $D
234a R Tunnel of Love @Vladimir Krisetskiy $A
234b R Tunnel of Love @Vladimir Krisetskiy $B
211a C Foam Weapons Kiosk @Matt Gaser ${"variant": "A"}
211b C Foam Weapons Kiosk @Matt Gaser ${"variant": "B"}
211c C Foam Weapons Kiosk @Matt Gaser ${"variant": "C"}
211d C Foam Weapons Kiosk @Matt Gaser ${"variant": "D"}
212a C Fortune Teller @Jamroz Gary ${"variant": "A"}
212b C Fortune Teller @Jamroz Gary ${"variant": "B"}
212c C Fortune Teller @Jamroz Gary ${"variant": "C"}
212d C Fortune Teller @Jamroz Gary ${"variant": "D"}
212e C Fortune Teller @Jamroz Gary ${"variant": "E"}
212f C Fortune Teller @Jamroz Gary ${"variant": "F"}
216a R Hall of Mirrors @Vincent Christiaens ${"variant": "A"}
216b R Hall of Mirrors @Vincent Christiaens ${"variant": "B"}
217a R Haunted House @Dmitry Burmak ${"variant": "A"}
217b R Haunted House @Dmitry Burmak ${"variant": "B"}
218a U Information Booth @Gaboleps ${"variant": "A"}
218b U Information Booth @Gaboleps ${"variant": "B"}
218c U Information Booth @Gaboleps ${"variant": "C"}
218d U Information Booth @Gaboleps ${"variant": "D"}
219a C Kiddie Coaster @Marco Bucci ${"variant": "A"}
219b C Kiddie Coaster @Marco Bucci ${"variant": "B"}
219c C Kiddie Coaster @Marco Bucci ${"variant": "C"}
219d C Kiddie Coaster @Marco Bucci ${"variant": "D"}
219e C Kiddie Coaster @Marco Bucci ${"variant": "E"}
219f C Kiddie Coaster @Marco Bucci ${"variant": "F"}
222a R Merry-Go-Round @Carl Critchlow ${"variant": "A"}
222b R Merry-Go-Round @Carl Critchlow ${"variant": "B"}
223a C Pick-a-Beeble @Dave Greco ${"variant": "A"}
223b C Pick-a-Beeble @Dave Greco ${"variant": "B"}
223c C Pick-a-Beeble @Dave Greco ${"variant": "C"}
223d C Pick-a-Beeble @Dave Greco ${"variant": "D"}
223e C Pick-a-Beeble @Dave Greco ${"variant": "E"}
223f C Pick-a-Beeble @Dave Greco ${"variant": "F"}
225a U Roller Coaster @Gabor Szikszai ${"variant": "A"}
225b U Roller Coaster @Gabor Szikszai ${"variant": "B"}
225c U Roller Coaster @Gabor Szikszai ${"variant": "C"}
225d U Roller Coaster @Gabor Szikszai ${"variant": "D"}
227a C Spinny Ride @Aaron J. Riley ${"variant": "A"}
227b C Spinny Ride @Aaron J. Riley ${"variant": "B"}
227c C Spinny Ride @Aaron J. Riley ${"variant": "C"}
227d C Spinny Ride @Aaron J. Riley ${"variant": "D"}
227e C Spinny Ride @Aaron J. Riley ${"variant": "E"}
227f C Spinny Ride @Aaron J. Riley ${"variant": "F"}
229a R Storybook Ride @Dmitry Burmak ${"variant": "A"}
229b R Storybook Ride @Dmitry Burmak ${"variant": "B"}
231a R Swinging Ship @Mike Burns ${"variant": "A"}
231b R Swinging Ship @Mike Burns ${"variant": "B"}
232a U Trash Bin @Greg Bobrowski ${"variant": "A"}
232b U Trash Bin @Greg Bobrowski ${"variant": "B"}
232c U Trash Bin @Greg Bobrowski ${"variant": "C"}
232d U Trash Bin @Greg Bobrowski ${"variant": "D"}
234a R Tunnel of Love @Vladimir Krisetskiy ${"variant": "A"}
234b R Tunnel of Love @Vladimir Krisetskiy ${"variant": "B"}
235 L Plains @Adam Paquette
236 L Island @Adam Paquette
237 L Swamp @Adam Paquette

View File

@@ -21,12 +21,12 @@ ScryfallCode=UST
9 U Half-Kitten, Half- @Andrea Radeck
10 C Humming- @Mark Behm
11 R Jackknight @Ben Wootten
12a U Knight of the Kitchen Sink @Mark A. Nelson $A
12b U Knight of the Kitchen Sink @Mark A. Nelson $B
12c U Knight of the Kitchen Sink @Mark A. Nelson $C
12d U Knight of the Kitchen Sink @Mark A. Nelson $D
12e U Knight of the Kitchen Sink @Mark A. Nelson $E
12f U Knight of the Kitchen Sink @Mark A. Nelson $F
12a U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "A"}
12b U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "B"}
12c U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "C"}
12d U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "D"}
12e U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "E"}
12f U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "F"}
13 U Knight of the Widget @Emrah Elmasli
14 U Midlife Upgrade @Hector Ortiz
15 R Oddly Uneven @Ben Wootten
@@ -66,12 +66,12 @@ ScryfallCode=UST
46 U Spy Eye @Ben Wootten
47 U Suspicious Nanny @Chris Seaman
48 C Time Out @Dave Allsop
49a R Very Cryptic Command @Wayne England $A
49b R Very Cryptic Command @Zoltan Boros $B
49c R Very Cryptic Command @Zoltan Boros $C
49d R Very Cryptic Command @Zoltan Boros $D
49e R Very Cryptic Command @Zoltan Boros $E
49f R Very Cryptic Command @Zoltan Boros $F
49a R Very Cryptic Command @Wayne England ${"variant": "A"}
49b R Very Cryptic Command @Zoltan Boros ${"variant": "B"}
49c R Very Cryptic Command @Zoltan Boros ${"variant": "C"}
49d R Very Cryptic Command @Zoltan Boros ${"variant": "D"}
49e R Very Cryptic Command @Zoltan Boros ${"variant": "E"}
49f R Very Cryptic Command @Zoltan Boros ${"variant": "F"}
50 C Wall of Fortune @Tom Babbey
51 C Big Boa Constrictor @Kari Christensen
52 C capital offense @Matt Dixon
@@ -92,12 +92,12 @@ ScryfallCode=UST
64 U Overt Operative @Bram Sels
65 U "Rumors of My Death..." @Alex Konstad
66 U Skull Saucer @Mike Burns
67a U Sly Spy @Michael Phillippi $A
67b U Sly Spy @Michael Phillippi $B
67c U Sly Spy @Michael Phillippi $C
67d U Sly Spy @Michael Phillippi $D
67e U Sly Spy @Michael Phillippi $E
67f U Sly Spy @Michael Phillippi $F
67a U Sly Spy @Michael Phillippi ${"variant": "A"}
67b U Sly Spy @Michael Phillippi ${"variant": "B"}
67c U Sly Spy @Michael Phillippi ${"variant": "C"}
67d U Sly Spy @Michael Phillippi ${"variant": "D"}
67e U Sly Spy @Michael Phillippi ${"variant": "E"}
67f U Sly Spy @Michael Phillippi ${"variant": "F"}
68 C Snickering Squirrel @Michael Phillippi
69 R Spike, Tournament Grinder @Zoltan Boros
70 U Squirrel-Powered Scheme @Even Amundsen
@@ -112,12 +112,12 @@ ScryfallCode=UST
79 C Common Iguana @Brynn Metheney
80 R The Countdown Is at One @Jesper Ejsing
81 C Feisty Stegosaurus @Kari Christensen
82a U Garbage Elemental @Hector Ortiz $A
82b U Garbage Elemental @Hector Ortiz $B
82c U Garbage Elemental @Hector Ortiz $C
82d U Garbage Elemental @Hector Ortiz $D
82e U Garbage Elemental @Hector Ortiz $E
82f U Garbage Elemental @Hector Ortiz $F
82a U Garbage Elemental @Hector Ortiz ${"variant": "A"}
82b U Garbage Elemental @Hector Ortiz ${"variant": "B"}
82c U Garbage Elemental @Hector Ortiz ${"variant": "C"}
82d U Garbage Elemental @Hector Ortiz ${"variant": "D"}
82e U Garbage Elemental @Hector Ortiz ${"variant": "E"}
82f U Garbage Elemental @Hector Ortiz ${"variant": "F"}
83 U Goblin Haberdasher @Jesper Ejsing
84 U Half-Orc, Half- @Kev Walker
85 C Hammer Helper @Dave Allsop
@@ -153,12 +153,12 @@ ScryfallCode=UST
110 C Ground Pounder @Warren Mahy
111 U Half-Squirrel, Half- @Andrea Radeck
112 R Hydradoodle @Mathias Kollros
113a R Ineffable Blessing @Milivoj Ćeran $A
113b R Ineffable Blessing @Milivoj Ćeran $B
113c R Ineffable Blessing @Milivoj Ćeran $C
113d R Ineffable Blessing @Milivoj Ćeran $D
113e R Ineffable Blessing @Milivoj Ćeran $E
113f R Ineffable Blessing @Milivoj Ćeran $F
113a R Ineffable Blessing @Milivoj Ćeran ${"variant": "A"}
113b R Ineffable Blessing @Milivoj Ćeran ${"variant": "B"}
113c R Ineffable Blessing @Milivoj Ćeran ${"variant": "C"}
113d R Ineffable Blessing @Milivoj Ćeran ${"variant": "D"}
113e R Ineffable Blessing @Milivoj Ćeran ${"variant": "E"}
113f R Ineffable Blessing @Milivoj Ćeran ${"variant": "F"}
114 C Joyride Rigger @Wayne Reynolds
115 U Monkey- @Andrea Radeck
116 C Mother Kangaroo @Andrea Radeck
@@ -195,12 +195,12 @@ ScryfallCode=UST
145c C Despondent Killbot @Alex Konstad
145d C Enraged Killbot @Alex Konstad
146 U Entirely Normal Armchair @Tom Babbey
147a R Everythingamajig @Chris Seaman $A
147b R Everythingamajig @Chris Seaman $B
147c R Everythingamajig @Chris Seaman $C
147d R Everythingamajig @Chris Seaman $D
147e R Everythingamajig @Chris Seaman $E
147f R Everythingamajig @Chris Seaman $F
147a R Everythingamajig @Chris Seaman ${"variant": "A"}
147b R Everythingamajig @Chris Seaman ${"variant": "B"}
147c R Everythingamajig @Chris Seaman ${"variant": "C"}
147d R Everythingamajig @Chris Seaman ${"variant": "D"}
147e R Everythingamajig @Chris Seaman ${"variant": "E"}
147f R Everythingamajig @Chris Seaman ${"variant": "F"}
148 C Gnome-Made Engine @Sean Murray
149 R Handy Dandy Clone Machine @Mike Burns
150 R Kindslaver @Zoltan Boros

View File

@@ -152,7 +152,7 @@ public class LimitedPlayer {
removedFromPool = true;
removedFromCardPool.add(bestPick);
if (choice.equals("Animus of Predation")) {
addLog(name() + " removed " + bestPick.getName() + " from the draft with " + choice + ".");
addLog(name() + " removed " + bestPick.getDisplayName() + " from the draft with " + choice + ".");
} else if (choice.equals("Cogwork Grinder")) {
addLog(name() + " removed a card face down from the draft with " + choice + ".");
}
@@ -169,14 +169,14 @@ public class LimitedPlayer {
// But just log that a reveal "happened"
addLog(this.name() + " revealed a card to " + fromPlayer.name() + " via Cogwork Spy.");
} else {
addLog(this.name() + " revealed " + bestPick.getName() + " to you with Cogwork Spy.");
addLog(this.name() + " revealed " + bestPick.getDisplayName() + " to you with Cogwork Spy.");
}
fromPlayer.playerFlags &= ~SpyNextCardDrafted;
}
if ((playerFlags & SearcherNoteNext) == SearcherNoteNext) {
addLog(name() + " revealed " + bestPick.getName() + " for Aether Searcher.");
addLog(name() + " revealed " + bestPick.getDisplayName() + " for Aether Searcher.");
playerFlags &= ~SearcherNoteNext;
List<String> note = noted.computeIfAbsent("Aether Searcher", k -> Lists.newArrayList());
note.add(String.valueOf(bestPick.getName()));
@@ -184,7 +184,7 @@ public class LimitedPlayer {
if ((playerFlags & SmugglerCaptainActive) == SmugglerCaptainActive) {
if (revealWithSmuggler(bestPick)) {
addLog(name() + " revealed " + bestPick.getName() + " for Smuggler Captain.");
addLog(name() + " revealed " + bestPick.getDisplayName() + " for Smuggler Captain.");
playerFlags &= ~SmugglerCaptainActive;
List<String> note = noted.computeIfAbsent("Smuggler Captain", k -> Lists.newArrayList());
note.add(String.valueOf(bestPick.getName()));
@@ -297,7 +297,7 @@ public class LimitedPlayer {
List<String> note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList());
note.add(String.valueOf(draftedThisRound));
addLog(name() + " revealed " + bestPick.getName() + " and noted " + draftedThisRound + " cards drafted this round.");
addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted " + draftedThisRound + " cards drafted this round.");
} else if (Iterables.contains(draftActions, "As you draft CARDNAME, the player to your right chooses a color, you choose another color, then the player to your left chooses a third color.")) {
List<String> chosenColors = new ArrayList<>();
@@ -320,7 +320,7 @@ public class LimitedPlayer {
List<String> note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList());
note.add(String.join(",", chosenColors));
addLog(name() + " revealed " + bestPick.getName() + " and noted " + String.join(",", chosenColors) + " chosen colors.");
addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted " + String.join(",", chosenColors) + " chosen colors.");
}
else {
if (Iterables.contains(draftActions, "You may look at the next card drafted from this booster pack.")) {
@@ -328,7 +328,7 @@ public class LimitedPlayer {
} else if (fromPlayer != null && Iterables.contains(draftActions, "Note the player who passed CARDNAME to you.")) {
List<String> note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList());
note.add(String.valueOf(fromPlayer.order));
addLog(name() + " revealed " + bestPick.getName() + " and noted " + fromPlayer.name() + " passed it.");
addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted " + fromPlayer.name() + " passed it.");
} else if (Iterables.contains(draftActions, "Reveal the next card you draft and note its name.")) {
playerFlags |= SearcherNoteNext;
} else if (Iterables.contains(draftActions, "The next time a player drafts a card from this booster pack, guess that card's name. Then that player reveals the drafted card.")) {
@@ -337,12 +337,12 @@ public class LimitedPlayer {
addSingleBoosterPack();
}
addLog(name() + " revealed " + bestPick.getName() + " as " + name() + " drafted it.");
addLog(name() + " revealed " + bestPick.getDisplayName() + " as " + name() + " drafted it.");
}
}
if (Iterables.contains(draftActions, "Draft CARDNAME face up.")) {
faceUp.add(bestPick);
addLog(name() + " drafted " + bestPick.getName() + " face up.");
addLog(name() + " drafted " + bestPick.getDisplayName() + " face up.");
if (!alreadyRevealed) {
showRevealedCard(bestPick);
}
@@ -564,7 +564,7 @@ public class LimitedPlayer {
List<String> note = noted.computeIfAbsent(found.getName(), k -> Lists.newArrayList());
revealed.add(bestPick);
note.add(bestPick.getName());
addLog(name() + " revealed " + bestPick.getName() + " and noted its name for Noble Banneret.");
addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted its name for Noble Banneret.");
addLog(name() + " has flipped Noble Banneret face down.");
alreadyRevealed = true;
@@ -613,7 +613,7 @@ public class LimitedPlayer {
List<String> note = noted.computeIfAbsent(found.getName(), k -> Lists.newArrayList());
revealed.add(bestPick);
note.addAll(bestPick.getRules().getType().getCreatureTypes());
addLog(name() + " revealed " + bestPick.getName() + " and noted - " + TextUtil.join(bestPick.getRules().getType().getCreatureTypes(), ",") + " for Paliano Vanguard.");
addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted - " + TextUtil.join(bestPick.getRules().getType().getCreatureTypes(), ",") + " for Paliano Vanguard.");
addLog(name() + " has flipped Paliano Vanguard face down.");
alreadyRevealed = true;
@@ -702,12 +702,12 @@ public class LimitedPlayer {
LimitedPlayer guesser = pack.getAwaitingGuess().getKey();
PaperCard guess = pack.getAwaitingGuess().getValue();
addLog(name() + " reveals " + drafted.getName() + " from " + guesser.name() + "'s guess of " + guess.getName() + " with Spire Phantasm.");
addLog(name() + " reveals " + drafted.getDisplayName() + " from " + guesser.name() + "'s guess of " + guess.getDisplayName() + " with Spire Phantasm.");
if (guess.equals(drafted)) {
addLog(guesser.name() + " correctly guessed " + guess.getName() + " with Spire Phantasm.");
addLog(guesser.name() + " correctly guessed " + guess.getDisplayName() + " with Spire Phantasm.");
guesser.getDraftNotes().computeIfAbsent("Spire Phantasm", k -> Lists.newArrayList()).add(guess.getName());
} else {
addLog(guesser.name() + " incorrectly guessed " + guess.getName() + " with Spire Phantasm.");
addLog(guesser.name() + " incorrectly guessed " + guess.getDisplayName() + " with Spire Phantasm.");
}
pack.resetAwaitingGuess();
@@ -774,7 +774,7 @@ public class LimitedPlayer {
continue;
}
addLog(player.name() + " offered " + offer.getName() + " to " + name() + " for " + exchangeCard.getName());
addLog(player.name() + " offered " + offer.getDisplayName() + " to " + name() + " for " + exchangeCard.getName());
offers.put(offer, player);
}
@@ -795,11 +795,11 @@ public class LimitedPlayer {
return SGuiChoose.oneOrNone("Choose a card to offer for trade: ", deckCards);
}
return SGuiChoose.oneOrNone("Choose a card to trade for " + offer.getName() + ": ", deckCards);
return SGuiChoose.oneOrNone("Choose a card to trade for " + offer.getDisplayName() + ": ", deckCards);
}
protected PaperCard chooseCardToExchange(PaperCard exchangeCard, Map<PaperCard, LimitedPlayer> offers) {
return SGuiChoose.oneOrNone("Choose a card to accept trade of " + exchangeCard + ": ", offers.keySet(), null, (card) -> card.getName() + " (" + offers.get(card).getName() + ")");
return SGuiChoose.oneOrNone("Choose a card to accept trade of " + exchangeCard + ": ", offers.keySet(), null, (card) -> card.getDisplayName() + " (" + offers.get(card).getName() + ")");
}
protected void exchangeAcceptedOffer(PaperCard exchangeCard, LimitedPlayer player, PaperCard offer) {

View File

@@ -68,8 +68,18 @@ public class ConquestCommander implements InventoryItem, IXmlWritable {
return card.getName();
}
@Override
public String getDisplayName() {
return card.getDisplayName();
}
@Override
public boolean hasFlavorName() {
return card.hasFlavorName();
}
public String getPlayerName() {
String name = card.getName();
String name = card.getDisplayName();
int idx = name.indexOf(',');
if (idx != -1) { //trim everything after the comma
name = name.substring(0, idx);

View File

@@ -291,16 +291,16 @@ public final class ConquestData {
commandersBeingExiled.add(commander); //cache commander to make it easier to remove later
}
if (commander.getDeck().getMain().contains(card)) {
commandersUsingCard.append("\n").append(CardTranslation.getTranslatedName(commander.getName()));
commandersUsingCard.append("\n").append(CardTranslation.getTranslatedName(commander.getDisplayName()));
}
}
// Android API StringBuilder isEmpty() is unavailable. https://developer.android.com/reference/java/lang/StringBuilder
if (commandersUsingCard.length() != 0) {
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblCommandersCardCannotBeExiledByCard", CardTranslation.getTranslatedName(card.getName()), commandersUsingCard), title, SOptionPane.INFORMATION_ICON);
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblCommandersCardCannotBeExiledByCard", CardTranslation.getTranslatedName(card.getDisplayName()), commandersUsingCard), title, SOptionPane.INFORMATION_ICON);
return false;
}
message.append("\n").append(CardTranslation.getTranslatedName(card.getName()));
message.append("\n").append(CardTranslation.getTranslatedName(card.getDisplayName()));
}
if (SOptionPane.showConfirmDialog(message.toString(), title, Localizer.getInstance().getMessage("lblOK"), Localizer.getInstance().getMessage("lblCancel"))) {

View File

@@ -143,7 +143,7 @@ public class QuestTournamentController {
final PaperCard card = GuiBase.getInterface().chooseCard(localizer.getMessage("lblSelectACard"), localizer.getMessage("lblSelectKeepCard"), prizes.selectRareCards);
prizes.addSelectedCard(card);
SOptionPane.showMessageDialog("'" + card.getName() + "' " + localizer.getMessage("lblAddToCollection"), localizer.getMessage("lblCardAdded"), FSkinProp.ICO_QUEST_STAKES);
SOptionPane.showMessageDialog("'" + card.getDisplayName() + "' " + localizer.getMessage("lblAddToCollection"), localizer.getMessage("lblCardAdded"), FSkinProp.ICO_QUEST_STAKES);
}
if (draft.getPlayerPlacement() == 1) {

View File

@@ -153,7 +153,7 @@ public class CardDetailUtil {
if (item instanceof PreconDeck) {
return ((PreconDeck) item).getDescription();
}
return item.getName();
return item.getDisplayName();
}
public static String formatCardName(final CardView card, final boolean canShow, final boolean forAltState) {
@@ -245,13 +245,13 @@ public class CardDetailUtil {
PaperCard origPaperCard = null;
Card origCard = null;
try {
if (!card.getName().isEmpty()) {
origPaperCard = FModel.getMagicDb().getCommonCards().getCard(card.getName());
if (!card.getOracleName().isEmpty()) {
origPaperCard = FModel.getMagicDb().getCommonCards().getCard(card.getOracleName());
} else {
// probably a morph or manifest, try to get its identity from the alternate state
String altName = card.getAlternateState().getName();
String altName = card.getAlternateState().getOracleName();
if (!altName.isEmpty()) {
origPaperCard = FModel.getMagicDb().getCommonCards().getCard(card.getAlternateState().getName());
origPaperCard = FModel.getMagicDb().getCommonCards().getCard(altName);
}
}
if (origPaperCard != null) {
@@ -259,7 +259,7 @@ public class CardDetailUtil {
}
origIdent = origCard != null ? getCurrentColors(origCard.isFaceDown() ? CardView.get(origCard).getState(false) : CardView.get(origCard).getCurrentState()) : "";
} catch(Exception ex) {
System.err.println("Unexpected behavior: card " + card.getName() + "[" + card.getId() + "] tripped an exception when trying to process current card colors.");
System.err.println("Unexpected behavior: card " + card.getOracleName() + "[" + card.getId() + "] tripped an exception when trying to process current card colors.");
}
isChanged = !curColors.equals(origIdent);
}
@@ -420,7 +420,7 @@ public class CardDetailUtil {
if (pl != null) {
Map<String, String> notes = pl.getDraftNotes();
if (notes != null) {
String note = notes.get(card.getName());
String note = notes.get(card.getOracleName());
if (note != null) {
area.append("\n");
area.append("Draft Notes: ").append(note);

View File

@@ -60,17 +60,7 @@ public class AdvancedSearch {
@Override
protected Set<String> getItemValues(PaperCard input) {
Set<String> names = new HashSet<>();
names.add(input.getName());
names.add(CardTranslation.getTranslatedName(input.getName()));
CardSplitType cardSplitType = input.getRules().getSplitType();
if (cardSplitType != CardSplitType.None && cardSplitType != CardSplitType.Split) {
if (input.getRules().getOtherPart() != null) {
names.add(input.getRules().getOtherPart().getName());
names.add(CardTranslation.getTranslatedName(input.getRules().getOtherPart().getName()));
}
}
return names;
return input.getAllSearchableNames();
}
}),
CARD_RULES_TEXT("lblRulesText", PaperCard.class, FilterOperator.STRINGS_OPS, new StringEvaluator<PaperCard>() {
@@ -310,7 +300,7 @@ public class AdvancedSearch {
INVITEM_NAME("lblName", InventoryItem.class, FilterOperator.STRING_OPS, new StringEvaluator<InventoryItem>() {
@Override
protected String getItemValue(InventoryItem input) {
return input.getName();
return input.getDisplayName();
}
}),
INVITEM_RULES_TEXT("lblRulesText", InventoryItem.class, FilterOperator.STRING_OPS, new StringEvaluator<InventoryItem>() {
@@ -573,7 +563,7 @@ public class AdvancedSearch {
DECK_NAME("lblName", DeckProxy.class, FilterOperator.STRING_OPS, new StringEvaluator<DeckProxy>() {
@Override
protected String getItemValue(DeckProxy input) {
return input.getName();
return input.getDisplayName();
}
}),
DECK_FOLDER("lblFolder", DeckProxy.class, FilterOperator.STRING_OPS, new StringEvaluator<DeckProxy>() {
@@ -675,7 +665,7 @@ public class AdvancedSearch {
COMMANDER_NAME("lblName", ConquestCommander.class, FilterOperator.STRING_OPS, new StringEvaluator<ConquestCommander>() {
@Override
protected String getItemValue(ConquestCommander input) {
return input.getName();
return input.getDisplayName();
}
}),
COMMANDER_ORIGIN("lblOrigin", ConquestCommander.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator<ConquestCommander, ConquestPlane>(ImmutableList.copyOf(FModel.getPlanes())) {
@@ -1391,7 +1381,7 @@ public class AdvancedSearch {
Integer amount = -1;
if (operator == FilterOperator.CONTAINS_X_COPIES_OF_CARD) { //prompt for quantity if needed
amount = SGuiChoose.getInteger(Localizer.getInstance().getMessage("lblHowManyCopiesOfN", CardTranslation.getTranslatedName(card.getName())), 0, 4);
amount = SGuiChoose.getInteger(Localizer.getInstance().getMessage("lblHowManyCopiesOfN", CardTranslation.getTranslatedName(card.getDisplayName())), 0, 4);
if (amount == null) {
return null;
}

View File

@@ -287,23 +287,6 @@ public abstract class AdvancedSearchParser {
}
break;
case "name":
switch(opUsed) {
case "!":
predicate = CardRulesPredicates.name(StringOp.EQUALS_IC, valueStr);
break;
case "!=":
predicate = CardRulesPredicates.name(StringOp.EQUALS_IC, valueStr).negate();
break;
case "=":
case ":":
predicate = CardRulesPredicates.name(StringOp.CONTAINS_IC, valueStr);
break;
}
break;
case "is":
if (opUsed.equals(":")) {
switch(valueStr) {
@@ -444,6 +427,25 @@ public abstract class AdvancedSearchParser {
}
break;
case "name":
switch(opUsed) {
case "!":
predicate = PaperCardPredicates.searchableName(StringOp.EQUALS_IC, valueStr);
break;
case "!=":
predicate = PaperCardPredicates.searchableName(StringOp.EQUALS_IC, valueStr).negate();
break;
case "=":
case ":":
predicate = PaperCardPredicates.searchableName(StringOp.CONTAINS_IC, valueStr);
break;
}
break;
case "is":
if (opUsed.equals(":")) {
switch(valueStr) {

View File

@@ -56,14 +56,14 @@ public enum ColumnDef {
if (from.getKey() instanceof PaperCard) {
String spire = ((PaperCard) from.getKey()).getMarkedColors() == null ? "" : ((PaperCard) from.getKey()).getMarkedColors().toString();
String sortableName = ((PaperCard)from.getKey()).getSortableName();
return sortableName == null ? TextUtil.toSortableName(from.getKey().getName() + spire) : sortableName + spire;
return sortableName == null ? TextUtil.toSortableName(from.getKey().getDisplayName() + spire) : sortableName + spire;
}
return TextUtil.toSortableName(from.getKey().getName());
return TextUtil.toSortableName(from.getKey().getDisplayName());
},
from -> {
if (from.getKey() instanceof PaperCard)
return from.getKey().toString();
return from.getKey().getName();
return CardTranslation.getTranslatedName(from.getKey().getDisplayName());
return from.getKey().getDisplayName();
}),
/**

View File

@@ -155,33 +155,28 @@ public class SFilterUtil {
}
}
Predicate<CardRules> textFilter;
if (advancedCardRulesPredicates.isEmpty()) {
if (BooleanExpression.isExpression(segment)) {
if (advancedCardRulesPredicates.isEmpty() && BooleanExpression.isExpression(segment)) {
BooleanExpression expression = new BooleanExpression(segment, inName, inType, inText, inCost);
try {
Predicate<CardRules> filter = expression.evaluate();
if (filter != null) {
textFilter = filter;
} else {
textFilter = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost);
if(advancedPaperCardPredicates.isEmpty())
return PaperCardPredicates.fromRules(filter);
return IterableUtil.and(advancedPaperCardPredicates).and(PaperCardPredicates.fromRules(filter));
}
}
catch (Exception e) {
e.printStackTrace();
textFilter = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost);
}
} else {
textFilter = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost);
}
} else {
Predicate<CardRules> advancedCardRulesPredicate = IterableUtil.and(advancedCardRulesPredicates);
Predicate<CardRules> regularPredicate = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost);
textFilter = advancedCardRulesPredicate.and(regularPredicate);
}
return PaperCardPredicates.fromRules(textFilter).and(IterableUtil.and(advancedPaperCardPredicates));
Predicate<PaperCard> cardFilter = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost);
if(!advancedPaperCardPredicates.isEmpty())
cardFilter = cardFilter.and(IterableUtil.and(advancedPaperCardPredicates));
if(!advancedCardRulesPredicates.isEmpty())
cardFilter = cardFilter.and(PaperCardPredicates.fromRules(IterableUtil.and(advancedCardRulesPredicates)));
return cardFilter;
}
private static List<String> getSplitText(String text) {
@@ -224,21 +219,28 @@ public class SFilterUtil {
return splitText;
}
private static Predicate<CardRules> buildRegularTextPredicate(List<String> tokens, boolean inName, boolean inType, boolean inText, boolean inCost) {
private static Predicate<PaperCard> buildRegularTextPredicate(List<String> tokens, boolean inName, boolean inType, boolean inText, boolean inCost) {
if (tokens.isEmpty()) {
return x -> true;
}
List<Predicate<CardRules>> terms = new ArrayList<>();
List<Predicate<PaperCard>> terms = new ArrayList<>();
for (String s : tokens) {
List<Predicate<CardRules>> subands = new ArrayList<>();
if (inName) { subands.add(CardRulesPredicates.name(StringOp.CONTAINS_IC, s)); }
if (inType) { subands.add(CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, s)); }
if (inText) { subands.add(CardRulesPredicates.rules(StringOp.CONTAINS_IC, s)); }
if (inCost) { subands.add(CardRulesPredicates.cost(StringOp.CONTAINS_IC, s)); }
terms.add(IterableUtil.or(subands));
Predicate<PaperCard> term;
if (inName && subands.isEmpty())
term = PaperCardPredicates.searchableName(StringOp.CONTAINS_IC, s);
else if (inName)
term = PaperCardPredicates.searchableName(StringOp.CONTAINS_IC, s).or(PaperCardPredicates.fromRules(IterableUtil.or(subands)));
else
term = PaperCardPredicates.fromRules(IterableUtil.or(subands));
terms.add(term);
}
return IterableUtil.and(terms);
}