Store cards by collectorNumber instead of artIndex; PaperCard flag support (#7240)

* Refactor - Unknown set code to constant

* Refactor - Support for multiple initial selections for getChoices

* Covert noSell and marked color identities into serializable flags

* Fix cards in deck not being converted to newer noSell format

* unused imports

* Fix NPE

* Cleanup card filter

* Remove 14-year-old check that shouldn't be possible anymore

* CRLF -> LF

---------

Co-authored-by: Jetz <Jetz722@gmail.com>
This commit is contained in:
Jetz72
2025-04-16 18:59:31 -04:00
committed by GitHub
parent c85214b9e3
commit 4714319204
63 changed files with 559 additions and 486 deletions

View File

@@ -158,7 +158,7 @@ public class ComputerUtilMana {
} }
// Mana abilities on the same card // Mana abilities on the same card
String shardMana = shard.toString().replaceAll("\\{", "").replaceAll("\\}", ""); String shardMana = shard.toShortString();
boolean payWithAb1 = ability1.getManaPart().mana(ability1).contains(shardMana); boolean payWithAb1 = ability1.getManaPart().mana(ability1).contains(shardMana);
boolean payWithAb2 = ability2.getManaPart().mana(ability2).contains(shardMana); boolean payWithAb2 = ability2.getManaPart().mana(ability2).contains(shardMana);

View File

@@ -42,7 +42,8 @@ import java.util.stream.Stream;
public final class CardDb implements ICardDatabase, IDeckGenPool { public final class CardDb implements ICardDatabase, IDeckGenPool {
public final static String foilSuffix = "+"; public final static String foilSuffix = "+";
public final static char NameSetSeparator = '|'; public final static char NameSetSeparator = '|';
public final static String colorIDPrefix = "#"; public final static String FlagPrefix = "#";
public static final String FlagSeparator = "\t";
private final String exlcudedCardName = "Concentrate"; private final String exlcudedCardName = "Concentrate";
private final String exlcudedCardSet = "DS0"; private final String exlcudedCardSet = "DS0";
@@ -93,19 +94,19 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
public int artIndex; public int artIndex;
public boolean isFoil; public boolean isFoil;
public String collectorNumber; public String collectorNumber;
public Set<String> colorID; public Map<String, String> flags;
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber) { private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber) {
this(name, edition, artIndex, isFoil, collectorNumber, null); this(name, edition, artIndex, isFoil, collectorNumber, null);
} }
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Set<String> colorID) { private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Map<String, String> flags) {
cardName = name; cardName = name;
this.edition = edition; this.edition = edition;
this.artIndex = artIndex; this.artIndex = artIndex;
this.isFoil = isFoil; this.isFoil = isFoil;
this.collectorNumber = collectorNumber; this.collectorNumber = collectorNumber;
this.colorID = colorID; this.flags = flags;
} }
public static boolean isFoilCardName(final String cardName){ public static boolean isFoilCardName(final String cardName){
@@ -120,7 +121,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
public static String compose(String cardName, String setCode) { public static String compose(String cardName, String setCode) {
setCode = setCode != null ? setCode : ""; if(setCode == null || StringUtils.isBlank(setCode) || setCode.equals(CardEdition.UNKNOWN_CODE))
setCode = "";
cardName = cardName != null ? cardName : ""; cardName = cardName != null ? cardName : "";
if (cardName.indexOf(NameSetSeparator) != -1) if (cardName.indexOf(NameSetSeparator) != -1)
// If cardName is another RequestString, just get card name and forget about the rest. // If cardName is another RequestString, just get card name and forget about the rest.
@@ -134,14 +136,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return requestInfo + NameSetSeparator + artIndex; return requestInfo + NameSetSeparator + artIndex;
} }
public static String compose(String cardName, String setCode, int artIndex, Set<String> colorID) {
String requestInfo = compose(cardName, setCode);
artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
String cid = colorID == null ? "" : NameSetSeparator +
colorID.toString().replace("[", colorIDPrefix).replace(", ", colorIDPrefix).replace("]", "");
return requestInfo + NameSetSeparator + artIndex + cid;
}
public static String compose(String cardName, String setCode, String collectorNumber) { public static String compose(String cardName, String setCode, String collectorNumber) {
String requestInfo = compose(cardName, setCode); String requestInfo = compose(cardName, setCode);
// CollectorNumber will be wrapped in square brackets // CollectorNumber will be wrapped in square brackets
@@ -149,6 +143,34 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return requestInfo + NameSetSeparator + collectorNumber; return requestInfo + NameSetSeparator + collectorNumber;
} }
public static String compose(String cardName, String setCode, int artIndex, Map<String, String> flags) {
String requestInfo = compose(cardName, setCode);
artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
if(flags == null)
return requestInfo + NameSetSeparator + artIndex;
return requestInfo + NameSetSeparator + artIndex + getFlagSegment(flags);
}
public static String compose(String cardName, String setCode, String collectorNumber, Map<String, String> flags) {
String requestInfo = compose(cardName, setCode);
collectorNumber = preprocessCollectorNumber(collectorNumber);
if(flags == null || flags.isEmpty())
return requestInfo + NameSetSeparator + collectorNumber;
return requestInfo + NameSetSeparator + collectorNumber + getFlagSegment(flags);
}
public static String compose(PaperCard card) {
String name = compose(card.getName(), card.isFoil());
return compose(name, card.getEdition(), card.getCollectorNumber(), card.getMarkedFlags().toMap());
}
public static String compose(String cardName, String setCode, int artIndex, String collectorNumber) {
String requestInfo = compose(cardName, setCode, artIndex);
// CollectorNumber will be wrapped in square brackets
collectorNumber = preprocessCollectorNumber(collectorNumber);
return requestInfo + NameSetSeparator + collectorNumber;
}
private static String preprocessCollectorNumber(String collectorNumber) { private static String preprocessCollectorNumber(String collectorNumber) {
if (collectorNumber == null) if (collectorNumber == null)
return ""; return "";
@@ -160,19 +182,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return collectorNumber; return collectorNumber;
} }
public static String compose(String cardName, String setCode, int artIndex, String collectorNumber) { private static String getFlagSegment(Map<String, String> flags) {
String requestInfo = compose(cardName, setCode, artIndex); if(flags == null)
// CollectorNumber will be wrapped in square brackets return "";
collectorNumber = preprocessCollectorNumber(collectorNumber); String flagText = flags.entrySet().stream()
return requestInfo + NameSetSeparator + collectorNumber; .map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining(FlagSeparator));
return NameSetSeparator + FlagPrefix + "{" + flagText + "}";
} }
private static boolean isCollectorNumber(String s) { private static boolean isCollectorNumber(String s) {
return s.startsWith("[") && s.endsWith("]"); return s.startsWith("[") && s.endsWith("]");
} }
private static boolean isColorIDString(String s) { private static boolean isFlagSegment(String s) {
return s.startsWith(colorIDPrefix); return s.startsWith(FlagPrefix);
} }
private static boolean isArtIndex(String s) { private static boolean isArtIndex(String s) {
@@ -201,44 +225,36 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return null; return null;
String[] info = TextUtil.split(reqInfo, NameSetSeparator); String[] info = TextUtil.split(reqInfo, NameSetSeparator);
int setPos; int index = 1;
int artPos;
int cNrPos;
int clrPos;
if (info.length >= 4) { // name|set|artIndex|[collNr]
setPos = isSetCode(info[1]) ? 1 : -1;
artPos = isArtIndex(info[2]) ? 2 : -1;
cNrPos = isCollectorNumber(info[3]) ? 3 : -1;
int pos = cNrPos > 0 ? -1 : 3;
clrPos = pos > 0 ? isColorIDString(info[pos]) ? pos : -1 : -1;
} else if (info.length == 3) { // name|set|artIndex (or CollNr)
setPos = isSetCode(info[1]) ? 1 : -1;
artPos = isArtIndex(info[2]) ? 2 : -1;
cNrPos = isCollectorNumber(info[2]) ? 2 : -1;
int pos = cNrPos > 0 ? -1 : 2;
clrPos = pos > 0 ? isColorIDString(info[pos]) ? pos : -1 : -1;
} else if (info.length == 2) { // name|set (or artIndex, even if not possible via compose)
setPos = isSetCode(info[1]) ? 1 : -1;
artPos = isArtIndex(info[1]) ? 1 : -1;
cNrPos = -1;
clrPos = -1;
} else {
setPos = -1;
artPos = -1;
cNrPos = -1;
clrPos = -1;
}
String cardName = info[0]; String cardName = info[0];
boolean isFoil = false; boolean isFoil = false;
int artIndex = IPaperCard.NO_ART_INDEX;
String setCode = null;
String collectorNumber = IPaperCard.NO_COLLECTOR_NUMBER;
Map<String, String> flags = null;
if (isFoilCardName(cardName)) { if (isFoilCardName(cardName)) {
cardName = cardName.substring(0, cardName.length() - foilSuffix.length()); cardName = cardName.substring(0, cardName.length() - foilSuffix.length());
isFoil = true; isFoil = true;
} }
int artIndex = artPos > 0 ? Integer.parseInt(info[artPos]) : IPaperCard.NO_ART_INDEX; // default: no art index
String collectorNumber = cNrPos > 0 ? info[cNrPos].substring(1, info[cNrPos].length() - 1) : IPaperCard.NO_COLLECTOR_NUMBER; if(info.length > index && isSetCode(info[index])) {
String setCode = setPos > 0 ? info[setPos] : null; setCode = info[index];
Set<String> colorID = clrPos > 0 ? Arrays.stream(info[clrPos].substring(1).split(colorIDPrefix)).collect(Collectors.toSet()) : null; index++;
if (setCode != null && setCode.equals(CardEdition.UNKNOWN.getCode())) { // ??? }
if(info.length > index && isArtIndex(info[index])) {
artIndex = Integer.parseInt(info[index]);
index++;
}
if(info.length > index && isCollectorNumber(info[index])) {
collectorNumber = info[index].substring(1, info[index].length() - 1);
index++;
}
if (info.length > index && isFlagSegment(info[index])) {
String flagText = info[index].substring(FlagPrefix.length());
flags = parseRequestFlags(flagText);
}
if (CardEdition.UNKNOWN_CODE.equals(setCode)) { // ???
setCode = null; setCode = null;
} }
if (setCode == null) { if (setCode == null) {
@@ -253,7 +269,29 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
// finally, check whether any between artIndex and CollectorNumber has been set // finally, check whether any between artIndex and CollectorNumber has been set
if (collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER) && artIndex == IPaperCard.NO_ART_INDEX) if (collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER) && artIndex == IPaperCard.NO_ART_INDEX)
artIndex = IPaperCard.DEFAULT_ART_INDEX; artIndex = IPaperCard.DEFAULT_ART_INDEX;
return new CardRequest(cardName, setCode, artIndex, isFoil, collectorNumber, colorID); return new CardRequest(cardName, setCode, artIndex, isFoil, collectorNumber, flags);
}
private static Map<String, String> parseRequestFlags(String flagText) {
flagText = flagText.trim();
if(flagText.isEmpty())
return null;
if(!flagText.startsWith("{")) {
//Legacy form for marked colors. They'll be of the form "W#B#R"
Map<String, String> flags = new HashMap<>();
String normalizedColorString = ColorSet.fromNames(flagText.split(FlagPrefix)).toString();
flags.put("markedColors", String.join("", normalizedColorString));
return flags;
}
flagText = flagText.substring(1, flagText.length() - 1); //Trim the braces.
//List of flags, a series of "key=value" text broken up by tabs.
return Arrays.stream(flagText.split(FlagSeparator))
.map(f -> f.split("=", 2))
.filter(f -> f.length > 0)
.collect(Collectors.toMap(
entry -> entry[0],
entry -> entry.length > 1 ? entry[1] : "true" //If there's no '=' in the entry, treat it as a boolean flag.
));
} }
} }
@@ -406,7 +444,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown)); addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown));
} else if (enableUnknownCards && !this.filtered.contains(cr.getName())) { } else if (enableUnknownCards && !this.filtered.contains(cr.getName())) {
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. "); System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special)); addCard(new PaperCard(cr, CardEdition.UNKNOWN_CODE, CardRarity.Special));
} }
} else { } else {
System.err.println("The custom card " + cr.getName() + " was not assigned to any set. Adding it to custom USER set, and will try to load custom art from USER edition."); System.err.println("The custom card " + cr.getName() + " was not assigned to any set. Adding it to custom USER set, and will try to load custom art from USER edition.");
@@ -592,15 +630,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
@Override @Override
public PaperCard getCard(final String cardName, String setCode, int artIndex, String collectorNumber) { public PaperCard getCard(final String cardName, String setCode, int artIndex, Map<String, String> flags) {
String reqInfo = CardRequest.compose(cardName, setCode, artIndex, collectorNumber); String reqInfo = CardRequest.compose(cardName, setCode, artIndex, flags);
CardRequest request = CardRequest.fromString(reqInfo);
return tryGetCard(request);
}
@Override
public PaperCard getCard(final String cardName, String setCode, int artIndex, Set<String> colorID) {
String reqInfo = CardRequest.compose(cardName, setCode, artIndex, colorID);
CardRequest request = CardRequest.fromString(reqInfo); CardRequest request = CardRequest.fromString(reqInfo);
return tryGetCard(request); return tryGetCard(request);
} }
@@ -611,14 +642,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return null; return null;
// 1. First off, try using all possible search parameters, to narrow down the actual cards looked for. // 1. First off, try using all possible search parameters, to narrow down the actual cards looked for.
String reqEditionCode = request.edition; String reqEditionCode = request.edition;
if (reqEditionCode != null && reqEditionCode.length() > 0) { if (reqEditionCode != null && !reqEditionCode.isEmpty()) {
// This get is robust even against expansion aliases (e.g. TE and TMP both valid for Tempest) - // This get is robust even against expansion aliases (e.g. TE and TMP both valid for Tempest) -
// MOST of the extensions have two short codes, 141 out of 221 (so far) // MOST of the extensions have two short codes, 141 out of 221 (so far)
// ALSO: Set Code are always UpperCase // ALSO: Set Code are always UpperCase
CardEdition edition = editions.get(reqEditionCode.toUpperCase()); CardEdition edition = editions.get(reqEditionCode.toUpperCase());
return this.getCardFromSet(request.cardName, edition, request.artIndex, PaperCard cardFromSet = this.getCardFromSet(request.cardName, edition, request.artIndex, request.collectorNumber, request.isFoil);
request.collectorNumber, request.isFoil, request.colorID); if(cardFromSet != null && request.flags != null)
cardFromSet = cardFromSet.copyWithFlags(request.flags);
return cardFromSet;
} }
// 2. Card lookup in edition with specified filter didn't work. // 2. Card lookup in edition with specified filter didn't work.
@@ -661,11 +695,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override @Override
public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil) { public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil) {
return getCardFromSet(cardName, edition, artIndex, collectorNumber, isFoil, null);
}
@Override
public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil, Set<String> colorID) {
if (edition == null || cardName == null) // preview cards if (edition == null || cardName == null) // preview cards
return null; // No cards will be returned return null; // No cards will be returned
@@ -674,18 +703,18 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
cardName = cardNameRequest.cardName; cardName = cardNameRequest.cardName;
isFoil = isFoil || cardNameRequest.isFoil; isFoil = isFoil || cardNameRequest.isFoil;
List<PaperCard> candidates = getAllCards(cardName, c -> { String code1 = edition.getCode(), code2 = edition.getCode2();
boolean artIndexFilter = true;
boolean collectorNumberFilter = true; Predicate<PaperCard> filter = (c) -> {
boolean setFilter = c.getEdition().equalsIgnoreCase(edition.getCode()) || String ed = c.getEdition();
c.getEdition().equalsIgnoreCase(edition.getCode2()); return ed.equalsIgnoreCase(code1) || ed.equalsIgnoreCase(code2);
if (artIndex > 0) };
artIndexFilter = (c.getArtIndex() == artIndex); if (artIndex > 0)
if ((collectorNumber != null) && (collectorNumber.length() > 0) filter = filter.and((c) -> artIndex == c.getArtIndex());
&& !(collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER))) if (collectorNumber != null && !collectorNumber.isEmpty() && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER))
collectorNumberFilter = (c.getCollectorNumber().equals(collectorNumber)); filter = filter.and((c) -> collectorNumber.equals(c.getCollectorNumber()));
return setFilter && artIndexFilter && collectorNumberFilter;
}); List<PaperCard> candidates = getAllCards(cardName, filter);
if (candidates.isEmpty()) if (candidates.isEmpty())
return null; return null;
@@ -699,7 +728,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
while (!candidate.hasImage() && candidatesIterator.hasNext()) while (!candidate.hasImage() && candidatesIterator.hasNext())
candidate = candidatesIterator.next(); candidate = candidatesIterator.next();
candidate = candidate.hasImage() ? candidate : firstCandidate; candidate = candidate.hasImage() ? candidate : firstCandidate;
return isFoil ? candidate.getFoiled().getColorIDVersion(colorID) : candidate.getColorIDVersion(colorID); return isFoil ? candidate.getFoiled() : candidate;
} }
/* /*
@@ -742,11 +771,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, filter); return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, filter);
} }
@Override
public PaperCard getCardFromEditions(final String cardInfo, final CardArtPreference artPreference, int artIndex, Set<String> colorID) {
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, null, false, null, colorID);
}
/* /*
* =============================================== * ===============================================
* 4. SPECIALISED CARD LOOKUP BASED ON * 4. SPECIALISED CARD LOOKUP BASED ON
@@ -820,12 +844,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex, private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex,
Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter){ Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter) {
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, releaseDate, releasedBeforeFlag, filter, null);
}
private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex,
Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter, Set<String> colorID){
if (cardInfo == null) if (cardInfo == null)
return null; return null;
final CardRequest cr = CardRequest.fromString(cardInfo); final CardRequest cr = CardRequest.fromString(cardInfo);
@@ -865,7 +884,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
for (PaperCard card : cards) { for (PaperCard card : cards) {
String setCode = card.getEdition(); String setCode = card.getEdition();
CardEdition ed; CardEdition ed;
if (setCode.equals(CardEdition.UNKNOWN.getCode())) if (setCode.equals(CardEdition.UNKNOWN_CODE))
ed = CardEdition.UNKNOWN; ed = CardEdition.UNKNOWN;
else else
ed = editions.get(card.getEdition()); ed = editions.get(card.getEdition());
@@ -906,7 +925,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
candidate = candidate.hasImage() ? candidate : firstCandidate; candidate = candidate.hasImage() ? candidate : firstCandidate;
//If any, we're sure that at least one candidate is always returned despite it having any image //If any, we're sure that at least one candidate is always returned despite it having any image
return cr.isFoil ? candidate.getFoiled().getColorIDVersion(colorID) : candidate.getColorIDVersion(colorID); return cr.isFoil ? candidate.getFoiled() : candidate;
} }
@Override @Override
@@ -1126,29 +1145,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
.anyMatch(rarity::equals); .anyMatch(rarity::equals);
} }
public StringBuilder appendCardToStringBuilder(PaperCard card, StringBuilder sb) {
final boolean hasBadSetInfo = card.getEdition().equals(CardEdition.UNKNOWN.getCode()) || StringUtils.isBlank(card.getEdition());
sb.append(card.getName());
if (card.isFoil()) {
sb.append(CardDb.foilSuffix);
}
if (!hasBadSetInfo) {
int artCount = getArtCount(card.getName(), card.getEdition(), card.getFunctionalVariant());
sb.append(CardDb.NameSetSeparator).append(card.getEdition());
if (artCount >= IPaperCard.DEFAULT_ART_INDEX) {
sb.append(CardDb.NameSetSeparator).append(card.getArtIndex()); // indexes start at 1 to match image file name conventions
}
if (card.getColorID() != null) {
sb.append(CardDb.NameSetSeparator);
for (String color : card.getColorID())
sb.append(CardDb.colorIDPrefix).append(color);
}
}
return sb;
}
public PaperCard createUnsupportedCard(String cardRequest) { public PaperCard createUnsupportedCard(String cardRequest) {
CardRequest request = CardRequest.fromString(cardRequest); CardRequest request = CardRequest.fromString(cardRequest);
CardEdition cardEdition = CardEdition.UNKNOWN; CardEdition cardEdition = CardEdition.UNKNOWN;
@@ -1250,7 +1246,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
} }
if (paperCards.isEmpty()) { if (paperCards.isEmpty()) {
paperCards.add(new PaperCard(rules, CardEdition.UNKNOWN.getCode(), CardRarity.Special)); paperCards.add(new PaperCard(rules, CardEdition.UNKNOWN_CODE, CardRarity.Special));
} }
// 2. add them to db // 2. add them to db
for (PaperCard paperCard : paperCards) { for (PaperCard paperCard : paperCards) {

View File

@@ -262,7 +262,11 @@ public final class CardEdition implements Comparable<CardEdition> {
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
public static final CardEdition UNKNOWN = new CardEdition("1990-01-01", "???", "??", Type.UNKNOWN, "Undefined", FoilType.NOT_SUPPORTED, new CardInSet[]{}); /**
* Equivalent to the set code of CardEdition.UNKNOWN
*/
public static final String UNKNOWN_CODE = "???";
public static final CardEdition UNKNOWN = new CardEdition("1990-01-01", UNKNOWN_CODE, "??", Type.UNKNOWN, "Undefined", FoilType.NOT_SUPPORTED, new CardInSet[]{});
private Date date; private Date date;
private String code; private String code;
private String code2; private String code2;

