From e623a1809c4a999ceb5e6531f940e27f1ff11c4c Mon Sep 17 00:00:00 2001 From: milimoe Date: Wed, 12 Feb 2025 23:58:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A7=86=E8=A7=89=E5=B0=8F?= =?UTF-8?q?=E8=AF=B4=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Utility/NovelConfig.cs | 181 ++++++++++++++++++ .../JsonConverter/NovelNodeConverter.cs | 85 ++++++++ .../JsonConverter/NovelOptionConverter.cs | 57 ++++++ Model/NovelNode.cs | 48 +++++ Service/JsonManager.cs | 3 +- 5 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 Api/Utility/NovelConfig.cs create mode 100644 Library/Common/JsonConverter/NovelNodeConverter.cs create mode 100644 Library/Common/JsonConverter/NovelOptionConverter.cs create mode 100644 Model/NovelNode.cs diff --git a/Api/Utility/NovelConfig.cs b/Api/Utility/NovelConfig.cs new file mode 100644 index 0000000..fa1b670 --- /dev/null +++ b/Api/Utility/NovelConfig.cs @@ -0,0 +1,181 @@ +using Milimoe.FunGame.Core.Library.Constant; +using Milimoe.FunGame.Core.Model; + +namespace Milimoe.FunGame.Core.Api.Utility +{ + /// + /// 视觉小说文本配置器 + /// 文件会保存为:程序目录/(通常是 novels)//.json + /// + /// + /// 新建一个配置文件,文件会保存为:程序目录/(通常是 novels)//.json + /// + /// + /// + public class NovelConfig(string novel_name, string file_name) : Dictionary + { + /// + /// 配置文件存放的根目录 + /// + public static string RootPath { get; set; } = "novels"; + + /// + /// 模组的名称 + /// + public string NovelName { get; set; } = novel_name; + + /// + /// 配置文件的名称(后缀将是.json) + /// + public string FileName { get; set; } = file_name; + + /// + /// 使用索引器给指定key赋值,不存在key会新增 + /// + /// + /// + public new NovelNode this[string key] + { + get => base[key]; + set + { + if (value != null) Add(key, value); + } + } + + /// + /// 获取指定key的value + /// + /// + /// + public NovelNode? Get(string key) + { + if (TryGetValue(key, out NovelNode? value) && value != null) + { + return value; + } + return null; + } + + /// + /// 添加一个配置,如果已存在key会覆盖 + /// + /// + /// + public new void Add(string key, NovelNode value) + { + if (value != null) + { + if (TryGetValue(key, out _)) base[key] = value; + else base.Add(key, value); + } + } + + /// + /// 从配置文件中读取配置。 + /// + /// 传入定义好的条件字典 + public void LoadConfig(Dictionary>? Predicates = null) + { + string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{NovelName}"; + string fpath = $@"{dpath}/{FileName}.json"; + if (Directory.Exists(dpath) && File.Exists(fpath)) + { + string json = File.ReadAllText(fpath, General.DefaultEncoding); + Dictionary dict = NetworkUtility.JsonDeserialize>(json) ?? []; + Clear(); + foreach (string key in dict.Keys) + { + NovelNode obj = dict[key]; + base.Add(key, obj); + if (obj.Values.TryGetValue(nameof(NovelNode.NextNodes), out object? value) && value is List nextKeys) + { + foreach (string nextKey in nextKeys) + { + if (dict.TryGetValue(nextKey, out NovelNode? node) && node != null) + { + obj.NextNodes.Add(node); + } + } + } + if (Predicates != null) + { + if (obj.Values.TryGetValue(nameof(NovelNode.AndPredicates), out object? value2) && value2 is List aps) + { + foreach (string ap in aps) + { + if (Predicates.TryGetValue(ap, out Func? value3) && value3 != null) + { + obj.AndPredicates[ap] = value3; + } + } + } + if (obj.Values.TryGetValue(nameof(NovelNode.OrPredicates), out value2) && value2 is List ops) + { + foreach (string op in ops) + { + if (Predicates.TryGetValue(op, out Func? value3) && value3 != null) + { + obj.OrPredicates[op] = value3; + } + } + } + } + foreach (NovelOption option in obj.Options) + { + if (option.Values.TryGetValue(nameof(NovelOption.Targets), out object? value2) && value2 is List targets) + { + foreach (string targetKey in targets) + { + if (dict.TryGetValue(targetKey, out NovelNode? node) && node != null) + { + option.Targets.Add(node); + } + } + } + if (Predicates != null) + { + if (option.Values.TryGetValue(nameof(NovelNode.AndPredicates), out object? value3) && value3 is List aps) + { + foreach (string ap in aps) + { + if (Predicates.TryGetValue(ap, out Func? value4) && value4 != null) + { + option.AndPredicates[ap] = value4; + } + } + } + if (option.Values.TryGetValue(nameof(NovelNode.OrPredicates), out value3) && value3 is List ops) + { + foreach (string op in ops) + { + if (Predicates.TryGetValue(op, out Func? value4) && value4 != null) + { + option.OrPredicates[op] = value4; + } + } + } + } + } + } + } + } + + /// + /// 将配置保存到配置文件。调用此方法会覆盖原有的.json,请注意备份 + /// + public void SaveConfig() + { + string json = NetworkUtility.JsonSerialize((Dictionary)this); + string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{NovelName}"; + string fpath = $@"{dpath}/{FileName}.json"; + if (!Directory.Exists(dpath)) + { + Directory.CreateDirectory(dpath); + } + using StreamWriter writer = new(fpath, false, General.DefaultEncoding); + writer.WriteLine(json); + writer.Flush(); + } + } +} diff --git a/Library/Common/JsonConverter/NovelNodeConverter.cs b/Library/Common/JsonConverter/NovelNodeConverter.cs new file mode 100644 index 0000000..c52d402 --- /dev/null +++ b/Library/Common/JsonConverter/NovelNodeConverter.cs @@ -0,0 +1,85 @@ +using System.Text.Json; +using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Library.Common.Architecture; +using Milimoe.FunGame.Core.Model; + +namespace Milimoe.FunGame.Core.Library.Common.JsonConverter +{ + public class NovelNodeConverter : BaseEntityConverter + { + public override NovelNode NewInstance() + { + return new NovelNode(); + } + + public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref NovelNode result, Dictionary convertingContext) + { + switch (propertyName) + { + case nameof(NovelNode.Key): + result.Key = reader.GetString() ?? ""; + break; + case nameof(NovelNode.Priority): + result.Priority = reader.GetInt32(); + break; + case nameof(NovelNode.Previous): + result.Values[nameof(NovelNode.Previous)] = reader.GetString() ?? ""; + break; + case nameof(NovelNode.NextNodes): + result.Values[nameof(NovelNode.NextNodes)] = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + break; + case nameof(NovelNode.Options): + result.Options = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + break; + case nameof(NovelNode.Name): + result.Name = reader.GetString() ?? ""; + break; + case nameof(NovelNode.Name2): + result.Name2 = reader.GetString() ?? ""; + break; + case nameof(NovelNode.Content): + result.Content = reader.GetString() ?? ""; + break; + case nameof(NovelNode.PortraitImagePath): + result.PortraitImagePath = reader.GetString() ?? ""; + break; + case nameof(NovelNode.AndPredicates): + result.Values[nameof(NovelNode.AndPredicates)] = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + break; + case nameof(NovelNode.OrPredicates): + result.Values[nameof(NovelNode.OrPredicates)] = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + break; + } + } + + public override void Write(Utf8JsonWriter writer, NovelNode value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteString(nameof(NovelNode.Key), value.Key); + writer.WriteNumber(nameof(NovelNode.Priority), value.Priority); + if (value.Previous != null) writer.WriteString(nameof(NovelNode.Previous), value.Previous.Key); + writer.WritePropertyName(nameof(NovelNode.NextNodes)); + JsonSerializer.Serialize(writer, value.NextNodes.Select(n => n.Key).ToList(), options); + if (value.Options.Count > 0) + { + writer.WritePropertyName(nameof(NovelNode.Options)); + JsonSerializer.Serialize(writer, value.Options, options); + } + writer.WriteString(nameof(NovelNode.Name), value.Name); + if (value.Name2 != "") writer.WriteString(nameof(NovelNode.Name2), value.Name2); + writer.WriteString(nameof(NovelNode.Content), value.Content); + if (value.PortraitImagePath != "") writer.WriteString(nameof(NovelNode.PortraitImagePath), value.PortraitImagePath); + if (value.AndPredicates.Count > 0) + { + writer.WritePropertyName(nameof(NovelNode.AndPredicates)); + JsonSerializer.Serialize(writer, value.AndPredicates.Keys.ToList(), options); + } + if (value.OrPredicates.Count > 0) + { + writer.WritePropertyName(nameof(NovelNode.OrPredicates)); + JsonSerializer.Serialize(writer, value.OrPredicates.Keys.ToList(), options); + } + writer.WriteEndObject(); + } + } +} diff --git a/Library/Common/JsonConverter/NovelOptionConverter.cs b/Library/Common/JsonConverter/NovelOptionConverter.cs new file mode 100644 index 0000000..da29c99 --- /dev/null +++ b/Library/Common/JsonConverter/NovelOptionConverter.cs @@ -0,0 +1,57 @@ +using System.Text.Json; +using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Library.Common.Architecture; +using Milimoe.FunGame.Core.Model; + +namespace Milimoe.FunGame.Core.Library.Common.JsonConverter +{ + public class NovelOptionConverter : BaseEntityConverter + { + public override NovelOption NewInstance() + { + return new NovelOption(); + } + + public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref NovelOption result, Dictionary convertingContext) + { + switch (propertyName) + { + case nameof(NovelOption.Key): + result.Key = reader.GetString() ?? ""; + break; + case nameof(NovelOption.Name): + result.Name = reader.GetString() ?? ""; + break; + case nameof(NovelOption.Targets): + result.Values[nameof(NovelOption.Targets)] = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + break; + case nameof(NovelNode.AndPredicates): + result.Values[nameof(NovelNode.AndPredicates)] = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + break; + case nameof(NovelNode.OrPredicates): + result.Values[nameof(NovelNode.OrPredicates)] = NetworkUtility.JsonDeserialize>(ref reader, options) ?? []; + break; + } + } + + public override void Write(Utf8JsonWriter writer, NovelOption value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteString(nameof(NovelOption.Key), value.Key); + writer.WriteString(nameof(NovelOption.Name), value.Name); + writer.WritePropertyName(nameof(NovelOption.Targets)); + JsonSerializer.Serialize(writer, value.Targets.Select(n => n.Key).ToList(), options); + if (value.AndPredicates.Count > 0) + { + writer.WritePropertyName(nameof(NovelNode.AndPredicates)); + JsonSerializer.Serialize(writer, value.AndPredicates.Keys.ToList(), options); + } + if (value.OrPredicates.Count > 0) + { + writer.WritePropertyName(nameof(NovelNode.OrPredicates)); + JsonSerializer.Serialize(writer, value.OrPredicates.Keys.ToList(), options); + } + writer.WriteEndObject(); + } + } +} diff --git a/Model/NovelNode.cs b/Model/NovelNode.cs new file mode 100644 index 0000000..27416bb --- /dev/null +++ b/Model/NovelNode.cs @@ -0,0 +1,48 @@ +namespace Milimoe.FunGame.Core.Model +{ + public class NovelNode + { + public string Key { get; set; } = ""; + public int Priority { get; set; } = 0; + public NovelNode? Previous { get; set; } = null; + public List NextNodes { get; set; } = []; + public NovelNode? Next => NextNodes.OrderByDescending(n => n.Priority).Where(n => n.ShowNode).FirstOrDefault(); + public List Options { get; set; } = []; + public List AvailableOptions => [.. Options.Where(o => o.ShowOption)]; + public string Name { get; set; } = ""; + public string Name2 { get; set; } = ""; + public string Content { get; set; } = ""; + public string PortraitImagePath { get; set; } = ""; + public Dictionary> AndPredicates { get; set; } = []; + public Dictionary> OrPredicates { get; set; } = []; + public bool ShowNode + { + get + { + bool andResult = AndPredicates.Values.All(predicate => predicate()); + bool orResult = OrPredicates.Values.Any(predicate => predicate()); + return andResult && (OrPredicates.Count == 0 || orResult); + } + } + internal Dictionary Values { get; set; } = []; + } + + public class NovelOption + { + public string Key { get; set; } = ""; + public string Name { get; set; } = ""; + public List Targets { get; set; } = []; + public Dictionary> AndPredicates { get; set; } = []; + public Dictionary> OrPredicates { get; set; } = []; + public bool ShowOption + { + get + { + bool andResult = AndPredicates.Values.All(predicate => predicate()); + bool orResult = OrPredicates.Values.Any(predicate => predicate()); + return andResult && (OrPredicates.Count == 0 || orResult); + } + } + internal Dictionary Values { get; set; } = []; + } +} diff --git a/Service/JsonManager.cs b/Service/JsonManager.cs index 1d9e161..4a00757 100644 --- a/Service/JsonManager.cs +++ b/Service/JsonManager.cs @@ -21,7 +21,8 @@ namespace Milimoe.FunGame.Core.Service ReferenceHandler = ReferenceHandler.IgnoreCycles, Converters = { new DateTimeConverter(), new DataTableConverter(), new DataSetConverter(), new UserConverter(), new RoomConverter(), new CharacterConverter(), new MagicResistanceConverter(), new EquipSlotConverter(), new SkillConverter(), new EffectConverter(), new ItemConverter(), - new InventoryConverter(), new NormalAttackConverter(), new ClubConverter(), new GoodsConverter(), new StoreConverter() + new InventoryConverter(), new NormalAttackConverter(), new ClubConverter(), new GoodsConverter(), new StoreConverter(), + new NovelOptionConverter(), new NovelNodeConverter() } };