288 lines
13 KiB
C#
288 lines
13 KiB
C#
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;
|
||
|
||
/// <summary>Only RPC endpoints used by the maintained 4229938 private-server feature set.</summary>
|
||
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<GameMethods.GetServerTime>();
|
||
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);
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
[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<SceneMethods.AskSetGamePause>();
|
||
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<SceneMethods.AskLoadSceneCompleted>();
|
||
return OnLoadSceneCompleted(msg.Context, args.sceneId, args.sessionId);
|
||
}
|
||
|
||
[Handler(MethodId.AskLoadGameResCompleted, HandlerPacketKind.Notify)]
|
||
private Task AskLoadGameResCompleted(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var args = msg.GetArgs<SceneMethods.AskLoadGameResCompleted>();
|
||
return OnLoadGameResourcesCompleted(msg.Context, args.sceneId);
|
||
}
|
||
|
||
[Handler(MethodId.AskLoadingFinished, HandlerPacketKind.Invoke)]
|
||
private async Task AskLoadingFinished(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var args = msg.GetArgs<SceneMethods.AskLoadingFinished>();
|
||
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<SceneMethods.AskSwitchSpirit>();
|
||
await conn.ReturnEmptyOkAsync(msg);
|
||
await OnSwitchSpirit(msg.Context, args.spiritId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切人演出 phase 2 的回执(67654364)。客户端播完 SyncPreSwitchSpirit 带的那段 Timeline 后调用,
|
||
/// 服务端此时才把控制权交给已经站在演出落点上的目标角色。
|
||
/// ⛑ 这是 INVOKE:不接会让客户端 await 到超时(硬规则 94/112)。
|
||
/// 没等到也不会卡 roster —— OnSwitchSpirit 开头有 PreSwitchTimeoutMs 兜底。
|
||
/// </summary>
|
||
[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<SceneMethods.AskSwitchWeapon>();
|
||
return OnSwitchWeapon(msg.Context, args.index);
|
||
}
|
||
|
||
[Handler(MethodId.AskSwitchFightStyle, HandlerPacketKind.Invoke)]
|
||
private async Task AskSwitchFightStyle(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var args = msg.GetArgs<GameMethods.AskSwitchFightStyle>();
|
||
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<GameMethods.AskSetWeaponFightStyle>();
|
||
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<GameMethods.AskLoadWeaponToSlot>();
|
||
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<GameMethods.AskDepositSpiritWeapon>();
|
||
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<GameMethods.AskExchangeWeaponSlot>();
|
||
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<SceneMethods.AskAddClientBuff>();
|
||
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<SceneMethods.AskRemoveClientBuff>();
|
||
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<SceneMethods.AskFeiSuoSuccess>();
|
||
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);
|
||
|
||
/// <summary>
|
||
/// 蹲伏暗杀候选上报(Notify)。客户端 <c>StealthManager</c> 每 0.4s 报一次「谁在我背后」,
|
||
/// 服务端回 <c>SyncEnemyCanAssassinate</c>(68218494) 授权 —— 没有这一来一回,
|
||
/// 客户端就不会挂暗杀交互,HUD 永远不出提示。
|
||
/// ⛑ 规则 94:MethodId 必须同时进 <c>GameRouter.cs</c> 的 <c>Enabled4229938MethodIds</c>。
|
||
/// </summary>
|
||
[Handler(MethodId.ReportCanAssassinateEnemies, HandlerPacketKind.Notify)]
|
||
private Task ReportCanAssassinateEnemies(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var args = msg.GetArgs<SceneMethods.ReportCanAssassinateEnemies4229938>();
|
||
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<SceneMethods.AskUseSkill>();
|
||
return OnClientUseSkill(msg.Context, args.data);
|
||
}
|
||
|
||
[Handler(MethodId.AskSkillUseWeaponDurability, HandlerPacketKind.Notify)]
|
||
private Task AskSkillUseWeaponDurability(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var args = msg.GetArgs<SceneMethods.AskSkillUseWeaponDurability>();
|
||
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<SceneMethods.AskWeaponEquipBullets>();
|
||
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<SceneMethods.ReportSkillEnd>();
|
||
return OnReportSkillEnd(msg.Context, args);
|
||
}
|
||
|
||
[Handler(MethodId.AskMultipleSkillHit2, HandlerPacketKind.Notify)]
|
||
private Task AskMultipleSkillHit2(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var args = msg.GetArgs<SceneMethods.AskMultipleSkillHit2>();
|
||
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<SceneMethods.LogicAgentSyncData>? samples = msg.MethodId switch
|
||
{
|
||
MethodId.AskReportLogicAgentSyncData => msg.GetArgs<SceneMethods.AskReportLogicAgentSyncData>().list,
|
||
MethodId.AskUnitMoveActionSimple => msg.GetArgs<SceneMethods.AskUnitMoveActionSimple>().actions.Select(x =>
|
||
new SceneMethods.LogicAgentSyncData { AgentId = x.UnitId, Position = x.Pos, Rotation = x.Rot }),
|
||
MethodId.AskUnitMoveActionSimpleWithGround => msg.GetArgs<SceneMethods.AskUnitMoveActionSimpleWithGround>().actions.Select(x =>
|
||
new SceneMethods.LogicAgentSyncData { AgentId = x.UnitId, Position = x.Pos, Rotation = x.Rot }),
|
||
MethodId.AskUnitMoveAction => msg.GetArgs<SceneMethods.AskUnitMoveAction>().actions.Select(x =>
|
||
new SceneMethods.LogicAgentSyncData { AgentId = x.UnitId, Position = x.Pos, Rotation = x.Rot }),
|
||
MethodId.AskUnitMoveActionWithGround => msg.GetArgs<SceneMethods.AskUnitMoveActionWithGround>().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);
|
||
}
|
||
}
|