Files

164 lines
8.1 KiB
C#

using Ananta.SDK.Network;
using Ananta.SDK.Rpc;
using Ananta.SDK.Serialization;
using Ananta.Server.Protocol.Client4229938;
using Ananta.Server.RpcTypes.Client4229938;
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// Gadget / SceneDevice interaction handlers (build 4229938).
///
/// Grid-streamed gadgets (see GadgetStreamingEngine) render from the client's baked data,
/// but actually USING them is a server round-trip: pressing F runs the gadget's local graph
/// which fires AskInteractCmd and AskSceneDevice* asks. This router answers each ask the way
/// a single-player authoritative server would — ack the check-index, then echo the change
/// back through the matching Sync* notify so the gadget graph proceeds (vending machine UI,
/// claw machine, doors, arcades...).
/// </summary>
internal sealed partial class GameRouter
{
[Handler(MethodId.AskInteractCmds, HandlerPacketKind.Notify)]
private async Task AskInteractCmds(Connection conn, UxRpcMessage msg)
{
var data = msg.GetArgs<SceneMethods.AskInteractCmds4229938>();
conn.Log.Info($"[GADGET] interact cmds x{data.Data.Count}");
await conn.Session.NotifyAsync(MethodId.IGameSceneToClient_SyncInteractCmds, UxSerializer.Serialize(
new SceneMethods.SyncInteractCmds4229938 { Data = data.Data }), CancellationToken.None);
}
// Handled in GameRouter.SceneDevices.cs (with loop protection and config toggles)
// ------------------------------------------------------------------
// NPC dialog (F on an NPC): the client resolves the dialog id from its own
// config and asks us to run it; we echo the dialog back so the UI opens.
// ------------------------------------------------------------------
[Handler(MethodId.AskAgentStartNpcDialog, HandlerPacketKind.Notify)]
private async Task AskAgentStartNpcDialog(Connection conn, UxRpcMessage msg)
{
var args = msg.GetArgs<SceneMethods.AskAgentStartNpcDialog4229938>();
conn.Log.Info($"[NPC] start dialog agent={args.AgentEntityId} dialog={args.NpcDialogId} duration={args.DialogDuration}");
await conn.Session.NotifyAsync(MethodId.SyncAgentPrepareStartNpcDialog, UxSerializer.Serialize(
new SceneMethods.SyncAgentPrepareStartNpcDialog4229938
{
AgentId = args.AgentEntityId,
NpcDialogId = args.NpcDialogId,
NpcSoundId = 0,
VirtualNpc = false,
EntireDialogId = 0,
}), CancellationToken.None);
}
[Handler(MethodId.AskAgentFinishNpcDialog, HandlerPacketKind.Notify)]
private async Task AskAgentFinishNpcDialog(Connection conn, UxRpcMessage msg)
{
var args = msg.GetArgs<SceneMethods.AskAgentFinishNpcDialog4229938>();
conn.Log.Info($"[NPC] finish dialog agent={args.AgentEntityId} dialog={args.NpcDialogId}");
await conn.Session.NotifyAsync(MethodId.SyncAgentFinishNpcDialog, UxSerializer.Serialize(
new SceneMethods.SyncAgentFinishNpcDialog4229938
{
AgentId = args.AgentEntityId,
}), CancellationToken.None);
}
// ------------------------------------------------------------------
// Gadget doors / scene rooms: interior transitions and door teleports.
// The client drives the movement itself; the server only acknowledges so
// the gadget graph doesn't stall on the ask.
// ------------------------------------------------------------------
// ------------------------------------------------------------------
// Gadget doors / scene rooms: the client asks on door enter/leave and the
// geometry of the interior only loads once the server answers with
// SyncSpoonRoomState — without it the player falls through the world.
// ------------------------------------------------------------------
[Handler(MethodId.AskGadgetSceneRoomTrigger, HandlerPacketKind.Invoke)]
private async Task AskGadgetSceneRoomTrigger(Connection conn, UxRpcMessage msg)
{
var args = msg.GetArgs<SceneMethods.AskGadgetSceneRoomTrigger4229938>();
conn.Log.Info($"[ROOM] scene room trigger room={args.SceneRoomId} gadget={args.GadgetUniqueId} enter={args.IsEnter}");
await conn.ReturnEmptyOkAsync(msg);
await PushRoomStateAsync(conn.Session, args.SceneRoomId, args.IsEnter);
}
[Handler(MethodId.AskEnterSceneRoom, HandlerPacketKind.Invoke)]
private async Task AskEnterSceneRoom(Connection conn, UxRpcMessage msg)
{
var args = msg.GetArgs<SceneMethods.AskEnterSceneRoom4229938>();
conn.Log.Info($"[ROOM] enter room={args.SceneRoomId} vehicle={args.IsVehicle}");
await conn.ReturnEmptyOkAsync(msg);
await PushRoomStateAsync(conn.Session, args.SceneRoomId, enabled: true);
}
[Handler(MethodId.AskExistSceneRoom, HandlerPacketKind.Invoke)]
private async Task AskExistSceneRoom(Connection conn, UxRpcMessage msg)
{
var args = msg.GetArgs<SceneMethods.AskExistSceneRoom4229938>();
conn.Log.Info($"[ROOM] leave room={args.SceneRoomId} vehicle={args.IsVehicle}");
await conn.ReturnEmptyOkAsync(msg);
await PushRoomStateAsync(conn.Session, args.SceneRoomId, enabled: false);
}
[Handler(MethodId.AskAgentSceneRoomTrigger, HandlerPacketKind.Invoke)]
private async Task AskAgentSceneRoomTrigger(Connection conn, UxRpcMessage msg)
{
var args = msg.GetArgs<SceneMethods.AskAgentSceneRoomTrigger4229938>();
conn.Log.Info($"[ROOM] agent room trigger room={args.SceneRoomId} agent={args.SpoonAgentId} enter={args.IsEnter}");
await conn.ReturnEmptyOkAsync(msg);
await PushRoomStateAsync(conn.Session, args.SceneRoomId, args.IsEnter);
}
private static async Task PushRoomStateAsync(TcpSession session, int sceneRoomId, bool enabled)
{
await session.NotifyAsync(MethodId.SyncSpoonRoomState, UxSerializer.Serialize(
new SceneMethods.SyncSpoonRoomState4229938
{
SceneRoomId = sceneRoomId,
Enabled = enabled,
TaskId = 0,
}), CancellationToken.None);
}
// ------------------------------------------------------------------
// NPC dating ("เดต") — the InviteRide family. The client's GM console can
// request a date with an NPC (GmStartInviteRideNpc); the server activates
// that NPC as the ride companion and pushes its interaction menu property.
// ------------------------------------------------------------------
[Handler(MethodId.GmStartInviteRideNpc, HandlerPacketKind.Invoke)]
private async Task GmStartInviteRideNpc(Connection conn, UxRpcMessage msg)
{
var args = msg.GetArgs<SceneMethods.GmStartInviteRideNpc4229938>();
conn.Log.Info($"[DATE] start invite-ride npc={args.NpcId}");
await conn.ReturnEmptyOkAsync(msg);
// Activate the companion...
await conn.Session.NotifyAsync(MethodId.SyncInviteRideNpcInfo, UxSerializer.Serialize(
new SceneMethods.SyncInviteRideNpcInfo4229938
{
InviteRideNpcInstanceId = args.NpcId,
IsInviteRideNpcActive = true,
}), CancellationToken.None);
// ...and give it the dating interaction menu (Type=21; menu entries render
// from the client's own configs — empty list keeps the default menu).
await conn.Session.NotifyAsync(MethodId.IGameSceneToClient_SyncAgentPropertyChange, UxSerializer.Serialize(
new SceneMethods.SyncAgentPropertyInviteRideChange4229938
{
AgentId = args.NpcId,
Type = 21,
}), CancellationToken.None);
}
/// <summary>Gift giving to an NPC — accepted, inventory is sandbox-unlimited.</summary>
[Handler(MethodId.AskInteractNpcWithGift, HandlerPacketKind.Invoke)]
private Task AskInteractNpcWithGift(Connection conn, UxRpcMessage msg)
{
var args = msg.GetArgs<SceneMethods.AskInteractNpcWithGift4229938>();
conn.Log.Info($"[NPC] gift activity={args.ActivityCfgId} item={args.ItemId} x{args.Count}");
return conn.ReturnEmptyOkAsync(msg);
}
}