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 bool Readonly { get; set; } = false; /// /// 模组的名称 /// public string NovelName { get; set; } = novel_name; /// /// 配置文件的名称(后缀将是.json) /// public string FileName { get; set; } = file_name; /// /// 断言方法字典 /// 都有显示的条件,反序列化 json 文件时,会根据其名称来分配具体的断言方法 /// public Dictionary> Predicates { get; set; } = []; /// /// 使用索引器给指定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) => base[key] = value; /// /// 从指定路径加载配置文件,并根据其文件名,转换为本框架所需的文件 /// 为 false 时将不会复制此文件至配置文件目录并且不允许 /// 需要注意: 用于检查加载的文件名是否在配置文件目录中已经存在 /// 如果不使用此检查,使用 时可能会覆盖原有文件(程序目录/(通常是 novels)//[所选的文件名].json) /// /// /// /// /// /// /// /// /// /// public static NovelConfig LoadFrom(string path, string novelName, bool copyToRootPath = true, bool checkConflict = true, Dictionary>? predicates = null) { if (!File.Exists(path)) { throw new FileNotFoundException($"找不到文件:{path}"); } string fileName = Path.GetFileNameWithoutExtension(path); NovelConfig config; if (copyToRootPath) { string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}"; string fpath = $@"{dpath}/{fileName}.json"; if (checkConflict && File.Exists(fpath)) { throw new InvalidOperationException($"文件 {fileName}.json 已存在,请先重命名。"); } // 确保目录存在 ExistsDirectoryAndCreate(novelName); // 复制文件内容 string json = File.ReadAllText(path, General.DefaultEncoding); if (NetworkUtility.JsonDeserialize>(json) is null) { throw new InvalidDataException($"文件 {path} 内容为空或格式不正确。"); } File.WriteAllText(fpath, json, General.DefaultEncoding); config = new(novelName, fileName); config.LoadConfig(predicates); } else { // 从新文件加载配置 config = new(novelName, fileName) { Readonly = true }; string json = File.ReadAllText(path, General.DefaultEncoding); Dictionary dict = NetworkUtility.JsonDeserialize>(json) ?? []; config.LoadConfig(dict, predicates); } return config; } /// /// 从配置文件中读取配置。 /// /// 传入定义好的条件字典 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) ?? []; LoadConfig(dict, predicates); } } /// /// 根据断言方法字典重新生成小说的字典 /// /// /// private void LoadConfig(Dictionary dict, Dictionary>? predicates = null) { if (predicates != null) { foreach (string key in predicates.Keys) { // 直接覆盖 Predicates[key] = predicates[key]; } } Clear(); foreach (string key in dict.Keys) { NovelNode obj = dict[key]; base.Add(key, obj); if (obj.Values.TryGetValue(nameof(NovelNode.Previous), out object? value) && value is string prevKey && dict.Values.FirstOrDefault(n => n.Key == prevKey) is NovelNode prev) { obj.Previous = prev; } if (obj.Values.TryGetValue(nameof(NovelNode.NextNodes), out 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() { if (Readonly) { return; } 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(); } /// /// 检查配置文件目录是否存在 /// /// /// public static bool ExistsDirectory(string novelName) { string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}"; return Directory.Exists(dpath); } /// /// 检查配置文件目录是否存在,不存在则创建 /// /// /// public static bool ExistsDirectoryAndCreate(string novelName) { string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}"; bool result = Directory.Exists(dpath); if (!result) { Directory.CreateDirectory(dpath); } return result; } /// /// 检查配置文件目录中是否存在指定文件 /// /// /// /// public static bool ExistsFile(string novelName, string fileName) { string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}"; string fpath = $@"{dpath}/{fileName}.json"; return File.Exists(fpath); } } }