From 708b2ec361054b6e82bcd61007aaf7a473c91855 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 8 Jan 2025 22:37:10 +0000 Subject: [PATCH 1/4] more serialization and other fixes --- Core/Models/Eft/Common/Tables/BotBase.cs | 10 +++- Core/Utils/ImporterUtil.cs | 15 ++++- .../DictionaryOfListOrTConverter.cs | 43 +++++++++++++++ .../Utils/Json/Converters/ListOrTConverter.cs | 55 +++++++++++++++++++ Core/Utils/Json/ListOrT.cs | 10 ++++ 5 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 Core/Utils/Json/Converters/DictionaryOfListOrTConverter.cs create mode 100644 Core/Utils/Json/Converters/ListOrTConverter.cs create mode 100644 Core/Utils/Json/ListOrT.cs diff --git a/Core/Models/Eft/Common/Tables/BotBase.cs b/Core/Models/Eft/Common/Tables/BotBase.cs index 46fc153b..6d2789cd 100644 --- a/Core/Models/Eft/Common/Tables/BotBase.cs +++ b/Core/Models/Eft/Common/Tables/BotBase.cs @@ -2,6 +2,7 @@ using System.Text.Json.Serialization; using Core.Models.Eft.Ragfair; using Core.Models.Enums; +using Core.Utils.Json; using Core.Utils.Json.Converters; namespace Core.Models.Eft.Common.Tables; @@ -293,6 +294,9 @@ public class BotBaseInventory [JsonPropertyName("favoriteItems")] public List? FavoriteItems { get; set; } + + [JsonPropertyName("hideoutCustomizationStashId")] + public string? HideoutCustomizationStashId { get; set; } } public class BaseJsonSkills @@ -304,8 +308,8 @@ public class BaseJsonSkills public class Skills { - [JsonConverter(typeof(ArrayToObjectFactoryConverter))] - public Dictionary? Common { get; set; } + [JsonConverter(typeof(DictionaryOfListOrTConverter))] + public Dictionary>? Common { get; set; } [JsonConverter(typeof(ArrayToObjectFactoryConverter))] public Dictionary? Mastering { get; set; } @@ -681,7 +685,7 @@ public class LastCompleted public class Notes { - [JsonPropertyName("notes")] + [JsonPropertyName("Notes")] public List? DataNotes { get; set; } } diff --git a/Core/Utils/ImporterUtil.cs b/Core/Utils/ImporterUtil.cs index 35291092..80bdf70b 100644 --- a/Core/Utils/ImporterUtil.cs +++ b/Core/Utils/ImporterUtil.cs @@ -2,6 +2,7 @@ using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Core.Annotations; +using Core.Utils.Json.Converters; namespace Core.Utils; @@ -12,6 +13,11 @@ public class ImporterUtil private readonly HashSet filesToIgnore = ["bearsuits.json", "usecsuits.json", "archivedquests.json"]; + private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions + { + UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, Converters = { new ListOrTConverterFactory() } + }; + public ImporterUtil(FileUtil fileUtil) { _fileUtil = fileUtil; @@ -58,8 +64,7 @@ public class ImporterUtil ); try { - var fileDeserialized = JsonSerializer.Deserialize(fileData, propertyType, - new JsonSerializerOptions { UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow }); + var fileDeserialized = JsonSerializer.Deserialize(fileData, propertyType, jsonSerializerOptions); if (onObjectDeserialized != null) onObjectDeserialized(file, fileDeserialized); @@ -77,13 +82,17 @@ public class ImporterUtil // deep tree search foreach (var directory in directories) { + var dictionaryLock = new object(); tasks.Add( Task.Factory.StartNew(() => { var setMethod = GetSetMethod(directory.Split("/").Last().Replace("_", ""), loadedType, out var matchedProperty, out var isDictionary); var loadTask = LoadRecursiveAsync($"{directory}/", matchedProperty); loadTask.Wait(); - setMethod.Invoke(result, isDictionary ? [directory, loadTask.Result] : [loadTask.Result]); + lock (dictionaryLock) + { + setMethod.Invoke(result, isDictionary ? [directory, loadTask.Result] : [loadTask.Result]); + } }) ); } diff --git a/Core/Utils/Json/Converters/DictionaryOfListOrTConverter.cs b/Core/Utils/Json/Converters/DictionaryOfListOrTConverter.cs new file mode 100644 index 00000000..aeb0c695 --- /dev/null +++ b/Core/Utils/Json/Converters/DictionaryOfListOrTConverter.cs @@ -0,0 +1,43 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Core.Utils.Json.Converters; + +public class DictionaryOfListOrTConverter : JsonConverterFactory +{ + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(Dictionary<,>) && + typeToConvert.GenericTypeArguments[1].IsGenericType && typeToConvert.GenericTypeArguments[1].GetGenericTypeDefinition() == typeof(ListOrT<>); + } + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return (JsonConverter)Activator.CreateInstance(typeof(DictionaryOfListOrTConverter<,>).MakeGenericType(typeToConvert.GenericTypeArguments[0], typeToConvert.GenericTypeArguments[1].GenericTypeArguments[0])); + } +} + +public class DictionaryOfListOrTConverter : JsonConverter>?> +{ + public override Dictionary>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartArray) + { + reader.Read(); + return default; + } + else + { + using (var jsonDocument = JsonDocument.ParseValue(ref reader)) + { + var jsonText = jsonDocument.RootElement.GetRawText(); + return JsonSerializer.Deserialize>>(jsonText, options); + } + } + } + + public override void Write(Utf8JsonWriter writer, Dictionary> value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); + } +} diff --git a/Core/Utils/Json/Converters/ListOrTConverter.cs b/Core/Utils/Json/Converters/ListOrTConverter.cs new file mode 100644 index 00000000..fca1deea --- /dev/null +++ b/Core/Utils/Json/Converters/ListOrTConverter.cs @@ -0,0 +1,55 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Core.Utils.Json.Converters; + +public class ListOrTConverterFactory : JsonConverterFactory +{ + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(ListOrT<>); + } + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return (JsonConverter)Activator.CreateInstance(typeof(ListOrTConverter<>).MakeGenericType(typeToConvert.GenericTypeArguments[0])); + } +} + +public class ListOrTConverter : JsonConverter?> +{ + public override ListOrT? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.StartArray: + using (var jsonDocument = JsonDocument.ParseValue(ref reader)) + { + var jsonText = jsonDocument.RootElement.GetRawText(); + var list = JsonSerializer.Deserialize>(jsonText, options); + return new ListOrT(list, default); + } + case JsonTokenType.StartObject: + using (var jsonDocument = JsonDocument.ParseValue(ref reader)) + { + var jsonText = jsonDocument.RootElement.GetRawText(); + var obj = JsonSerializer.Deserialize(jsonText, options); + return new ListOrT(null, obj); + } + default: + throw new Exception($"Unable to translate object type {reader.TokenType} to ListOrT."); + } + } + + public override void Write(Utf8JsonWriter writer, ListOrT value, JsonSerializerOptions options) + { + if (value.IsItem) + { + JsonSerializer.Serialize(writer, value.Item, options); + } + else + { + JsonSerializer.Serialize(writer, value.List, options); + } + } +} diff --git a/Core/Utils/Json/ListOrT.cs b/Core/Utils/Json/ListOrT.cs new file mode 100644 index 00000000..bfa0aae4 --- /dev/null +++ b/Core/Utils/Json/ListOrT.cs @@ -0,0 +1,10 @@ +namespace Core.Utils.Json; + +public class ListOrT(List? list, T? item) +{ + public List? List { get; } = list; + public T? Item { get; } = item; + + public bool IsItem => Item != null; + public bool IsList => List != null; +} From 9be88e76ef3ea2841ff6248b547b0a4d1e01f7cd Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 8 Jan 2025 23:22:23 +0000 Subject: [PATCH 2/4] more stuff --- Core/Models/Eft/Common/Tables/BotBase.cs | 3 +- Core/Models/Eft/Hideout/QteData.cs | 4 +- Core/Utils/ImporterUtil.cs | 2 +- .../Converters/DictionaryOrListConverter.cs | 55 +++++++++++++++++++ Core/Utils/Json/DictionaryOrList.cs | 10 ++++ 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 Core/Utils/Json/Converters/DictionaryOrListConverter.cs create mode 100644 Core/Utils/Json/DictionaryOrList.cs diff --git a/Core/Models/Eft/Common/Tables/BotBase.cs b/Core/Models/Eft/Common/Tables/BotBase.cs index 6d2789cd..8e6bd87d 100644 --- a/Core/Models/Eft/Common/Tables/BotBase.cs +++ b/Core/Models/Eft/Common/Tables/BotBase.cs @@ -308,8 +308,7 @@ public class BaseJsonSkills public class Skills { - [JsonConverter(typeof(DictionaryOfListOrTConverter))] - public Dictionary>? Common { get; set; } + public DictionaryOrList? Common { get; set; } [JsonConverter(typeof(ArrayToObjectFactoryConverter))] public Dictionary? Mastering { get; set; } diff --git a/Core/Models/Eft/Hideout/QteData.cs b/Core/Models/Eft/Hideout/QteData.cs index 90ed9c94..2cdb8931 100644 --- a/Core/Models/Eft/Hideout/QteData.cs +++ b/Core/Models/Eft/Hideout/QteData.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Text.Json.Serialization; using Core.Models.Eft.Health; using Core.Models.Enums; @@ -166,6 +167,7 @@ public class SkillRequirement : QteRequirement public RequirementType? Type { get; set; } = Models.Enums.Hideout.RequirementType.Skill; [JsonPropertyName("skillName")] + [JsonConverter(typeof(JsonStringEnumConverter))] public SkillTypes? SkillName { get; set; } [JsonPropertyName("skillLevel")] @@ -254,4 +256,4 @@ public class BodyPartBuffRequirement : QteRequirement [JsonPropertyName("excluded")] public bool? Excluded { get; set; } -} \ No newline at end of file +} diff --git a/Core/Utils/ImporterUtil.cs b/Core/Utils/ImporterUtil.cs index 80bdf70b..b257b7e3 100644 --- a/Core/Utils/ImporterUtil.cs +++ b/Core/Utils/ImporterUtil.cs @@ -15,7 +15,7 @@ public class ImporterUtil private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { - UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, Converters = { new ListOrTConverterFactory() } + UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, Converters = { new ListOrTConverterFactory(), new DictionaryOrListConverter() } }; public ImporterUtil(FileUtil fileUtil) diff --git a/Core/Utils/Json/Converters/DictionaryOrListConverter.cs b/Core/Utils/Json/Converters/DictionaryOrListConverter.cs new file mode 100644 index 00000000..c6913deb --- /dev/null +++ b/Core/Utils/Json/Converters/DictionaryOrListConverter.cs @@ -0,0 +1,55 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Core.Utils.Json.Converters; + +public class DictionaryOrListConverter: JsonConverterFactory +{ + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DictionaryOrList<,>); + } + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return (JsonConverter)Activator.CreateInstance(typeof(DictionaryOrListConverter<,>).MakeGenericType(typeToConvert.GenericTypeArguments[0], typeToConvert.GenericTypeArguments[1])); + } +} + +public class DictionaryOrListConverter : JsonConverter?> +{ + public override DictionaryOrList? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.StartArray: + using (var jsonDocument = JsonDocument.ParseValue(ref reader)) + { + var jsonText = jsonDocument.RootElement.GetRawText(); + var list = JsonSerializer.Deserialize>(jsonText, options); + return new DictionaryOrList(null, list); + } + case JsonTokenType.StartObject: + using (var jsonDocument = JsonDocument.ParseValue(ref reader)) + { + var jsonText = jsonDocument.RootElement.GetRawText(); + var dictionary = JsonSerializer.Deserialize>(jsonText, options); + return new DictionaryOrList(dictionary, null); + } + default: + throw new Exception($"Unable to translate object type {reader.TokenType} to ListOrT."); + } + } + + public override void Write(Utf8JsonWriter writer, DictionaryOrList value, JsonSerializerOptions options) + { + if (value.IsList) + { + JsonSerializer.Serialize(writer, value.List, options); + } + else + { + JsonSerializer.Serialize(writer, value.Dictionary, options); + } + } +} diff --git a/Core/Utils/Json/DictionaryOrList.cs b/Core/Utils/Json/DictionaryOrList.cs new file mode 100644 index 00000000..bdcb6ceb --- /dev/null +++ b/Core/Utils/Json/DictionaryOrList.cs @@ -0,0 +1,10 @@ +namespace Core.Utils.Json; + +public class DictionaryOrList(Dictionary? dictionary, List? list) +{ + public Dictionary? Dictionary { get; } = dictionary; + public List? List { get; } = list; + + public bool IsList => List != null; + public bool IsDictionary => Dictionary != null; +} From d0b1df4ebb50275c7eca6899972a94f0742cd07c Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 8 Jan 2025 23:26:22 +0000 Subject: [PATCH 3/4] smol --- Core/Models/Eft/Common/Tables/Achievement.cs | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Core/Models/Eft/Common/Tables/Achievement.cs b/Core/Models/Eft/Common/Tables/Achievement.cs index 152466c6..4cf81a8c 100644 --- a/Core/Models/Eft/Common/Tables/Achievement.cs +++ b/Core/Models/Eft/Common/Tables/Achievement.cs @@ -17,7 +17,7 @@ public class Achievement public List? Rewards { get; set; } [JsonPropertyName("conditions")] - public QuestConditionTypes? Conditions { get; set; } + public AchievementQuestConditionTypes? Conditions { get; set; } [JsonPropertyName("instantComplete")] public bool? InstantComplete { get; set; } @@ -48,4 +48,22 @@ public class Achievement [JsonPropertyName("index")] public int? Index { get; set; } -} \ No newline at end of file +} + +public class AchievementQuestConditionTypes +{ + [JsonPropertyName("started")] + public List? Started { get; set; } + + [JsonPropertyName("availableForFinish")] + public List? AvailableForFinish { get; set; } + + [JsonPropertyName("availableForStart")] + public List? AvailableForStart { get; set; } + + [JsonPropertyName("success")] + public List? Success { get; set; } + + [JsonPropertyName("fail")] + public List? Fail { get; set; } +} From 541eeb1d4e714ea491fdd905376154833c18b880 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 8 Jan 2025 23:33:18 +0000 Subject: [PATCH 4/4] database fully loading --- Core/Models/Eft/Common/Tables/BotBase.cs | 1 + Core/Models/Eft/Common/Tables/ProfileTemplate.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Core/Models/Eft/Common/Tables/BotBase.cs b/Core/Models/Eft/Common/Tables/BotBase.cs index 8e6bd87d..2ca32223 100644 --- a/Core/Models/Eft/Common/Tables/BotBase.cs +++ b/Core/Models/Eft/Common/Tables/BotBase.cs @@ -13,6 +13,7 @@ public class BotBase public string? Id { get; set; } [JsonPropertyName("aid")] + [JsonConverter(typeof(StringToNumberFactoryConverter))] public double? Aid { get; set; } /** SPT property - use to store player id - TODO - move to AID ( account id as guid of choice) */ diff --git a/Core/Models/Eft/Common/Tables/ProfileTemplate.cs b/Core/Models/Eft/Common/Tables/ProfileTemplate.cs index 172f2d3c..9e48cd39 100644 --- a/Core/Models/Eft/Common/Tables/ProfileTemplate.cs +++ b/Core/Models/Eft/Common/Tables/ProfileTemplate.cs @@ -61,6 +61,12 @@ public class TemplateSide [JsonPropertyName("trader")] public ProfileTraderTemplate? Trader { get; set; } + + [JsonPropertyName("equipmentBuilds")] + public object? EquipmentBuilds { get; set; } + + [JsonPropertyName("weaponbuilds")] + public object? WeaponBuilds { get; set; } } public class ProfileTraderTemplate @@ -69,7 +75,7 @@ public class ProfileTraderTemplate public Dictionary? InitialLoyaltyLevel { get; set; } [JsonPropertyName("initialStanding")] - public Dictionary? InitialStanding { get; set; } + public Dictionary? InitialStanding { get; set; } [JsonPropertyName("setQuestsAvailableForStart")] public bool? SetQuestsAvailableForStart { get; set; } @@ -94,4 +100,4 @@ public class ProfileTraderTemplate /** What traders should have their clothing unlocked/purchased on creation */ [JsonPropertyName("purchaseAllClothingByDefaultForTrader")] public List? PurchaseAllClothingByDefaultForTrader { get; set; } -} \ No newline at end of file +}