using Ananta.SDK.Network; using Ananta.SDK.Serialization; using Ananta.Server.ClientData.Client4229938; using Ananta.Server.Protocol.Client4229938; using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game; namespace Ananta.Server.Handlers.Game; /// /// 剧情对话 / 台詞播放(2026-10-02 从捐赠端 PrivateServerPor 移植)。 /// /// ★ 链路极短:服务端只发一个 SyncShowDialog(64967628),里面就 3 样东西 /// —— dialogId、beginDialog(默认同 id)、param(15 字段,实测**全填 0** 也能播)。 /// 台词文本、镜头、语音都由客户端按自己的 DialogConfig 演,服务端不搬运内容。 /// /// ⛑ 为什么 param 敢全填 0:SyncShowDialog 是 LuaAndCSharp(有真 reader, /// RPCDeserializeAuto.lua:4924),param 是 complex(可选,空则 null)。 /// 填全零等价于「不给上下文」,客户端按 dialogId 自己演 —— 这正是 GM 面板要的效果。 /// /// ⛑ 表查不到也能下发: 只是「列清单 + 取文案」用, /// 客户端有自己的 DialogConfig,id 不在我们索引里照样会播。 /// internal sealed partial class GameRouter { /// 推一条台词(含过场镜头)。 internal static async Task<(bool Ok, string Message)> PushStoryDialogAsync(uint dialogId) { var target = GameAdminBridge.RequirePlayerSession(); if (target is null) return (false, "还没有玩家会话(先用游戏客户端进世界)"); if (dialogId == 0) return (false, "缺少 dialogId"); var session = target.Value.Session; try { await session.NotifyAsync(MethodId.SyncShowDialog, UxSerializer.Serialize(new GameMethods.SyncShowDialog4229938 { dialogId = dialogId, beginDialog = dialogId, param = new GameMethods.DialogParam4229938 { Reason = 0, NpcTemplateId = 0, NpcInstanceId = 0, BlackContinue = false, FromTaskId = 0, FromEventId = 0, // ★ 必须是 false:true 表示「客户端自己发起的对话」,会走另一条校验路径。 FromClient = false, DialogCameraSpawnId = 0, SpoonNodeId = 0, }, }), CancellationToken.None); var entry = StoryDialogue4229938.Find(dialogId); session.Log.Info($"[STORY] 已下发 SyncShowDialog dialogId={dialogId} " + $"spk={entry?.Speaker ?? "(索引里没有)"} cutscene={(entry?.IsCutscene ?? false)}"); return (true, $"已下发台词 {dialogId}{(entry is null ? "(索引里没有这条,客户端按自己的表播)" : $":{entry.Speaker} — {entry.Message}")}"); } catch (Exception ex) { return (false, $"下发失败:{ex.Message}"); } } /// 只推语音(不带画面/字幕),配合 用。 internal static async Task<(bool Ok, string Message)> PushStoryDialogVoiceAsync(uint dialogId) { var target = GameAdminBridge.RequirePlayerSession(); if (target is null) return (false, "还没有玩家会话(先用游戏客户端进世界)"); if (dialogId == 0) return (false, "缺少 dialogId"); var session = target.Value.Session; try { await session.NotifyAsync(MethodId.SyncShowDialogVoice, UxSerializer.Serialize(new GameMethods.SyncShowDialogVoice4229938 { // ⛑ 硬规则 120:回给客户端的标识填 pid,不是服务端 uid。 pid = Profile.PlayerPid, dialogId = dialogId, }), CancellationToken.None); session.Log.Info($"[STORY] 已下发 SyncShowDialogVoice dialogId={dialogId}"); return (true, $"已下发语音 {dialogId}"); } catch (Exception ex) { return (false, $"下发失败:{ex.Message}"); } } }