From 4714319204de28cd53e2d0216992e41bb77d476a Mon Sep 17 00:00:00 2001 From: Jetz72 Date: Wed, 16 Apr 2025 18:59:31 -0400 Subject: [PATCH] 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 --- .../main/java/forge/ai/ComputerUtilMana.java | 2 +- .../src/main/java/forge/card/CardDb.java | 238 +++++++++--------- .../src/main/java/forge/card/CardEdition.java | 6 +- .../src/main/java/forge/card/CardRules.java | 1 + .../src/main/java/forge/card/ColorSet.java | 16 +- .../main/java/forge/card/ICardDatabase.java | 70 +++--- .../src/main/java/forge/card/MagicColor.java | 7 + .../java/forge/card/mana/ManaCostShard.java | 15 +- .../src/main/java/forge/deck/CardPool.java | 29 +-- forge-core/src/main/java/forge/deck/Deck.java | 66 ++--- .../main/java/forge/deck/DeckRecognizer.java | 6 +- .../src/main/java/forge/deck/DeckSection.java | 2 +- .../java/forge/deck/io/DeckSerializer.java | 2 + .../src/main/java/forge/item/IPaperCard.java | 4 +- .../src/main/java/forge/item/PaperCard.java | 214 +++++++++++----- .../src/main/java/forge/item/PaperToken.java | 3 +- .../src/main/java/forge/util/ItemPool.java | 7 + .../src/main/java/forge/game/GameAction.java | 12 +- .../game/ability/effects/CloneEffect.java | 2 +- .../ability/effects/CopyPermanentEffect.java | 2 +- .../src/main/java/forge/game/card/Card.java | 22 +- .../main/java/forge/game/card/CardState.java | 2 +- .../main/java/forge/game/card/CardView.java | 8 +- .../game/spellability/AbilityManaPart.java | 6 +- .../forge/trackable/TrackableProperty.java | 2 +- .../src/main/java/forge/GuiDesktop.java | 2 +- .../main/java/forge/gui/CardDetailPanel.java | 2 +- .../src/main/java/forge/gui/GuiChoose.java | 4 +- .../src/main/java/forge/gui/ListChooser.java | 15 +- .../deckeditor/controllers/ACEditorBase.java | 7 +- .../java/forge/screens/match/CMatchUI.java | 3 +- .../forge/card/CardDbCardMockTestCase.java | 24 +- forge-gui-mobile/src/forge/GuiMobile.java | 2 +- .../adventure/player/AdventurePlayer.java | 77 +++--- .../adventure/scene/AdventureDeckEditor.java | 35 +-- .../stage/ConsoleCommandInterpreter.java | 1 - .../src/forge/adventure/util/Config.java | 2 +- .../src/forge/adventure/util/Reward.java | 6 + .../src/forge/card/CardImageRenderer.java | 2 +- .../src/forge/deck/FDeckEditor.java | 32 ++- .../forge/itemmanager/views/ImageView.java | 20 +- .../forge/screens/match/MatchController.java | 2 +- .../planarconquest/ConquestAEtherScreen.java | 2 +- .../src/forge/toolbox/FChoiceList.java | 9 + .../src/forge/toolbox/GuiChoose.java | 2 +- .../src/forge/toolbox/ListChooser.java | 7 +- forge-gui/res/languages/de-DE.properties | 2 +- forge-gui/res/languages/en-US.properties | 2 +- forge-gui/res/languages/es-ES.properties | 2 +- forge-gui/res/languages/fr-FR.properties | 2 +- forge-gui/res/languages/it-IT.properties | 2 +- forge-gui/res/languages/ja-JP.properties | 2 +- forge-gui/res/languages/pt-BR.properties | 2 +- forge-gui/res/languages/zh-CN.properties | 2 +- .../forge/gamemodes/net/ProtocolMethod.java | 2 +- .../gamemodes/net/server/NetGuiGame.java | 2 +- .../quest/QuestWinLoseController.java | 5 +- .../java/forge/gui/card/CardDetailUtil.java | 6 +- .../download/GuiDownloadSetPicturesLQ.java | 2 +- .../java/forge/gui/interfaces/IGuiBase.java | 2 +- .../java/forge/gui/interfaces/IGuiGame.java | 2 +- .../main/java/forge/gui/util/SGuiChoose.java | 6 +- .../java/forge/itemmanager/ColumnDef.java | 2 +- 63 files changed, 559 insertions(+), 486 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index e616ecc1687..b2aabd58c50 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -158,7 +158,7 @@ public class ComputerUtilMana { } // 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 payWithAb2 = ability2.getManaPart().mana(ability2).contains(shardMana); diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index e421281dccc..6b43856142a 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -42,7 +42,8 @@ import java.util.stream.Stream; public final class CardDb implements ICardDatabase, IDeckGenPool { public final static String foilSuffix = "+"; 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 exlcudedCardSet = "DS0"; @@ -93,19 +94,19 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { public int artIndex; public boolean isFoil; public String collectorNumber; - public Set colorID; + public Map flags; private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber) { this(name, edition, artIndex, isFoil, collectorNumber, null); } - private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Set colorID) { + private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Map flags) { cardName = name; this.edition = edition; this.artIndex = artIndex; this.isFoil = isFoil; this.collectorNumber = collectorNumber; - this.colorID = colorID; + this.flags = flags; } 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) { - setCode = setCode != null ? setCode : ""; + if(setCode == null || StringUtils.isBlank(setCode) || setCode.equals(CardEdition.UNKNOWN_CODE)) + setCode = ""; cardName = cardName != null ? cardName : ""; if (cardName.indexOf(NameSetSeparator) != -1) // 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; } - public static String compose(String cardName, String setCode, int artIndex, Set 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) { String requestInfo = compose(cardName, setCode); // CollectorNumber will be wrapped in square brackets @@ -149,6 +143,34 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return requestInfo + NameSetSeparator + collectorNumber; } + public static String compose(String cardName, String setCode, int artIndex, Map 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 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) { if (collectorNumber == null) return ""; @@ -160,19 +182,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return collectorNumber; } - 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 getFlagSegment(Map flags) { + if(flags == null) + return ""; + String flagText = flags.entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.joining(FlagSeparator)); + return NameSetSeparator + FlagPrefix + "{" + flagText + "}"; } private static boolean isCollectorNumber(String s) { return s.startsWith("[") && s.endsWith("]"); } - private static boolean isColorIDString(String s) { - return s.startsWith(colorIDPrefix); + private static boolean isFlagSegment(String s) { + return s.startsWith(FlagPrefix); } private static boolean isArtIndex(String s) { @@ -201,44 +225,36 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return null; String[] info = TextUtil.split(reqInfo, NameSetSeparator); - int setPos; - 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; - } + int index = 1; String cardName = info[0]; boolean isFoil = false; + int artIndex = IPaperCard.NO_ART_INDEX; + String setCode = null; + String collectorNumber = IPaperCard.NO_COLLECTOR_NUMBER; + Map flags = null; if (isFoilCardName(cardName)) { cardName = cardName.substring(0, cardName.length() - foilSuffix.length()); 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; - String setCode = setPos > 0 ? info[setPos] : null; - Set colorID = clrPos > 0 ? Arrays.stream(info[clrPos].substring(1).split(colorIDPrefix)).collect(Collectors.toSet()) : null; - if (setCode != null && setCode.equals(CardEdition.UNKNOWN.getCode())) { // ??? + + if(info.length > index && isSetCode(info[index])) { + setCode = info[index]; + index++; + } + 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; } 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 if (collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER) && artIndex == IPaperCard.NO_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 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 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)); } 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. "); - addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special)); + addCard(new PaperCard(cr, CardEdition.UNKNOWN_CODE, CardRarity.Special)); } } 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."); @@ -592,15 +630,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } @Override - public PaperCard getCard(final String cardName, String setCode, int artIndex, String collectorNumber) { - String reqInfo = CardRequest.compose(cardName, setCode, artIndex, collectorNumber); - CardRequest request = CardRequest.fromString(reqInfo); - return tryGetCard(request); - } - - @Override - public PaperCard getCard(final String cardName, String setCode, int artIndex, Set colorID) { - String reqInfo = CardRequest.compose(cardName, setCode, artIndex, colorID); + public PaperCard getCard(final String cardName, String setCode, int artIndex, Map flags) { + String reqInfo = CardRequest.compose(cardName, setCode, artIndex, flags); CardRequest request = CardRequest.fromString(reqInfo); return tryGetCard(request); } @@ -611,14 +642,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return null; // 1. First off, try using all possible search parameters, to narrow down the actual cards looked for. 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) - // MOST of the extensions have two short codes, 141 out of 221 (so far) // ALSO: Set Code are always UpperCase CardEdition edition = editions.get(reqEditionCode.toUpperCase()); - return this.getCardFromSet(request.cardName, edition, request.artIndex, - request.collectorNumber, request.isFoil, request.colorID); + PaperCard cardFromSet = this.getCardFromSet(request.cardName, edition, request.artIndex, request.collectorNumber, request.isFoil); + if(cardFromSet != null && request.flags != null) + cardFromSet = cardFromSet.copyWithFlags(request.flags); + + return cardFromSet; } // 2. Card lookup in edition with specified filter didn't work. @@ -661,11 +695,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { @Override 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 colorID) { if (edition == null || cardName == null) // preview cards return null; // No cards will be returned @@ -674,18 +703,18 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { cardName = cardNameRequest.cardName; isFoil = isFoil || cardNameRequest.isFoil; - List candidates = getAllCards(cardName, c -> { - boolean artIndexFilter = true; - boolean collectorNumberFilter = true; - boolean setFilter = c.getEdition().equalsIgnoreCase(edition.getCode()) || - c.getEdition().equalsIgnoreCase(edition.getCode2()); - if (artIndex > 0) - artIndexFilter = (c.getArtIndex() == artIndex); - if ((collectorNumber != null) && (collectorNumber.length() > 0) - && !(collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER))) - collectorNumberFilter = (c.getCollectorNumber().equals(collectorNumber)); - return setFilter && artIndexFilter && collectorNumberFilter; - }); + String code1 = edition.getCode(), code2 = edition.getCode2(); + + Predicate filter = (c) -> { + String ed = c.getEdition(); + return ed.equalsIgnoreCase(code1) || ed.equalsIgnoreCase(code2); + }; + if (artIndex > 0) + filter = filter.and((c) -> artIndex == c.getArtIndex()); + if (collectorNumber != null && !collectorNumber.isEmpty() && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)) + filter = filter.and((c) -> collectorNumber.equals(c.getCollectorNumber())); + + List candidates = getAllCards(cardName, filter); if (candidates.isEmpty()) return null; @@ -699,7 +728,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { while (!candidate.hasImage() && candidatesIterator.hasNext()) candidate = candidatesIterator.next(); 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); } - @Override - public PaperCard getCardFromEditions(final String cardInfo, final CardArtPreference artPreference, int artIndex, Set colorID) { - return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, null, false, null, colorID); - } - /* * =============================================== * 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, - Date releaseDate, boolean releasedBeforeFlag, Predicate 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 filter, Set colorID){ + Date releaseDate, boolean releasedBeforeFlag, Predicate filter) { if (cardInfo == null) return null; final CardRequest cr = CardRequest.fromString(cardInfo); @@ -865,7 +884,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { for (PaperCard card : cards) { String setCode = card.getEdition(); CardEdition ed; - if (setCode.equals(CardEdition.UNKNOWN.getCode())) + if (setCode.equals(CardEdition.UNKNOWN_CODE)) ed = CardEdition.UNKNOWN; else ed = editions.get(card.getEdition()); @@ -906,7 +925,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } candidate = candidate.hasImage() ? candidate : firstCandidate; //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 @@ -1126,29 +1145,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { .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) { CardRequest request = CardRequest.fromString(cardRequest); CardEdition cardEdition = CardEdition.UNKNOWN; @@ -1250,7 +1246,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } } 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 for (PaperCard paperCard : paperCards) { diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index b11655eb28d..4833d8d2769 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -262,7 +262,11 @@ public final class CardEdition implements Comparable { 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 String code; private String code2; diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 616d4d3cee2..fe6f4aba3bf 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -396,6 +396,7 @@ public final class CardRules implements ICardCharacteristics { } public int getSetColorID() { + //Could someday generalize this to support other kinds of markings. return setColorID; } diff --git a/forge-core/src/main/java/forge/card/ColorSet.java b/forge-core/src/main/java/forge/card/ColorSet.java index 4726b309eb9..abf25c085da 100644 --- a/forge-core/src/main/java/forge/card/ColorSet.java +++ b/forge-core/src/main/java/forge/card/ColorSet.java @@ -25,6 +25,8 @@ import forge.util.BinaryUtil; import java.io.Serializable; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** *