View File

@@ -396,6 +396,7 @@ public final class CardRules implements ICardCharacteristics {
} }
public int getSetColorID() { public int getSetColorID() {
//Could someday generalize this to support other kinds of markings.
return setColorID; return setColorID;
} }

View File

@@ -25,6 +25,8 @@ import forge.util.BinaryUtil;
import java.io.Serializable; import java.io.Serializable;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* <p>CardColor class.</p> * <p>CardColor class.</p>
@@ -291,14 +293,8 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
*/ */
@Override @Override
public String toString() { public String toString() {
if (this.orderWeight == -1) { final ManaCostShard[] orderedShards = getOrderedShards();
return "n/a"; return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
}
final String toReturn = MagicColor.toLongString(myColor);
if (toReturn.equals(MagicColor.Constant.COLORLESS) && myColor != 0) {
return "multi";
}
return toReturn;
} }
/** /**
@@ -376,6 +372,10 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
} }
} }
public Stream<MagicColor.Color> stream() {
return this.toEnumSet().stream();
}
//Get array of mana cost shards for color set in the proper order //Get array of mana cost shards for color set in the proper order
public ManaCostShard[] getOrderedShards() { public ManaCostShard[] getOrderedShards() {
return shardOrderLookup[myColor]; return shardOrderLookup[myColor];

View File

@@ -5,43 +5,42 @@ import forge.item.PaperCard;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.Set;
/**
* Magic Cards Database.
* --------------------
* This interface defines the general API for Database Access and Cards' Lookup.
* <p>
* Methods for single Card's lookup currently support three alternative strategies:
* 1. [getCard]: Card search based on a single card's attributes
* (i.e. name, edition, art, collectorNumber)
* <p>
* 2. [getCardFromSet]: Card Lookup from a single Expansion set.
* Particularly useful in Deck Editors when a specific Set is specified.
* <p>
* 3. [getCardFromEditions]: Card search considering a predefined `SetPreference` policy and/or a specified Date
* when no expansion is specified for a card.
* This method is particularly useful for Re-prints whenever no specific
* Expansion is specified (e.g. in Deck Import) and a decision should be made
* on which card to pick. This methods allows to adopt a SetPreference selection
* policy to make this decision.
* <p>
* The API also includes methods to fetch Collection of Card (i.e. PaperCard instances):
* - all cards (no filter)
* - all unique cards (by name)
* - all prints of a single card
* - all cards from a single Expansion Set
* - all cards compliant with a filter condition (i.e. Predicate)
* <p>
* Finally, various utility methods are supported:
* - Get the foil version of a Card (if Any)
* - Get the Order Number of a Card in an Expansion Set
* - Get the number of Print/Arts for a card in a Set (useful for those exp. having multiple arts)
* */
public interface ICardDatabase extends Iterable<PaperCard> { public interface ICardDatabase extends Iterable<PaperCard> {
/**
* Magic Cards Database.
* --------------------
* This interface defines the general API for Database Access and Cards' Lookup.
*
* Methods for single Card's lookup currently support three alternative strategies:
* 1. [getCard]: Card search based on a single card's attributes
* (i.e. name, edition, art, collectorNumber)
*
* 2. [getCardFromSet]: Card Lookup from a single Expansion set.
* Particularly useful in Deck Editors when a specific Set is specified.
*
* 3. [getCardFromEditions]: Card search considering a predefined `SetPreference` policy and/or a specified Date
* when no expansion is specified for a card.
* This method is particularly useful for Re-prints whenever no specific
* Expansion is specified (e.g. in Deck Import) and a decision should be made
* on which card to pick. This methods allows to adopt a SetPreference selection
* policy to make this decision.
*
* The API also includes methods to fetch Collection of Card (i.e. PaperCard instances):
* - all cards (no filter)
* - all unique cards (by name)
* - all prints of a single card
* - all cards from a single Expansion Set
* - all cards compliant with a filter condition (i.e. Predicate)
*
* Finally, various utility methods are supported:
* - Get the foil version of a Card (if Any)
* - Get the Order Number of a Card in an Expansion Set
* - Get the number of Print/Arts for a card in a Set (useful for those exp. having multiple arts)
* */
/* SINGLE CARD RETRIEVAL METHODS /* SINGLE CARD RETRIEVAL METHODS
* ============================= */ * ============================= */
// 1. Card Lookup by attributes // 1. Card Lookup by attributes
@@ -50,22 +49,19 @@ public interface ICardDatabase extends Iterable<PaperCard> {
PaperCard getCard(String cardName, String edition, int artIndex); PaperCard getCard(String cardName, String edition, int artIndex);
// [NEW Methods] Including the card CollectorNumber as criterion for DB lookup // [NEW Methods] Including the card CollectorNumber as criterion for DB lookup
PaperCard getCard(String cardName, String edition, String collectorNumber); PaperCard getCard(String cardName, String edition, String collectorNumber);
PaperCard getCard(String cardName, String edition, int artIndex, String collectorNumber); PaperCard getCard(String cardName, String edition, int artIndex, Map<String, String> flags);
PaperCard getCard(String cardName, String edition, int artIndex, Set<String> colorID);
// 2. Card Lookup from a single Expansion Set // 2. Card Lookup from a single Expansion Set
PaperCard getCardFromSet(String cardName, CardEdition edition, boolean isFoil); // NOT yet used, included for API symmetry PaperCard getCardFromSet(String cardName, CardEdition edition, boolean isFoil); // NOT yet used, included for API symmetry
PaperCard getCardFromSet(String cardName, CardEdition edition, String collectorNumber, boolean isFoil); PaperCard getCardFromSet(String cardName, CardEdition edition, String collectorNumber, boolean isFoil);
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, boolean isFoil); PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, boolean isFoil);
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil); PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil);
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil, Set<String> colorID);
// 3. Card lookup based on CardArtPreference Selection Policy // 3. Card lookup based on CardArtPreference Selection Policy
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference);
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, Predicate<PaperCard> filter); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, Predicate<PaperCard> filter);
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex);
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Predicate<PaperCard> filter); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Predicate<PaperCard> filter);
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Set<String> colorID);
// 4. Specialised Card Lookup on CardArtPreference Selection and Release Date // 4. Specialised Card Lookup on CardArtPreference Selection and Release Date
PaperCard getCardFromEditionsReleasedBefore(String cardName, CardArtPreference artPreference, Date releaseDate); PaperCard getCardFromEditionsReleasedBefore(String cardName, CardArtPreference artPreference, Date releaseDate);

