using System.Text.Json; using Ananta.Server.Configuration; namespace Ananta.Server.ClientData.Client4229938; /// 一条台词索引项。字段与 DialogIndex.json 的行一一对应。 internal sealed record DialogEntry( uint Id, string Message, string Speaker, uint CameraId, int Type) { /// 带镜头(cam != 0)= 这条是「过场对话」,会拉运镜;纯台词 cam 为 0。 internal bool IsCutscene => CameraId != 0; } /// /// 台詞索引(Configs/DialogIndex.json,58932 条,其中带镜头过场 1205 条)。 /// /// ★ 用途:GM 面板「剧情对话」子页用 列出可播的台词, /// 玩家/开发者选中一条后由服务端推 SyncShowDialog(64967628) 让客户端播出来。 /// type 分布(前几类):15=18941 / 14=16625 / 10=9503 / 13=6724 / 22=5258。 /// /// ⛑ 这份表只用于「列清单 + 取文案」;真正播什么由客户端按自己的 DialogConfig 演, /// 服务端发的只是 id —— 所以表里查不到的 id 也照样能下发(客户端有自己的数据)。 /// internal static class StoryDialogue4229938 { private static readonly Lazy<(IReadOnlyList All, Dictionary ById)> Cache = new(Load); internal static IReadOnlyList All => Cache.Value.All; internal static int Count => Cache.Value.All.Count; internal static int CutsceneCount => Cache.Value.All.Count(x => x.IsCutscene); internal static DialogEntry? Find(uint id) => Cache.Value.ById.TryGetValue(id, out var e) ? e : null; /// 按关键词(文案 / 说话人 / id 片段)搜索; 可只看过场。 internal static IReadOnlyList Search(string? keyword, bool? cutsceneOnly, int limit) { IEnumerable q = Cache.Value.All; if (cutsceneOnly is { } want) q = q.Where(x => x.IsCutscene == want); if (!string.IsNullOrWhiteSpace(keyword)) { var k = keyword.Trim(); q = q.Where(x => x.Message.Contains(k, StringComparison.OrdinalIgnoreCase) || x.Speaker.Contains(k, StringComparison.OrdinalIgnoreCase) || x.Id.ToString().Contains(k, StringComparison.Ordinal)); } return q.Take(Math.Clamp(limit, 1, 3000)).ToList(); } private static (IReadOnlyList, Dictionary) Load() { try { var root = PrivateServerConfigStore.ResolveProjectPath( PrivateServerConfigStore.Current.Paths.ClientConfigs); var path = Path.Combine(root, "DialogIndex.json"); if (!File.Exists(path)) return ([], new Dictionary()); using var doc = JsonDocument.Parse(File.ReadAllText(path)); if (!doc.RootElement.TryGetProperty("dialogs", out var arr) || arr.ValueKind != JsonValueKind.Array) return ([], new Dictionary()); var list = new List(arr.GetArrayLength()); var byId = new Dictionary(arr.GetArrayLength()); foreach (var item in arr.EnumerateArray()) { static string Str(JsonElement e, string name) => e.TryGetProperty(name, out var n) && n.ValueKind == JsonValueKind.String ? n.GetString() ?? string.Empty : string.Empty; var entry = new DialogEntry( item.TryGetProperty("id", out var pi) && pi.TryGetUInt32(out var id) ? id : 0u, Str(item, "msg"), Str(item, "spk"), item.TryGetProperty("cam", out var pc) && pc.TryGetUInt32(out var cam) ? cam : 0u, item.TryGetProperty("type", out var pt) && pt.TryGetInt32(out var tv) ? tv : 0); list.Add(entry); if (entry.Id != 0) byId[entry.Id] = entry; } Console.WriteLine($"[STORY] 台词索引已载入 {list.Count} 条(带镜头过场 {list.Count(x => x.IsCutscene)} 条)"); return (list, byId); } catch (Exception ex) { Console.WriteLine($"[STORY] 载入台词索引失败: {ex.GetType().Name}: {ex.Message}"); return ([], new Dictionary()); } } }