96 lines
4.4 KiB
C#
96 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}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|