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());
}
}
}