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

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);
}
}