From 77decf983e795ecbb36e3f4431c05d363ac114a6 Mon Sep 17 00:00:00 2001 From: David Le Bansais Date: Sat, 1 Jun 2024 15:44:54 +0200 Subject: [PATCH] Added support for "white" mods on items --- .../Preprocessing/Objects/Ability/Ability.cs | 65 +++- Pipeline/Preprocessing/Objects/Item/Item.cs | 15 +- Preprocessor/Preprocessor.csproj | 4 +- .../CombatParser/CombatParser.Sentences.cs | 1 + Translator/CombatParser/CombatParser.cs | 334 ++++++++++++++++-- Translator/EffectVerification.cs | 1 + Translator/Enums/AbilityKeyword.cs | 2 + Translator/TextMaps.cs | 2 + Translator/Translator.csproj | 4 +- 9 files changed, 393 insertions(+), 35 deletions(-) diff --git a/Pipeline/Preprocessing/Objects/Ability/Ability.cs b/Pipeline/Preprocessing/Objects/Ability/Ability.cs index 4f37788d..c61f7f77 100644 --- a/Pipeline/Preprocessing/Objects/Ability/Ability.cs +++ b/Pipeline/Preprocessing/Objects/Ability/Ability.cs @@ -91,13 +91,48 @@ public Ability(RawAbility rawAbility) // Remove Lint_NotLearnable for an ability that we can actually learn. if (InternalName == "SwordSlash" && Keywords is not null) + Keywords = RemoveKeyword(Keywords, "Lint_NotLearnable"); + + // Add new keywords needed for combat parsing. + if (Skill == "Knife" && DamageType == "Slashing") + Keywords = AddKeyword(Keywords, "KnifeSlashing"); + if (Skill == "Staff" && DamageType == "Crushing") + Keywords = AddKeyword(Keywords, "StaffCrushing"); + + if (Name is not null && Name.StartsWith("Life Steal") && SpecialInfo is not null && SpecialInfo.StartsWith("Steals ")) { - List KeywordsList = Keywords.ToList(); - KeywordsList.Remove("Lint_NotLearnable"); - Keywords = KeywordsList.ToArray(); + Debug.Assert(PvE is not null && PvE.SpecialValues is null); + + SpecialValue LifeStealSpecialValue = new(); + LifeStealSpecialValue.Label = "Steals"; + LifeStealSpecialValue.Suffix = "Health."; + string[] Parts = SpecialInfo.Split(' '); + Debug.Assert(Parts.Length == 3); + LifeStealSpecialValue.Value = int.Parse(Parts[1]); + + SpecialInfo = null; + PvE!.SpecialValues = new SpecialValue[] { LifeStealSpecialValue }; } } + private static string[]? AddKeyword(string[]? Keywords, string keywordToAdd) + { + List KeywordsList = Keywords.ToList(); + KeywordsList.Add(keywordToAdd); + Keywords = KeywordsList.ToArray(); + + return Keywords; + } + + private static string[]? RemoveKeyword(string[]? Keywords, string keywordToRemove) + { + List KeywordsList = Keywords.ToList(); + KeywordsList.Remove(keywordToRemove); + Keywords = KeywordsList.ToArray(); + + return Keywords; + } + private static ConditionalKeyword[]? ParseConditionalKeywords(RawConditionalKeyword[]? rawConditionalKeywords) { if (rawConditionalKeywords is null) @@ -368,11 +403,27 @@ public RawAbility ToRawAbility() Result.WorksWhileMounted = WorksWhileMounted; Result.WorksWhileStunned = WorksWhileStunned; - if (InternalName == "SwordSlash" && Result.Keywords is not null) + if (Result.Keywords is not null) + { + if (InternalName == "SwordSlash") + Result.Keywords = AddKeyword(Result.Keywords, "Lint_NotLearnable"); + + if (Skill == "Knife" && DamageType == "Slashing") + Result.Keywords = RemoveKeyword(Result.Keywords, "KnifeSlashing"); + if (Skill == "Staff" && DamageType == "Crushing") + Result.Keywords = RemoveKeyword(Result.Keywords, "StaffCrushing"); + } + + if (Result.Name is not null && Result.Name.StartsWith("Life Steal") && Result.PvE?.SpecialValues is not null && Result.PvE.SpecialValues.Length == 1) { - List KeywordsList = Result.Keywords.ToList(); - KeywordsList.Add("Lint_NotLearnable"); - Result.Keywords = KeywordsList.ToArray(); + SpecialValue LifeStealSpecialValue = Result.PvE.SpecialValues[0]; + if (LifeStealSpecialValue.Label == "Steals" && LifeStealSpecialValue.Suffix == "Health.") + { + Debug.Assert(Result.SpecialInfo is null); + + Result.SpecialInfo = $"{LifeStealSpecialValue.Label} {LifeStealSpecialValue.Value} {LifeStealSpecialValue.Suffix}"; + Result.PvE.SpecialValues = null; + } } return Result; diff --git a/Pipeline/Preprocessing/Objects/Item/Item.cs b/Pipeline/Preprocessing/Objects/Item/Item.cs index 7de2f428..a254fb45 100644 --- a/Pipeline/Preprocessing/Objects/Item/Item.cs +++ b/Pipeline/Preprocessing/Objects/Item/Item.cs @@ -145,8 +145,13 @@ private static ItemEffect ParseEffectDescription(string rawEffectDescription) string EffectString = rawEffectDescription.Substring(1, rawEffectDescription.Length - 2); Result = ParseAttributeEffectDescription(EffectString); } - else if (rawEffectDescription.Contains("{") || rawEffectDescription.Contains("}")) - throw new PreprocessorException(); + else + { + Result.Description = Result.Description.Replace("Windstrike", "Wind Strike"); + + if (rawEffectDescription.Contains("{") || rawEffectDescription.Contains("}")) + throw new PreprocessorException(); + } return Result; } @@ -436,7 +441,11 @@ public RawItem ToRawItem() private static string EffectDescriptionToString(ItemEffect effectDescription) { if (effectDescription.Description is not null) - return effectDescription.Description; + { + string Description = effectDescription.Description; + Description = Description.Replace("Wind Strike", "Windstrike"); + return Description; + } else return $"{{{effectDescription.AttributeName}}}{{{effectDescription.AttributeEffect!.Value.ToString(CultureInfo.InvariantCulture)}}}"; } diff --git a/Preprocessor/Preprocessor.csproj b/Preprocessor/Preprocessor.csproj index 87ceb9e1..74c01b9b 100644 --- a/Preprocessor/Preprocessor.csproj +++ b/Preprocessor/Preprocessor.csproj @@ -13,8 +13,8 @@ PG json preprocessor Copyright © 2023 David Le Bansais - 1.1.0.4864 - 1.1.0.1 + 1.1.0.4869 + 1.1.0.2 https://github.com/dlebansais/PgJsonParse en-US Preprocessor diff --git a/Translator/CombatParser/CombatParser.Sentences.cs b/Translator/CombatParser/CombatParser.Sentences.cs index 5bc7bc71..e4dfb5c4 100644 --- a/Translator/CombatParser/CombatParser.Sentences.cs +++ b/Translator/CombatParser/CombatParser.Sentences.cs @@ -191,6 +191,7 @@ public partial class CombatParser new Sentence("Deal %f #D damage", CombatKeyword.DamageBoost), new Sentence("Deal %f direct #D damage", CombatKeyword.DamageBoost), new Sentence("Deal direct #D damage to deal %f damage", CombatKeyword.DamageBoost), + new Sentence("Deal direct #D damage", CombatKeyword.ChangeDamageType), new Sentence("Critical hit deal %f damage", new List() { CombatKeyword.DamageBoost, CombatKeyword.ApplyToCrits }), new Sentence("Crit Damage %f", new List() { CombatKeyword.DamageBoost, CombatKeyword.ApplyToCrits }), new Sentence("When they critically hit", CombatKeyword.ApplyToCrits), diff --git a/Translator/CombatParser/CombatParser.cs b/Translator/CombatParser/CombatParser.cs index df64dc2d..e528e7ae 100644 --- a/Translator/CombatParser/CombatParser.cs +++ b/Translator/CombatParser/CombatParser.cs @@ -25,6 +25,7 @@ public void AnalyzeCachedData(List validSlotList, List objectL SkillTable.Add(AsSkill.Key, AsSkill); List SkillList = new List(); + Dictionary> ItemList = new(); foreach (object Item in objectList) switch (Item) @@ -46,6 +47,196 @@ public void AnalyzeCachedData(List validSlotList, List objectL InitValidAbilityList(SkillTable); FilterValidPowers(validSlotList, SkillList, out _, out List PowerSimpleEffectList); + + List VerifiedItemPowers = new() + { + "Item_40311_0", + "Item_40312_0", + "Item_40404_0", + "Item_40405_0", + "Item_40406_0", + "Item_40407_0", + "Item_40407_0", + "Item_40408_0", + "Item_40409_0", + "Item_40410_0", + "Item_41264_0", + "Item_41265_0", + "Item_41266_0", + "Item_41267_0", + "Item_41268_0", + "Item_41269_0", + "Item_41270_0", + "Item_41309_0", + "Item_41310_0", + "Item_42057_0", + "Item_42058_0", + "Item_42085_0", + "Item_42087_0", + "Item_42091_0", + "Item_42095_0", + "Item_42351_0", + "Item_42352_0", + "Item_42353_0", + "Item_42354_0", + "Item_42355_0", + "Item_42356_0", + "Item_42357_0", + "Item_42358_0", + "Item_42359_0", + "Item_42360_0", + "Item_42384_0", + "Item_42385_0", + "Item_42386_0", + "Item_42387_0", + "Item_42388_0", + "Item_42389_0", + "Item_42390_0", + "Item_42446_0", + "Item_42447_0", + "Item_42448_0", + "Item_42449_0", + "Item_42450_0", + "Item_42466_0", + "Item_42467_0", + "Item_42468_0", + "Item_42469_0", + "Item_42470_0", + "Item_43118_0", + "Item_43119_0", + "Item_44309_0", + "Item_44310_0", + "Item_46012_0", + "Item_46012_1", + "Item_46013_0", + "Item_46013_1", + "Item_46014_0", + "Item_46026_0", + "Item_46045_0", + "Item_46055_0", + "Item_46734_0", + "Item_46735_0", + "Item_46736_0", + "Item_48083_0", + "Item_48084_0", + "Item_48085_0", + "Item_48086_0", + "Item_48087_0", + "Item_48088_0", + "Item_48089_0", + "Item_48090_0", + "Item_48091_0", + "Item_48093_0", + "Item_48094_0", + "Item_48095_0", + "Item_48096_0", + "Item_48097_0", + "Item_48098_0", + "Item_48099_0", + "Item_48100_0", + "Item_48101_0", + "Item_48181_0", + "Item_48182_0", + "Item_49062_0", + "Item_49063_0", + "Item_49064_0", + "Item_49065_0", + "Item_49066_0", + "Item_49067_0", + "Item_49068_0", + "Item_49069_0", + "Item_49070_0", + "Item_51029_0", + "Item_52028_0", + "Item_52030_0", + "Item_52611_0", + "Item_54001_0", + "Item_54002_0", + "Item_54003_0", + "Item_54004_0", + "Item_54005_0", + "Item_54006_0", + "Item_54007_0", + "Item_54008_0", + "Item_54301_0", + "Item_54302_0", + "Item_54616_0", + "Item_54617_0", + "Item_54618_0", + "Item_54619_0", + "Item_54620_0", + "Item_54621_0", + "Item_54622_0", + "Item_54623_0", + "Item_54624_0", + "Item_54625_0", + "Item_54626_0", + "Item_54627_0", + "Item_54628_0", + "Item_55509_0", + "Item_45641_0", + "Item_45637_0", + "Item_45533_0", + "Item_45721_0", + "Item_44551_0", + "Item_47040_0", + "Item_47042_0", + "Item_47051_0", + "Item_47068_0", + "Item_47052_0", + "Item_55613_0", + "Item_42094_0", + "Item_46036_0", + "Item_46051_0", + "Item_46052_0", + }; + + foreach (object Item in objectList) + if (Item is PgItem AsItem) + { + if (AsItem.EquipSlot == ItemSlot.Feet || + AsItem.EquipSlot == ItemSlot.Head || + AsItem.EquipSlot == ItemSlot.Chest || + AsItem.EquipSlot == ItemSlot.Legs || + AsItem.EquipSlot == ItemSlot.Necklace || + AsItem.EquipSlot == ItemSlot.Ring || + AsItem.EquipSlot == ItemSlot.Hands || + AsItem.EquipSlot == ItemSlot.MainHand || + AsItem.EquipSlot == ItemSlot.OffHand || + AsItem.EquipSlot == ItemSlot.OffHandShield || + AsItem.EquipSlot == ItemSlot.Waist || + AsItem.EquipSlot == ItemSlot.Racial) + { + List ItemEffects = new(); + foreach (var Effect in AsItem.EffectDescriptionList) + if (Effect is PgItemEffectSimple AsSimpleEffect) + ItemEffects.Add(AsSimpleEffect); + + if (ItemEffects.Count > 0) + { + ItemList.Add(AsItem, ItemEffects); + int Index = 0; + foreach (PgItemEffectSimple SimpleEffect in ItemEffects) + { + PgPowerEffectSimple NewPowerEffect = new(); + NewPowerEffect.Description = SimpleEffect.Description; + PgPowerTier NewTier = new(); + NewTier.EffectList.Add(NewPowerEffect); + PgPower NewPower = new(); + NewPower.Key = $"Item_{AsItem.Key}_{Index}"; + NewPower.SlotList.Add(AsItem.EquipSlot); + NewPower.TierList.Add(NewTier); + + PowerSimpleEffectList.Insert(0, NewPower); + Index++; + + if (!VerifiedItemPowers.Contains(NewPower.Key)) + Debug.WriteLine($"UNVERIFIED {NewPower.Key} {AsItem.Name}: #{Index} {NewPowerEffect.Description} ({AsItem.EquipSlot})"); + } + } + } + } + FilterValidEffects(out Dictionary>> AllEffectTable); FindAbilitiesWithMatchingEffect(); FindPowersWithMatchingEffect(AllEffectTable, PowerSimpleEffectList, out Dictionary> PowerToEffectTable, out List UnmatchedPowerList, out List UnmatchedEffectList, out Dictionary> CandidateEffectTable); @@ -65,7 +256,7 @@ public void AnalyzeCachedData(List validSlotList, List objectL WritePowerEffectJson("combateffects.json", StringKeyTable, AnalyzedPowerKeyToCompleteEffectTable); WriteBuffEffectJson("buffeffects.json", StringKeyTable.Count, AnalyzedPowerKeyToCompleteEffectTable, EffectKeyList); WritePowerKeyToCompleteEffectFile("PowerKeyToCompleteEffect.cs", StringKeyTable, AnalyzedPowerKeyToCompleteEffectTable, EffectKeyList); - WritePowerCSV("combateffects.csv", StringKeyTable, AnalyzedPowerKeyToCompleteEffectTable, objectList); + // WritePowerCSV("combateffects.csv", StringKeyTable, AnalyzedPowerKeyToCompleteEffectTable, objectList); for (int i = 0; i < StringKeyTable.Count + EffectKeyList.Count; i++) { @@ -939,6 +1130,9 @@ public static IList GetSpecialValueList(PgAbility ability) { "Minor Healing (Targeted)", new List() { AbilityKeyword.MinorHealTargeted } }, { "All Mentalism and Psychology attack", new List() { AbilityKeyword.MentalismAttack, AbilityKeyword.PsychologyAttack } }, { "All non-basic attack", new List() { Internal_NonBasic } }, + { "Knife ability that normally deal Slashing damage", new List() { AbilityKeyword.KnifeSlashing } }, + { "Staff ability that normally deal Crushing damage", new List() { AbilityKeyword.StaffCrushing } }, + //{ "Sword Slash, Riposte, Windstrike, and Finishing Blow", new List() { AbilityKeyword.SwordSlash, AbilityKeyword.Riposte, AbilityKeyword.WindStrike, AbilityKeyword.FinishingBlow } }, }; private List GenericAbilityList = new List() @@ -993,6 +1187,12 @@ public static IList GetSpecialValueList(PgAbility ability) AbilityKeyword.MinorHealTargeted, AbilityKeyword.MentalismAttack, AbilityKeyword.PsychologyAttack, + AbilityKeyword.KnifeSlashing, + AbilityKeyword.StaffCrushing, + //AbilityKeyword.SwordSlash, + //AbilityKeyword.Riposte, + //AbilityKeyword.WindStrike, + //AbilityKeyword.FinishingBlow, Internal_NonBasic, }; @@ -1026,6 +1226,10 @@ private void GetAbilityNames(List skillList, out List abilityNa foreach (PgAbility Item in ValidAbilityList) { + if (Item.Name == "Windstrike") + { + } + string Skill_Key = FromSkillKey(Item.Skill_Key ?? throw new NullReferenceException()); PgSkill AbilitySkill = (PgSkill)(Skill_Key.Length == 0 ? PgSkill.Unknown : (Skill_Key == "AnySkill" ? PgSkill.AnySkill : ParsingContext.ObjectKeyTable[typeof(PgSkill)][Skill_Key].Item)); @@ -1643,6 +1847,7 @@ private void WritePowerEffectJson(string fileName, List stringKeyTable { Dictionary PowerToEffectTable = new(); + // Regular powers for (int i = 0; i < stringKeyTable.Count; i++) { string[] StringKeyArray; @@ -1662,33 +1867,35 @@ private void WritePowerEffectJson(string fileName, List stringKeyTable if (PgModEffect is not null) { string[] Splitted = StringKey.Split('_'); - Debug.Assert(Splitted.Length == 2); - int TierPowerId = int.Parse(Splitted[0]); + if (Splitted.Length == 2) + { + int TierPowerId = int.Parse(Splitted[0]); - if (PowerId < 0) - PowerId = TierPowerId; - else - Debug.Assert(PowerId == TierPowerId); + if (PowerId < 0) + PowerId = TierPowerId; + else + Debug.Assert(PowerId == TierPowerId); - int Tier = int.Parse(Splitted[1]); + int Tier = int.Parse(Splitted[1]); - PowerTierToEffect? NewPowerTierToEffect = ToPowerTierEffect(PgModEffect, hasTier: true, Tier); + PowerTierToEffect? NewPowerTierToEffect = ToPowerTierEffect(PgModEffect, hasTier: true, Tier); - if (NewPowerTierToEffect is not null) - { - if (PgModEffect.SecondaryModEffect is not null) + if (NewPowerTierToEffect is not null) { - Debug.Assert(PgModEffect.SecondaryModEffect.EffectKey == string.Empty); - Debug.Assert(PgModEffect.SecondaryModEffect.Description == string.Empty); + if (PgModEffect.SecondaryModEffect is not null) + { + Debug.Assert(PgModEffect.SecondaryModEffect.EffectKey == string.Empty); + Debug.Assert(PgModEffect.SecondaryModEffect.Description == string.Empty); - PowerTierToEffect? SecondaryPowerTierToEffect = ToPowerTierEffect(PgModEffect.SecondaryModEffect, hasTier: false, 0); - Debug.Assert(SecondaryPowerTierToEffect is not null); + PowerTierToEffect? SecondaryPowerTierToEffect = ToPowerTierEffect(PgModEffect.SecondaryModEffect, hasTier: false, 0); + Debug.Assert(SecondaryPowerTierToEffect is not null); - if (SecondaryPowerTierToEffect is not null) - NewPowerTierToEffect.Xtra = SecondaryPowerTierToEffect; - } + if (SecondaryPowerTierToEffect is not null) + NewPowerTierToEffect.Xtra = SecondaryPowerTierToEffect; + } - Tiers.Add(NewPowerTierToEffect); + Tiers.Add(NewPowerTierToEffect); + } } } } @@ -1714,6 +1921,90 @@ private void WritePowerEffectJson(string fileName, List stringKeyTable } } + // Powers from items + for (int i = 0; i < stringKeyTable.Count; i++) + { + string[] StringKeyArray; + StringKeyArray = stringKeyTable[i]; + PgModEffect[] ModEffectArray = powerKeyToCompleteEffectTable[i]; + + Debug.Assert(StringKeyArray.Length == ModEffectArray.Length); + + string[] Splitted = StringKeyArray[0].Split('_'); + if (Splitted.Length != 4 || Splitted[0] != "Item") + continue; + + int ItemPowerId = 0x08000000 + int.Parse(Splitted[1]); + if (PowerToEffectTable.TryGetValue(ItemPowerId, out PowerToEffect PowerToEffect)) + continue; + + List Tiers = new(); + + for (int j = i; j < stringKeyTable.Count; j++) + { + StringKeyArray = stringKeyTable[j]; + ModEffectArray = powerKeyToCompleteEffectTable[j]; + + Debug.Assert(StringKeyArray.Length == ModEffectArray.Length); + + Splitted = StringKeyArray[0].Split('_'); + if (Splitted.Length == 4 && Splitted[0] == "Item") + { + int OtherTierPowerId = 0x08000000 + int.Parse(Splitted[1]); + + if (OtherTierPowerId == ItemPowerId) + { + if (i != j) + { + } + + PgModEffect PgModEffect = ModEffectArray[0]; + + int Tier = int.Parse(Splitted[2]); + + PowerTierToEffect? NewPowerTierToEffect = ToPowerTierEffect(PgModEffect, hasTier: true, Tier); + + if (NewPowerTierToEffect is not null) + { + if (PgModEffect.SecondaryModEffect is not null) + { + Debug.Assert(PgModEffect.SecondaryModEffect.EffectKey == string.Empty); + Debug.Assert(PgModEffect.SecondaryModEffect.Description == string.Empty); + + PowerTierToEffect? SecondaryPowerTierToEffect = ToPowerTierEffect(PgModEffect.SecondaryModEffect, hasTier: false, 0); + Debug.Assert(SecondaryPowerTierToEffect is not null); + + if (SecondaryPowerTierToEffect is not null) + NewPowerTierToEffect.Xtra = SecondaryPowerTierToEffect; + } + + Tiers.Add(NewPowerTierToEffect); + } + } + } + } + + if (Tiers.Count > 0) + { + Debug.Assert(ItemPowerId > 0); + + PowerToEffect NewPowerToEffect = new() { Tiers = Tiers.ToArray() }; + + if (HardcodedEffectAllTiersTable.ContainsKey(ItemPowerId)) + { + foreach (PowerTierToEffect PowerTierToEffect in NewPowerToEffect.Tiers) + PowerTierToEffect.AdditionalEffects = HardcodedEffectAllTiersTable[ItemPowerId]; + } + else if (HardcodedEffectTable.ContainsKey(ItemPowerId)) + { + for (int j = 0; j < NewPowerToEffect.Tiers.Length && j < HardcodedEffectTable[ItemPowerId].Count; j++) + NewPowerToEffect.Tiers[j].AdditionalEffects = HardcodedEffectTable[ItemPowerId][j + 1]; + } + + PowerToEffectTable.Add(ItemPowerId, NewPowerToEffect); + } + } + JsonSerializerOptions WriteOptions = new(); WriteOptions.WriteIndented = true; WriteOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; @@ -4100,7 +4391,7 @@ private void AnalyzeRemainingPowers(List abilityNameList, Dictionary() { new EffectVerificationEntry() { Prefix = "Reaps", Suffix = "of the Health damage done (up to the max)" }, + new EffectVerificationEntry() { Prefix = "Steals", Suffix = "Health." }, } }, { diff --git a/Translator/Enums/AbilityKeyword.cs b/Translator/Enums/AbilityKeyword.cs index 7ce7daf0..6ec7c1eb 100644 --- a/Translator/Enums/AbilityKeyword.cs +++ b/Translator/Enums/AbilityKeyword.cs @@ -558,5 +558,7 @@ public enum AbilityKeyword HymnOfResurrection3Enabled, Lint_MonsterAbility, MinorHealSelf, + KnifeSlashing, + StaffCrushing, } } diff --git a/Translator/TextMaps.cs b/Translator/TextMaps.cs index 25098a2b..784e14eb 100644 --- a/Translator/TextMaps.cs +++ b/Translator/TextMaps.cs @@ -862,6 +862,8 @@ static TextMaps() { AbilityKeyword.HymnOfResurrection3Enabled, "Hymn Of Resurrection #3 Enabled" }, { AbilityKeyword.Lint_MonsterAbility, "Monster Ability" }, { AbilityKeyword.MinorHealSelf, "Minor Heal Self" }, + { AbilityKeyword.KnifeSlashing, "" }, + { AbilityKeyword.StaffCrushing, "" }, }; public static Dictionary AbilityPetTypeTextMap { get; } = new Dictionary() diff --git a/Translator/Translator.csproj b/Translator/Translator.csproj index e61e0423..7c2efe82 100644 --- a/Translator/Translator.csproj +++ b/Translator/Translator.csproj @@ -13,8 +13,8 @@ PG json translator Copyright © 2019 David Le Bansais - 1.1.0.4868 - 1.1.0.4852 + 1.1.0.4869 + 1.1.0.4853 https://github.com/dlebansais/PgJsonParse en-US Translator