This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/Ananta.Server/Ananta.Core/ClientData/Client4229938/StoryDialogue4229938.cs
T

106 lines
4.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text.Json;
using Ananta.Server.Configuration;
namespace Ananta.Server.ClientData.Client4229938;
/// <summary>一条台词索引项。字段与 <c>DialogIndex.json</c> 的行一一对应。</summary>
internal sealed record DialogEntry(
uint Id,
string Message,
string Speaker,
uint CameraId,
int Type)
{
/// <summary>带镜头(cam != 0)= 这条是「过场对话」,会拉运镜;纯台词 cam 为 0。</summary>
internal bool IsCutscene => CameraId != 0;
}
/// <summary>
/// 台詞索引(<c>Configs/DialogIndex.json</c>,58932 条,其中带镜头过场 1205 条)。
///
/// ★ 用途:GM 面板「剧情对话」子页用 <see cref="Search"/> 列出可播的台词,
/// 玩家/开发者选中一条后由服务端推 <c>SyncShowDialog</c>(64967628) 让客户端播出来。
/// type 分布(前几类):15=18941 / 14=16625 / 10=9503 / 13=6724 / 22=5258。
///
/// ⛑ 这份表只用于「列清单 + 取文案」;真正播什么由客户端按自己的 DialogConfig 演,
/// 服务端发的只是 id —— 所以表里查不到的 id 也照样能下发(客户端有自己的数据)。
/// </summary>
internal static class StoryDialogue4229938
{
private static readonly Lazy<(IReadOnlyList<DialogEntry> All, Dictionary<uint, DialogEntry> ById)> Cache = new(Load);
internal static IReadOnlyList<DialogEntry> 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;
/// <summary>按关键词(文案 / 说话人 / id 片段)搜索;<paramref name="cutsceneOnly"/> 可只看过场。</summary>
internal static IReadOnlyList<DialogEntry> Search(string? keyword, bool? cutsceneOnly, int limit)
{
IEnumerable<DialogEntry> 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<DialogEntry>, Dictionary<uint, DialogEntry>) Load()
{
try
{
var root = PrivateServerConfigStore.ResolveProjectPath(
PrivateServerConfigStore.Current.Paths.ClientConfigs);
var path = Path.Combine(root, "DialogIndex.json");
if (!File.Exists(path))
return ([], new Dictionary<uint, DialogEntry>());
using var doc = JsonDocument.Parse(File.ReadAllText(path));
if (!doc.RootElement.TryGetProperty("dialogs", out var arr) || arr.ValueKind != JsonValueKind.Array)
return ([], new Dictionary<uint, DialogEntry>());
var list = new List<DialogEntry>(arr.GetArrayLength());
var byId = new Dictionary<uint, DialogEntry>(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<uint, DialogEntry>());
}
}
}