97 lines
4.4 KiB
C#
97 lines
4.4 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 剧情对话 / 台詞播放(2026-10-02 从捐赠端 PrivateServerPor 移植)。
|
||
///
|
||
/// ★ 链路极短:服务端只发一个 <c>SyncShowDialog</c>(64967628),里面就 3 样东西
|
||
/// —— <c>dialogId</c>、<c>beginDialog</c>(默认同 id)、<c>param</c>(15 字段,实测**全填 0** 也能播)。
|
||
/// 台词文本、镜头、语音都由客户端按自己的 <c>DialogConfig</c> 演,服务端不搬运内容。
|
||
///
|
||
/// ⛑ 为什么 param 敢全填 0:<c>SyncShowDialog</c> 是 LuaAndCSharp(有真 reader,
|
||
/// <c>RPCDeserializeAuto.lua:4924</c>),param 是 complex(可选,空则 null)。
|
||
/// 填全零等价于「不给上下文」,客户端按 dialogId 自己演 —— 这正是 GM 面板要的效果。
|
||
///
|
||
/// ⛑ 表查不到也能下发:<see cref="StoryDialogue4229938"/> 只是「列清单 + 取文案」用,
|
||
/// 客户端有自己的 DialogConfig,id 不在我们索引里照样会播。
|
||
/// </summary>
|
||
internal sealed partial class GameRouter
|
||
{
|
||
/// <summary>推一条台词(含过场镜头)。</summary>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>只推语音(不带画面/字幕),配合 <see cref="PushStoryDialogAsync"/> 用。</summary>
|
||
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}");
|
||
}
|
||
}
|
||
}
|