using Ananta.SDK.Rpc; using Ananta.Server.Protocol.Client4229938; using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game; using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene; namespace Ananta.Server.Handlers.Game; /// Only RPC endpoints used by the maintained 4229938 private-server feature set. internal sealed partial class GameRouter { [Handler(MethodId.LoginGame, HandlerPacketKind.Notify)] private Task LoginGame(Connection conn, UxRpcMessage msg) { // 把登录时那个 GATE 会话绑到 game 会话上 —— GATE Lua 通道(45437825)只能走它。 // 照 v0.5-2 捐赠端 Endpoints.cs:16-17;不绑 ⇒ 客户端 Lua 永远发不出去。 BindGateSession4229938(conn.Session); return SendInitialGameState(msg.Context); } [Handler(MethodId.RequestGameSceneData, HandlerPacketKind.Notify)] private Task RequestGameSceneData(Connection conn, UxRpcMessage msg) => SendEnterSceneIfNeeded(msg.Context); [Handler(MethodId.GetServerTimeGame)] private async Task GetServerTime(Connection conn, UxRpcMessage msg) { if (msg.IsInvoke) { await conn.ReturnEmptyOkAsync(msg); return; } var args = msg.GetArgs(); await conn.NotifyAsync(MethodId.SendServerTimeGame, LoginCodec.ServerTime(args.clientUnixTime)); } [Handler(MethodId.AskAllSpiritPanelData, HandlerPacketKind.Invoke)] private Task AskAllSpiritPanelData(Connection conn, UxRpcMessage msg) => conn.ReturnAsync(msg, RuntimePayloadFactory.MinimalAllSpiritPanelData4229938()); [Handler(MethodId.GetSpriteToken, HandlerPacketKind.Invoke)] private Task GetSpriteToken4229938(Connection conn, UxRpcMessage msg) => conn.ReturnAsync(msg, new GameMethods.SpriteToken4229938 { Token = string.Empty, ExpireTimeStamp = 0 }); [Handler(MethodId.CheckPSOPermissions, HandlerPacketKind.Invoke)] private Task CheckPsoPermissions(Connection conn, UxRpcMessage msg) => conn.ReturnAsync(msg, false); /// /// AskDeleteTask(63618078) — the "settle / abandon the running task" confirmation. /// /// The box itself only appears when the client thinks a task is active; SyncPlayerAllTask /// is sent with CurrentTask = 0 so ESC keeps opening the phone instead. This handler exists /// because the confirmation can still be reached (e.g. via a task HUD button), and an /// unanswered INVOKE leaves the client awaiting forever. /// /// Deliberately does not parse the args: the payload's varint framing for (taskId, fail) was /// never confirmed, and guessing it risks a read error. The server owns no task state to /// mutate anyway, so an empty OK is the whole answer. /// [Handler(MethodId.AskDeleteTask, HandlerPacketKind.Invoke)] private Task AskDeleteTask(Connection conn, UxRpcMessage msg) => conn.ReturnEmptyOkAsync(msg); [Handler(MethodId.AskSetGamePause, HandlerPacketKind.Invoke)] private async Task AskSetGamePause(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await conn.NotifyAsync(MethodId.SyncGamePause, new SceneMethods.SyncGamePause { pause = args.value }); } [Handler(MethodId.AskLoadSceneCompleted, HandlerPacketKind.Notify)] private Task AskLoadSceneCompleted(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); return OnLoadSceneCompleted(msg.Context, args.sceneId, args.sessionId); } [Handler(MethodId.AskLoadGameResCompleted, HandlerPacketKind.Notify)] private Task AskLoadGameResCompleted(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); return OnLoadGameResourcesCompleted(msg.Context, args.sceneId); } [Handler(MethodId.AskLoadingFinished, HandlerPacketKind.Invoke)] private async Task AskLoadingFinished(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnLoadingFinished(msg.Context, args.sceneId, args.sessionId); } [Handler(MethodId.AskLoadedInSameScene, HandlerPacketKind.Notify)] private Task AskLoadedInSameScene(Connection conn, UxRpcMessage msg) => OnLoadedInSameScene(msg.Context); [Handler(MethodId.AskSwitchSpirit)] private async Task AskSwitchSpirit(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnSwitchSpirit(msg.Context, args.spiritId); } /// /// 切人演出 phase 2 的回执(67654364)。客户端播完 SyncPreSwitchSpirit 带的那段 Timeline 后调用, /// 服务端此时才把控制权交给已经站在演出落点上的目标角色。 /// ⛑ 这是 INVOKE:不接会让客户端 await 到超时(硬规则 94/112)。 /// 没等到也不会卡 roster —— OnSwitchSpirit 开头有 PreSwitchTimeoutMs 兜底。 /// [Handler(MethodId.ReportPreSwitchSpiritFinish)] private async Task ReportPreSwitchSpiritFinish(Connection conn, UxRpcMessage msg) { await conn.ReturnEmptyOkAsync(msg); await OnPreSwitchSpiritFinish(msg.Context); } [Handler(MethodId.AskSwitchWeapon, HandlerPacketKind.Notify)] private Task AskSwitchWeapon(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); return OnSwitchWeapon(msg.Context, args.index); } [Handler(MethodId.AskSwitchFightStyle, HandlerPacketKind.Invoke)] private async Task AskSwitchFightStyle(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnSwitchFightStyle(msg.Context, args.spiritId, args.fightStyleTypeId, args.fightStyleId); } [Handler(MethodId.AskSetWeaponFightStyle, HandlerPacketKind.Invoke)] private async Task AskSetWeaponFightStyle(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnSetWeaponFightStyle(msg.Context, args.weaponInstanceId, args.fightStyleId); } [Handler(MethodId.AskLoadWeaponToSlot, HandlerPacketKind.Invoke)] private async Task AskLoadWeaponToSlot(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnLoadWeaponToSlot(msg.Context, args.spiritId, args.weaponId, args.slotIndex); } [Handler(MethodId.AskDepositSpiritWeapon, HandlerPacketKind.Invoke)] private async Task AskDepositSpiritWeapon(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnDepositSpiritWeapon(msg.Context, args.spiritId, args.slotIndex); } [Handler(MethodId.AskExchangeWeaponSlot, HandlerPacketKind.Invoke)] private async Task AskExchangeWeaponSlot(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnExchangeWeaponSlot(msg.Context, args.fromSpirit, args.fromIndex, args.toSpirit, args.toIndex); } [Handler(MethodId.AskAddClientBuff)] private async Task AskAddClientBuff(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnClientBuffAdd(msg.Context, args.unitId, args.buffId); } [Handler(MethodId.AskRemoveClientBuff)] private async Task AskRemoveClientBuff(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnClientBuffRemove(msg.Context, args.unitId, args.buffId); } [Handler(MethodId.AskEnterFeiSuoCrouch, HandlerPacketKind.Notify)] private Task AskEnterFeiSuoCrouch(Connection conn, UxRpcMessage msg) => OnEnterFeiSuoCrouch(msg.Context); [Handler(MethodId.AskFeiSuoSuccess, HandlerPacketKind.Invoke)] private async Task AskFeiSuoSuccess(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await OnFeiSuoSuccess(msg.Context, args.feiSuoId); await conn.ReturnEmptyOkAsync(msg); } [Handler(MethodId.AskLeaveFeiSuoCrouch, HandlerPacketKind.Notify)] private Task AskLeaveFeiSuoCrouch(Connection conn, UxRpcMessage msg) => OnLeaveFeiSuoCrouch(msg.Context); /// /// 蹲伏暗杀候选上报(Notify)。客户端 StealthManager 每 0.4s 报一次「谁在我背后」, /// 服务端回 SyncEnemyCanAssassinate(68218494) 授权 —— 没有这一来一回, /// 客户端就不会挂暗杀交互,HUD 永远不出提示。 /// ⛑ 规则 94:MethodId 必须同时进 GameRouter.cs 的 Enabled4229938MethodIds。 /// [Handler(MethodId.ReportCanAssassinateEnemies, HandlerPacketKind.Notify)] private Task ReportCanAssassinateEnemies(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); return OnReportCanAssassinateEnemies(msg.Context, args.enemyids); } [Handler(MethodId.AskClientUseCommonSkill, HandlerPacketKind.Invoke)] [Handler(MethodId.AskUseSkill, HandlerPacketKind.Invoke)] private Task AskUseSkill(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); return OnClientUseSkill(msg.Context, args.data); } [Handler(MethodId.AskSkillUseWeaponDurability, HandlerPacketKind.Notify)] private Task AskSkillUseWeaponDurability(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); return OnSkillUseWeaponDurability(msg.Context, args.skillid, args.triggerindex); } [Handler(MethodId.AskWeaponEquipBullets, HandlerPacketKind.Invoke)] private async Task AskWeaponEquipBullets(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); await conn.ReturnEmptyOkAsync(msg); await OnWeaponEquipBullets(msg.Context, args.weaponinstanceid, args.bulletid); } [Handler(MethodId.ReportSkillEnd, HandlerPacketKind.Notify)] private Task ReportSkillEnd(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); return OnReportSkillEnd(msg.Context, args); } [Handler(MethodId.AskMultipleSkillHit2, HandlerPacketKind.Notify)] private Task AskMultipleSkillHit2(Connection conn, UxRpcMessage msg) { var args = msg.GetArgs(); return OnSkillHit(msg.Context, args.skillHitData); } [Handler(MethodId.AskInterruptSkillExecute, HandlerPacketKind.Invoke)] [Handler(MethodId.AskInterruptSkillExecuteStiff, HandlerPacketKind.Invoke)] [Handler(MethodId.AskSkillExecuteEnd, HandlerPacketKind.Invoke)] [Handler(MethodId.AskSkillExecute, HandlerPacketKind.Invoke)] private static Task AcceptCombatInvoke(Connection conn, UxRpcMessage msg) => conn.ReturnEmptyOkAsync(msg); // AskSkillDestructibleCreate is answered in GameRouter.WeaponDrop.cs — dropping a weapon needs // a real destructible id, and the hard 0 this used to return made every later step of the drop // (absorb animation, Q prompt, pick-up) address an entity that does not exist. [Handler(MethodId.AskReportLogicAgentSyncData)] [Handler(MethodId.AskUnitMoveActionSimple, HandlerPacketKind.Notify)] [Handler(MethodId.AskUnitMoveActionSimpleWithGround, HandlerPacketKind.Notify)] [Handler(MethodId.AskUnitMoveActionWithGround, HandlerPacketKind.Notify)] [Handler(MethodId.AskUnitMoveAction, HandlerPacketKind.Notify)] private async Task MovementReport(Connection conn, UxRpcMessage msg) { IEnumerable? samples = msg.MethodId switch { MethodId.AskReportLogicAgentSyncData => msg.GetArgs().list, MethodId.AskUnitMoveActionSimple => msg.GetArgs().actions.Select(x => new SceneMethods.LogicAgentSyncData { AgentId = x.UnitId, Position = x.Pos, Rotation = x.Rot }), MethodId.AskUnitMoveActionSimpleWithGround => msg.GetArgs().actions.Select(x => new SceneMethods.LogicAgentSyncData { AgentId = x.UnitId, Position = x.Pos, Rotation = x.Rot }), MethodId.AskUnitMoveAction => msg.GetArgs().actions.Select(x => new SceneMethods.LogicAgentSyncData { AgentId = x.UnitId, Position = x.Pos, Rotation = x.Rot }), MethodId.AskUnitMoveActionWithGround => msg.GetArgs().actions.Select(x => new SceneMethods.LogicAgentSyncData { AgentId = x.UnitId, Position = x.Pos, Rotation = x.Rot }), _ => null }; await conn.ReturnEmptyOkAsync(msg); await OnMovementReport(msg.Context, samples); } }