View File

@@ -1,6 +1,7 @@
package forge.card; package forge.card;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import forge.deck.DeckRecognizer;
/** /**
* Holds byte values for each color magic has. * Holds byte values for each color magic has.
@@ -187,6 +188,12 @@ public final class MagicColor {
public String getName() { public String getName() {
return name; return name;
} }
public String getLocalizedName() {
//Should probably move some of this logic back here, or at least to a more general location.
return DeckRecognizer.getLocalisedMagicColorName(getName());
}
public byte getColormask() { public byte getColormask() {
return colormask; return colormask;
} }

View File

@@ -94,6 +94,7 @@ public enum ManaCostShard {
/** The cmpc. */ /** The cmpc. */
private final float cmpc; private final float cmpc;
private final String stringValue; private final String stringValue;
private final String shortStringValue;
/** The image key. */ /** The image key. */
private final String imageKey; private final String imageKey;
@@ -125,6 +126,7 @@ public enum ManaCostShard {
this.cmc = this.getCMC(); this.cmc = this.getCMC();
this.cmpc = this.getCmpCost(); this.cmpc = this.getCmpCost();
this.stringValue = "{" + sValue + "}"; this.stringValue = "{" + sValue + "}";
this.shortStringValue = sValue;
this.imageKey = imgKey; this.imageKey = imgKey;
} }
@@ -232,16 +234,21 @@ public enum ManaCostShard {
return ManaCostShard.valueOf(atoms); return ManaCostShard.valueOf(atoms);
} }
/* /**
* (non-Javadoc) * @return the string representation of this shard - e.g. "{W}" "{2/U}" "{G/P}"
*
* @see java.lang.Object#toString()
*/ */
@Override @Override
public final String toString() { public final String toString() {
return this.stringValue; return this.stringValue;
} }
/**
* @return The string representation of this shard without brackets - e.g. "W" "2/U" "G/P"
*/
public final String toShortString() {
return this.shortStringValue;
}
/** /**
* Gets the cmc. * Gets the cmc.
* *

View File

@@ -52,7 +52,7 @@ public class CardPool extends ItemPool<PaperCard> {
public void add(final String cardRequest, final int amount) { public void add(final String cardRequest, final int amount) {
CardDb.CardRequest request = CardDb.CardRequest.fromString(cardRequest); CardDb.CardRequest request = CardDb.CardRequest.fromString(cardRequest);
this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount, false, request.colorID); this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount, false, request.flags);
} }
public void add(final String cardName, final String setCode) { public void add(final String cardName, final String setCode) {
@@ -71,7 +71,7 @@ public class CardPool extends ItemPool<PaperCard> {
public void add(String cardName, String setCode, int artIndex, final int amount) { public void add(String cardName, String setCode, int artIndex, final int amount) {
this.add(cardName, setCode, artIndex, amount, false, null); this.add(cardName, setCode, artIndex, amount, false, null);
} }
public void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Set<String> colorID) { public void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Map<String, String> flags) {
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases(); Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
PaperCard paperCard = null; PaperCard paperCard = null;
String selectedDbName = ""; String selectedDbName = "";
@@ -81,7 +81,7 @@ public class CardPool extends ItemPool<PaperCard> {
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){ for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
String dbName = entry.getKey(); String dbName = entry.getKey();
CardDb db = entry.getValue(); CardDb db = entry.getValue();
paperCard = db.getCard(cardName, setCode, artIndex, colorID); paperCard = db.getCard(cardName, setCode, artIndex, flags);
if (paperCard != null) { if (paperCard != null) {
selectedDbName = dbName; selectedDbName = dbName;
break; break;
@@ -123,7 +123,7 @@ public class CardPool extends ItemPool<PaperCard> {
int cnt = artGroups[i - 1]; int cnt = artGroups[i - 1];
if (cnt <= 0) if (cnt <= 0)
continue; continue;
PaperCard randomCard = cardDb.getCard(cardName, setCode, i, colorID); PaperCard randomCard = cardDb.getCard(cardName, setCode, i, flags);
this.add(randomCard, cnt); this.add(randomCard, cnt);
} }
} }
@@ -430,7 +430,6 @@ public class CardPool extends ItemPool<PaperCard> {
public String toCardList(String separator) { public String toCardList(String separator) {
List<Entry<PaperCard, Integer>> main2sort = Lists.newArrayList(this); List<Entry<PaperCard, Integer>> main2sort = Lists.newArrayList(this);
main2sort.sort(ItemPoolSorter.BY_NAME_THEN_SET); main2sort.sort(ItemPoolSorter.BY_NAME_THEN_SET);
final CardDb commonDb = StaticData.instance().getCommonCards();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
boolean isFirst = true; boolean isFirst = true;
@@ -441,10 +440,8 @@ public class CardPool extends ItemPool<PaperCard> {
else else
isFirst = false; isFirst = false;
CardDb db = !e.getKey().getRules().isVariant() ? commonDb : StaticData.instance().getVariantCards();
sb.append(e.getValue()).append(" "); sb.append(e.getValue()).append(" ");
db.appendCardToStringBuilder(e.getKey(), sb); sb.append(CardDb.CardRequest.compose(e.getKey()));
} }
return sb.toString(); return sb.toString();
} }
@@ -463,20 +460,4 @@ public class CardPool extends ItemPool<PaperCard> {
} }
return filteredPool; return filteredPool;
} }
/**
* Applies a predicate to this CardPool's cards.
* @param predicate the Predicate to apply to this CardPool
* @return a new CardPool made from this CardPool with only the cards that agree with the provided Predicate
*/
public CardPool getFilteredPoolWithCardsCount(Predicate<PaperCard> predicate) {
CardPool filteredPool = new CardPool();
for (Entry<PaperCard, Integer> entry : this.items.entrySet()) {
PaperCard pc = entry.getKey();
int count = entry.getValue();
if (predicate.test(pc))
filteredPool.add(pc, count);
}
return filteredPool;
}
} }

View File

@@ -247,7 +247,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
Map<String, List<String>> referenceDeckLoadingMap; Map<String, List<String>> referenceDeckLoadingMap;
if (deferredSections != null) { if (deferredSections != null) {
this.validateDeferredSections(); this.normalizeDeferredSections();
referenceDeckLoadingMap = new HashMap<>(this.deferredSections); referenceDeckLoadingMap = new HashMap<>(this.deferredSections);
} else } else
referenceDeckLoadingMap = new HashMap<>(loadedSections); referenceDeckLoadingMap = new HashMap<>(loadedSections);
@@ -267,7 +267,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
continue; continue;
final List<String> cardsInSection = s.getValue(); final List<String> cardsInSection = s.getValue();
ArrayList<String> cardNamesWithNoEdition = getAllCardNamesWithNoSpecifiedEdition(cardsInSection); ArrayList<String> cardNamesWithNoEdition = getAllCardNamesWithNoSpecifiedEdition(cardsInSection);
if (cardNamesWithNoEdition.size() > 0) { if (!cardNamesWithNoEdition.isEmpty()) {
includeCardsFromUnspecifiedSet = true; includeCardsFromUnspecifiedSet = true;
if (smartCardArtSelection) if (smartCardArtSelection)
cardsWithNoEdition.put(sec, cardNamesWithNoEdition); cardsWithNoEdition.put(sec, cardNamesWithNoEdition);
@@ -281,10 +281,10 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition); optimiseCardArtSelectionInDeckSections(cardsWithNoEdition);
} }
private void validateDeferredSections() { private void normalizeDeferredSections() {
/* /*
Construct a temporary (DeckSection, CardPool) Maps, to be sanitised and finalised Construct a temporary (DeckSection, CardPool) Maps, to be sanitised and finalised
before copying into `this.parts`. This sanitisation is applied because of the before copying into `this.parts`. This sanitization is applied because of the
validation schema introduced in DeckSections. validation schema introduced in DeckSections.
*/ */
Map<String, List<String>> validatedSections = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); Map<String, List<String>> validatedSections = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
@@ -296,61 +296,33 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
} }
final List<String> cardsInSection = s.getValue(); final List<String> cardsInSection = s.getValue();
List<Pair<String, Integer>> originalCardRequests = CardPool.processCardList(cardsInSection);
CardPool pool = CardPool.fromCardList(cardsInSection); CardPool pool = CardPool.fromCardList(cardsInSection);
if (pool.countDistinct() == 0) if (pool.countDistinct() == 0)
continue; // pool empty, no card has been found! continue; // pool empty, no card has been found!
// Filter pool by applying DeckSection Validation schema for Card Types (to avoid inconsistencies) List<String> validatedSection = validatedSections.computeIfAbsent(s.getKey(), (k) -> new ArrayList<>());
CardPool filteredPool = pool.getFilteredPoolWithCardsCount(deckSection::validate); for (Entry<PaperCard, Integer> entry : pool) {
// Add all the cards from ValidPool anyway! PaperCard card = entry.getKey();
List<String> whiteList = validatedSections.getOrDefault(s.getKey(), null); String normalizedRequest = getPoolRequest(entry);
if (whiteList == null) if(deckSection.validate(card))
whiteList = new ArrayList<>(); validatedSection.add(normalizedRequest);
for (Entry<PaperCard, Integer> entry : filteredPool) { else {
String poolRequest = getPoolRequest(entry, originalCardRequests); // Card was in the wrong section. Move it to the right section.
whiteList.add(poolRequest); DeckSection cardSection = DeckSection.matchingSection(card);
assert(cardSection.validate(card)); //Card doesn't fit in the matchingSection?
List<String> sectionCardList = validatedSections.computeIfAbsent(cardSection.name(), (k) -> new ArrayList<>());
sectionCardList.add(normalizedRequest);
}
} }
validatedSections.put(s.getKey(), whiteList);
if (filteredPool.countDistinct() != pool.countDistinct()) {
CardPool blackList = pool.getFilteredPoolWithCardsCount(input -> !(deckSection.validate(input)));
for (Entry<PaperCard, Integer> entry : blackList) {
DeckSection cardSection = DeckSection.matchingSection(entry.getKey());
String poolRequest = getPoolRequest(entry, originalCardRequests);
List<String> sectionCardList = validatedSections.getOrDefault(cardSection.name(), null);
if (sectionCardList == null)
sectionCardList = new ArrayList<>();
sectionCardList.add(poolRequest);
validatedSections.put(cardSection.name(), sectionCardList);
} // end for blacklist
} // end if
} // end main for on deferredSections } // end main for on deferredSections
// Overwrite deferredSections // Overwrite deferredSections
this.deferredSections = validatedSections; this.deferredSections = validatedSections;
} }
private String getPoolRequest(Entry<PaperCard, Integer> entry, List<Pair<String, Integer>> originalCardRequests) { private String getPoolRequest(Entry<PaperCard, Integer> entry) {
PaperCard card = entry.getKey();
int amount = entry.getValue(); int amount = entry.getValue();
String poolCardRequest = CardDb.CardRequest.compose( String poolCardRequest = CardDb.CardRequest.compose(entry.getKey());
card.isFoil() ? CardDb.CardRequest.compose(card.getName(), true) : card.getName(),
card.getEdition(), card.getArtIndex(), card.getColorID());
String originalRequestCandidate = null;
for (Pair<String, Integer> originalRequest : originalCardRequests){
String cardRequest = originalRequest.getLeft();
if (!StringUtils.startsWithIgnoreCase(poolCardRequest, cardRequest))
continue;
originalRequestCandidate = cardRequest;
int cardAmount = originalRequest.getRight();
if (amount == cardAmount)
return String.format("%d %s", cardAmount, cardRequest);
}
// This is just in case, it should never happen as we're
if (originalRequestCandidate != null)
return String.format("%d %s", amount, originalRequestCandidate);
return String.format("%d %s", amount, poolCardRequest); return String.format("%d %s", amount, poolCardRequest);
} }

View File

@@ -987,7 +987,7 @@ public class DeckRecognizer {
private static String getMagicColourLabel(MagicColor.Color magicColor) { private static String getMagicColourLabel(MagicColor.Color magicColor) {
if (magicColor == null) // Multicolour if (magicColor == null) // Multicolour
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour")); return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
return String.format("%s %s", getLocalisedMagicColorName(magicColor.getName()), magicColor.getSymbol()); return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol());
} }
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{ private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
@@ -1006,8 +1006,8 @@ public class DeckRecognizer {
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|| magicColor1 == MagicColor.Color.COLORLESS) || magicColor1 == MagicColor.Color.COLORLESS)
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2)); return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
String localisedName1 = getLocalisedMagicColorName(magicColor1.getName()); String localisedName1 = magicColor1.getLocalizedName();
String localisedName2 = getLocalisedMagicColorName(magicColor2.getName()); String localisedName2 = magicColor2.getLocalizedName();
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask()); String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol); return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
} }

View File

@@ -88,7 +88,7 @@ public enum DeckSection {
CardType t = card.getRules().getType(); CardType t = card.getRules().getType();
// NOTE: Same rules applies to both Deck and Side, despite "Conspiracy cards" are allowed // NOTE: Same rules applies to both Deck and Side, despite "Conspiracy cards" are allowed
// in the SideBoard (see Rule 313.2) // in the SideBoard (see Rule 313.2)
// Those will be matched later, in case (see `Deck::validateDeferredSections`) // Those will be matched later, in case (see `Deck::normalizeDeferredSections`)
return !t.isConspiracy() && !t.isDungeon() && !t.isPhenomenon() && !t.isPlane() && !t.isScheme() && !t.isVanguard(); return !t.isConspiracy() && !t.isDungeon() && !t.isPhenomenon() && !t.isPlane() && !t.isScheme() && !t.isVanguard();
}; };

