Skip to content

Commit c6c0d4f

Browse files
committed
Add per-printing card art language index and language-aware unique card selection
1 parent 7fc7287 commit c6c0d4f

16 files changed

Lines changed: 54220 additions & 13 deletions

File tree

forge-core/src/main/java/forge/StaticData.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import java.io.File;
1717
import java.util.*;
18+
import java.util.function.BiPredicate;
1819
import java.util.function.Predicate;
1920
import java.util.concurrent.CompletableFuture;
2021
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -64,9 +65,12 @@ public class StaticData {
6465
private static StaticData lastInstance = null;
6566

6667
public StaticData(CardStorageReader cardReader, CardStorageReader customCardReader, String editionFolder, String customEditionsFolder, String blockDataFolder, String cardArtPreference, boolean enableUnknownCards, boolean loadNonLegalCards) {
67-
this(cardReader, null, customCardReader, null, editionFolder, customEditionsFolder, blockDataFolder, "", cardArtPreference, enableUnknownCards, loadNonLegalCards, false, false);
68+
this(cardReader, null, customCardReader, null, editionFolder, customEditionsFolder, blockDataFolder, "", cardArtPreference, enableUnknownCards, loadNonLegalCards, false, false, null);
6869
}
6970
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, CardStorageReader customCardReader, CardStorageReader customTokenReader, String editionFolder, String customEditionsFolder, String blockDataFolder, String setLookupFolder, String cardArtPreference, boolean enableUnknownCards, boolean loadNonLegalCards, boolean allowCustomCardsInDecksConformance, boolean enableSmartCardArtSelection) {
71+
this(cardReader, tokenReader, customCardReader, customTokenReader, editionFolder, customEditionsFolder, blockDataFolder, setLookupFolder, cardArtPreference, enableUnknownCards, loadNonLegalCards, allowCustomCardsInDecksConformance, enableSmartCardArtSelection, null);
72+
}
73+
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, CardStorageReader customCardReader, CardStorageReader customTokenReader, String editionFolder, String customEditionsFolder, String blockDataFolder, String setLookupFolder, String cardArtPreference, boolean enableUnknownCards, boolean loadNonLegalCards, boolean allowCustomCardsInDecksConformance, boolean enableSmartCardArtSelection, BiPredicate<String, String> preferredLanguageAvailability) {
7074
this.cardReader = cardReader;
7175
this.tokenReader = tokenReader;
7276
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
@@ -131,6 +135,8 @@ public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, C
131135

132136
commonCards.setCardArtPreference(cardArtPreference);
133137
variantCards.setCardArtPreference(cardArtPreference);
138+
commonCards.setPreferredLanguageAvailability(preferredLanguageAvailability);
139+
variantCards.setPreferredLanguageAvailability(preferredLanguageAvailability);
134140

135141
//must initialize after establish field values for the sake of card image logic
136142
commonCards.initialize(false, false, enableUnknownCards);
@@ -956,6 +962,11 @@ public void setCardArtPreference(String artPreference) {
956962
this.variantCards.setCardArtPreference(artPreference);
957963
}
958964

965+
public void setPreferredLanguageAvailability(BiPredicate<String, String> availability) {
966+
this.commonCards.setPreferredLanguageAvailability(availability);
967+
this.variantCards.setPreferredLanguageAvailability(availability);
968+
}
969+
959970
public boolean isEnabledCardArtSmartSelection() {
960971
return this.enableSmartCardArtSelection;
961972
}

forge-core/src/main/java/forge/card/CardDb.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import java.util.*;
3434
import java.util.Map.Entry;
35+
import java.util.function.BiPredicate;
3536
import java.util.function.Predicate;
3637
import java.util.stream.Collectors;
3738
import java.util.stream.Stream;
@@ -98,6 +99,8 @@ public int compare(CardEdition o1, CardEdition o2) {
9899

99100
// Placeholder to setup default art Preference - to be moved from Static Data!
100101
private CardArtPreference defaultCardArtPreference;
102+
private BiPredicate<String, String> preferredLanguageAvailability;
103+
private boolean initialized;
101104

102105
public static class CardRequest {
103106
public String cardName;
@@ -536,6 +539,7 @@ public void initialize(boolean logMissingPerEdition, boolean logMissingSummary,
536539
}
537540
}
538541

542+
initialized = true;
539543
reIndex();
540544
}
541545

@@ -583,11 +587,23 @@ private void reIndex() {
583587
private PaperCard getBestUniquePrint(final Collection<PaperCard> cards) {
584588
return cards.stream()
585589
.filter(pc -> !pc.getRarity().equals(CardRarity.Special))
586-
.min(Comparator.comparing((PaperCard pc) -> editions.get(pc.getEdition()), defaultCardArtPreference)
590+
.min(Comparator.comparing((PaperCard pc) -> isPreferredLanguagePrint(pc) ? 0 : 1)
591+
.thenComparing((PaperCard pc) -> editions.get(pc.getEdition()), defaultCardArtPreference)
587592
.thenComparing(PaperCard::getCollectorNumber))
588593
.orElseGet(() -> cards.iterator().next());
589594
}
590595

596+
private boolean isPreferredLanguagePrint(PaperCard pc) {
597+
if (preferredLanguageAvailability == null) {
598+
return false;
599+
}
600+
CardEdition edition = editions.get(pc.getEdition());
601+
if (edition == null) {
602+
return false;
603+
}
604+
return preferredLanguageAvailability.test(edition.getScryfallCode(), pc.getCollectorNumber());
605+
}
606+
591607
public boolean setPreferredArt(String cardName, String setCode, int artIndex) {
592608
String cardRequestForPreferredArt = CardRequest.compose(cardName, setCode, artIndex);
593609
PaperCard pc = this.getCard(cardRequestForPreferredArt);
@@ -613,6 +629,13 @@ public void setCardArtPreference(boolean latestArt, boolean coreExpansionOnly){
613629
}
614630
}
615631

632+
public void setPreferredLanguageAvailability(BiPredicate<String, String> availability) {
633+
this.preferredLanguageAvailability = availability;
634+
if (initialized) {
635+
reIndex();
636+
}
637+
}
638+
616639
/**
617640
* Retrieves a CardRules matching the provided name.
618641
* @param allowAltNames If false, the name must be the exact name of the card in its default state. If true, flavor

forge-gui-desktop/src/main/java/forge/itemmanager/CardManager.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import forge.screens.home.quest.DialogChooseFormats;
1717
import forge.screens.home.quest.DialogChooseSets;
1818
import forge.screens.match.controllers.CDetailPicture;
19+
import forge.util.CardLanguageIndex;
1920
import forge.util.Localizer;
2021

2122
import javax.swing.*;
@@ -62,6 +63,9 @@ protected Iterable<Entry<PaperCard, Integer>> getUnique(Iterable<Entry<PaperCard
6263
entriesByName.put(cardName, item);
6364
}
6465

66+
final String preferredLang = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_PREFER_LANG_FOR_UNIQUE_CARDS)
67+
? CardLanguageIndex.getPreferredCardLangCode() : null;
68+
6569
// Now we're ready to go on with retrieving cards to be returned
6670
Map<PaperCard, Integer> cardsMap = new HashMap<>();
6771
for (String cardName : entriesByName.keySet()) {
@@ -85,7 +89,7 @@ protected Iterable<Entry<PaperCard, Integer>> getUnique(Iterable<Entry<PaperCard
8589
// Policy is too strict for current PaperCard in Entry. Remove any filter
8690
acceptedEditions.addAll(entriesByEdition.keySet());
8791

88-
Entry<PaperCard, Integer> cardEntry = getCardEntryToAdd(entriesByEdition, acceptedEditions);
92+
Entry<PaperCard, Integer> cardEntry = getCardEntryToAdd(entriesByEdition, acceptedEditions, preferredLang);
8993
if (cardEntry != null)
9094
cardsMap.put(cardEntry.getKey(), cardEntry.getValue());
9195
}
@@ -95,7 +99,7 @@ protected Iterable<Entry<PaperCard, Integer>> getUnique(Iterable<Entry<PaperCard
9599
// Select the Card Art Entry to add, based on current Card Art Preference Order.
96100
// This method will prefer the entry currently having an image. If that's not the case,
97101
private Entry<PaperCard, Integer> getCardEntryToAdd(ListMultimap<CardEdition, Entry<PaperCard, Integer>> entriesByEdition,
98-
List<CardEdition> acceptedEditions) {
102+
List<CardEdition> acceptedEditions, String preferredLang) {
99103
// Use standard sort + index, for better performance!
100104
Collections.sort(acceptedEditions);
101105
if (StaticData.instance().cardArtPreferenceIsLatest())
@@ -107,6 +111,20 @@ private Entry<PaperCard, Integer> getCardEntryToAdd(ListMultimap<CardEdition, En
107111
CardEdition cardEdition = editionIterator.next();
108112
// These are now the entries to add to Cards Map
109113
List<Entry<PaperCard, Integer>> cardEntries = entriesByEdition.get(cardEdition);
114+
115+
if (preferredLang != null) {
116+
for (Entry<PaperCard, Integer> entry : cardEntries) {
117+
if (isPreferredLanguagePrint(entry.getKey(), preferredLang)) {
118+
if (firstCandidateEntryFound == null)
119+
firstCandidateEntryFound = entry;
120+
candidateEntry = entry;
121+
break;
122+
}
123+
}
124+
if (candidateEntry != null)
125+
break;
126+
}
127+
110128
Iterator<Entry<PaperCard, Integer>> entriesIterator = cardEntries.iterator();
111129
candidateEntry = entriesIterator.hasNext() ? entriesIterator.next() : null;
112130
if (candidateEntry != null && firstCandidateEntryFound == null)
@@ -123,6 +141,14 @@ private Entry<PaperCard, Integer> getCardEntryToAdd(ListMultimap<CardEdition, En
123141
return candidateEntry != null ? candidateEntry : firstCandidateEntryFound;
124142
}
125143

144+
private boolean isPreferredLanguagePrint(PaperCard card, String preferredLang) {
145+
CardEdition edition = StaticData.instance().getEditions().get(card.getEdition());
146+
if (edition == null) {
147+
return false;
148+
}
149+
return CardLanguageIndex.instance().isAvailableInLanguage(edition.getScryfallCode(), card.getCollectorNumber(), preferredLang);
150+
}
151+
126152
/* Static overrides shared with SpellShopManager*/
127153

128154
public static void addDefaultFilters(final ItemManager<? super PaperCard> itemManager) {

forge-gui-desktop/src/main/java/forge/itemmanager/ItemManager.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@
1818
package forge.itemmanager;
1919

2020
import com.google.common.collect.Lists;
21+
import forge.StaticData;
22+
import forge.card.CardEdition;
2123
import forge.gui.GuiUtils;
2224
import forge.gui.UiCommand;
25+
import forge.item.IPaperCard;
2326
import forge.item.InventoryItem;
2427
import forge.item.PaperCard;
2528
import forge.itemmanager.filters.ItemFilter;
29+
import forge.localinstance.properties.ForgePreferences.FPref;
30+
import forge.model.FModel;
2631
import forge.itemmanager.views.ImageView;
2732
import forge.itemmanager.views.ItemListView;
2833
import forge.itemmanager.views.ItemTableColumn;
@@ -998,7 +1003,35 @@ public void refresh() {
9981003
}
9991004

10001005
protected Iterable<Entry<T, Integer>> getUnique(final Iterable<Entry<T, Integer>> items) {
1001-
return Aggregates.uniqueByLast(items, from -> from.getKey().getName());
1006+
if (!FModel.getPreferences().getPrefBoolean(FPref.UI_PREFER_LANG_FOR_UNIQUE_CARDS)) {
1007+
return Aggregates.uniqueByLast(items, from -> from.getKey().getName());
1008+
}
1009+
final String preferredLang = CardLanguageIndex.getPreferredCardLangCode();
1010+
if (preferredLang == null) {
1011+
return Aggregates.uniqueByLast(items, from -> from.getKey().getName());
1012+
}
1013+
1014+
final Map<String, Entry<T, Integer>> uniques = new LinkedHashMap<>();
1015+
for (final Entry<T, Integer> entry : items) {
1016+
final String key = entry.getKey().getName();
1017+
final Entry<T, Integer> existing = uniques.get(key);
1018+
if (existing == null || isPreferredLanguagePrint(entry.getKey(), preferredLang)) {
1019+
uniques.put(key, entry);
1020+
}
1021+
}
1022+
return uniques.values();
1023+
}
1024+
1025+
private boolean isPreferredLanguagePrint(final T item, final String preferredLang) {
1026+
if (!(item instanceof IPaperCard)) {
1027+
return false;
1028+
}
1029+
final IPaperCard card = (IPaperCard) item;
1030+
final CardEdition edition = StaticData.instance().getEditions().get(card.getEdition());
1031+
if (edition == null) {
1032+
return false;
1033+
}
1034+
return CardLanguageIndex.instance().isAvailableInLanguage(edition.getScryfallCode(), card.getCollectorNumber(), preferredLang);
10021035
}
10031036

10041037
/**

forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import forge.toolbox.FComboBoxPanel;
2929
import forge.toolbox.FLabel;
3030
import forge.toolbox.FOptionPane;
31+
import forge.util.CardLanguageIndex;
3132
import forge.util.Localizer;
3233
import forge.view.arcane.PlayArea;
3334
import org.apache.commons.lang3.StringUtils;
@@ -206,6 +207,7 @@ public void initialize() {
206207
initializeDefaultFontSizeComboBox();
207208
initializeCardArtFormatComboBox();
208209
initializeCardArtPreference();
210+
initializeCardDownloadLanguageComboBox();
209211
initializeAutoUpdaterComboBox();
210212
initializeServerUPnPComboBox();
211213
initializeMulliganRuleComboBox();
@@ -419,6 +421,46 @@ private void initializeAutoUpdaterComboBox() {
419421
panel.setComboBox(comboBox, selectedItem);
420422
}
421423

424+
private void initializeCardDownloadLanguageComboBox() {
425+
final Map<String, String> cardLangMapping = ForgeConstants.getScryfallCardLanguageMapping();
426+
final String[] localizedOptions = cardLangMapping.keySet().toArray(new String[0]);
427+
428+
final FPref cardLangPreference = FPref.UI_CARD_DOWNLOAD_LANG;
429+
430+
final FComboBoxPanel<String> panel = this.view.getCbpCardDownloadLangComboBoxPanel();
431+
final FComboBox<String> comboBox = createLocalizedComboBox(localizedOptions, cardLangPreference, cardLangMapping);
432+
comboBox.addItemListener(e -> applyPreferredLanguageAvailability());
433+
434+
final String savedCode = this.prefs.getPref(cardLangPreference);
435+
final String selectedDisplayName = cardLangMapping.entrySet().stream()
436+
.filter(entry -> entry.getValue().equals(savedCode))
437+
.map(Map.Entry::getKey)
438+
.findFirst()
439+
.orElse("English");
440+
441+
panel.setComboBox(comboBox, selectedDisplayName);
442+
443+
final JCheckBox cbPreferLang = this.view.getCbPreferLangForUniqueCards();
444+
cbPreferLang.setSelected(this.prefs.getPrefBoolean(FPref.UI_PREFER_LANG_FOR_UNIQUE_CARDS));
445+
cbPreferLang.addItemListener(e -> {
446+
this.prefs.setPref(FPref.UI_PREFER_LANG_FOR_UNIQUE_CARDS, String.valueOf(cbPreferLang.isSelected()));
447+
this.prefs.save();
448+
applyPreferredLanguageAvailability();
449+
});
450+
451+
applyPreferredLanguageAvailability();
452+
}
453+
454+
private void applyPreferredLanguageAvailability() {
455+
String langCode = this.prefs.getPref(FPref.UI_CARD_DOWNLOAD_LANG);
456+
boolean preferForUnique = this.prefs.getPrefBoolean(FPref.UI_PREFER_LANG_FOR_UNIQUE_CARDS);
457+
if (!preferForUnique || langCode == null || langCode.isEmpty() || "en".equalsIgnoreCase(langCode)) {
458+
FModel.getMagicDb().setPreferredLanguageAvailability(null);
459+
} else {
460+
FModel.getMagicDb().setPreferredLanguageAvailability((setCode, cn) -> CardLanguageIndex.instance().isAvailableInLanguage(setCode, cn, langCode));
461+
}
462+
}
463+
422464
private void initializeServerUPnPComboBox() {
423465
// Step 1: Define the localized strings and mappings
424466
final Map<String, String> upnpPreferenceMapping = ForgeConstants.getUPnPPreferenceMapping();

forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
8686
private final JCheckBox cbSROptimize = new OptionsCheckBox(localizer.getMessage("cbSROptimize"));
8787
private final JCheckBox cbFilteredHands = new OptionsCheckBox(localizer.getMessage("cbFilteredHands"));
8888
private final JCheckBox cbImageFetcher = new OptionsCheckBox(localizer.getMessage("cbImageFetcher"));
89+
private final JCheckBox cbPreferLangForUniqueCards = new OptionsCheckBox(localizer.getMessage("cbPreferLangForUniqueCards"));
8990
private final JCheckBox cbDisableCardImages = new OptionsCheckBox(localizer.getMessage("lblDisableCardImages"));
9091
private final JCheckBox cbCloneImgSource = new OptionsCheckBox(localizer.getMessage("cbCloneImgSource"));
9192
private final JCheckBox cbScaleLarger = new OptionsCheckBox(localizer.getMessage("cbScaleLarger"));
@@ -145,6 +146,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
145146
private final FComboBoxPanel<String> cbpDefaultFontSize = new FComboBoxPanel<>(localizer.getMessage("cbpDefaultFontSize")+":");
146147
private final FComboBoxPanel<String> cbpCardArtFormat = new FComboBoxPanel<>(localizer.getMessage("cbpCardArtFormat")+":");
147148
private final FComboBoxPanel<String> cbpCardArtPreference = new FComboBoxPanel<>(localizer.getMessage("lblPreferredArt")+":");
149+
private final FComboBoxPanel<String> cbpCardDownloadLang = new FComboBoxPanel<>(localizer.getMessage("cbpCardDownloadLang")+":");
148150
private final FComboBoxPanel<String> cbpMulliganRule = new FComboBoxPanel<>(localizer.getMessage("cbpMulliganRule")+":");
149151
private final FComboBoxPanel<String> cbpSoundSets = new FComboBoxPanel<>(localizer.getMessage("cbpSoundSets")+":");
150152
private final FComboBoxPanel<String> cbpMusicSets = new FComboBoxPanel<>(localizer.getMessage("cbpMusicSets")+":");
@@ -396,6 +398,12 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
396398
pnlPrefs.add(cbImageFetcher, titleConstraints);
397399
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlImageFetcher")), descriptionConstraints);
398400

401+
pnlPrefs.add(cbpCardDownloadLang, comboBoxConstraints);
402+
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlCardDownloadLang")), descriptionConstraints);
403+
404+
pnlPrefs.add(cbPreferLangForUniqueCards, titleConstraints);
405+
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlPreferLangForUniqueCards")), descriptionConstraints);
406+
399407
pnlPrefs.add(cbDisableCardImages, titleConstraints);
400408
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlDisableCardImages")), descriptionConstraints);
401409

@@ -782,6 +790,14 @@ public JCheckBox getCbImageFetcher() {
782790
return cbImageFetcher;
783791
}
784792

793+
public JCheckBox getCbPreferLangForUniqueCards() {
794+
return cbPreferLangForUniqueCards;
795+
}
796+
797+
public FComboBoxPanel<String> getCbpCardDownloadLangComboBoxPanel() {
798+
return cbpCardDownloadLang;
799+
}
800+
785801
/** @return {@link javax.swing.JCheckBox} */
786802
public JCheckBox getCbDisableCardImages() {
787803
return cbDisableCardImages;

0 commit comments

Comments
 (0)