239 lines
12 KiB
C#
239 lines
12 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.AskInteractCmd, HandlerPacketKind.Notify)]
|
|
private async Task AskInteractCmd(Connection conn, UxRpcMessage msg)
|
|
{
|
|
var data = msg.GetArgs<SceneMethods.InteractCmdData4229938>();
|
|
conn.Log.Info($"[GADGET] interact cmd type={data.CmdType} receiver={data.Receiver} sender={data.Sender}");
|
|
await conn.Session.NotifyAsync(MethodId.SyncInteractCmd, UxSerializer.Serialize(
|
|
new SceneMethods.SyncInteractCmd4229938 { Data = data }), CancellationToken.None);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
|
|
[Handler(MethodId.AskSceneDeviceChangeState, HandlerPacketKind.Invoke)]
|
|
private async Task AskSceneDeviceChangeState(Connection conn, UxRpcMessage msg)
|
|
{
|
|
var args = msg.GetArgs<SceneMethods.AskSceneDeviceChangeState4229938>();
|
|
conn.Log.Info($"[GADGET] change state type={args.Type} id={args.Info.Id} stateType={args.Info.StateType} state={args.Info.State} check={args.Info.StateCheckIndex}");
|
|
// Ack the requested check index so the gadget graph's condition passes, then mirror
|
|
// the new state back — the client applies it on the Sync, not on the invoke return.
|
|
await conn.ReturnAsync(msg, new SceneMethods.SceneDeviceCheckIndexCallback4229938
|
|
{
|
|
CheckIndex = args.Info.StateCheckIndex,
|
|
Error = 0,
|
|
});
|
|
await conn.Session.NotifyAsync(MethodId.SyncSceneDeviceStateChange, UxSerializer.Serialize(
|
|
new SceneMethods.SyncSceneDeviceStateChange4229938
|
|
{
|
|
Type = args.Type,
|
|
Id = args.Info.Id,
|
|
StateType = args.Info.StateType,
|
|
State = args.Info.State,
|
|
StateCheckIndex = args.Info.StateCheckIndex,
|
|
}), CancellationToken.None);
|
|
}
|
|
|
|
[Handler(MethodId.AskSceneDeviceRecordValue, HandlerPacketKind.Invoke)]
|
|
private async Task AskSceneDeviceRecordValue(Connection conn, UxRpcMessage msg)
|
|
{
|
|
var args = msg.GetArgs<SceneMethods.AskSceneDeviceRecordValue4229938>();
|
|
conn.Log.Info($"[GADGET] record value type={args.Type} id={args.Info.Id} name={args.Info.ValueName} value='{args.Info.Value}' check={args.Info.ValueCheckIndex}");
|
|
await conn.ReturnAsync(msg, new SceneMethods.SceneDeviceCheckIndexCallback4229938
|
|
{
|
|
CheckIndex = args.Info.ValueCheckIndex,
|
|
Error = 0,
|
|
});
|
|
// Mirror the recorded value so the device graph sees the new value on Sync.
|
|
await conn.Session.NotifyAsync(MethodId.SyncSceneDeviceValueChange, UxSerializer.Serialize(
|
|
new SceneMethods.SyncSceneDeviceValueChange4229938
|
|
{
|
|
Type = args.Type,
|
|
Id = args.Info.Id,
|
|
ValueName = args.Info.ValueName,
|
|
Value = args.Info.Value,
|
|
ValueCheckIndex = args.Info.ValueCheckIndex,
|
|
}), CancellationToken.None);
|
|
await conn.Session.NotifyAsync(MethodId.SyncSceneDeviceValueChange, UxSerializer.Serialize(
|
|
new SceneMethods.SyncSceneDeviceValueChange4229938
|
|
{
|
|
Type = args.Type,
|
|
Id = args.Info.Id,
|
|
ValueName = args.Info.ValueName,
|
|
Value = args.Info.Value,
|
|
ValueCheckIndex = args.Info.ValueCheckIndex,
|
|
}), CancellationToken.None);
|
|
}
|
|
|
|
[Handler(MethodId.AskSceneDeviceSendSignal, HandlerPacketKind.Invoke)]
|
|
private async Task AskSceneDeviceSendSignal(Connection conn, UxRpcMessage msg)
|
|
{
|
|
var args = msg.GetArgs<SceneMethods.AskSceneDeviceSendSignal4229938>();
|
|
conn.Log.Info($"[GADGET] send signal type={args.Type} id={args.Id} signal='{args.SignalName}'");
|
|
await conn.ReturnEmptyOkAsync(msg);
|
|
await conn.Session.NotifyAsync(MethodId.SyncSceneDeviceSignalSend, UxSerializer.Serialize(
|
|
new SceneMethods.SyncSceneDeviceSignalSend4229938
|
|
{
|
|
Type = args.Type,
|
|
Id = args.Id,
|
|
SignalName = args.SignalName,
|
|
}), CancellationToken.None);
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// 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);
|
|
}
|
|
}
|