View File

@@ -61,6 +61,8 @@ public class DeckSerializer {
} }
for(Entry<DeckSection, CardPool> s : d) { for(Entry<DeckSection, CardPool> s : d) {
if(s.getValue().isEmpty())
continue;
out.add(TextUtil.enclosedBracket(s.getKey().toString())); out.add(TextUtil.enclosedBracket(s.getKey().toString()));
out.add(s.getValue().toCardList(System.lineSeparator())); out.add(s.getValue().toCardList(System.lineSeparator()));
} }

View File

@@ -2,10 +2,10 @@ package forge.item;
import forge.card.CardRarity; import forge.card.CardRarity;
import forge.card.CardRules; import forge.card.CardRules;
import forge.card.ColorSet;
import forge.card.ICardFace; import forge.card.ICardFace;
import java.io.Serializable; import java.io.Serializable;
import java.util.Set;
public interface IPaperCard extends InventoryItem, Serializable { public interface IPaperCard extends InventoryItem, Serializable {
@@ -20,7 +20,7 @@ public interface IPaperCard extends InventoryItem, Serializable {
String getEdition(); String getEdition();
String getCollectorNumber(); String getCollectorNumber();
String getFunctionalVariant(); String getFunctionalVariant();
Set<String> getColorID(); ColorSet getMarkedColors();
int getArtIndex(); int getArtIndex();
boolean isFoil(); boolean isFoil();
boolean isToken(); boolean isToken();

View File

@@ -28,8 +28,10 @@ import org.apache.commons.lang3.StringUtils;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.util.Optional; import java.io.ObjectStreamException;
import java.util.Set; import java.io.Serial;
import java.util.*;
import java.util.stream.Collectors;
/** /**
* A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade). * A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade).
@@ -39,6 +41,7 @@ import java.util.Set;
* @author Forge * @author Forge
*/ */
public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet, IPaperCard { public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet, IPaperCard {
@Serial
private static final long serialVersionUID = 2942081982620691205L; private static final long serialVersionUID = 2942081982620691205L;
// Reference to rules // Reference to rules
@@ -55,16 +58,15 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
private String artist; private String artist;
private final int artIndex; private final int artIndex;
private final boolean foil; private final boolean foil;
private Boolean hasImage; private final PaperCardFlags flags;
private final boolean noSell; private final String sortableName;
private Set<String> colorID;
private String sortableName;
private final String functionalVariant; private final String functionalVariant;
// Calculated fields are below: // Calculated fields are below:
private transient CardRarity rarity; // rarity is given in ctor when set is assigned private transient CardRarity rarity; // rarity is given in ctor when set is assigned
// Reference to a new instance of Self, but foiled! // Reference to a new instance of Self, but foiled!
private transient PaperCard foiledVersion, noSellVersion; private transient PaperCard foiledVersion, noSellVersion, flaglessVersion;
private transient Boolean hasImage;
@Override @Override
public String getName() { public String getName() {
@@ -89,8 +91,8 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
} }
@Override @Override
public Set<String> getColorID() { public ColorSet getMarkedColors() {
return colorID; return this.flags.markedColors;
} }
@Override @Override
@@ -147,32 +149,32 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
return unFoiledVersion; return unFoiledVersion;
} }
public PaperCard getNoSellVersion() { public PaperCard getNoSellVersion() {
if (this.noSell) if (this.flags.noSellValue)
return this; return this;
if (this.noSellVersion == null) { if (this.noSellVersion == null)
this.noSellVersion = new PaperCard(this.rules, this.edition, this.rarity, this.noSellVersion = new PaperCard(this, this.flags.withNoSellValueFlag(true));
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, true);
}
return this.noSellVersion; return this.noSellVersion;
} }
public PaperCard getSellable() {
if (!this.noSell)
return this;
PaperCard sellable = new PaperCard(this.rules, this.edition, this.rarity, public PaperCard copyWithoutFlags() {
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, false); if(this.flaglessVersion == null) {
return sellable; if(this.flags == PaperCardFlags.IDENTITY_FLAGS)
this.flaglessVersion = this;
else
this.flaglessVersion = new PaperCard(this, null);
}
return flaglessVersion;
} }
public PaperCard getColorIDVersion(Set<String> colors) { public PaperCard copyWithFlags(Map<String, String> flags) {
if (colors == null && this.colorID == null) if(flags == null || flags.isEmpty())
return this.copyWithoutFlags();
return new PaperCard(this, new PaperCardFlags(flags));
}
public PaperCard copyWithMarkedColors(ColorSet colors) {
if(Objects.equals(colors, this.flags.markedColors))
return this; return this;
if (this.colorID != null && this.colorID.equals(colors)) return new PaperCard(this, this.flags.withMarkedColors(colors));
return this;
if (colors != null && colors.equals(this.colorID))
return this;
return new PaperCard(this.rules, this.edition, this.rarity,
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, this.noSell, colors);
} }
@Override @Override
public String getItemType() { public String getItemType() {
@@ -180,8 +182,12 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
return localizer.getMessage("lblCard"); return localizer.getMessage("lblCard");
} }
public boolean isNoSell() { public PaperCardFlags getMarkedFlags() {
return noSell; return this.flags;
}
public boolean hasNoSellValue() {
return this.flags.noSellValue;
} }
public boolean hasImage() { public boolean hasImage() {
return hasImage(false); return hasImage(false);
@@ -198,38 +204,41 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
IPaperCard.NO_COLLECTOR_NUMBER, IPaperCard.NO_ARTIST_NAME, IPaperCard.NO_FUNCTIONAL_VARIANT); IPaperCard.NO_COLLECTOR_NUMBER, IPaperCard.NO_ARTIST_NAME, IPaperCard.NO_FUNCTIONAL_VARIANT);
} }
public PaperCard(final PaperCard copyFrom, final PaperCardFlags flags) {
this(copyFrom.rules, copyFrom.edition, copyFrom.rarity, copyFrom.artIndex, copyFrom.foil, copyFrom.collectorNumber,
copyFrom.artist, copyFrom.functionalVariant, flags);
this.flaglessVersion = copyFrom.flaglessVersion;
}
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
final int artIndex0, final boolean foil0, final String collectorNumber0, final int artIndex0, final boolean foil0, final String collectorNumber0,
final String artist0, final String functionalVariant) { final String artist0, final String functionalVariant) {
this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, false); this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, null);
} }
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, protected PaperCard(final CardRules rules, final String edition, final CardRarity rarity,
final int artIndex0, final boolean foil0, final String collectorNumber0, final int artIndex, final boolean foil, final String collectorNumber,
final String artist0, final String functionalVariant, final boolean noSell0) { final String artist, final String functionalVariant, final PaperCardFlags flags) {
this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, noSell0, null); if (rules == null || edition == null || rarity == null) {
}
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
final int artIndex0, final boolean foil0, final String collectorNumber0,
final String artist0, final String functionalVariant, final boolean noSell0, final Set<String> colorID0) {
if (rules0 == null || edition0 == null || rarity0 == null) {
throw new IllegalArgumentException("Cannot create card without rules, edition or rarity"); throw new IllegalArgumentException("Cannot create card without rules, edition or rarity");
} }
rules = rules0; this.rules = rules;
name = rules0.getName(); name = rules.getName();
edition = edition0; this.edition = edition;
artIndex = Math.max(artIndex0, IPaperCard.DEFAULT_ART_INDEX); this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
foil = foil0; this.foil = foil;
rarity = rarity0; this.rarity = rarity;
artist = TextUtil.normalizeText(artist0); this.artist = TextUtil.normalizeText(artist);
collectorNumber = (collectorNumber0 != null) && (collectorNumber0.length() > 0) ? collectorNumber0 : IPaperCard.NO_COLLECTOR_NUMBER; this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER;
// If the user changes the language this will make cards sort by the old language until they restart the game. // 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 // This is a good tradeoff
sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules0.getName())); sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules.getName()));
this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT; this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT;
noSell = noSell0;
colorID = colorID0; if(flags == null || flags.equals(PaperCardFlags.IDENTITY_FLAGS))
this.flags = PaperCardFlags.IDENTITY_FLAGS;
else
this.flags = flags;
} }
public static PaperCard FAKE_CARD = new PaperCard(CardRules.getUnsupportedCardNamed("Fake Card"), "Fake Edition", CardRarity.Common); public static PaperCard FAKE_CARD = new PaperCard(CardRules.getUnsupportedCardNamed("Fake Card"), "Fake Edition", CardRarity.Common);
@@ -256,8 +265,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
} }
if (!getCollectorNumber().equals(other.getCollectorNumber())) if (!getCollectorNumber().equals(other.getCollectorNumber()))
return false; return false;
// colorID can be NULL if (!Objects.equals(flags, other.flags))
if (getColorID() != other.getColorID())
return false; return false;
return (other.foil == foil) && (other.artIndex == artIndex); return (other.foil == foil) && (other.artIndex == artIndex);
} }
@@ -269,13 +277,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
*/ */
@Override @Override
public int hashCode() { public int hashCode() {
final int code = (name.hashCode() * 11) + (edition.hashCode() * 59) + return Objects.hash(name, edition, collectorNumber, artIndex, foil, flags);
(artIndex * 2) + (getCollectorNumber().hashCode() * 383);
final int id = Optional.ofNullable(colorID).map(Set::hashCode).orElse(0);
if (foil) {
return code + id + 1;
}
return code + id;
} }
// FIXME: Check // FIXME: Check
@@ -339,6 +341,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
return Integer.compare(artIndex, o.getArtIndex()); return Integer.compare(artIndex, o.getArtIndex());
} }
@Serial
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
// default deserialization // default deserialization
ois.defaultReadObject(); ois.defaultReadObject();
@@ -354,6 +357,14 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
rarity = pc.getRarity(); rarity = pc.getRarity();
} }
@Serial
private Object readResolve() throws ObjectStreamException {
//If we deserialize an old PaperCard with no flags, reinitialize as a fresh copy to set default flags.
if(this.flags == null)
return new PaperCard(this, null);
return this;
}
@Override @Override
public String getImageKey(boolean altState) { public String getImageKey(boolean altState) {
String normalizedName = StringUtils.stripAccents(name); String normalizedName = StringUtils.stripAccents(name);
@@ -493,4 +504,85 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
public boolean isRebalanced() { public boolean isRebalanced() {
return StaticData.instance().isRebalanced(name); return StaticData.instance().isRebalanced(name);
} }
/**
* Contains properties of a card which distinguish it from an otherwise identical copy of the card with the same
* name, edition, and collector number. Examples include permanent markings on the card, and flags for Adventure
* mode.
*/
public static class PaperCardFlags {
/**
* Chosen colors, for cards like Cryptic Spires.
*/
public final ColorSet markedColors;
/**
* Removes the sell value of the card in Adventure mode.
*/
public final boolean noSellValue;
//TODO: Could probably move foil here.
static final PaperCardFlags IDENTITY_FLAGS = new PaperCardFlags(Map.of());
protected PaperCardFlags(Map<String, String> flags) {
if(flags.containsKey("markedColors"))
markedColors = ColorSet.fromNames(flags.get("markedColors").split(""));
else
markedColors = null;
noSellValue = flags.containsKey("noSellValue");
}
//Copy constructor. There are some better ways to do this, and they should be explored once we have more than 4
//or 5 fields here. Just need to ensure it's impossible to accidentally change a field while the PaperCardFlags
//object is in use.
private PaperCardFlags(PaperCardFlags copyFrom, ColorSet markedColors, Boolean noSellValue) {
if(markedColors == null)
markedColors = copyFrom.markedColors;
else if(markedColors.isColorless())
markedColors = null;
this.markedColors = markedColors;
this.noSellValue = noSellValue != null ? noSellValue : copyFrom.noSellValue;
}
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
if(markedColors == null)
markedColors = ColorSet.getNullColor();
return new PaperCardFlags(this, markedColors, null);
}
public PaperCardFlags withNoSellValueFlag(boolean noSellValue) {
return new PaperCardFlags(this, null, noSellValue);
}
private Map<String, String> asMap;
public Map<String, String> toMap() {
if(asMap != null)
return asMap;
Map<String, String> out = new HashMap<>();
if(markedColors != null && !markedColors.isColorless())
out.put("markedColors", markedColors.toString());
if(noSellValue)
out.put("noSellValue", "true");
asMap = out;
return out;
}
@Override
public String toString() {
return this.toMap().entrySet().stream()
.map((e) -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("\t"));
}
@Override
public boolean equals(Object o) {
if (!(o instanceof PaperCardFlags that)) return false;
return noSellValue == that.noSellValue && Objects.equals(markedColors, that.markedColors);
}
@Override
public int hashCode() {
return Objects.hash(markedColors, noSellValue);
}
}
} }

View File

@@ -7,7 +7,6 @@ import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
public class PaperToken implements InventoryItemFromSet, IPaperCard { public class PaperToken implements InventoryItemFromSet, IPaperCard {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -153,7 +152,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
} }
@Override @Override
public Set<String> getColorID() { public ColorSet getMarkedColors() {
return null; return null;
} }

View File

@@ -269,6 +269,13 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
// need not set out-of-sync: either remove did set, or nothing was removed // need not set out-of-sync: either remove did set, or nothing was removed
} }
public void removeIf(Predicate<T> test) {
for (final T item : items.keySet()) {
if (test.test(item))
remove(item);
}
}
public void clear() { public void clear() {
items.clear(); items.clear();
} }

View File

