Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.StoryDialogue.cs
T

97 lines
4.4 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 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}");
}
}
}