CardColor class.

@@ -291,14 +293,8 @@ public final class ColorSet implements Comparable, Iterable, Ser */ @Override public String toString() { - if (this.orderWeight == -1) { - return "n/a"; - } - final String toReturn = MagicColor.toLongString(myColor); - if (toReturn.equals(MagicColor.Constant.COLORLESS) && myColor != 0) { - return "multi"; - } - return toReturn; + final ManaCostShard[] orderedShards = getOrderedShards(); + return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining()); } /** @@ -376,6 +372,10 @@ public final class ColorSet implements Comparable, Iterable, Ser } } + public Stream stream() { + return this.toEnumSet().stream(); + } + //Get array of mana cost shards for color set in the proper order public ManaCostShard[] getOrderedShards() { return shardOrderLookup[myColor]; diff --git a/forge-core/src/main/java/forge/card/ICardDatabase.java b/forge-core/src/main/java/forge/card/ICardDatabase.java index 26f90087a7f..b06a0c08b77 100644 --- a/forge-core/src/main/java/forge/card/ICardDatabase.java +++ b/forge-core/src/main/java/forge/card/ICardDatabase.java @@ -5,43 +5,42 @@ import forge.item.PaperCard; import java.util.Collection; import java.util.Date; +import java.util.Map; import java.util.function.Predicate; import java.util.stream.Stream; -import java.util.Set; +/** + * 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) + * */ public interface ICardDatabase extends Iterable { - /** - * 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 * ============================= */ // 1. Card Lookup by attributes @@ -50,22 +49,19 @@ public interface ICardDatabase extends Iterable { PaperCard getCard(String cardName, String edition, int artIndex); // [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, int artIndex, String collectorNumber); - PaperCard getCard(String cardName, String edition, int artIndex, Set colorID); + PaperCard getCard(String cardName, String edition, int artIndex, Map flags); // 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, String collectorNumber, 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, Set colorID); // 3. Card lookup based on CardArtPreference Selection Policy PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, Predicate filter); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Predicate filter); - PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Set colorID); // 4. Specialised Card Lookup on CardArtPreference Selection and Release Date PaperCard getCardFromEditionsReleasedBefore(String cardName, CardArtPreference artPreference, Date releaseDate); diff --git a/forge-core/src/main/java/forge/card/MagicColor.java b/forge-core/src/main/java/forge/card/MagicColor.java index 0592b0a9d04..f1e29ebf0e1 100644 --- a/forge-core/src/main/java/forge/card/MagicColor.java +++ b/forge-core/src/main/java/forge/card/MagicColor.java @@ -1,6 +1,7 @@ package forge.card; import com.google.common.collect.ImmutableList; +import forge.deck.DeckRecognizer; /** * Holds byte values for each color magic has. @@ -187,6 +188,12 @@ public final class MagicColor { public String getName() { 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() { return colormask; } diff --git a/forge-core/src/main/java/forge/card/mana/ManaCostShard.java b/forge-core/src/main/java/forge/card/mana/ManaCostShard.java index cd4fa49e72b..1d558520a25 100644 --- a/forge-core/src/main/java/forge/card/mana/ManaCostShard.java +++ b/forge-core/src/main/java/forge/card/mana/ManaCostShard.java @@ -94,6 +94,7 @@ public enum ManaCostShard { /** The cmpc. */ private final float cmpc; private final String stringValue; + private final String shortStringValue; /** The image key. */ private final String imageKey; @@ -125,6 +126,7 @@ public enum ManaCostShard { this.cmc = this.getCMC(); this.cmpc = this.getCmpCost(); this.stringValue = "{" + sValue + "}"; + this.shortStringValue = sValue; this.imageKey = imgKey; } @@ -232,16 +234,21 @@ public enum ManaCostShard { return ManaCostShard.valueOf(atoms); } - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() + /** + * @return the string representation of this shard - e.g. "{W}" "{2/U}" "{G/P}" */ @Override public final String toString() { 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. * diff --git a/forge-core/src/main/java/forge/deck/CardPool.java b/forge-core/src/main/java/forge/deck/CardPool.java index 1e568f4b96f..4c0b38debd9 100644 --- a/forge-core/src/main/java/forge/deck/CardPool.java +++ b/forge-core/src/main/java/forge/deck/CardPool.java @@ -52,7 +52,7 @@ public class CardPool extends ItemPool { public void add(final String cardRequest, final int amount) { 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) { @@ -71,7 +71,7 @@ public class CardPool extends ItemPool { public void add(String cardName, String setCode, int artIndex, final int amount) { this.add(cardName, setCode, artIndex, amount, false, null); } - public void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Set colorID) { + public void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Map flags) { Map dbs = StaticData.instance().getAvailableDatabases(); PaperCard paperCard = null; String selectedDbName = ""; @@ -81,7 +81,7 @@ public class CardPool extends ItemPool { for (Map.Entry entry: dbs.entrySet()){ String dbName = entry.getKey(); CardDb db = entry.getValue(); - paperCard = db.getCard(cardName, setCode, artIndex, colorID); + paperCard = db.getCard(cardName, setCode, artIndex, flags); if (paperCard != null) { selectedDbName = dbName; break; @@ -123,7 +123,7 @@ public class CardPool extends ItemPool { int cnt = artGroups[i - 1]; if (cnt <= 0) continue; - PaperCard randomCard = cardDb.getCard(cardName, setCode, i, colorID); + PaperCard randomCard = cardDb.getCard(cardName, setCode, i, flags); this.add(randomCard, cnt); } } @@ -430,7 +430,6 @@ public class CardPool extends ItemPool { public String toCardList(String separator) { List> main2sort = Lists.newArrayList(this); main2sort.sort(ItemPoolSorter.BY_NAME_THEN_SET); - final CardDb commonDb = StaticData.instance().getCommonCards(); StringBuilder sb = new StringBuilder(); boolean isFirst = true; @@ -441,10 +440,8 @@ public class CardPool extends ItemPool { else isFirst = false; - CardDb db = !e.getKey().getRules().isVariant() ? commonDb : StaticData.instance().getVariantCards(); sb.append(e.getValue()).append(" "); - db.appendCardToStringBuilder(e.getKey(), sb); - + sb.append(CardDb.CardRequest.compose(e.getKey())); } return sb.toString(); } @@ -463,20 +460,4 @@ public class CardPool extends ItemPool { } 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 predicate) { - CardPool filteredPool = new CardPool(); - for (Entry entry : this.items.entrySet()) { - PaperCard pc = entry.getKey(); - int count = entry.getValue(); - if (predicate.test(pc)) - filteredPool.add(pc, count); - } - return filteredPool; - } } diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java index 28fb01218e3..ee86e133174 100644 --- a/forge-core/src/main/java/forge/deck/Deck.java +++ b/forge-core/src/main/java/forge/deck/Deck.java @@ -247,7 +247,7 @@ public class Deck extends DeckBase implements Iterable> referenceDeckLoadingMap; if (deferredSections != null) { - this.validateDeferredSections(); + this.normalizeDeferredSections(); referenceDeckLoadingMap = new HashMap<>(this.deferredSections); } else referenceDeckLoadingMap = new HashMap<>(loadedSections); @@ -267,7 +267,7 @@ public class Deck extends DeckBase implements Iterable cardsInSection = s.getValue(); ArrayList cardNamesWithNoEdition = getAllCardNamesWithNoSpecifiedEdition(cardsInSection); - if (cardNamesWithNoEdition.size() > 0) { + if (!cardNamesWithNoEdition.isEmpty()) { includeCardsFromUnspecifiedSet = true; if (smartCardArtSelection) cardsWithNoEdition.put(sec, cardNamesWithNoEdition); @@ -281,10 +281,10 @@ public class Deck extends DeckBase implements Iterable> validatedSections = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); @@ -296,61 +296,33 @@ public class Deck extends DeckBase implements Iterable cardsInSection = s.getValue(); - List> originalCardRequests = CardPool.processCardList(cardsInSection); CardPool pool = CardPool.fromCardList(cardsInSection); if (pool.countDistinct() == 0) continue; // pool empty, no card has been found! - // Filter pool by applying DeckSection Validation schema for Card Types (to avoid inconsistencies) - CardPool filteredPool = pool.getFilteredPoolWithCardsCount(deckSection::validate); - // Add all the cards from ValidPool anyway! - List whiteList = validatedSections.getOrDefault(s.getKey(), null); - if (whiteList == null) - whiteList = new ArrayList<>(); - for (Entry entry : filteredPool) { - String poolRequest = getPoolRequest(entry, originalCardRequests); - whiteList.add(poolRequest); + List validatedSection = validatedSections.computeIfAbsent(s.getKey(), (k) -> new ArrayList<>()); + for (Entry entry : pool) { + PaperCard card = entry.getKey(); + String normalizedRequest = getPoolRequest(entry); + if(deckSection.validate(card)) + validatedSection.add(normalizedRequest); + else { + // Card was in the wrong section. Move it to the right section. + DeckSection cardSection = DeckSection.matchingSection(card); + assert(cardSection.validate(card)); //Card doesn't fit in the matchingSection? + List 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 entry : blackList) { - DeckSection cardSection = DeckSection.matchingSection(entry.getKey()); - String poolRequest = getPoolRequest(entry, originalCardRequests); - List 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 // Overwrite deferredSections this.deferredSections = validatedSections; } - private String getPoolRequest(Entry entry, List> originalCardRequests) { - PaperCard card = entry.getKey(); + private String getPoolRequest(Entry entry) { int amount = entry.getValue(); - String poolCardRequest = CardDb.CardRequest.compose( - card.isFoil() ? CardDb.CardRequest.compose(card.getName(), true) : card.getName(), - card.getEdition(), card.getArtIndex(), card.getColorID()); - String originalRequestCandidate = null; - for (Pair 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); + String poolCardRequest = CardDb.CardRequest.compose(entry.getKey()); return String.format("%d %s", amount, poolCardRequest); } diff --git a/forge-core/src/main/java/forge/deck/DeckRecognizer.java b/forge-core/src/main/java/forge/deck/DeckRecognizer.java index 3b77b0375a4..ef9ee92a76d 100644 --- a/forge-core/src/main/java/forge/deck/DeckRecognizer.java +++ b/forge-core/src/main/java/forge/deck/DeckRecognizer.java @@ -987,7 +987,7 @@ public class DeckRecognizer { private static String getMagicColourLabel(MagicColor.Color magicColor) { if (magicColor == null) // 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 manaSymbolsMap = new HashMap() {{ @@ -1006,8 +1006,8 @@ public class DeckRecognizer { if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS || magicColor1 == MagicColor.Color.COLORLESS) return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2)); - String localisedName1 = getLocalisedMagicColorName(magicColor1.getName()); - String localisedName2 = getLocalisedMagicColorName(magicColor2.getName()); + String localisedName1 = magicColor1.getLocalizedName(); + String localisedName2 = magicColor2.getLocalizedName(); String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask()); return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol); } diff --git a/forge-core/src/main/java/forge/deck/DeckSection.java b/forge-core/src/main/java/forge/deck/DeckSection.java index 5c2b125abe8..03924ae1725 100644 --- a/forge-core/src/main/java/forge/deck/DeckSection.java +++ b/forge-core/src/main/java/forge/deck/DeckSection.java @@ -88,7 +88,7 @@ public enum DeckSection { CardType t = card.getRules().getType(); // NOTE: Same rules applies to both Deck and Side, despite "Conspiracy cards" are allowed // 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(); }; diff --git a/forge-core/src/main/java/forge/deck/io/DeckSerializer.java b/forge-core/src/main/java/forge/deck/io/DeckSerializer.java index 98a290691fd..3ac9fa68c75 100644 --- a/forge-core/src/main/java/forge/deck/io/DeckSerializer.java +++ b/forge-core/src/main/java/forge/deck/io/DeckSerializer.java @@ -61,6 +61,8 @@ public class DeckSerializer { } for(Entry s : d) { + if(s.getValue().isEmpty()) + continue; out.add(TextUtil.enclosedBracket(s.getKey().toString())); out.add(s.getValue().toCardList(System.lineSeparator())); } diff --git a/forge-core/src/main/java/forge/item/IPaperCard.java b/forge-core/src/main/java/forge/item/IPaperCard.java index 090e24d46fc..9d16618c2c4 100644 --- a/forge-core/src/main/java/forge/item/IPaperCard.java +++ b/forge-core/src/main/java/forge/item/IPaperCard.java @@ -2,10 +2,10 @@ package forge.item; import forge.card.CardRarity; import forge.card.CardRules; +import forge.card.ColorSet; import forge.card.ICardFace; import java.io.Serializable; -import java.util.Set; public interface IPaperCard extends InventoryItem, Serializable { @@ -20,7 +20,7 @@ public interface IPaperCard extends InventoryItem, Serializable { String getEdition(); String getCollectorNumber(); String getFunctionalVariant(); - Set getColorID(); + ColorSet getMarkedColors(); int getArtIndex(); boolean isFoil(); boolean isToken(); diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 258d8d1977c..783c756d5ca 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -28,8 +28,10 @@ import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.io.ObjectInputStream; -import java.util.Optional; -import java.util.Set; +import java.io.ObjectStreamException; +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). @@ -39,6 +41,7 @@ import java.util.Set; * @author Forge */ public class PaperCard implements Comparable, InventoryItemFromSet, IPaperCard { + @Serial private static final long serialVersionUID = 2942081982620691205L; // Reference to rules @@ -55,16 +58,15 @@ public class PaperCard implements Comparable, InventoryItemFromSet, private String artist; private final int artIndex; private final boolean foil; - private Boolean hasImage; - private final boolean noSell; - private Set colorID; - private String sortableName; + private final PaperCardFlags flags; + private final String sortableName; private final String functionalVariant; // Calculated fields are below: private transient CardRarity rarity; // rarity is given in ctor when set is assigned // 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 public String getName() { @@ -89,8 +91,8 @@ public class PaperCard implements Comparable, InventoryItemFromSet, } @Override - public Set getColorID() { - return colorID; + public ColorSet getMarkedColors() { + return this.flags.markedColors; } @Override @@ -147,32 +149,32 @@ public class PaperCard implements Comparable, InventoryItemFromSet, return unFoiledVersion; } public PaperCard getNoSellVersion() { - if (this.noSell) + if (this.flags.noSellValue) return this; - if (this.noSellVersion == null) { - this.noSellVersion = new PaperCard(this.rules, this.edition, this.rarity, - this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, true); - } + if (this.noSellVersion == null) + this.noSellVersion = new PaperCard(this, this.flags.withNoSellValueFlag(true)); return this.noSellVersion; } - public PaperCard getSellable() { - if (!this.noSell) - return this; - PaperCard sellable = new PaperCard(this.rules, this.edition, this.rarity, - this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, false); - return sellable; + public PaperCard copyWithoutFlags() { + if(this.flaglessVersion == null) { + if(this.flags == PaperCardFlags.IDENTITY_FLAGS) + this.flaglessVersion = this; + else + this.flaglessVersion = new PaperCard(this, null); + } + return flaglessVersion; } - public PaperCard getColorIDVersion(Set colors) { - if (colors == null && this.colorID == null) + public PaperCard copyWithFlags(Map flags) { + 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; - if (this.colorID != null && this.colorID.equals(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); + return new PaperCard(this, this.flags.withMarkedColors(colors)); } @Override public String getItemType() { @@ -180,8 +182,12 @@ public class PaperCard implements Comparable, InventoryItemFromSet, return localizer.getMessage("lblCard"); } - public boolean isNoSell() { - return noSell; + public PaperCardFlags getMarkedFlags() { + return this.flags; + } + + public boolean hasNoSellValue() { + return this.flags.noSellValue; } public boolean hasImage() { return hasImage(false); @@ -198,38 +204,41 @@ public class PaperCard implements Comparable, InventoryItemFromSet, 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, final int artIndex0, final boolean foil0, final String collectorNumber0, 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, - final int artIndex0, final boolean foil0, final String collectorNumber0, - final String artist0, final String functionalVariant, final boolean noSell0) { - this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, noSell0, 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 colorID0) { - if (rules0 == null || edition0 == null || rarity0 == null) { + protected PaperCard(final CardRules rules, final String edition, final CardRarity rarity, + final int artIndex, final boolean foil, final String collectorNumber, + final String artist, final String functionalVariant, final PaperCardFlags flags) { + if (rules == null || edition == null || rarity == null) { throw new IllegalArgumentException("Cannot create card without rules, edition or rarity"); } - rules = rules0; - name = rules0.getName(); - edition = edition0; - artIndex = Math.max(artIndex0, IPaperCard.DEFAULT_ART_INDEX); - foil = foil0; - rarity = rarity0; - artist = TextUtil.normalizeText(artist0); - collectorNumber = (collectorNumber0 != null) && (collectorNumber0.length() > 0) ? collectorNumber0 : IPaperCard.NO_COLLECTOR_NUMBER; + this.rules = rules; + name = rules.getName(); + this.edition = edition; + this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX); + this.foil = foil; + this.rarity = rarity; + this.artist = TextUtil.normalizeText(artist); + 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. // 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; - 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); @@ -256,8 +265,7 @@ public class PaperCard implements Comparable, InventoryItemFromSet, } if (!getCollectorNumber().equals(other.getCollectorNumber())) return false; - // colorID can be NULL - if (getColorID() != other.getColorID()) + if (!Objects.equals(flags, other.flags)) return false; return (other.foil == foil) && (other.artIndex == artIndex); } @@ -269,13 +277,7 @@ public class PaperCard implements Comparable, InventoryItemFromSet, */ @Override public int hashCode() { - final int code = (name.hashCode() * 11) + (edition.hashCode() * 59) + - (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; + return Objects.hash(name, edition, collectorNumber, artIndex, foil, flags); } // FIXME: Check @@ -339,6 +341,7 @@ public class PaperCard implements Comparable, InventoryItemFromSet, return Integer.compare(artIndex, o.getArtIndex()); } + @Serial private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { // default deserialization ois.defaultReadObject(); @@ -354,6 +357,14 @@ public class PaperCard implements Comparable, InventoryItemFromSet, 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 public String getImageKey(boolean altState) { String normalizedName = StringUtils.stripAccents(name); @@ -493,4 +504,85 @@ public class PaperCard implements Comparable, InventoryItemFromSet, public boolean isRebalanced() { 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 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 asMap; + public Map toMap() { + if(asMap != null) + return asMap; + Map 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); + } + } } diff --git a/forge-core/src/main/java/forge/item/PaperToken.java b/forge-core/src/main/java/forge/item/PaperToken.java index 81ca24faaa2..7b19e40567d 100644 --- a/forge-core/src/main/java/forge/item/PaperToken.java +++ b/forge-core/src/main/java/forge/item/PaperToken.java @@ -7,7 +7,6 @@ import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Locale; -import java.util.Set; public class PaperToken implements InventoryItemFromSet, IPaperCard { private static final long serialVersionUID = 1L; @@ -153,7 +152,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard { } @Override - public Set getColorID() { + public ColorSet getMarkedColors() { return null; } diff --git a/forge-core/src/main/java/forge/util/ItemPool.java b/forge-core/src/main/java/forge/util/ItemPool.java index 65ba1758dbf..0b274411f73 100644 --- a/forge-core/src/main/java/forge/util/ItemPool.java +++ b/forge-core/src/main/java/forge/util/ItemPool.java @@ -269,6 +269,13 @@ public class ItemPool implements Iterable test) { + for (final T item : items.keySet()) { + if (test.test(item)) + remove(item); + } + } + public void clear() { items.clear(); } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index cb1c2137635..fb1e045e700 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -22,6 +22,7 @@ import forge.GameCommand; import forge.StaticData; import forge.card.CardStateName; import forge.card.CardType.Supertype; +import forge.card.ColorSet; import forge.card.GamePieceType; import forge.card.MagicColor; import forge.deck.DeckSection; @@ -541,8 +542,8 @@ public class GameAction { game.addLeftGraveyardThisTurn(lastKnownInfo); } - if (c.hasChosenColorSpire()) { - copied.setChosenColorID(ImmutableSet.copyOf(c.getChosenColorID())); + if (c.hasMarkedColor()) { + copied.setMarkedColors(c.getMarkedColors()); } copied.updateStateForView(); @@ -2414,15 +2415,14 @@ public class GameAction { 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 // 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()) { - List colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS); String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " + Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2)); SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction); sa.putParam("AILogic", "MostProminentInComputerDeck"); - Set chosenColors = new HashSet<>(takesAction.getController().chooseColors(prompt, sa, 2, 2, colorChoices)); - c.setChosenColorID(chosenColors); + ColorSet chosenColors = ColorSet.fromNames(takesAction.getController().chooseColors(prompt, sa, 2, 2, MagicColor.Constant.ONLY_COLORS)); + c.setMarkedColors(chosenColors); } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java index dc82aa3b2ab..ebaa3a5e933 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java @@ -200,7 +200,7 @@ public class CloneEffect extends SpellAbilityEffect { tgtCard.addRemembered(cardToCopy); } // spire - tgtCard.setChosenColorID(cardToCopy.getChosenColorID()); + tgtCard.setMarkedColors(cardToCopy.getMarkedColors()); game.fireEvent(new GameEventCardStatsChanged(tgtCard)); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java index e98d3fdfc22..ff8be9e0861 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java @@ -314,7 +314,7 @@ public class CopyPermanentEffect extends TokenEffectBase { } } // spire - copy.setChosenColorID(original.getChosenColorID()); + copy.setMarkedColors(original.getMarkedColors()); copy.setTokenSpawningAbility(sa); copy.setGamePieceType(GamePieceType.TOKEN); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 2b25611b352..853459a0f6c 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -296,7 +296,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr private String chosenType2 = ""; private List notedTypes = new ArrayList<>(); private List chosenColors; - private Set chosenColorID; + private ColorSet markedColor; private List chosenName = new ArrayList<>(); private Integer chosenNumber; private Player chosenPlayer; @@ -409,7 +409,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr view.updateClassLevel(this); view.updateDraftAction(this); if (paperCard != null) - setChosenColorID(paperCard.getColorID()); + setMarkedColors(paperCard.getMarkedColors()); } public int getHiddenId() { @@ -2231,18 +2231,18 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr public boolean hasChosenColor(String s) { return chosenColors != null && chosenColors.contains(s); } - public final Set getChosenColorID() { - if (chosenColorID == null) { - return Sets.newHashSet(); + public final ColorSet getMarkedColors() { + if (markedColor == null) { + return ColorSet.getNullColor(); } - return chosenColorID; + return markedColor; } - public final void setChosenColorID(final Set s) { - chosenColorID = s; - view.updateChosenColorID(this); + public final void setMarkedColors(final ColorSet s) { + markedColor = s; + view.updateMarkedColors(this); } - public boolean hasChosenColorSpire() { - return chosenColorID != null && !chosenColorID.isEmpty(); + public boolean hasMarkedColor() { + return markedColor != null && !markedColor.isColorless(); } public final Card getChosenCard() { return getChosenCards().getFirst(); diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 3782eb72c89..017190111f2 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -77,7 +77,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable { private KeywordCollection cachedKeywords = new KeywordCollection(); private CardRarity rarity = CardRarity.Unknown; - private String setCode = CardEdition.UNKNOWN.getCode(); + private String setCode = CardEdition.UNKNOWN_CODE; private final CardStateView view; private final Card card; diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index e130438e132..1bf6f39051b 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -433,11 +433,11 @@ public class CardView extends GameEntityView { void updateChosenColors(Card c) { set(TrackableProperty.ChosenColors, c.getChosenColors()); } - public Set getChosenColorID() { - return get(TrackableProperty.ChosenColorID); + public ColorSet getMarkedColors() { + return get(TrackableProperty.MarkedColors); } - void updateChosenColorID(Card c) { - set(TrackableProperty.ChosenColorID, c.getChosenColorID()); + void updateMarkedColors(Card c) { + set(TrackableProperty.MarkedColors, c.getMarkedColors()); } public FCollectionView getMergedCardsCollection() { return get(TrackableProperty.MergedCardsCollection); diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index e9b9ca9072d..b256fb90ac2 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -703,10 +703,10 @@ public class AbilityManaPart implements java.io.Serializable { return ""; } Card card = sa.getHostCard(); - if (card != null && card.hasChosenColorSpire()) { + if (card != null && card.hasMarkedColor()) { StringBuilder values = new StringBuilder(); - for (String s : card.getChosenColorID()) { - values.append(MagicColor.toShortString(MagicColor.fromName(s))).append(" "); + for (byte c : card.getMarkedColors()) { + values.append(MagicColor.toShortString(c)).append(" "); } return values.toString(); } diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index b748d403df0..59b8f2a260e 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -67,7 +67,6 @@ public enum TrackableProperty { ChosenType2(TrackableTypes.StringType), NotedTypes(TrackableTypes.StringListType), ChosenColors(TrackableTypes.StringListType), - ChosenColorID(TrackableTypes.StringSetType), ChosenCards(TrackableTypes.CardViewCollectionType), ChosenNumber(TrackableTypes.StringType), StoredRolls(TrackableTypes.StringListType), @@ -102,6 +101,7 @@ public enum TrackableProperty { NeedsTransformAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze), NeedsUntapAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze), NeedsTapAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze), + MarkedColors(TrackableTypes.ColorSetType), ImprintedCards(TrackableTypes.CardViewCollectionType), ExiledCards(TrackableTypes.CardViewCollectionType), diff --git a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java index f24977f3cf1..bcd38dd843d 100644 --- a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java +++ b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java @@ -182,7 +182,7 @@ public class GuiDesktop implements IGuiBase { } @Override - public List getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display) { + public List getChoices(final String message, final int min, final int max, final Collection choices, final Collection selected, final Function display) { /*if ((choices != null && !choices.isEmpty() && choices.iterator().next() instanceof GameObject) || selected instanceof GameObject) { System.err.println("Warning: GameObject passed to GUI! Printing stack trace."); Thread.dumpStack(); diff --git a/forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java b/forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java index 87fa9628d2e..24e32480b8b 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java +++ b/forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java @@ -214,7 +214,7 @@ public class CardDetailPanel extends SkinnedPanel { set = state.getSetCode(); rarity = state.getRarity(); } else { - set = CardEdition.UNKNOWN.getCode(); + set = CardEdition.UNKNOWN_CODE; rarity = CardRarity.Unknown; } setInfoLabel.setText(set); diff --git a/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java b/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java index a23e06dc9a8..8bf7a45ff4d 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java +++ b/forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java @@ -134,10 +134,10 @@ public class GuiChoose { return getChoices(message, min, max, choices, null, null); } - public static List getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display) { + public static List getChoices(final String message, final int min, final int max, final Collection choices, final Collection selected, final Function display) { return getChoices(message, min, max, choices, selected, display, null); } - public static List getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display, final CMatchUI matchUI) { + public static List getChoices(final String message, final int min, final int max, final Collection choices, final Collection selected, final Function display, final CMatchUI matchUI) { if (choices == null || choices.isEmpty()) { if (min == 0) { return new ArrayList<>(); diff --git a/forge-gui-desktop/src/main/java/forge/gui/ListChooser.java b/forge-gui-desktop/src/main/java/forge/gui/ListChooser.java index 9a9c5c6fdf8..4410caf709f 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/ListChooser.java +++ b/forge-gui-desktop/src/main/java/forge/gui/ListChooser.java @@ -152,16 +152,13 @@ public class ListChooser { /** @return boolean */ public boolean show() { - return show(list.get(0)); + return show(null); } /** * 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 item) { if (this.called) { throw new IllegalStateException("Already shown"); } @@ -169,8 +166,12 @@ public class ListChooser { do { //invoke later so selected item not set until dialog open SwingUtilities.invokeLater(() -> { - if (list.contains(item)) { - lstChoices.setSelectedValue(item, true); + if (item != null) { + int[] indices = item.stream() + .mapToInt(list::indexOf) + .filter(i -> i >= 0) + .toArray(); + lstChoices.setSelectedIndices(indices); } else { lstChoices.setSelectedIndex(0); diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java index 9bb889ecbb2..c635bb1a0e1 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java @@ -20,16 +20,15 @@ package forge.screens.deckeditor.controllers; import java.awt.Toolkit; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; -import java.util.HashSet; import java.util.List; import java.util.Map.Entry; -import java.util.Set; import javax.swing.JMenu; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; +import forge.card.ColorSet; import forge.card.MagicColor; import forge.deck.CardPool; import forge.deck.Deck; @@ -582,9 +581,9 @@ public abstract class ACEditorBase 0) { GuiUtils.addMenuItem(menu, label, null, () -> { - Set colors = new HashSet<>(GuiChoose.getChoices(localizer.getMessage("lblChooseNColors", Lang.getNumeral(val)), val, val, MagicColor.Constant.ONLY_COLORS)); + List colors = GuiChoose.getChoices(localizer.getMessage("lblChooseNColors", Lang.getNumeral(val)), val, val, MagicColor.Constant.ONLY_COLORS); // make an updated version - PaperCard updated = existingCard.getColorIDVersion(colors); + PaperCard updated = existingCard.copyWithMarkedColors(ColorSet.fromNames(colors)); // remove *quantity* instances of existing card CDeckEditorUI.SINGLETON_INSTANCE.removeSelectedCards(false, 1); // add *quantity* into the deck and set them as selected diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index b1f5006f0bd..79b3652b991 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -24,7 +24,6 @@ import java.awt.image.BufferedImage; import java.util.*; import java.util.List; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -1072,7 +1071,7 @@ public final class CMatchUI } @Override - public List getChoices(final String message, final int min, final int max, final List choices, final T selected, final Function display) { + public List getChoices(final String message, final int min, final int max, final List choices, final List selected, final Function display) { /*if ((choices != null && !choices.isEmpty() && choices.iterator().next() instanceof GameObject) || selected instanceof GameObject) { System.err.println("Warning: GameObject passed to GUI! Printing stack trace."); Thread.dumpStack(); diff --git a/forge-gui-desktop/src/test/java/forge/card/CardDbCardMockTestCase.java b/forge-gui-desktop/src/test/java/forge/card/CardDbCardMockTestCase.java index 20aeb4b1856..8b5d605d9fd 100644 --- a/forge-gui-desktop/src/test/java/forge/card/CardDbCardMockTestCase.java +++ b/forge-gui-desktop/src/test/java/forge/card/CardDbCardMockTestCase.java @@ -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 public void testNullIsReturnedWithWrongInfo() { String wrongEditionCode = "M11"; @@ -307,11 +292,6 @@ public class CardDbCardMockTestCase extends CardMockTestCase { // Wrong collector number card = this.cardDb.getCard(cardNameShivanDragon, editionShivanDragon, "wrongCN"); 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 @@ -2105,7 +2085,7 @@ public class CardDbCardMockTestCase extends CardMockTestCase { public void testGetCardFromUnknownSet() { String unknownCardName = "Unknown Card Name"; PaperCard unknownCard = new PaperCard(CardRules.getUnsupportedCardNamed(unknownCardName), - CardEdition.UNKNOWN.getCode(), CardRarity.Unknown); + CardEdition.UNKNOWN_CODE, CardRarity.Unknown); this.cardDb.addCard(unknownCard); assertTrue(this.cardDb.getAllCards().contains(unknownCard)); assertNotNull(this.cardDb.getAllCards(unknownCardName)); @@ -2114,7 +2094,7 @@ public class CardDbCardMockTestCase extends CardMockTestCase { PaperCard retrievedPaperCard = this.cardDb.getCard(unknownCardName); assertNotNull(retrievedPaperCard); assertEquals(retrievedPaperCard.getName(), unknownCardName); - assertEquals(retrievedPaperCard.getEdition(), CardEdition.UNKNOWN.getCode()); + assertEquals(retrievedPaperCard.getEdition(), CardEdition.UNKNOWN_CODE); } @Test diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index 8c08c740071..27e7d68408a 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -197,7 +197,7 @@ public class GuiMobile implements IGuiBase { } @Override - public List getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display) { + public List getChoices(final String message, final int min, final int max, final Collection choices, final Collection selected, final Function display) { return new WaitCallback>() { @Override public void run() { diff --git a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java index 251dd0d0857..375a631c5fc 100644 --- a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java +++ b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java @@ -120,7 +120,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent { statistic.clear(); newCards.clear(); autoSellCards.clear(); - noSellCards.clear(); AdventureEventController.clear(); AdventureQuestController.clear(); } @@ -134,7 +133,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent { private final ItemPool newCards = new ItemPool<>(PaperCard.class); public final ItemPool autoSellCards = new ItemPool<>(PaperCard.class); - public final ItemPool noSellCards = new ItemPool<>(PaperCard.class); public void create(String n, Deck startingDeck, boolean male, int race, int avatar, boolean isFantasy, boolean isUsingCustomDeck, DifficultyData difficultyData) { clear(); @@ -471,10 +469,43 @@ public class AdventurePlayer implements Serializable, SaveFileContent { } } if (data.containsKey("noSellCards")) { + //Legacy list of unsellable cards. Now done via CardRequest flags. Convert the corresponding cards. PaperCard[] items = (PaperCard[]) data.readObject("noSellCards"); - for (PaperCard item : items) { - if (item != null) - noSellCards.add(item.getNoSellVersion()); + CardPool noSellPool = new CardPool(); + noSellPool.addAllFlat(List.of(items)); + for (Map.Entry 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 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")) { @@ -582,7 +613,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent { data.storeObject("newCards", newCards.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; } @@ -636,11 +666,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent { if (reward.isAutoSell()) { autoSellCards.add(reward.getCard()); refreshEditor(); - } else if (reward.isNoSell()) { - if (reward.getCard() != null) { - noSellCards.add(reward.getCard().getNoSellVersion()); - refreshEditor(); - } } break; case Gold: @@ -908,8 +933,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent { } public int cardSellPrice(PaperCard card) { - int valuable = cards.count(card) - noSellCards.count(card); - if (valuable == 0) { + if (card.hasNoSellValue()) { return 0; } @@ -920,23 +944,13 @@ public class AdventurePlayer implements Serializable, SaveFileContent { } 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) return 0; + if (result == null || result < 1) + return 0; - float earned = 0; - - int valuableCount = cards.count(card) - noSellCards.count(card); - int noValueToSell = result - valuableCount; - 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); - } + int amountToSell = Math.min(result, cards.count(card)); + if(!cards.remove(card, amountToSell)) + return 0; //Failed to sell? + float earned = cardSellPrice(card) * amountToSell; if (addGold) { addGold((int) earned); @@ -1198,10 +1212,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent { return autoSellCards; } - public ItemPool getNoSellCards() { - return noSellCards; - } - public ItemPool getSellableCards() { ItemPool sellableCards = new ItemPool<>(PaperCard.class); sellableCards.addAllFlat(cards.toFlatList()); @@ -1236,7 +1246,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent { collectionCards.addAll(cards); if (!allCards) { collectionCards.removeAll(autoSellCards); - collectionCards.removeAll(noSellCards); } return collectionCards; diff --git a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java index 500e3d4b941..28847b8d322 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java +++ b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java @@ -211,14 +211,13 @@ public class AdventureDeckEditor extends TabPageScreen { }); } - int noSellCount = Current.player().noSellCards.count(card); int autoSellCount = Current.player().autoSellCards.count(card); int sellableCount = Current.player().getSellableCards().count(card); - if (noSellCount > 0) { - FMenuItem unsellableCount = new FMenuItem(Forge.getLocalizer().getMessage("lblUnsellableCount", noSellCount), null, null); - unsellableCount.setEnabled(false); - menu.addItem(unsellableCount); + if (card.hasNoSellValue()) { + FMenuItem unsellableIndicator = new FMenuItem(Forge.getLocalizer().getMessage("lblUnsellable"), null, null); + unsellableIndicator.setEnabled(false); + menu.addItem(unsellableIndicator); } if (sellableCount > 0) { @@ -248,12 +247,13 @@ public class AdventureDeckEditor extends TabPageScreen { if (showCollectionCards) { collectionPool.addAllFlat(AdventurePlayer.current().getCollectionCards(false).toFlatList()); } - if (showNoSellCards) { - collectionPool.addAllFlat(AdventurePlayer.current().getNoSellCards().toFlatList()); - collectionPool.removeAllFlat(cardsInUse.toFlatList()); - } else { - cardsInUse.removeAllFlat(AdventurePlayer.current().getNoSellCards().toFlatList()); - collectionPool.removeAllFlat(cardsInUse.toFlatList()); + else if(showNoSellCards) { + collectionPool.addAll(AdventurePlayer.current().getCollectionCards(false).getFilteredPool(PaperCard::hasNoSellValue)); + } + + collectionPool.removeAllFlat(cardsInUse.toFlatList()); + if (!showNoSellCards) { + collectionPool.removeIf(PaperCard::hasNoSellValue); } if (showAutoSellCards) { collectionPool.addAllFlat(AdventurePlayer.current().getAutoSellCards().toFlatList()); @@ -1120,12 +1120,13 @@ public class AdventureDeckEditor extends TabPageScreen { if (showCollectionCards) { adventurePool.addAll(AdventurePlayer.current().getCollectionCards(false)); } - if (showNoSellCards) { - adventurePool.addAll(AdventurePlayer.current().getNoSellCards()); - adventurePool.removeAll(cardsInUse); - } else { - cardsInUse.removeAll(AdventurePlayer.current().getNoSellCards()); - adventurePool.removeAll(cardsInUse); + else if(showNoSellCards) { + adventurePool.addAll(AdventurePlayer.current().getCollectionCards(false).getFilteredPool(PaperCard::hasNoSellValue)); + } + + adventurePool.removeAll(cardsInUse); + if (!showNoSellCards) { + adventurePool.removeIf(PaperCard::hasNoSellValue); } if (showAutoSellCards) { adventurePool.addAll(AdventurePlayer.current().getAutoSellCards()); diff --git a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java index b7961a2a31c..0ba3b6ae009 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java +++ b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java @@ -223,7 +223,6 @@ public class ConsoleCommandInterpreter { PaperCard card = StaticData.instance().getCommonCards().getCard(s[0]); if (card == null) return "Cannot find card: " + s[0]; Current.player().addCard(card.getNoSellVersion()); - Current.player().noSellCards.add(card.getNoSellVersion()); return "Added card: " + s[0]; }); registerCommand(new String[]{"give", "item"}, s -> { diff --git a/forge-gui-mobile/src/forge/adventure/util/Config.java b/forge-gui-mobile/src/forge/adventure/util/Config.java index d2bf9ca66c6..c9a55d71215 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Config.java +++ b/forge-gui-mobile/src/forge/adventure/util/Config.java @@ -356,7 +356,7 @@ public class Config { final List 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())); rules.setCustom(); - PaperCard card = new PaperCard(rules, CardEdition.UNKNOWN.getCode(), CardRarity.Special) { + PaperCard card = new PaperCard(rules, CardEdition.UNKNOWN_CODE, CardRarity.Special) { @Override public String getImageKey(boolean altState) { return ImageKeys.ADVENTURECARD_PREFIX + getName(); diff --git a/forge-gui-mobile/src/forge/adventure/util/Reward.java b/forge-gui-mobile/src/forge/adventure/util/Reward.java index 1c94fd9bc33..265c1cb71f4 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Reward.java +++ b/forge-gui-mobile/src/forge/adventure/util/Reward.java @@ -44,6 +44,8 @@ public class Reward { this.card = card; count = 0; this.isNoSell = isNoSell; + if(isNoSell) + this.card = card.getNoSellVersion(); } public Reward(Type type, int count) { @@ -60,6 +62,10 @@ public class Reward { this.deck = deck; count = 0; 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() { diff --git a/forge-gui-mobile/src/forge/card/CardImageRenderer.java b/forge-gui-mobile/src/forge/card/CardImageRenderer.java index 8f7f917a461..9c8cda736cb 100644 --- a/forge-gui-mobile/src/forge/card/CardImageRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardImageRenderer.java @@ -1143,7 +1143,7 @@ public class CardImageRenderer { String set = state.getSetCode(); CardRarity rarity = state.getRarity(); if (!canShow) { - set = CardEdition.UNKNOWN.getCode(); + set = CardEdition.UNKNOWN_CODE; rarity = CardRarity.Unknown; } if (!StringUtils.isEmpty(set)) { diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java index 0ea718507d4..c007c33c48c 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java +++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java @@ -9,6 +9,7 @@ import forge.Forge.KeyInputAdapter; import forge.Graphics; import forge.assets.*; import forge.card.CardEdition; +import forge.card.ColorSet; import forge.card.MagicColor; import forge.deck.io.DeckPreferences; import forge.gamemodes.limited.BoosterDraft; @@ -1767,8 +1768,7 @@ public class FDeckEditor extends TabPageScreen { CardManagerPage cardSourceSection; DeckSection destination = DeckSection.matchingSection(card); final DeckSectionPage destinationPage = parentScreen.getPageForSection(destination); - // val for colorID setup - int val; + int markedColorCount = card.getRules().getSetColorID(); switch (deckSection) { default: case Main: @@ -1811,14 +1811,18 @@ public class FDeckEditor extends TabPageScreen { } } 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 -> { - //sort options so current option is on top and selected by default - Set colorChoices = new HashSet<>(MagicColor.Constant.ONLY_COLORS); - GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblChooseAColor", Lang.getNumeral(val)), val, val, colorChoices, new Callback<>() { + Set currentColors; + if(card.getMarkedColors() != null) + 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 public void run(List result) { - addCard(card.getColorIDVersion(new HashSet<>(result))); + addCard(card.copyWithMarkedColors(ColorSet.fromNames(result))); removeCard(card); } }); @@ -1863,14 +1867,18 @@ public class FDeckEditor extends TabPageScreen { } } 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 -> { - //sort options so current option is on top and selected by default - Set colorChoices = new HashSet<>(MagicColor.Constant.ONLY_COLORS); - GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblChooseAColor", Lang.getNumeral(val)), val, val, colorChoices, new Callback<>() { + Set currentColors; + if(card.getMarkedColors() != null) + 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 public void run(List result) { - addCard(card.getColorIDVersion(new HashSet<>(result))); + addCard(card.copyWithMarkedColors(ColorSet.fromNames(result))); removeCard(card); } }); diff --git a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java index 9d34a582970..9e8292b8b0b 100644 --- a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java +++ b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java @@ -17,6 +17,7 @@ import forge.assets.*; import forge.assets.FSkinColor.Colors; import forge.card.*; import forge.card.CardRenderer.CardStackPosition; +import forge.card.mana.ManaCostShard; import forge.deck.*; import forge.deck.io.DeckPreferences; import forge.game.card.CardView; @@ -34,11 +35,8 @@ import forge.util.ImageUtil; import forge.util.TextUtil; import forge.util.Utils; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -980,7 +978,7 @@ public class ImageView extends ItemView { private boolean selected, deckSelectMode, showRanking; private final float IMAGE_SIZE = CardRenderer.MANA_SYMBOL_SIZE; private DeckProxy deckProxy = null; - private String colorID = null; + private String markedColors = null; private FImageComplex deckCover = null; private Texture dpImg = null; //private TextureRegion tr; @@ -1007,8 +1005,10 @@ public class ImageView extends ItemView { draftRankImage = FSkinImage.DRAFTRANK_C; } } - if (((PaperCard) item).getColorID() != null) { - colorID = ((PaperCard) item).getColorID().stream().map(MagicColor::toSymbol).collect(Collectors.joining()); + if (((PaperCard) item).getMarkedColors() != null) { + markedColors = Arrays.stream(((PaperCard) item).getMarkedColors().getOrderedShards()) + .map(ManaCostShard::toString) + .collect(Collectors.joining()); } } } @@ -1082,7 +1082,7 @@ public class ImageView extends ItemView { cardPrice = ((ShopScene) Forge.getCurrentScene()).getCardPrice((PaperCard) item); drawCardLabel(g, "$" + cardPrice, Color.GOLD, x, y ,w ,h); } 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); if (nfs != null) g.drawImage(nfs, x, y, w, h); @@ -1092,8 +1092,8 @@ public class ImageView extends ItemView { } } // spire colors - if (colorID != null && !colorID.isEmpty()) { - textRenderer.drawText(g, colorID, FSkinFont.forHeight(w / 5), Color.WHITE, x, y + h / 4, w, h, y, h, false, Align.center, true); + if (markedColors != null && !markedColors.isEmpty()) { + 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) { CardRenderer.drawCard(g, ((ConquestCommander) item).getCard(), x, y, w, h, pos); diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index 50f009d0608..45e47ff423b 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -660,7 +660,7 @@ public class MatchController extends AbstractGuiGame { } @Override - public List getChoices(final String message, final int min, final int max, final List choices, final T selected, final Function display) { + public List getChoices(final String message, final int min, final int max, final List choices, final List selected, final Function display) { return GuiBase.getInterface().getChoices(message, min, max, choices, selected, display); } diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestAEtherScreen.java b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestAEtherScreen.java index ac451ffdd1e..a8adcd9ad24 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestAEtherScreen.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestAEtherScreen.java @@ -355,7 +355,7 @@ public class ConquestAEtherScreen extends FScreen { caption = caption0; options = ImmutableList.copyOf(options0); setSelectedOption(options.get(0)); - setCommand(e -> GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblSelectCaptionFilter", caption), 0, 1, options, selectedOption, null, new Callback>() { + setCommand(e -> GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblSelectCaptionFilter", caption), 0, 1, options, Set.of(selectedOption), null, new Callback<>() { @Override public void run(List result) { if (!result.isEmpty()) { diff --git a/forge-gui-mobile/src/forge/toolbox/FChoiceList.java b/forge-gui-mobile/src/forge/toolbox/FChoiceList.java index 305a6610d37..4caffab2cba 100644 --- a/forge-gui-mobile/src/forge/toolbox/FChoiceList.java +++ b/forge-gui-mobile/src/forge/toolbox/FChoiceList.java @@ -3,6 +3,7 @@ package forge.toolbox; import static forge.card.CardRenderer.MANA_SYMBOL_SIZE; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -271,6 +272,14 @@ public class FChoiceList extends FList implements ActivateHandler { setSelectedIndex(getIndexOf(choice)); } + public void setSelectedItems(Collection 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) { return choice.toString(); } diff --git a/forge-gui-mobile/src/forge/toolbox/GuiChoose.java b/forge-gui-mobile/src/forge/toolbox/GuiChoose.java index 09e642050b7..ab5f95b4f71 100644 --- a/forge-gui-mobile/src/forge/toolbox/GuiChoose.java +++ b/forge-gui-mobile/src/forge/toolbox/GuiChoose.java @@ -230,7 +230,7 @@ public class GuiChoose { getChoices(message, min, max, choices, null, null, callback); } - public static void getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display, final Callback> callback) { + public static void getChoices(final String message, final int min, final int max, final Collection choices, final Collection selected, final Function display, final Callback> callback) { if (choices == null || choices.isEmpty()) { if (min == 0) { callback.run(new ArrayList<>()); diff --git a/forge-gui-mobile/src/forge/toolbox/ListChooser.java b/forge-gui-mobile/src/forge/toolbox/ListChooser.java index a381d4cef34..e1e2e2458ff 100644 --- a/forge-gui-mobile/src/forge/toolbox/ListChooser.java +++ b/forge-gui-mobile/src/forge/toolbox/ListChooser.java @@ -201,11 +201,8 @@ public class ListChooser extends FContainer { /** * 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 item, final boolean selectMax) { if (called) { throw new IllegalStateException("Already shown"); } @@ -226,7 +223,7 @@ public class ListChooser extends FContainer { } } else { - lstChoices.setSelectedItem(item); + lstChoices.setSelectedItems(item); } optionPane.show(); } diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 3259f6db1a6..b2243b5a215 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -3462,7 +3462,7 @@ AdvBossIntro34=Es gibt kein Zurück! AdvBossIntro35=Jetzt geht es um alles oder nichts! lblYouDied={0}, du bist gestorben!!! lblSellFor=Verkaufen für -lblUnsellableCount=Unverkäuflich ({0}) +lblUnsellable=Unverkäuflich lblAutoSell=AUTOVERKAUF lblNoSell=NICHT VERKAUFEN lbltoSell=Zu verkaufen ({0}/{1}) diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 731850f000e..af858fe6fe1 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -3210,7 +3210,7 @@ AdvBossIntro34=There's no turning back! AdvBossIntro35=It's all or nothing now! lblYouDied={0}, You Died!!! lblSellFor=Sell for -lblUnsellableCount=Unsellable ({0}) +lblUnsellable=Unsellable lblAutoSell=AUTO-SELL lblNoSell=NO-SELL lbltoSell=To Sell ({0}/{1}) diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 9a09fe4c670..a22ba8de6af 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -3473,7 +3473,7 @@ AdvBossIntro34=¡No hay marcha atrás! AdvBossIntro35=¡Ahora es todo o nada! lblYouDied={0}, ¡¡¡Moriste!!! lblSellFor=Vender por -lblUnsellableCount=No vendible ({0}) +lblUnsellable=No vendible lblAutoSell=AUTOVENTA lblNoSell=NO VENDER lbltoSell=Para vender ({0}/{1}) diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index 0e172852fb8..efedf5b2dfb 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -3467,7 +3467,7 @@ AdvBossIntro34=Il n'y a pas de retour en arrière! AdvBossIntro35=C'est tout ou rien maintenant ! lblYouDied={0}, tu es mort !!! lblSellFor=Vendre pour -lblUnsellableCount=Invendable ({0}) +lblUnsellable=Invendable lblAutoSell=VENTE AUTOMATIQUE lblNoSell=NON-VENTE lbltoSell=À vendre ({0}/{1}) diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 6e03ba9cf7d..bb83f78c6ad 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -3465,7 +3465,7 @@ AdvBossIntro34=Non si può tornare indietro! AdvBossIntro35=Adesso è tutto o niente! lblYouDied={0}, sei morto!!! lblSellFor=Vendi per -lblUnsellableCount=Invendibile ({0}) +lblUnsellable=Invendibile lblAutoSell=AUTOVENDITA lblNoSell=NON VENDITA lbltoSell=Da vendere ({0}/{1}) diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 0ba38f6c389..751e9e0ff9a 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -3461,7 +3461,7 @@ AdvBossIntro34=もう後戻りはできない! AdvBossIntro35=もう、オール・オア・ナッシングだ! lblYouDied={0}、死んだ!!! lblSellFor=で売る -lblUnsellableCount=販売不可 ({0}) +lblUnsellable=販売不可 lblAutoSell=自動販売 lblNoSell=販売禁止 lbltoSell=販売する ({0}/{1}) diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 5bc4b09ab91..890b257c718 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -3551,7 +3551,7 @@ AdvBossIntro34=Não há como voltar atrás! AdvBossIntro35=É tudo ou nada agora! lblYouDied={0}, você morreu!!! lblSellFor=Vender por -lblUnsellableCount=Invendável ({0}) +lblUnsellable=Invendável lblAutoSell=VENDA AUTOMÁTICA lblNoSell=NÃO VENDER lbltoSell=Para Vender ({0}/{1}) diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 23e3c280083..430b67d3217 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -3452,7 +3452,7 @@ AdvBossIntro34=没有回头路了! AdvBossIntro35=现在要么全有要么全无! lblYouDied={0},你死了!!! lblSellFor=售价为 -lblUnsellableCount=无法出售({0}) +lblUnsellable=无法出售 lblAutoSell=自动销售 lblNoSell=不卖 lbltoSell=出售({0}/{1}) diff --git a/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java b/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java index 334bc6acea8..08391004abf 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java @@ -64,7 +64,7 @@ public enum ProtocolMethod { 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), 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), sideboard (Mode.SERVER, List.class, CardPool.class, CardPool.class, String.class), chooseSingleEntityForEffect(Mode.SERVER, GameEntityView.class, String.class, List.class, DelayedReveal.class, Boolean.TYPE), diff --git a/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java b/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java index 04f0e1fa70b..a4b391684ac 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java @@ -246,7 +246,7 @@ public class NetGuiGame extends AbstractGuiGame { } @Override - public List getChoices(final String message, final int min, final int max, final List choices, final T selected, final Function display) { + public List getChoices(final String message, final int min, final int max, final List choices, final List selected, final Function display) { return sendAndWait(ProtocolMethod.getChoices, message, min, max, choices, selected, display); } diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestWinLoseController.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestWinLoseController.java index 5ab40dd8a9d..05c22231bb4 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestWinLoseController.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestWinLoseController.java @@ -32,6 +32,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map.Entry; @@ -510,11 +511,11 @@ public class QuestWinLoseController { final List formats = new ArrayList<>(); final String preferredFormat = FModel.getQuestPreferences().getPref(QPref.BOOSTER_FORMAT); - GameFormat pref = null; + Collection pref = null; for (final GameFormat f : FModel.getFormats().getSanctionedList()) { formats.add(f); if (f.toString().equals(preferredFormat)) { - pref = f; + pref = List.of(f); } } diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index 0f4501c4f9b..2a4b251771c 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -129,7 +129,7 @@ public class CardDetailUtil { } 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) { @@ -450,10 +450,10 @@ public class CardDetailUtil { } // chosen spire - if (card.getChosenColorID() != null && !card.getChosenColorID().isEmpty()) { + if (card.getMarkedColors() != null && !card.getMarkedColors().isColorless()) { area.append("\n"); 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(")"); } diff --git a/forge-gui/src/main/java/forge/gui/download/GuiDownloadSetPicturesLQ.java b/forge-gui/src/main/java/forge/gui/download/GuiDownloadSetPicturesLQ.java index 11dfb32c1e0..38d23f64b39 100644 --- a/forge-gui/src/main/java/forge/gui/download/GuiDownloadSetPicturesLQ.java +++ b/forge-gui/src/main/java/forge/gui/download/GuiDownloadSetPicturesLQ.java @@ -55,7 +55,7 @@ public class GuiDownloadSetPicturesLQ extends GuiDownloadService { } 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 continue; } diff --git a/forge-gui/src/main/java/forge/gui/interfaces/IGuiBase.java b/forge-gui/src/main/java/forge/gui/interfaces/IGuiBase.java index 3aedb40ecbf..bf74ef77fc0 100644 --- a/forge-gui/src/main/java/forge/gui/interfaces/IGuiBase.java +++ b/forge-gui/src/main/java/forge/gui/interfaces/IGuiBase.java @@ -38,7 +38,7 @@ public interface IGuiBase { void showImageDialog(ISkinImage image, String message, String title); int showOptionDialog(String message, String title, FSkinProp icon, List options, int defaultOption); String showInputDialog(String message, String title, FSkinProp icon, String initialInput, List inputOptions, boolean isNumeric); - List getChoices(String message, int min, int max, Collection choices, T selected, Function display); + List getChoices(String message, int min, int max, Collection choices, Collection selected, Function display); List order(String title, String top, int remainingObjectsMin, int remainingObjectsMax, List sourceChoices, List destChoices); String showFileDialog(String title, String defaultDir); File getSaveFile(File defaultFile); diff --git a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java index d05fd621644..8889d594cd5 100644 --- a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java @@ -150,7 +150,7 @@ public interface IGuiGame { List getChoices(String message, int min, int max, List choices); - List getChoices(String message, int min, int max, List choices, T selected, Function display); + List getChoices(String message, int min, int max, List choices, List selected, Function display); // Get Integer in range Integer getInteger(String message, int min); diff --git a/forge-gui/src/main/java/forge/gui/util/SGuiChoose.java b/forge-gui/src/main/java/forge/gui/util/SGuiChoose.java index de64b8f41e6..8d20c032978 100644 --- a/forge-gui/src/main/java/forge/gui/util/SGuiChoose.java +++ b/forge-gui/src/main/java/forge/gui/util/SGuiChoose.java @@ -43,7 +43,7 @@ public class SGuiChoose { if ((choices == null) || choices.isEmpty()) { return null; } - final List choice = SGuiChoose.getChoices(message, 0, 1, choices, selected, display); + final List choice = SGuiChoose.getChoices(message, 0, 1, choices, selected == null ? null : List.of(selected), display); return choice.isEmpty() ? null : choice.get(0); } @@ -147,12 +147,12 @@ public class SGuiChoose { return getChoices(message, min, max, Arrays.asList(choices), null, null); } public static List getChoices(final String message, final int min, final int max, final T[] choices, final T selected, final Function 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 List getChoices(final String message, final int min, final int max, final Collection choices) { return getChoices(message, min, max, choices, null, null); } - public static List getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display) { + public static List getChoices(final String message, final int min, final int max, final Collection choices, final Collection selected, final Function display) { return GuiBase.getInterface().getChoices(message, min, max, choices, selected, display); } diff --git a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java index 19fe2078bb8..b4966b4b6c4 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java +++ b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java @@ -54,7 +54,7 @@ public enum ColumnDef { NAME("lblName", "lblName", 180, false, SortState.ASC, from -> { 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(); return sortableName == null ? TextUtil.toSortableName(from.getKey().getName() + spire) : sortableName + spire; }