@@ -22,6 +22,7 @@ import forge.GameCommand;
import forge.StaticData; import forge.StaticData;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.CardType.Supertype; import forge.card.CardType.Supertype;
import forge.card.ColorSet;
import forge.card.GamePieceType; import forge.card.GamePieceType;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.deck.DeckSection; import forge.deck.DeckSection;
@@ -541,8 +542,8 @@ public class GameAction {
game.addLeftGraveyardThisTurn(lastKnownInfo); game.addLeftGraveyardThisTurn(lastKnownInfo);
} }
if (c.hasChosenColorSpire()) { if (c.hasMarkedColor()) {
copied.setChosenColorID(ImmutableSet.copyOf(c.getChosenColorID())); copied.setMarkedColors(c.getMarkedColors());
} }
copied.updateStateForView(); copied.updateStateForView();
@@ -2414,15 +2415,14 @@ public class GameAction {
for (Card c : spires) { for (Card c : spires) {
// TODO: only do this for the AI, for the player part, get the encoded color from the deck file and pass // TODO: only do this for the AI, for the player part, get the encoded color from the deck file and pass
// it to either player or the papercard object so it feels like rule based for the player side.. // it to either player or the papercard object so it feels like rule based for the player side..
if (!c.hasChosenColorSpire()) { if (!c.hasMarkedColor()) {
if (takesAction.isAI()) { if (takesAction.isAI()) {
List<String> colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS);
String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " + String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " +
Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2)); Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2));
SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction); SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction);
sa.putParam("AILogic", "MostProminentInComputerDeck"); sa.putParam("AILogic", "MostProminentInComputerDeck");
Set<String> chosenColors = new HashSet<>(takesAction.getController().chooseColors(prompt, sa, 2, 2, colorChoices)); ColorSet chosenColors = ColorSet.fromNames(takesAction.getController().chooseColors(prompt, sa, 2, 2, MagicColor.Constant.ONLY_COLORS));
c.setChosenColorID(chosenColors); c.setMarkedColors(chosenColors);
} }
} }
} }

View File

@@ -200,7 +200,7 @@ public class CloneEffect extends SpellAbilityEffect {
tgtCard.addRemembered(cardToCopy); tgtCard.addRemembered(cardToCopy);
} }
// spire // spire
tgtCard.setChosenColorID(cardToCopy.getChosenColorID()); tgtCard.setMarkedColors(cardToCopy.getMarkedColors());
game.fireEvent(new GameEventCardStatsChanged(tgtCard)); game.fireEvent(new GameEventCardStatsChanged(tgtCard));
} }

View File

@@ -314,7 +314,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
} }
} }
// spire // spire
copy.setChosenColorID(original.getChosenColorID()); copy.setMarkedColors(original.getMarkedColors());
copy.setTokenSpawningAbility(sa); copy.setTokenSpawningAbility(sa);
copy.setGamePieceType(GamePieceType.TOKEN); copy.setGamePieceType(GamePieceType.TOKEN);

View File

