diff --git a/gemp-lotr/gemp-lotr-common/pom.xml b/gemp-lotr/gemp-lotr-common/pom.xml index 70a5a3e55..ed32ce696 100644 --- a/gemp-lotr/gemp-lotr-common/pom.xml +++ b/gemp-lotr/gemp-lotr-common/pom.xml @@ -12,8 +12,8 @@ com.google.guava - guava-collections - r03 + guava + 33.1.0-jre org.apache.logging.log4j diff --git a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/GameText.java b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/GameText.java new file mode 100644 index 000000000..af3ecadbf --- /dev/null +++ b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/GameText.java @@ -0,0 +1,100 @@ +package com.gempukku.lotro.common; + +import java.text.Normalizer; + +public class GameText { + + public static String ConvertTextToHTML(String text) { + String html = GameText.ConvertNanDeckNotation(text); + html = GameText.ConvertAnnotatedHTMLToFormattedHTML(html); + html = GameText.ConvertPlainQuotesToSmartQuotes(html); + html = Names.AddAccentsToPlainText(html); + + return html; + } + public static String ConvertNanDeckNotation(String text) { + return text + //Replacing a single backslash and other line breaks + .replaceAll("\\\\", "
") + .replaceAll("\n", "
") + .replaceAll("\\n", "
") + //non-breaking space + .replaceAll("_", " ") + //em dash helper + .replaceAll("---", "—") + //en dash helper + .replaceAll("--", "–") + //en dash instead of hyphen in front of negative numbers + .replaceAll("-(\\d)", "–$1"); + } + + public static String ConvertAnnotatedHTMLToFormattedHTML(String text) { + return text + .replaceAll("", "") + .replaceAll("", "") + .replaceAll("", "") + .replaceAll("", "") + .replaceAll("", "") + .replaceAll("", "") + .replaceAll("
", "
"); + //TODO: symbol replacement + } + + public static String ConvertPlainQuotesToSmartQuotes(String text) { + + return text + //Quote characters at the beginning and end of the string are unambiguous + .replaceAll("^'", "‘") + .replaceAll("'$", "’") + .replaceAll("^\"", "“") + .replaceAll("\"$", "”") + //If a space is on one side, also unambiguous + .replaceAll(" '", " ‘") + .replaceAll("' ", "’ ") + .replaceAll(" \"", " “") + .replaceAll("\" ", "” ") + //When next to punctuation, it must be an end quote + .replaceAll("([.,?!/])'", "$1’") + .replaceAll("([.,?!/])\"", "$1”") + //If we've figured out one of the smart quotes and it's right next + // to a dumb quote, we can use that to figure out the other + .replaceAll("\"‘", "“‘") + .replaceAll("\"’", "”’") + .replaceAll("‘\"", "‘“") + .replaceAll("’\"", "’”") + .replaceAll("'“", "‘“") + .replaceAll("'”", "’”") + .replaceAll("“'", "“‘") + .replaceAll("”'", "”’") + //Possessives (the s' case is already handled above) + .replaceAll("'s", "’s") + //If any pair of dumb quotes has evaded the above, we will assume they + // are open or closed depending on order + .replaceAll("\"'", "“‘") + .replaceAll("'\"", "’”") + //Any remaining single quotes must be in a contraction like I'm + .replaceAll("'", "’") + //Any remaining double quotes are just asking for it + .replaceAll("\"", "“"); + } + + public static String ConvertSmartQuotesToPlainQuotes(String text) { + return text + .replaceAll("’", "'") + .replaceAll("‘", "'") + .replaceAll("”", "\"") + .replaceAll("“", "\""); + } + + public static String SanitizeHTMLToSearchText(String text) { + String output = Normalizer.normalize(text, Normalizer.Form.NFD) + //Removes any unicode "combining mark", aka diacritics + .replaceAll("\\p{M}", ""); + output = ConvertSmartQuotesToPlainQuotes(output); + + return output + .replaceAll("", "") + .replaceAll("", "") + .replaceAll("\\s*
\\s*", " "); + } +} diff --git a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/Names.java b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/Names.java index ec2d3787b..03032dea0 100644 --- a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/Names.java +++ b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/Names.java @@ -25,6 +25,7 @@ public class Names { .replaceAll("‘", "'") .replaceAll("”", "\"") .replaceAll("“", "\"") + //Removes any unicode "combining mark", aka diacritics .replaceAll("\\p{M}", "") .replaceAll(" ", "") .replaceAll("_", "") @@ -37,6 +38,73 @@ public class Names { .replaceAll("‘", "'") .replaceAll("”", "\"") .replaceAll("“", "\"") + //Removes any unicode "combining mark", aka diacritics .replaceAll("\\p{M}", ""); } + + public static String AddAccentsToPlainText(String text) { + return text + .replaceAll("Anarion", "Anárion") + .replaceAll("Anduril", "Andúril") + .replaceAll("Annun", "Annûn") + .replaceAll("Attea", "Attëa") + .replaceAll("Barad-dur", "Barad-dûr") + .replaceAll("Baruk khazad", "Baruk Khazâd") + .replaceAll("Cantea", "Cantëa") + .replaceAll("Cirdan", "Círdan") + .replaceAll("Curunir", "Curunír") + .replaceAll("Deagol", "Déagol") + .replaceAll("Deor", "Déor") + .replaceAll("Dinendal", "Dínendal") + .replaceAll("Dunadan", "Dúnadan") + .replaceAll("Dunedain", "Dúnedain") + .replaceAll("Enquea", "Enquëa") + .replaceAll("Eomer", "Éomer") + .replaceAll("Eomund", "Éomund") + .replaceAll("Eored", "Éored") + .replaceAll("Eothain", "Éothain") + .replaceAll("Eowyn", "Éowyn") + .replaceAll("Erethon", "Erethón") + .replaceAll("Fror", "Frór") + .replaceAll("Ghan-buri-ghan", "Ghan-bûri-ghan") + .replaceAll("Gloin", "Glóin") + .replaceAll("Grima", "Gríma") + .replaceAll("Grishnakh", "Grishnákh") + .replaceAll("Guthlaf", "Guthláf") + .replaceAll("Guthwine", "Gúthwinë") + .replaceAll("Hama", "Háma") + .replaceAll("Jarnsmid", "Járnsmid") + .replaceAll("Khazad ai-menu", "Khazâd ai-mênu") + .replaceAll("Khazad-dum", "Khazad-dûm") + .replaceAll("Lathspell", "Láthspell") + .replaceAll("Leod", "Léod") + .replaceAll("Leofric", "Léofric") + .replaceAll("Leowyn", "Léowyn") + .replaceAll("Lorien", "Lórien") + .replaceAll("Lothlorien", "Lothlórien") + .replaceAll("Luthien", "Lúthien") + .replaceAll("Mauhur", "Mauhúr") + .replaceAll("Miruvore", "Miruvóre") + .replaceAll("Mumak", "Mûmak") + .replaceAll("Mumakil", "Mûmakil") + .replaceAll("Namarie", "Namárië") + .replaceAll("Nazgul", "Nazgûl") + .replaceAll("Nertea", "Nertëa") + .replaceAll("Numenor", "Númenor") + .replaceAll("Numenorean", "Númenorean") + .replaceAll("Otsea", "Otsëa") + .replaceAll("Palantir", "Palantír") + .replaceAll("Rhun", "Rhûn") + .replaceAll("Rumil", "Rúmil") + .replaceAll("Simbelmyne", "Simbelmynë") + .replaceAll("Smeagol", "Sméagol") + .replaceAll("Theoden", "Théoden") + .replaceAll("Theodred", "Théodred") + .replaceAll("Thonnas", "Thónnas") + .replaceAll("Thror", "Thrór") + .replaceAll("Toldea", "Toldëa") + .replaceAll("Udun", "Udûn") + .replaceAll("Ugluk", "Uglúk") + .replaceAll("Ulaire", "Úlairë"); + } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/BuiltLotroCardBlueprint.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/BuiltLotroCardBlueprint.java index ad4daa119..8266cdfe8 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/BuiltLotroCardBlueprint.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/BuiltLotroCardBlueprint.java @@ -17,6 +17,7 @@ import com.gempukku.lotro.logic.timing.EffectResult; import java.util.*; public class BuiltLotroCardBlueprint implements LotroCardBlueprint { + private String id; private String title; private String sanitizedTitle; private String subtitle; @@ -38,6 +39,12 @@ public class BuiltLotroCardBlueprint implements LotroCardBlueprint { private SitesBlock allyHomeBlock; private int[] allyHomeSites = new int[0]; + private String gameText; + private String formattedGameText; + private String loreText; + private String promoText; + + private List requirements; private List targetFilters; @@ -84,6 +91,10 @@ public class BuiltLotroCardBlueprint implements LotroCardBlueprint { // Building methods + public void setBlueprintId(String id) { + this.id = id; + } + public void setAllyHomeSites(SitesBlock block, int[] numbers) { this.allyHomeBlock = block; this.allyHomeSites = numbers; @@ -270,6 +281,19 @@ public class BuiltLotroCardBlueprint implements LotroCardBlueprint { this.subtitle = subtitle; } + public void setGameText(String text) { + this.formattedGameText = GameText.ConvertTextToHTML(text.trim()); + this.gameText = GameText.SanitizeHTMLToSearchText(formattedGameText); + } + + public void setLore(String text) { + this.loreText = GameText.ConvertTextToHTML(text.trim()); + } + + public void setPromoText(String text) { + this.promoText = GameText.ConvertTextToHTML(text.trim()); + } + public void setUnique(boolean unique) { this.unique = unique; } @@ -332,6 +356,19 @@ public class BuiltLotroCardBlueprint implements LotroCardBlueprint { // Implemented methods + @Override + public String getId() { + return id; + } + + @Override + public void setId(String id) { + if(this.id != null) + throw new UnsupportedOperationException("Id for this blueprint has already been set"); + + this.id = id; + } + @Override public Side getSide() { return side; diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/LotroCardBlueprintBuilder.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/LotroCardBlueprintBuilder.java index 81eb3d1de..33fa34f63 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/LotroCardBlueprintBuilder.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/LotroCardBlueprintBuilder.java @@ -45,9 +45,9 @@ public class LotroCardBlueprintBuilder implements CardGenerationEnvironment { fieldProcessors.put("target", new TargetFieldProcessor()); fieldProcessors.put("effects", new EffectFieldProcessor()); - fieldProcessors.put("gametext", new NullProcessor()); - fieldProcessors.put("lore", new NullProcessor()); - fieldProcessors.put("promotext", new NullProcessor()); + fieldProcessors.put("gametext", new GameTextFieldProcessor()); + fieldProcessors.put("lore", new LoreFieldProcessor()); + fieldProcessors.put("promotext", new PromoTextFieldProcessor()); //Soon! But not yet fieldProcessors.put("cardinfo", new NullProcessor()); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/GameTextFieldProcessor.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/GameTextFieldProcessor.java new file mode 100644 index 000000000..01b72a49a --- /dev/null +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/GameTextFieldProcessor.java @@ -0,0 +1,15 @@ +package com.gempukku.lotro.cards.build.field; + +import com.gempukku.lotro.cards.build.BuiltLotroCardBlueprint; +import com.gempukku.lotro.cards.build.CardGenerationEnvironment; +import com.gempukku.lotro.cards.build.FieldProcessor; +import com.gempukku.lotro.cards.build.InvalidCardDefinitionException; + +public class GameTextFieldProcessor implements FieldProcessor { + @Override + public void processField(String key, Object value, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { + final String[] textLines = FieldUtils.getStringArray(value, key); + String gametext = String.join("
", textLines); + blueprint.setGameText(gametext); + } +} diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/LoreFieldProcessor.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/LoreFieldProcessor.java new file mode 100644 index 000000000..320a88857 --- /dev/null +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/LoreFieldProcessor.java @@ -0,0 +1,15 @@ +package com.gempukku.lotro.cards.build.field; + +import com.gempukku.lotro.cards.build.BuiltLotroCardBlueprint; +import com.gempukku.lotro.cards.build.CardGenerationEnvironment; +import com.gempukku.lotro.cards.build.FieldProcessor; +import com.gempukku.lotro.cards.build.InvalidCardDefinitionException; + +public class LoreFieldProcessor implements FieldProcessor { + @Override + public void processField(String key, Object value, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { + final String[] textLines = FieldUtils.getStringArray(value, key); + String lore = String.join("
", textLines); + blueprint.setLore(lore); + } +} diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/PromoTextFieldProcessor.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/PromoTextFieldProcessor.java new file mode 100644 index 000000000..695be7446 --- /dev/null +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/PromoTextFieldProcessor.java @@ -0,0 +1,15 @@ +package com.gempukku.lotro.cards.build.field; + +import com.gempukku.lotro.cards.build.BuiltLotroCardBlueprint; +import com.gempukku.lotro.cards.build.CardGenerationEnvironment; +import com.gempukku.lotro.cards.build.FieldProcessor; +import com.gempukku.lotro.cards.build.InvalidCardDefinitionException; + +public class PromoTextFieldProcessor implements FieldProcessor { + @Override + public void processField(String key, Object value, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { + final String[] textLines = FieldUtils.getStringArray(value, key); + String promoText = String.join("
", textLines); + blueprint.setPromoText(promoText); + } +} diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/TitleFieldProcessor.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/TitleFieldProcessor.java index 68a0a3909..2eccb26d9 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/TitleFieldProcessor.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/TitleFieldProcessor.java @@ -8,15 +8,10 @@ import com.gempukku.lotro.cards.build.InvalidCardDefinitionException; public class TitleFieldProcessor implements FieldProcessor { @Override public void processField(String key, Object value, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - boolean unique = false; String title = FieldUtils.getString(value, key); - //Deprecated, don't do this. Use the "unique" field instead. - if (title.startsWith("*")) { - unique = true; - title = title.substring(1); - } - if (unique) - blueprint.setUnique(true); + if (title.startsWith("*")) + throw new InvalidCardDefinitionException("Do not use '*' to indicate uniqueness. Use the 'unique' boolean field instead."); + blueprint.setTitle(title); } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroCardBlueprint.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroCardBlueprint.java index 382d43326..437f378c4 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroCardBlueprint.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroCardBlueprint.java @@ -16,6 +16,15 @@ public interface LotroCardBlueprint { LEFT, RIGHT } + default LotroCardBlueprint getParent() { + return this; + } + + String getId(); + + //This isn't ideal, but there's 2,000 java cards that already exist. + // Adding the argument to their constructors would be a chore. + void setId(String id); Side getSide(); CardType getCardType(); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroCardBlueprintLibrary.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroCardBlueprintLibrary.java index e13ac5568..352280a3b 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroCardBlueprintLibrary.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroCardBlueprintLibrary.java @@ -200,16 +200,19 @@ public class LotroCardBlueprintLibrary { final JSONObject cardsFile = (JSONObject) parser.parse(json); final Set> cardsInFile = cardsFile.entrySet(); for (Map.Entry cardEntry : cardsInFile) { - String blueprint = cardEntry.getKey(); - if (validateNew) - if (_blueprints.containsKey(blueprint)) - logger.error(blueprint + " - Replacing existing card definition!"); + String blueprintId = cardEntry.getKey(); + if (validateNew && _blueprints.containsKey(blueprintId)) { + logger.error(blueprintId + " from " + + file.getAbsolutePath() + " - Replacing existing card definition!"); + } final JSONObject cardDefinition = cardEntry.getValue(); try { - final LotroCardBlueprint lotroCardBlueprint = cardBlueprintBuilder.buildFromJson(cardDefinition); - _blueprints.put(blueprint, lotroCardBlueprint); + final var lotroCardBlueprint = cardBlueprintBuilder.buildFromJson(cardDefinition); + lotroCardBlueprint.setId(blueprintId); + _blueprints.put(blueprintId, lotroCardBlueprint); } catch (InvalidCardDefinitionException exp) { - logger.error("Unable to load card " + blueprint, exp); + logger.error("Unable to load card " + blueprintId + + " from " + file.getAbsolutePath(), exp); } } } catch (FileNotFoundException exp) { @@ -238,6 +241,7 @@ public class LotroCardBlueprintLibrary { try { // Ensure it's loaded LotroCardBlueprint blueprint = findJavaBlueprint(blueprintId); + blueprint.setId(blueprintId); _blueprints.put(blueprintId, blueprint); } catch (CardNotFoundException exp) { throw new RuntimeException("Unable to start the server, due to invalid (missing) card definition - " + blueprintId); @@ -442,30 +446,6 @@ public class LotroCardBlueprintLibrary { return String.valueOf(id); } - private void determineNeedsLoadingFlag(JSONObject setDefinition, Set flags) { - Boolean needsLoading = (Boolean) setDefinition.get("needsLoading"); - if (needsLoading == null) - needsLoading = true; - if (needsLoading) - flags.add("needsLoading"); - } - - private void determineMerchantableFlag(JSONObject setDefinition, Set flags) { - Boolean merchantable = (Boolean) setDefinition.get("merchantable"); - if (merchantable == null) - merchantable = true; - if (merchantable) - flags.add("merchantable"); - } - - private void determineOriginalSetFlag(JSONObject setDefinition, Set flags) { - Boolean originalSet = (Boolean) setDefinition.get("originalSet"); - if (originalSet == null) - originalSet = true; - if (originalSet) - flags.add("originalSet"); - } - private void readSetRarityFile(DefaultSetDefinition rarity, String setNo, String rarityFile) throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(_raritiesFolder, rarityFile)), StandardCharsets.UTF_8)); try { diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/cardtype/AbstractLotroCardBlueprint.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/cardtype/AbstractLotroCardBlueprint.java index c4b9705f3..4b82d90ef 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/cardtype/AbstractLotroCardBlueprint.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/cardtype/AbstractLotroCardBlueprint.java @@ -18,6 +18,8 @@ import java.util.Map; import java.util.Set; public abstract class AbstractLotroCardBlueprint implements LotroCardBlueprint { + + private String _id; private final int _twilightCost; private final String _name; private final String _sanitizedName; @@ -89,6 +91,19 @@ public abstract class AbstractLotroCardBlueprint implements LotroCardBlueprint { return _cardType; } + @Override + public final String getId() { + return _id; + } + + @Override + public void setId(String id) { + if(_id != null) + throw new UnsupportedOperationException("Id for this blueprint has already been set"); + + _id = id; + } + @Override public final Side getSide() { return _side;