@@ -296,7 +296,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
private String chosenType2 = ""; private String chosenType2 = "";
private List<String> notedTypes = new ArrayList<>(); private List<String> notedTypes = new ArrayList<>();
private List<String> chosenColors; private List<String> chosenColors;
private Set<String> chosenColorID; private ColorSet markedColor;
private List<String> chosenName = new ArrayList<>(); private List<String> chosenName = new ArrayList<>();
private Integer chosenNumber; private Integer chosenNumber;
private Player chosenPlayer; private Player chosenPlayer;
@@ -409,7 +409,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
view.updateClassLevel(this); view.updateClassLevel(this);
view.updateDraftAction(this); view.updateDraftAction(this);
if (paperCard != null) if (paperCard != null)
setChosenColorID(paperCard.getColorID()); setMarkedColors(paperCard.getMarkedColors());
} }
public int getHiddenId() { public int getHiddenId() {
@@ -2231,18 +2231,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
public boolean hasChosenColor(String s) { public boolean hasChosenColor(String s) {
return chosenColors != null && chosenColors.contains(s); return chosenColors != null && chosenColors.contains(s);
} }
public final Set<String> getChosenColorID() { public final ColorSet getMarkedColors() {
if (chosenColorID == null) { if (markedColor == null) {
return Sets.newHashSet(); return ColorSet.getNullColor();
} }
return chosenColorID; return markedColor;
} }
public final void setChosenColorID(final Set<String> s) { public final void setMarkedColors(final ColorSet s) {
chosenColorID = s; markedColor = s;
view.updateChosenColorID(this); view.updateMarkedColors(this);
} }
public boolean hasChosenColorSpire() { public boolean hasMarkedColor() {
return chosenColorID != null && !chosenColorID.isEmpty(); return markedColor != null && !markedColor.isColorless();
} }
public final Card getChosenCard() { public final Card getChosenCard() {
return getChosenCards().getFirst(); return getChosenCards().getFirst();

View File

@@ -77,7 +77,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
private KeywordCollection cachedKeywords = new KeywordCollection(); private KeywordCollection cachedKeywords = new KeywordCollection();
private CardRarity rarity = CardRarity.Unknown; private CardRarity rarity = CardRarity.Unknown;
private String setCode = CardEdition.UNKNOWN.getCode(); private String setCode = CardEdition.UNKNOWN_CODE;
private final CardStateView view; private final CardStateView view;
private final Card card; private final Card card;

View File

@@ -433,11 +433,11 @@ public class CardView extends GameEntityView {
void updateChosenColors(Card c) { void updateChosenColors(Card c) {
set(TrackableProperty.ChosenColors, c.getChosenColors()); set(TrackableProperty.ChosenColors, c.getChosenColors());
} }
public Set<String> getChosenColorID() { public ColorSet getMarkedColors() {
return get(TrackableProperty.ChosenColorID); return get(TrackableProperty.MarkedColors);
} }
void updateChosenColorID(Card c) { void updateMarkedColors(Card c) {
set(TrackableProperty.ChosenColorID, c.getChosenColorID()); set(TrackableProperty.MarkedColors, c.getMarkedColors());
} }
public FCollectionView<CardView> getMergedCardsCollection() { public FCollectionView<CardView> getMergedCardsCollection() {
return get(TrackableProperty.MergedCardsCollection); return get(TrackableProperty.MergedCardsCollection);

View File

@@ -703,10 +703,10 @@ public class AbilityManaPart implements java.io.Serializable {
return ""; return "";
} }
Card card = sa.getHostCard(); Card card = sa.getHostCard();
if (card != null && card.hasChosenColorSpire()) { if (card != null && card.hasMarkedColor()) {
StringBuilder values = new StringBuilder(); StringBuilder values = new StringBuilder();
for (String s : card.getChosenColorID()) { for (byte c : card.getMarkedColors()) {
values.append(MagicColor.toShortString(MagicColor.fromName(s))).append(" "); values.append(MagicColor.toShortString(c)).append(" ");
} }
return values.toString(); return values.toString();
} }

View File

@@ -67,7 +67,6 @@ public enum TrackableProperty {
ChosenType2(TrackableTypes.StringType), ChosenType2(TrackableTypes.StringType),
NotedTypes(TrackableTypes.StringListType), NotedTypes(TrackableTypes.StringListType),
ChosenColors(TrackableTypes.StringListType), ChosenColors(TrackableTypes.StringListType),
ChosenColorID(TrackableTypes.StringSetType),
ChosenCards(TrackableTypes.CardViewCollectionType), ChosenCards(TrackableTypes.CardViewCollectionType),
ChosenNumber(TrackableTypes.StringType), ChosenNumber(TrackableTypes.StringType),
StoredRolls(TrackableTypes.StringListType), StoredRolls(TrackableTypes.StringListType),
@@ -102,6 +101,7 @@ public enum TrackableProperty {
NeedsTransformAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze), NeedsTransformAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze),
NeedsUntapAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze), NeedsUntapAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze),
NeedsTapAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze), NeedsTapAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze),
MarkedColors(TrackableTypes.ColorSetType),
ImprintedCards(TrackableTypes.CardViewCollectionType), ImprintedCards(TrackableTypes.CardViewCollectionType),
ExiledCards(TrackableTypes.CardViewCollectionType), ExiledCards(TrackableTypes.CardViewCollectionType),

View File

@@ -182,7 +182,7 @@ public class GuiDesktop implements IGuiBase {
} }
@Override @Override
public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) { public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final Collection<T> selected, final Function<T, String> display) {
/*if ((choices != null && !choices.isEmpty() && choices.iterator().next() instanceof GameObject) || selected instanceof GameObject) { /*if ((choices != null && !choices.isEmpty() && choices.iterator().next() instanceof GameObject) || selected instanceof GameObject) {
System.err.println("Warning: GameObject passed to GUI! Printing stack trace."); System.err.println("Warning: GameObject passed to GUI! Printing stack trace.");
Thread.dumpStack(); Thread.dumpStack();

View File

@@ -214,7 +214,7 @@ public class CardDetailPanel extends SkinnedPanel {
set = state.getSetCode(); set = state.getSetCode();
rarity = state.getRarity(); rarity = state.getRarity();
} else { } else {
set = CardEdition.UNKNOWN.getCode(); set = CardEdition.UNKNOWN_CODE;
rarity = CardRarity.Unknown; rarity = CardRarity.Unknown;
} }
setInfoLabel.setText(set); setInfoLabel.setText(set);

View File

@@ -134,10 +134,10 @@ public class GuiChoose {
return getChoices(message, min, max, choices, null, null); return getChoices(message, min, max, choices, null, null);
} }
public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) { public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final Collection<T> selected, final Function<T, String> display) {
return getChoices(message, min, max, choices, selected, display, null); return getChoices(message, min, max, choices, selected, display, null);
} }
public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display, final CMatchUI matchUI) { public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final Collection<T> selected, final Function<T, String> display, final CMatchUI matchUI) {
if (choices == null || choices.isEmpty()) { if (choices == null || choices.isEmpty()) {
if (min == 0) { if (min == 0) {
return new ArrayList<>(); return new ArrayList<>();

View File

@@ -152,16 +152,13 @@ public class ListChooser<T> {
/** @return boolean */ /** @return boolean */
public boolean show() { public boolean show() {
return show(list.get(0)); return show(null);
} }
/** /**
* Shows the dialog and returns after the dialog was closed. * Shows the dialog and returns after the dialog was closed.
*
* @param index0 index to select when shown
* @return a boolean.
*/ */
public boolean show(final T item) { public boolean show(final Collection<T> item) {
if (this.called) { if (this.called) {
throw new IllegalStateException("Already shown"); throw new IllegalStateException("Already shown");
} }
@@ -169,8 +166,12 @@ public class ListChooser<T> {
do { do {
//invoke later so selected item not set until dialog open //invoke later so selected item not set until dialog open
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
if (list.contains(item)) { if (item != null) {
lstChoices.setSelectedValue(item, true); int[] indices = item.stream()
.mapToInt(list::indexOf)
.filter(i -> i >= 0)
.toArray();
lstChoices.setSelectedIndices(indices);
} }
else { else {
lstChoices.setSelectedIndex(0); lstChoices.setSelectedIndex(0);

View File

@@ -20,16 +20,15 @@ package forge.screens.deckeditor.controllers;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.event.InputEvent; import java.awt.event.InputEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.deck.CardPool; import forge.deck.CardPool;
import forge.deck.Deck; import forge.deck.Deck;
@@ -582,9 +581,9 @@ public abstract class ACEditorBase<TItem extends InventoryItem, TModel extends D
int val; int val;
if ((val = existingCard.getRules().getSetColorID()) > 0) { if ((val = existingCard.getRules().getSetColorID()) > 0) {
GuiUtils.addMenuItem(menu, label, null, () -> { GuiUtils.addMenuItem(menu, label, null, () -> {
Set<String> colors = new HashSet<>(GuiChoose.getChoices(localizer.getMessage("lblChooseNColors", Lang.getNumeral(val)), val, val, MagicColor.Constant.ONLY_COLORS)); List<String> colors = GuiChoose.getChoices(localizer.getMessage("lblChooseNColors", Lang.getNumeral(val)), val, val, MagicColor.Constant.ONLY_COLORS);
// make an updated version // make an updated version
PaperCard updated = existingCard.getColorIDVersion(colors); PaperCard updated = existingCard.copyWithMarkedColors(ColorSet.fromNames(colors));
// remove *quantity* instances of existing card // remove *quantity* instances of existing card
CDeckEditorUI.SINGLETON_INSTANCE.removeSelectedCards(false, 1); CDeckEditorUI.SINGLETON_INSTANCE.removeSelectedCards(false, 1);
// add *quantity* into the deck and set them as selected // add *quantity* into the deck and set them as selected

View File

@@ -24,7 +24,6 @@ import java.awt.image.BufferedImage;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function; import java.util.function.Function;
@@ -1072,7 +1071,7 @@ public final class CMatchUI
} }
@Override @Override
public <T> List<T> getChoices(final String message, final int min, final int max, final List<T> choices, final T selected, final Function<T, String> display) { public <T> List<T> getChoices(final String message, final int min, final int max, final List<T> choices, final List<T> selected, final Function<T, String> display) {
/*if ((choices != null && !choices.isEmpty() && choices.iterator().next() instanceof GameObject) || selected instanceof GameObject) { /*if ((choices != null && !choices.isEmpty() && choices.iterator().next() instanceof GameObject) || selected instanceof GameObject) {
System.err.println("Warning: GameObject passed to GUI! Printing stack trace."); System.err.println("Warning: GameObject passed to GUI! Printing stack trace.");
Thread.dumpStack(); Thread.dumpStack();

View File

@@ -277,21 +277,6 @@ public class CardDbCardMockTestCase extends CardMockTestCase {
} }
} }
@Test
public void testGetCardByNameSetArtIndexAndCollectorNumber() {
// MultiArt Card
PaperCard card;
for (int i = 0; i < 4; i++) {
int artIndex = i + 1;
card = this.cardDb.getCard(cardNameHymnToTourach, editionHymnToTourach, artIndex,
collectorNumbersHymnToTourach[i]);
assertEquals(card.getName(), cardNameHymnToTourach);
assertEquals(card.getEdition(), editionHymnToTourach);
assertEquals(card.getCollectorNumber(), collectorNumbersHymnToTourach[i]);
assertEquals(card.getArtIndex(), artIndex);
}
}
@Test @Test
public void testNullIsReturnedWithWrongInfo() { public void testNullIsReturnedWithWrongInfo() {
String wrongEditionCode = "M11"; String wrongEditionCode = "M11";
@@ -307,11 +292,6 @@ public class CardDbCardMockTestCase extends CardMockTestCase {
// Wrong collector number // Wrong collector number
card = this.cardDb.getCard(cardNameShivanDragon, editionShivanDragon, "wrongCN"); card = this.cardDb.getCard(cardNameShivanDragon, editionShivanDragon, "wrongCN");
assertNull(card); assertNull(card);
// wrong artIndex or collector number in getCard Full Info
card = this.cardDb.getCard(cardNameShivanDragon, editionShivanDragon, 3, collNrShivanDragon);
assertNull(card);
card = this.cardDb.getCard(cardNameShivanDragon, editionShivanDragon, 1, "wrongCN");
assertNull(card);
} }
@Test @Test
@@ -2105,7 +2085,7 @@ public class CardDbCardMockTestCase extends CardMockTestCase {
public void testGetCardFromUnknownSet() { public void testGetCardFromUnknownSet() {
String unknownCardName = "Unknown Card Name"; String unknownCardName = "Unknown Card Name";
PaperCard unknownCard = new PaperCard(CardRules.getUnsupportedCardNamed(unknownCardName), PaperCard unknownCard = new PaperCard(CardRules.getUnsupportedCardNamed(unknownCardName),
CardEdition.UNKNOWN.getCode(), CardRarity.Unknown); CardEdition.UNKNOWN_CODE, CardRarity.Unknown);
this.cardDb.addCard(unknownCard); this.cardDb.addCard(unknownCard);
assertTrue(this.cardDb.getAllCards().contains(unknownCard)); assertTrue(this.cardDb.getAllCards().contains(unknownCard));
assertNotNull(this.cardDb.getAllCards(unknownCardName)); assertNotNull(this.cardDb.getAllCards(unknownCardName));
@@ -2114,7 +2094,7 @@ public class CardDbCardMockTestCase extends CardMockTestCase {
PaperCard retrievedPaperCard = this.cardDb.getCard(unknownCardName); PaperCard retrievedPaperCard = this.cardDb.getCard(unknownCardName);
assertNotNull(retrievedPaperCard); assertNotNull(retrievedPaperCard);
assertEquals(retrievedPaperCard.getName(), unknownCardName); assertEquals(retrievedPaperCard.getName(), unknownCardName);
assertEquals(retrievedPaperCard.getEdition(), CardEdition.UNKNOWN.getCode()); assertEquals(retrievedPaperCard.getEdition(), CardEdition.UNKNOWN_CODE);
} }
@Test @Test

View File

@@ -197,7 +197,7 @@ public class GuiMobile implements IGuiBase {
} }
@Override @Override
public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) { public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final Collection<T> selected, final Function<T, String> display) {
return new WaitCallback<List<T>>() { return new WaitCallback<List<T>>() {
@Override @Override
public void run() { public void run() {

View File

@@ -120,7 +120,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
statistic.clear(); statistic.clear();
newCards.clear(); newCards.clear();
autoSellCards.clear(); autoSellCards.clear();
noSellCards.clear();
AdventureEventController.clear(); AdventureEventController.clear();
AdventureQuestController.clear(); AdventureQuestController.clear();
} }
@@ -134,7 +133,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
private final ItemPool<PaperCard> newCards = new ItemPool<>(PaperCard.class); private final ItemPool<PaperCard> newCards = new ItemPool<>(PaperCard.class);
public final ItemPool<PaperCard> autoSellCards = new ItemPool<>(PaperCard.class); public final ItemPool<PaperCard> autoSellCards = new ItemPool<>(PaperCard.class);
public final ItemPool<PaperCard> noSellCards = new ItemPool<>(PaperCard.class);
public void create(String n, Deck startingDeck, boolean male, int race, int avatar, boolean isFantasy, boolean isUsingCustomDeck, DifficultyData difficultyData) { public void create(String n, Deck startingDeck, boolean male, int race, int avatar, boolean isFantasy, boolean isUsingCustomDeck, DifficultyData difficultyData) {
clear(); clear();
@@ -471,10 +469,43 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
} }
} }
if (data.containsKey("noSellCards")) { if (data.containsKey("noSellCards")) {
//Legacy list of unsellable cards. Now done via CardRequest flags. Convert the corresponding cards.
PaperCard[] items = (PaperCard[]) data.readObject("noSellCards"); PaperCard[] items = (PaperCard[]) data.readObject("noSellCards");
for (PaperCard item : items) { CardPool noSellPool = new CardPool();
if (item != null) noSellPool.addAllFlat(List.of(items));
noSellCards.add(item.getNoSellVersion()); for (Map.Entry<PaperCard, Integer> noSellEntry : noSellPool) {
PaperCard item = noSellEntry.getKey();
if (item == null)
continue;
int totalCopies = cards.count(item);
int noSellCopies = Math.min(noSellEntry.getValue(), totalCopies);
if (!cards.remove(item, noSellCopies)) {
System.err.printf("Failed to update noSellValue flag - %s%n", item);
continue;
}
int remainingSellableCopies = totalCopies - noSellCopies;
PaperCard noSellVersion = item.getNoSellVersion();
cards.add(noSellVersion, noSellCopies);
System.out.printf("Converted legacy noSellCards item - %s (%d / %d copies)%n", item, noSellCopies, totalCopies);
//Also go through their decks and update cards there.
for (Deck deck : decks) {
int inUse = 0;
for (Map.Entry<DeckSection, CardPool> section : deck) {
CardPool pool = section.getValue();
inUse += pool.count(item);
if(inUse > remainingSellableCopies) {
int toConvert = inUse - remainingSellableCopies;
pool.remove(item, toConvert);
pool.add(noSellVersion, toConvert);
System.out.printf("- Converted %d copies in deck - %s/%s%n", toConvert, deck.getName(), section.getKey());
}
}
}
} }
} }
if (data.containsKey("autoSellCards")) { if (data.containsKey("autoSellCards")) {
@@ -582,7 +613,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
data.storeObject("newCards", newCards.toFlatList().toArray(new PaperCard[0])); data.storeObject("newCards", newCards.toFlatList().toArray(new PaperCard[0]));
data.storeObject("autoSellCards", autoSellCards.toFlatList().toArray(new PaperCard[0])); data.storeObject("autoSellCards", autoSellCards.toFlatList().toArray(new PaperCard[0]));
data.storeObject("noSellCards", noSellCards.toFlatList().toArray(new PaperCard[0]));
return data; return data;
} }
@@ -636,11 +666,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
if (reward.isAutoSell()) { if (reward.isAutoSell()) {
autoSellCards.add(reward.getCard()); autoSellCards.add(reward.getCard());
refreshEditor(); refreshEditor();
} else if (reward.isNoSell()) {
if (reward.getCard() != null) {
noSellCards.add(reward.getCard().getNoSellVersion());
refreshEditor();
}
} }
break; break;
case Gold: case Gold:
@@ -908,8 +933,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
} }
public int cardSellPrice(PaperCard card) { public int cardSellPrice(PaperCard card) {
int valuable = cards.count(card) - noSellCards.count(card); if (card.hasNoSellValue()) {
if (valuable == 0) {
return 0; return 0;
} }
@@ -920,23 +944,13 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
} }
public int sellCard(PaperCard card, Integer result, boolean addGold) { public int sellCard(PaperCard card, Integer result, boolean addGold) {
// When selling cards, always try to sell cards worth something before selling cards that aren't worth anything if (result == null || result < 1)
if (result == null || result < 1) return 0; return 0;
float earned = 0; int amountToSell = Math.min(result, cards.count(card));
if(!cards.remove(card, amountToSell))
int valuableCount = cards.count(card) - noSellCards.count(card); return 0; //Failed to sell?
int noValueToSell = result - valuableCount; float earned = cardSellPrice(card) * amountToSell;
int amountValuableToSell = Math.min(result, valuableCount);
if (amountValuableToSell > 0) {
earned += cardSellPrice(card) * amountValuableToSell;
cards.remove(card, amountValuableToSell);
}
if (noValueToSell > 0) {
cards.remove(card, noValueToSell);
noSellCards.remove(card, noValueToSell);
}
if (addGold) { if (addGold) {
addGold((int) earned); addGold((int) earned);
@@ -1198,10 +1212,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
return autoSellCards; return autoSellCards;
} }
public ItemPool<PaperCard> getNoSellCards() {
return noSellCards;
}
public ItemPool<PaperCard> getSellableCards() { public ItemPool<PaperCard> getSellableCards() {
ItemPool<PaperCard> sellableCards = new ItemPool<>(PaperCard.class); ItemPool<PaperCard> sellableCards = new ItemPool<>(PaperCard.class);
sellableCards.addAllFlat(cards.toFlatList()); sellableCards.addAllFlat(cards.toFlatList());
@@ -1236,7 +1246,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
collectionCards.addAll(cards); collectionCards.addAll(cards);
if (!allCards) { if (!allCards) {
collectionCards.removeAll(autoSellCards); collectionCards.removeAll(autoSellCards);
collectionCards.removeAll(noSellCards);
} }
return collectionCards; return collectionCards;

View File

@@ -211,14 +211,13 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
}); });
} }
int noSellCount = Current.player().noSellCards.count(card);
int autoSellCount = Current.player().autoSellCards.count(card); int autoSellCount = Current.player().autoSellCards.count(card);
int sellableCount = Current.player().getSellableCards().count(card); int sellableCount = Current.player().getSellableCards().count(card);
if (noSellCount > 0) { if (card.hasNoSellValue()) {
FMenuItem unsellableCount = new FMenuItem(Forge.getLocalizer().getMessage("lblUnsellableCount", noSellCount), null, null); FMenuItem unsellableIndicator = new FMenuItem(Forge.getLocalizer().getMessage("lblUnsellable"), null, null);
unsellableCount.setEnabled(false); unsellableIndicator.setEnabled(false);
menu.addItem(unsellableCount); menu.addItem(unsellableIndicator);
} }
if (sellableCount > 0) { if (sellableCount > 0) {
@@ -248,12 +247,13 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
if (showCollectionCards) { if (showCollectionCards) {
collectionPool.addAllFlat(AdventurePlayer.current().getCollectionCards(false).toFlatList()); collectionPool.addAllFlat(AdventurePlayer.current().getCollectionCards(false).toFlatList());
} }
if (showNoSellCards) { else if(showNoSellCards) {
collectionPool.addAllFlat(AdventurePlayer.current().getNoSellCards().toFlatList()); collectionPool.addAll(AdventurePlayer.current().getCollectionCards(false).getFilteredPool(PaperCard::hasNoSellValue));
collectionPool.removeAllFlat(cardsInUse.toFlatList()); }
} else {
cardsInUse.removeAllFlat(AdventurePlayer.current().getNoSellCards().toFlatList()); collectionPool.removeAllFlat(cardsInUse.toFlatList());
collectionPool.removeAllFlat(cardsInUse.toFlatList()); if (!showNoSellCards) {
collectionPool.removeIf(PaperCard::hasNoSellValue);
} }
if (showAutoSellCards) { if (showAutoSellCards) {
collectionPool.addAllFlat(AdventurePlayer.current().getAutoSellCards().toFlatList()); collectionPool.addAllFlat(AdventurePlayer.current().getAutoSellCards().toFlatList());
@@ -1120,12 +1120,13 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
if (showCollectionCards) { if (showCollectionCards) {
adventurePool.addAll(AdventurePlayer.current().getCollectionCards(false)); adventurePool.addAll(AdventurePlayer.current().getCollectionCards(false));
} }
if (showNoSellCards) { else if(showNoSellCards) {
adventurePool.addAll(AdventurePlayer.current().getNoSellCards()); adventurePool.addAll(AdventurePlayer.current().getCollectionCards(false).getFilteredPool(PaperCard::hasNoSellValue));
adventurePool.removeAll(cardsInUse); }
} else {
cardsInUse.removeAll(AdventurePlayer.current().getNoSellCards()); adventurePool.removeAll(cardsInUse);
adventurePool.removeAll(cardsInUse); if (!showNoSellCards) {
adventurePool.removeIf(PaperCard::hasNoSellValue);
} }
if (showAutoSellCards) { if (showAutoSellCards) {
adventurePool.addAll(AdventurePlayer.current().getAutoSellCards()); adventurePool.addAll(AdventurePlayer.current().getAutoSellCards());

View File

@@ -223,7 +223,6 @@ public class ConsoleCommandInterpreter {
PaperCard card = StaticData.instance().getCommonCards().getCard(s[0]); PaperCard card = StaticData.instance().getCommonCards().getCard(s[0]);
if (card == null) return "Cannot find card: " + s[0]; if (card == null) return "Cannot find card: " + s[0];
Current.player().addCard(card.getNoSellVersion()); Current.player().addCard(card.getNoSellVersion());
Current.player().noSellCards.add(card.getNoSellVersion());
return "Added card: " + s[0]; return "Added card: " + s[0];
}); });
registerCommand(new String[]{"give", "item"}, s -> { registerCommand(new String[]{"give", "item"}, s -> {

View File

@@ -356,7 +356,7 @@ public class Config {
final List<String> lines = FileUtil.readAllLines(new InputStreamReader(fileInputStream, Charset.forName(CardStorageReader.DEFAULT_CHARSET_NAME)), true); final List<String> lines = FileUtil.readAllLines(new InputStreamReader(fileInputStream, Charset.forName(CardStorageReader.DEFAULT_CHARSET_NAME)), true);
CardRules rules = rulesReader.readCard(lines, com.google.common.io.Files.getNameWithoutExtension(cardFile.getName())); CardRules rules = rulesReader.readCard(lines, com.google.common.io.Files.getNameWithoutExtension(cardFile.getName()));
rules.setCustom(); rules.setCustom();
PaperCard card = new PaperCard(rules, CardEdition.UNKNOWN.getCode(), CardRarity.Special) { PaperCard card = new PaperCard(rules, CardEdition.UNKNOWN_CODE, CardRarity.Special) {
@Override @Override
public String getImageKey(boolean altState) { public String getImageKey(boolean altState) {
return ImageKeys.ADVENTURECARD_PREFIX + getName(); return ImageKeys.ADVENTURECARD_PREFIX + getName();

View File

@@ -44,6 +44,8 @@ public class Reward {
this.card = card; this.card = card;
count = 0; count = 0;
this.isNoSell = isNoSell; this.isNoSell = isNoSell;
if(isNoSell)
this.card = card.getNoSellVersion();
} }
public Reward(Type type, int count) { public Reward(Type type, int count) {
@@ -60,6 +62,10 @@ public class Reward {
this.deck = deck; this.deck = deck;
count = 0; count = 0;
this.isNoSell = isNoSell; this.isNoSell = isNoSell;
if(isNoSell)
deck.getTags().add("noSell");
//Could go through the deck and replace everything in it with the noSellValue version but the tag should
//handle that later.
} }
public PaperCard getCard() { public PaperCard getCard() {

View File

@@ -1143,7 +1143,7 @@ public class CardImageRenderer {
String set = state.getSetCode(); String set = state.getSetCode();
CardRarity rarity = state.getRarity(); CardRarity rarity = state.getRarity();
if (!canShow) { if (!canShow) {
set = CardEdition.UNKNOWN.getCode(); set = CardEdition.UNKNOWN_CODE;
rarity = CardRarity.Unknown; rarity = CardRarity.Unknown;
} }
if (!StringUtils.isEmpty(set)) { if (!StringUtils.isEmpty(set)) {

View File

@@ -9,6 +9,7 @@ import forge.Forge.KeyInputAdapter;
import forge.Graphics; import forge.Graphics;
import forge.assets.*; import forge.assets.*;
import forge.card.CardEdition; import forge.card.CardEdition;
import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.deck.io.DeckPreferences; import forge.deck.io.DeckPreferences;
import forge.gamemodes.limited.BoosterDraft; import forge.gamemodes.limited.BoosterDraft;
@@ -1767,8 +1768,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
CardManagerPage cardSourceSection; CardManagerPage cardSourceSection;
DeckSection destination = DeckSection.matchingSection(card); DeckSection destination = DeckSection.matchingSection(card);
final DeckSectionPage destinationPage = parentScreen.getPageForSection(destination); final DeckSectionPage destinationPage = parentScreen.getPageForSection(destination);
// val for colorID setup int markedColorCount = card.getRules().getSetColorID();
int val;
switch (deckSection) { switch (deckSection) {
default: default:
case Main: case Main:
@@ -1811,14 +1811,18 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
} }
} }
addCommanderItems(menu, card); addCommanderItems(menu, card);
if ((val = card.getRules().getSetColorID()) > 0) { if (markedColorCount > 0) {
menu.addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblColorIdentity"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, e -> { menu.addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblColorIdentity"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, e -> {
//sort options so current option is on top and selected by default Set<String> currentColors;
Set<String> colorChoices = new HashSet<>(MagicColor.Constant.ONLY_COLORS); if(card.getMarkedColors() != null)
GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblChooseAColor", Lang.getNumeral(val)), val, val, colorChoices, new Callback<>() { currentColors = card.getMarkedColors().stream().map(MagicColor.Color::getName).collect(Collectors.toSet());
else
currentColors = null;
String prompt = Forge.getLocalizer().getMessage("lblChooseAColor", Lang.getNumeral(markedColorCount));
GuiChoose.getChoices(prompt, markedColorCount, markedColorCount, MagicColor.Constant.ONLY_COLORS, currentColors, null, new Callback<>() {
@Override @Override
public void run(List<String> result) { public void run(List<String> result) {
addCard(card.getColorIDVersion(new HashSet<>(result))); addCard(card.copyWithMarkedColors(ColorSet.fromNames(result)));
removeCard(card); removeCard(card);
} }
}); });
@@ -1863,14 +1867,18 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
} }
} }
addCommanderItems(menu, card); addCommanderItems(menu, card);
if ((val = card.getRules().getSetColorID()) > 0) { if (markedColorCount > 0) {
menu.addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblColorIdentity"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, e -> { menu.addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblColorIdentity"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, e -> {
//sort options so current option is on top and selected by default Set<String> currentColors;
Set<String> colorChoices = new HashSet<>(MagicColor.Constant.ONLY_COLORS); if(card.getMarkedColors() != null)
GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblChooseAColor", Lang.getNumeral(val)), val, val, colorChoices, new Callback<>() { currentColors = card.getMarkedColors().stream().map(MagicColor.Color::getName).collect(Collectors.toSet());
else
currentColors = null;
String prompt = Forge.getLocalizer().getMessage("lblChooseAColor", Lang.getNumeral(markedColorCount));
GuiChoose.getChoices(prompt, markedColorCount, markedColorCount, MagicColor.Constant.ONLY_COLORS, currentColors, null, new Callback<>() {
@Override @Override
public void run(List<String> result) { public void run(List<String> result) {
addCard(card.getColorIDVersion(new HashSet<>(result))); addCard(card.copyWithMarkedColors(ColorSet.fromNames(result)));
removeCard(card); removeCard(card);
} }
}); });

View File

@@ -17,6 +17,7 @@ import forge.assets.*;
import forge.assets.FSkinColor.Colors; import forge.assets.FSkinColor.Colors;
import forge.card.*; import forge.card.*;
import forge.card.CardRenderer.CardStackPosition; import forge.card.CardRenderer.CardStackPosition;
import forge.card.mana.ManaCostShard;
import forge.deck.*; import forge.deck.*;
import forge.deck.io.DeckPreferences; import forge.deck.io.DeckPreferences;
import forge.game.card.CardView; import forge.game.card.CardView;
@@ -34,11 +35,8 @@ import forge.util.ImageUtil;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.Utils; import forge.util.Utils;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@@ -980,7 +978,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
private boolean selected, deckSelectMode, showRanking; private boolean selected, deckSelectMode, showRanking;
private final float IMAGE_SIZE = CardRenderer.MANA_SYMBOL_SIZE; private final float IMAGE_SIZE = CardRenderer.MANA_SYMBOL_SIZE;
private DeckProxy deckProxy = null; private DeckProxy deckProxy = null;
private String colorID = null; private String markedColors = null;
private FImageComplex deckCover = null; private FImageComplex deckCover = null;
private Texture dpImg = null; private Texture dpImg = null;
//private TextureRegion tr; //private TextureRegion tr;
@@ -1007,8 +1005,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
draftRankImage = FSkinImage.DRAFTRANK_C; draftRankImage = FSkinImage.DRAFTRANK_C;
} }
} }
if (((PaperCard) item).getColorID() != null) { if (((PaperCard) item).getMarkedColors() != null) {
colorID = ((PaperCard) item).getColorID().stream().map(MagicColor::toSymbol).collect(Collectors.joining()); markedColors = Arrays.stream(((PaperCard) item).getMarkedColors().getOrderedShards())
.map(ManaCostShard::toString)
.collect(Collectors.joining());
} }
} }
} }
@@ -1082,7 +1082,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
cardPrice = ((ShopScene) Forge.getCurrentScene()).getCardPrice((PaperCard) item); cardPrice = ((ShopScene) Forge.getCurrentScene()).getCardPrice((PaperCard) item);
drawCardLabel(g, "$" + cardPrice, Color.GOLD, x, y ,w ,h); drawCardLabel(g, "$" + cardPrice, Color.GOLD, x, y ,w ,h);
} else { } else {
if (((PaperCard) item).isNoSell() && itemManager.showNFSWatermark() && !Config.instance().getSettingData().disableNotForSale) { if (((PaperCard) item).hasNoSellValue() && itemManager.showNFSWatermark() && !Config.instance().getSettingData().disableNotForSale) {
Texture nfs = Forge.getAssets().getTexture(getDefaultSkinFile("nfs.png"), false); Texture nfs = Forge.getAssets().getTexture(getDefaultSkinFile("nfs.png"), false);
if (nfs != null) if (nfs != null)
g.drawImage(nfs, x, y, w, h); g.drawImage(nfs, x, y, w, h);
@@ -1092,8 +1092,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
} }
} }
// spire colors // spire colors
if (colorID != null && !colorID.isEmpty()) { if (markedColors != null && !markedColors.isEmpty()) {
textRenderer.drawText(g, colorID, FSkinFont.forHeight(w / 5), Color.WHITE, x, y + h / 4, w, h, y, h, false, Align.center, true); textRenderer.drawText(g, markedColors, FSkinFont.forHeight(w / 5), Color.WHITE, x, y + h / 4, w, h, y, h, false, Align.center, true);
} }
} else if (item instanceof ConquestCommander) { } else if (item instanceof ConquestCommander) {
CardRenderer.drawCard(g, ((ConquestCommander) item).getCard(), x, y, w, h, pos); CardRenderer.drawCard(g, ((ConquestCommander) item).getCard(), x, y, w, h, pos);

View File

@@ -660,7 +660,7 @@ public class MatchController extends AbstractGuiGame {
} }
@Override @Override
public <T> List<T> getChoices(final String message, final int min, final int max, final List<T> choices, final T selected, final Function<T, String> display) { public <T> List<T> getChoices(final String message, final int min, final int max, final List<T> choices, final List<T> selected, final Function<T, String> display) {
return GuiBase.getInterface().getChoices(message, min, max, choices, selected, display); return GuiBase.getInterface().getChoices(message, min, max, choices, selected, display);
} }

View File

@@ -355,7 +355,7 @@ public class ConquestAEtherScreen extends FScreen {
caption = caption0; caption = caption0;
options = ImmutableList.copyOf(options0); options = ImmutableList.copyOf(options0);
setSelectedOption(options.get(0)); setSelectedOption(options.get(0));
setCommand(e -> GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblSelectCaptionFilter", caption), 0, 1, options, selectedOption, null, new Callback<List<AEtherFilter>>() { setCommand(e -> GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblSelectCaptionFilter", caption), 0, 1, options, Set.of(selectedOption), null, new Callback<>() {
@Override @Override
public void run(List<AEtherFilter> result) { public void run(List<AEtherFilter> result) {
if (!result.isEmpty()) { if (!result.isEmpty()) {

View File

@@ -3,6 +3,7 @@ package forge.toolbox;
import static forge.card.CardRenderer.MANA_SYMBOL_SIZE; import static forge.card.CardRenderer.MANA_SYMBOL_SIZE;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -271,6 +272,14 @@ public class FChoiceList<T> extends FList<T> implements ActivateHandler {
setSelectedIndex(getIndexOf(choice)); setSelectedIndex(getIndexOf(choice));
} }
public void setSelectedItems(Collection<T> items) {
selectedIndices.clear();
items.stream().mapToInt(this::getIndexOf).filter(i -> i >= 0).forEach(selectedIndices::add);
if(!items.isEmpty())
scrollIntoView(getIndexOf(items.iterator().next()));
onSelectionChange();
}
protected String getChoiceText(T choice) { protected String getChoiceText(T choice) {
return choice.toString(); return choice.toString();
} }

View File

@@ -230,7 +230,7 @@ public class GuiChoose {
getChoices(message, min, max, choices, null, null, callback); getChoices(message, min, max, choices, null, null, callback);
} }
public static <T> void getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display, final Callback<List<T>> callback) { public static <T> void getChoices(final String message, final int min, final int max, final Collection<T> choices, final Collection<T> selected, final Function<T, String> display, final Callback<List<T>> callback) {
if (choices == null || choices.isEmpty()) { if (choices == null || choices.isEmpty()) {
if (min == 0) { if (min == 0) {
callback.run(new ArrayList<>()); callback.run(new ArrayList<>());

View File

@@ -201,11 +201,8 @@ public class ListChooser<T> extends FContainer {
/** /**
* Shows the dialog and returns after the dialog was closed. * Shows the dialog and returns after the dialog was closed.
*
* @param index0 index to select when shown
* @return a boolean.
*/ */
public void show(final T item, final boolean selectMax) { public void show(final Collection<T> item, final boolean selectMax) {
if (called) { if (called) {
throw new IllegalStateException("Already shown"); throw new IllegalStateException("Already shown");
} }
@@ -226,7 +223,7 @@ public class ListChooser<T> extends FContainer {
} }
} }
else { else {
lstChoices.setSelectedItem(item); lstChoices.setSelectedItems(item);
} }
optionPane.show(); optionPane.show();
} }

View File

@@ -3462,7 +3462,7 @@ AdvBossIntro34=Es gibt kein Zurück!
AdvBossIntro35=Jetzt geht es um alles oder nichts! AdvBossIntro35=Jetzt geht es um alles oder nichts!
lblYouDied={0}, du bist gestorben!!! lblYouDied={0}, du bist gestorben!!!
lblSellFor=Verkaufen für lblSellFor=Verkaufen für
lblUnsellableCount=Unverkäuflich ({0}) lblUnsellable=Unverkäuflich
lblAutoSell=AUTOVERKAUF lblAutoSell=AUTOVERKAUF
lblNoSell=NICHT VERKAUFEN lblNoSell=NICHT VERKAUFEN
lbltoSell=Zu verkaufen ({0}/{1}) lbltoSell=Zu verkaufen ({0}/{1})

View File

@@ -3210,7 +3210,7 @@ AdvBossIntro34=There's no turning back!
AdvBossIntro35=It's all or nothing now! AdvBossIntro35=It's all or nothing now!
lblYouDied={0}, You Died!!! lblYouDied={0}, You Died!!!
lblSellFor=Sell for lblSellFor=Sell for
lblUnsellableCount=Unsellable ({0}) lblUnsellable=Unsellable
lblAutoSell=AUTO-SELL lblAutoSell=AUTO-SELL
lblNoSell=NO-SELL lblNoSell=NO-SELL
lbltoSell=To Sell ({0}/{1}) lbltoSell=To Sell ({0}/{1})

View File

@@ -3473,7 +3473,7 @@ AdvBossIntro34=¡No hay marcha atrás!
AdvBossIntro35=¡Ahora es todo o nada! AdvBossIntro35=¡Ahora es todo o nada!
lblYouDied={0}, ¡¡¡Moriste!!! lblYouDied={0}, ¡¡¡Moriste!!!
lblSellFor=Vender por lblSellFor=Vender por
lblUnsellableCount=No vendible ({0}) lblUnsellable=No vendible
lblAutoSell=AUTOVENTA lblAutoSell=AUTOVENTA
lblNoSell=NO VENDER lblNoSell=NO VENDER
lbltoSell=Para vender ({0}/{1}) lbltoSell=Para vender ({0}/{1})

View File

@@ -3467,7 +3467,7 @@ AdvBossIntro34=Il n'y a pas de retour en arrière!
AdvBossIntro35=C'est tout ou rien maintenant ! AdvBossIntro35=C'est tout ou rien maintenant !
lblYouDied={0}, tu es mort !!! lblYouDied={0}, tu es mort !!!
lblSellFor=Vendre pour lblSellFor=Vendre pour
lblUnsellableCount=Invendable ({0}) lblUnsellable=Invendable
lblAutoSell=VENTE AUTOMATIQUE lblAutoSell=VENTE AUTOMATIQUE
lblNoSell=NON-VENTE lblNoSell=NON-VENTE
lbltoSell=À vendre ({0}/{1}) lbltoSell=À vendre ({0}/{1})

View File

@@ -3465,7 +3465,7 @@ AdvBossIntro34=Non si può tornare indietro!
AdvBossIntro35=Adesso è tutto o niente! AdvBossIntro35=Adesso è tutto o niente!
lblYouDied={0}, sei morto!!! lblYouDied={0}, sei morto!!!
lblSellFor=Vendi per lblSellFor=Vendi per
lblUnsellableCount=Invendibile ({0}) lblUnsellable=Invendibile
lblAutoSell=AUTOVENDITA lblAutoSell=AUTOVENDITA
lblNoSell=NON VENDITA lblNoSell=NON VENDITA
lbltoSell=Da vendere ({0}/{1}) lbltoSell=Da vendere ({0}/{1})

View File

@@ -3461,7 +3461,7 @@ AdvBossIntro34=もう後戻りはできない!
AdvBossIntro35=もう、オール・オア・ナッシングだ! AdvBossIntro35=もう、オール・オア・ナッシングだ!
lblYouDied={0}、死んだ!!! lblYouDied={0}、死んだ!!!
lblSellFor=で売る lblSellFor=で売る
lblUnsellableCount=販売不可 ({0}) lblUnsellable=販売不可
lblAutoSell=自動販売 lblAutoSell=自動販売
lblNoSell=販売禁止 lblNoSell=販売禁止
lbltoSell=販売する ({0}/{1}) lbltoSell=販売する ({0}/{1})

View File

@@ -3551,7 +3551,7 @@ AdvBossIntro34=Não há como voltar atrás!
AdvBossIntro35=É tudo ou nada agora! AdvBossIntro35=É tudo ou nada agora!
lblYouDied={0}, você morreu!!! lblYouDied={0}, você morreu!!!
lblSellFor=Vender por lblSellFor=Vender por
lblUnsellableCount=Invendável ({0}) lblUnsellable=Invendável
lblAutoSell=VENDA AUTOMÁTICA lblAutoSell=VENDA AUTOMÁTICA
lblNoSell=NÃO VENDER lblNoSell=NÃO VENDER
lbltoSell=Para Vender ({0}/{1}) lbltoSell=Para Vender ({0}/{1})

View File

@@ -3452,7 +3452,7 @@ AdvBossIntro34=没有回头路了!
AdvBossIntro35=现在要么全有要么全无! AdvBossIntro35=现在要么全有要么全无!
lblYouDied={0},你死了!!! lblYouDied={0},你死了!!!
lblSellFor=售价为 lblSellFor=售价为
lblUnsellableCount=无法出售{0} lblUnsellable=无法出售
lblAutoSell=自动销售 lblAutoSell=自动销售
lblNoSell=不卖 lblNoSell=不卖
lbltoSell=出售({0}/{1} lbltoSell=出售({0}/{1}

View File

@@ -64,7 +64,7 @@ public enum ProtocolMethod {
showOptionDialog (Mode.SERVER, Integer.TYPE, String.class, String.class, FSkinProp.class, List/*String*/.class, Integer.TYPE), showOptionDialog (Mode.SERVER, Integer.TYPE, String.class, String.class, FSkinProp.class, List/*String*/.class, Integer.TYPE),
showInputDialog (Mode.SERVER, String.class, String.class, String.class, FSkinProp.class, String.class, List/*String*/.class, Boolean.TYPE), showInputDialog (Mode.SERVER, String.class, String.class, String.class, FSkinProp.class, String.class, List/*String*/.class, Boolean.TYPE),
confirm (Mode.SERVER, Boolean.TYPE, CardView.class, String.class, Boolean.TYPE, List/*String*/.class), confirm (Mode.SERVER, Boolean.TYPE, CardView.class, String.class, Boolean.TYPE, List/*String*/.class),
getChoices (Mode.SERVER, List.class, String.class, Integer.TYPE, Integer.TYPE, List.class, Object.class, Function.class), getChoices (Mode.SERVER, List.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, Function.class),
order (Mode.SERVER, List.class, String.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, CardView.class, Boolean.TYPE), order (Mode.SERVER, List.class, String.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, CardView.class, Boolean.TYPE),
sideboard (Mode.SERVER, List.class, CardPool.class, CardPool.class, String.class), sideboard (Mode.SERVER, List.class, CardPool.class, CardPool.class, String.class),
chooseSingleEntityForEffect(Mode.SERVER, GameEntityView.class, String.class, List.class, DelayedReveal.class, Boolean.TYPE), chooseSingleEntityForEffect(Mode.SERVER, GameEntityView.class, String.class, List.class, DelayedReveal.class, Boolean.TYPE),

View File

@@ -246,7 +246,7 @@ public class NetGuiGame extends AbstractGuiGame {
} }
@Override @Override
public <T> List<T> getChoices(final String message, final int min, final int max, final List<T> choices, final T selected, final Function<T, String> display) { public <T> List<T> getChoices(final String message, final int min, final int max, final List<T> choices, final List<T> selected, final Function<T, String> display) {
return sendAndWait(ProtocolMethod.getChoices, message, min, max, choices, selected, display); return sendAndWait(ProtocolMethod.getChoices, message, min, max, choices, selected, display);
} }

View File

@@ -32,6 +32,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -510,11 +511,11 @@ public class QuestWinLoseController {
final List<GameFormat> formats = new ArrayList<>(); final List<GameFormat> formats = new ArrayList<>();
final String preferredFormat = FModel.getQuestPreferences().getPref(QPref.BOOSTER_FORMAT); final String preferredFormat = FModel.getQuestPreferences().getPref(QPref.BOOSTER_FORMAT);
GameFormat pref = null; Collection<GameFormat> pref = null;
for (final GameFormat f : FModel.getFormats().getSanctionedList()) { for (final GameFormat f : FModel.getFormats().getSanctionedList()) {
formats.add(f); formats.add(f);
if (f.toString().equals(preferredFormat)) { if (f.toString().equals(preferredFormat)) {
pref = f; pref = List.of(f);
} }
} }

View File

@@ -129,7 +129,7 @@ public class CardDetailUtil {
} }
public static String getCurrentColors(final CardStateView c) { public static String getCurrentColors(final CardStateView c) {
return c.getColors().toEnumSet().stream().map(MagicColor.Color::getSymbol).collect(Collectors.joining()); return c.getColors().stream().map(MagicColor.Color::getSymbol).collect(Collectors.joining());
} }
public static DetailColors getRarityColor(final CardRarity rarity) { public static DetailColors getRarityColor(final CardRarity rarity) {
@@ -450,10 +450,10 @@ public class CardDetailUtil {
} }
// chosen spire // chosen spire
if (card.getChosenColorID() != null && !card.getChosenColorID().isEmpty()) { if (card.getMarkedColors() != null && !card.getMarkedColors().isColorless()) {
area.append("\n"); area.append("\n");
area.append("(").append(Localizer.getInstance().getMessage("lblSelected")).append(": "); area.append("(").append(Localizer.getInstance().getMessage("lblSelected")).append(": ");
area.append(Lang.joinHomogenous(card.getChosenColorID().stream().map(DeckRecognizer::getLocalisedMagicColorName).collect(Collectors.toList()))); area.append(Lang.joinHomogenous(card.getMarkedColors().stream().map(MagicColor.Color::getLocalizedName).collect(Collectors.toList())));
area.append(")"); area.append(")");
} }

View File

@@ -55,7 +55,7 @@ public class GuiDownloadSetPicturesLQ extends GuiDownloadService {
} }
final String setCode2 = setMapping.get(setCode3); final String setCode2 = setMapping.get(setCode3);
if (StringUtils.isBlank(setCode3) || CardEdition.UNKNOWN.getCode().equals(setCode3)) { if (StringUtils.isBlank(setCode3) || CardEdition.UNKNOWN_CODE.equals(setCode3)) {
// we don't want cards from unknown sets // we don't want cards from unknown sets
continue; continue;
} }

View File

@@ -38,7 +38,7 @@ public interface IGuiBase {
void showImageDialog(ISkinImage image, String message, String title); void showImageDialog(ISkinImage image, String message, String title);
int showOptionDialog(String message, String title, FSkinProp icon, List<String> options, int defaultOption); int showOptionDialog(String message, String title, FSkinProp icon, List<String> options, int defaultOption);
String showInputDialog(String message, String title, FSkinProp icon, String initialInput, List<String> inputOptions, boolean isNumeric); String showInputDialog(String message, String title, FSkinProp icon, String initialInput, List<String> inputOptions, boolean isNumeric);
<T> List<T> getChoices(String message, int min, int max, Collection<T> choices, T selected, Function<T, String> display); <T> List<T> getChoices(String message, int min, int max, Collection<T> choices, Collection<T> selected, Function<T, String> display);
<T> List<T> order(String title, String top, int remainingObjectsMin, int remainingObjectsMax, List<T> sourceChoices, List<T> destChoices); <T> List<T> order(String title, String top, int remainingObjectsMin, int remainingObjectsMax, List<T> sourceChoices, List<T> destChoices);
String showFileDialog(String title, String defaultDir); String showFileDialog(String title, String defaultDir);
File getSaveFile(File defaultFile); File getSaveFile(File defaultFile);

View File

@@ -150,7 +150,7 @@ public interface IGuiGame {
<T> List<T> getChoices(String message, int min, int max, List<T> choices); <T> List<T> getChoices(String message, int min, int max, List<T> choices);
<T> List<T> getChoices(String message, int min, int max, List<T> choices, T selected, Function<T, String> display); <T> List<T> getChoices(String message, int min, int max, List<T> choices, List<T> selected, Function<T, String> display);
// Get Integer in range // Get Integer in range
Integer getInteger(String message, int min); Integer getInteger(String message, int min);

View File

@@ -43,7 +43,7 @@ public class SGuiChoose {
if ((choices == null) || choices.isEmpty()) { if ((choices == null) || choices.isEmpty()) {
return null; return null;
} }
final List<T> choice = SGuiChoose.getChoices(message, 0, 1, choices, selected, display); final List<T> choice = SGuiChoose.getChoices(message, 0, 1, choices, selected == null ? null : List.of(selected), display);
return choice.isEmpty() ? null : choice.get(0); return choice.isEmpty() ? null : choice.get(0);
} }
@@ -147,12 +147,12 @@ public class SGuiChoose {
return getChoices(message, min, max, Arrays.asList(choices), null, null); return getChoices(message, min, max, Arrays.asList(choices), null, null);
} }
public static <T> List<T> getChoices(final String message, final int min, final int max, final T[] choices, final T selected, final Function<T, String> display) { public static <T> List<T> getChoices(final String message, final int min, final int max, final T[] choices, final T selected, final Function<T, String> display) {
return getChoices(message, min, max, Arrays.asList(choices), selected, display); return getChoices(message, min, max, Arrays.asList(choices), selected == null ? null : List.of(selected), display);
} }
public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices) { public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices) {
return getChoices(message, min, max, choices, null, null); return getChoices(message, min, max, choices, null, null);
} }
public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) { public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final Collection<T> selected, final Function<T, String> display) {
return GuiBase.getInterface().getChoices(message, min, max, choices, selected, display); return GuiBase.getInterface().getChoices(message, min, max, choices, selected, display);
} }

View File

@@ -54,7 +54,7 @@ public enum ColumnDef {
NAME("lblName", "lblName", 180, false, SortState.ASC, NAME("lblName", "lblName", 180, false, SortState.ASC,
from -> { from -> {
if (from.getKey() instanceof PaperCard) { if (from.getKey() instanceof PaperCard) {
String spire = ((PaperCard) from.getKey()).getColorID() == null ? "" : ((PaperCard) from.getKey()).getColorID().toString(); String spire = ((PaperCard) from.getKey()).getMarkedColors() == null ? "" : ((PaperCard) from.getKey()).getMarkedColors().toString();
String sortableName = ((PaperCard)from.getKey()).getSortableName(); String sortableName = ((PaperCard)from.getKey()).getSortableName();
return sortableName == null ? TextUtil.toSortableName(from.getKey().getName() + spire) : sortableName + spire; return sortableName == null ? TextUtil.toSortableName(from.getKey().getName() + spire) : sortableName + spire;
} }