Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.UnhandledBatch.cs

327 lines
16 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Ananta.SDK.Rpc;
using Ananta.Server.Configuration; // IdCounterStore(硬规则 104 的落盘号段)
using Ananta.Server.Protocol.Client4229938;
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// Batch implementation of top unhandled methods from unknown-methods.log:
/// camera telemetry (silent no-ops), vehicle driving/radio/horn/top-speed/nitro set,
/// indoor/building/safe-area transitions, and a second neutral UI/economy batch.
/// </summary>
internal sealed partial class GameRouter
{
// --- Camera telemetry: pure client-side state, zero server value. Silent accept. ---
[Handler(MethodId.AskPlayerCameraMove, HandlerPacketKind.Notify)]
private Task CamMove(Connection conn, UxRpcMessage msg) => Task.CompletedTask;
[Handler(MethodId.AskUpdatePlayerCameraRotation, HandlerPacketKind.Notify)]
private Task CamRot(Connection conn, UxRpcMessage msg) => Task.CompletedTask;
[Handler(MethodId.AskUpdatePlayerCameraFOV, HandlerPacketKind.Notify)]
private Task CamFov(Connection conn, UxRpcMessage msg) => Task.CompletedTask;
[Handler(MethodId.AskUpdatePlayerCameraAspectRatio, HandlerPacketKind.Notify)]
private Task CamAspect(Connection conn, UxRpcMessage msg) => Task.CompletedTask;
// --- Vehicle driving telemetry ---
[Handler(MethodId.ReportDrivingVehicle, HandlerPacketKind.Notify)]
private Task ReportDrivingVehicle(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.ReportDrivingVehicleArgs4229938>(out var args) && args is not null)
{
var runtime = GetVehicleRuntime(msg.Context);
lock (runtime.SyncRoot)
{
if (runtime.Vehicles.TryGetValue(args.vehicleId, out var record))
{
record.DistanceDriven += args.deltaDistance;
if (record.IsDriving != args.isDriving)
{
record.IsDriving = args.isDriving;
conn.Log.Info($"[VEHICLE] driving vehicle={args.vehicleId} on={args.isDriving} odo={record.DistanceDriven:F0}m");
}
}
}
}
return Task.CompletedTask;
}
[Handler(MethodId.AskVehicleNitroValue, HandlerPacketKind.Notify)]
private Task AskVehicleNitroValue(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskVehicleNitroValueArgs4229938>(out var args) && args is not null)
{
var runtime = GetVehicleRuntime(msg.Context);
lock (runtime.SyncRoot)
{
if (runtime.Vehicles.TryGetValue(args.vehicleId, out var record))
record.LastNitroValue = args.value;
}
}
return Task.CompletedTask;
}
[Handler(MethodId.AskVehicleNitro)]
private async Task AskVehicleNitro(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskVehicleNitroArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[VEHICLE] nitro vehicle={args.VehicleId} value={args.NitrogenValue:F1} begin={args.BeginOrEnd}");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskModifyVehicleTopSpeed, HandlerPacketKind.Notify)]
private Task AskModifyVehicleTopSpeed(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskModifyVehicleTopSpeedArgs4229938>(out var args) && args is not null)
{
var runtime = GetVehicleRuntime(msg.Context);
lock (runtime.SyncRoot)
{
if (runtime.Vehicles.TryGetValue(args.vehicleId, out var record))
{
record.TopSpeed = args.topSpeed;
record.HasTopSpeed = args.beginOrEnd;
}
}
conn.Log.Info($"[VEHICLE] topspeed vehicle={args.vehicleId} v={args.topSpeed:F1} active={args.beginOrEnd}");
}
return Task.CompletedTask;
}
[Handler(MethodId.AskVehicleHorn, HandlerPacketKind.Notify)]
private Task AskVehicleHorn(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskVehicleHornArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[VEHICLE] horn entity={args.entityId} play={args.play}");
return Task.CompletedTask;
}
[Handler(MethodId.AskVehicleContactDamage, HandlerPacketKind.Notify)]
private Task AskVehicleContactDamage(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskVehicleContactDamageArgs4229938>(out var args) && args?.data is not null)
conn.Log.Info($"[VEHICLE] contact vehicle={args.vehicleId} mass={args.data.VehicleMass:F0} layer={args.data.Layer} other={args.data.OtherVehicleEntityId}");
else
conn.Log.Warn($"[VEHICLE] AskVehicleContactDamage undecodable bytes={msg.Body.Length}");
return Task.CompletedTask;
}
[Handler(MethodId.AskVehicleInteractConfig, HandlerPacketKind.Notify)]
private Task AskVehicleInteractConfig(Connection conn, UxRpcMessage msg)
{
conn.Log.Info($"[VEHICLE] interact-config bytes={msg.Body.Length} hex={Convert.ToHexString(msg.Body, 0, Math.Min(msg.Body.Length, 48))}");
return Task.CompletedTask;
}
[Handler(MethodId.AskUpdateVehicleDestructibleParts, HandlerPacketKind.Notify)]
private Task AskUpdateVehicleDestructibleParts(Connection conn, UxRpcMessage msg)
{
conn.Log.Info($"[VEHICLE] destructible-parts bytes={msg.Body.Length} hex={Convert.ToHexString(msg.Body, 0, Math.Min(msg.Body.Length, 48))}");
return Task.CompletedTask;
}
// AskGetVehicleRadioContent (67725622) 与 AskSwitchVehicleRadio (67959772) 以前在这里占位
// (只打日志 + 回全零 typed-default)。2026-09-27 搬到 GameRouter.VehicleRadio.cs:
// 现在会真的按 RadioContentCatalog 下发 DJ 口播 / 广告 / 新闻的 id。
// ⛑ 不要在别处再挂同名 handler —— 同一 methodId 两个 invoke handler 会
// "Duplicate invoke handler at startup"。
// --- Indoor / building / safe-area transitions (logged accepts; sector control later) ---
[Handler(MethodId.SyncChangeSafeArea, HandlerPacketKind.Notify)]
private Task SyncChangeSafeArea(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.SyncChangeSafeAreaArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[SCENE] safe-area region={args.regionId}");
return Task.CompletedTask;
}
[Handler(MethodId.SyncChangeBuilding)]
private async Task SyncChangeBuilding(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.SyncChangeBuildingArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[SCENE] building={args.buildingId} floor={args.floorId}");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.SyncChangeIndoor)]
private async Task SyncChangeIndoor(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.SyncChangeIndoorArgs4229938>(out var args) && args is not null)
{
conn.Log.Info($"[SCENE] indoor config={args.indoorConfigId} bound={args.boundId}");
// ★ 这是室内判定的权威来源:客户端进/出 GpsBound 时自己报 indoorConfigId。
// 服务端的几何最近邻在 MiniMapPos 矩形重叠的场馆(ACD 76 / ACD快乐房 129)会抖,
// 所以 GameRouter.IndoorSector.cs 优先读这里。见 IndoorSectorSettings.PreferClientReport。
var state = GetWorldState(msg.Context);
lock (state.SyncRoot)
{
state.ClientIndoorConfigId = args.indoorConfigId;
state.ClientIndoorReportMs = Environment.TickCount64;
}
}
await conn.ReturnEmptyOkAsync(msg);
}
// --- Neutral UI/economy batch 2: exact typed-default replies, info-level logging ---
// AskGetAllMetroInfos used to be answered here with the generic empty list. It now has a real
// scheduler in GameRouter.Metro.cs (round 54/55): keep the attribute off so the router does not see
// two invoke handlers for the same method id (Duplicate invoke handler at startup).
[Handler(MethodId.GetMailHeadList, HandlerPacketKind.Invoke)]
// AskMomentsPostSimpleInfos used to be answered here with the generic empty list. It now has a
// real catalogue in GameRouter.Bubble.cs (趴趴 / Bubble): keep the attribute off so the router
// does not see two invoke handlers for the same method id.
[Handler(MethodId.AskAkxSessionList, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskPanelOpenOrClose, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskQueryPlayerUnlockNameEffect, HandlerPacketKind.Invoke)]
[Handler(MethodId.GetPersonalInfo, HandlerPacketKind.Invoke)]
private Task KnownNeutralInvokeBatch2(Connection conn, UxRpcMessage msg)
{
if (DefaultReturnCatalog4229938.TryGet(msg.MethodId, out var body, out var shape))
{
conn.Log.Info($"[RPC4229938] neutral {Ananta.SDK.Logging.RpcMethodNames.Display(msg.MethodId)} -> {shape} {body.Length}b");
return msg.Context.ReturnAsync(body);
}
return conn.ReturnEmptyOkAsync(msg);
}
// --- Skill/combat accepts: logged, protocol-correct empty replies (authoritative later) ---
[Handler(MethodId.AskInterruptSkillExecute2)]
[Handler(MethodId.AskSkillExecute3)]
[Handler(MethodId.AskSkillExecuteEnd2)]
[Handler(MethodId.AskSkillAddState)]
[Handler(MethodId.AskSkillOpenShield)]
[Handler(MethodId.AskSkillCloseShield)]
[Handler(MethodId.AskSkillTimeCurve)]
[Handler(MethodId.ReportSkillAnimationEnd)]
[Handler(MethodId.AskSkillSpawnItem)]
[Handler(MethodId.AskSpoonClientAttack)]
[Handler(MethodId.AskVehicleSkillDamage)]
private async Task SkillNeutralAccepts(Connection conn, UxRpcMessage msg)
{
conn.Log.Info($"[COMBAT] accept {Ananta.SDK.Logging.RpcMethodNames.Display(msg.MethodId)} bytes={msg.Body.Length}");
await conn.ReturnEmptyOkAsync(msg);
}
// -----------------------------------------------------------------------------------------
// ⛑⛑ 2026-09-29 修正:这两条**不是 void**。
// 客户端 `midToReturnMessageReader` 里两者都是 `reader.ReadUInt64(reader)`(裸 u64,无 presence),
// 而原来挂在 SkillNeutralAccepts 上走 `ReturnEmptyOkAsync` —— 那个是 **0 字节载荷**
// (`Connection.cs:30` `ReturnEmptyAsync(..., Array.Empty<byte>())`)⇒ 客户端 reader 读空 buffer。
// 实测触发频率:**[COMBAT] 三个会话共 29 次**,是本轮回归检查里最高频的一条。
// 修法照抄同类已有实现 `AskItemDestructibleCreate`(GameRouter.MiscAcks.cs:200)——
// 用硬规则 104 的落盘号段发型实例 id(只回号不建实体,目的是让客户端 await 落地 + 拿得到 uid)。
// ⛑ 回滚:把这两行属性挪回上面的 SkillNeutralAccepts 即可(handler 不注册走 typed-default)。
// -----------------------------------------------------------------------------------------
[Handler(MethodId.AskSkillDestructibleCreateGadget, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskSkillDestructibleCreateVehicle, HandlerPacketKind.Invoke)]
private Task AskSkillDestructibleCreate(Connection conn, UxRpcMessage msg)
{
var id = unchecked((ulong)IdCounterStore.Next(DestructibleInstanceIdCounter, DestructibleInstanceIdFloor));
conn.Log.Info($"[COMBAT] skill-destructible-create {Ananta.SDK.Logging.RpcMethodNames.Display(msg.MethodId)} " +
$"bytes={msg.Body.Length} -> id={id}");
return conn.ReturnAsync(msg, id);
}
// --- Destructibles / weapon red dots / discards / hangup (fresh log batch) ---
[Handler(MethodId.AskNotifyDestructibleHits)]
private async Task AskNotifyDestructibleHits(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskNotifyDestructibleHitsArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[DESTRUCTIBLE] hits targets={args.hits.Count}");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskOperateDestructibleObject)]
private async Task AskOperateDestructibleObject(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskOperateDestructibleObjectArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[DESTRUCTIBLE] operate id={args.id} op={args.operation}");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskMoveDestructibleObjects, HandlerPacketKind.Notify)]
private Task AskMoveDestructibleObjects(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskMoveDestructibleObjectsArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[DESTRUCTIBLE] move count={args.ids.Count}");
return Task.CompletedTask;
}
[Handler(MethodId.AskBreakDestructibleObjects, HandlerPacketKind.Notify)]
private Task AskBreakDestructibleObjects(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskBreakDestructibleObjectsArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[DESTRUCTIBLE] break breaker={args.breaker} count={args.brokenInfos.Count}");
else
conn.Log.Warn($"[DESTRUCTIBLE] AskBreakDestructibleObjects undecodable bytes={msg.Body.Length}");
return Task.CompletedTask;
}
[Handler(MethodId.AskReadWeaponRedDots, HandlerPacketKind.Notify)]
private Task AskReadWeaponRedDots(Connection conn, UxRpcMessage msg) => Task.CompletedTask;
[Handler(MethodId.AskDiscardWeaponByInstanceId)]
private async Task AskDiscardWeaponByInstanceId(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskDiscardWeaponByInstanceIdArgs4229938>(out var args) && args is not null)
{
var state = GetWorldState(msg.Context);
var slots = WeaponSlotIds(state, args.spiritId);
var index = slots.IndexOf(args.weaponInstanceId);
if (index >= 0 && IsEditableWeaponSlot(index))
{
slots[index] = 0;
conn.Log.Info($"[ARMORY] discard instance={args.weaponInstanceId} spirit={args.spiritId} slot={index} dropout={args.isDropOut}");
await PublishWeaponSlotsAfterMutation(msg.Context, args.spiritId, $"discard:{args.weaponInstanceId}");
}
else
{
conn.Log.Warn($"[ARMORY] discard rejected instance={args.weaponInstanceId} spirit={args.spiritId}");
}
}
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskDiscardWeapon, HandlerPacketKind.Notify)]
private async Task AskDiscardWeapon(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskDiscardWeaponArgs4229938>(out var args) && args is not null)
{
var state = GetWorldState(msg.Context);
await OnDepositSpiritWeapon(msg.Context, state.ActiveSpiritTemplateId, args.index);
conn.Log.Info($"[ARMORY] discard-index slot={args.index}");
}
}
[Handler(MethodId.RequestPlayerStartHangup)]
private async Task RequestPlayerStartHangup(Connection conn, UxRpcMessage msg)
{
GetWorldState(msg.Context).IsHangup = true;
conn.Log.Info("[HANGUP] start");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.RequestPlayerStopHangup)]
private async Task RequestPlayerStopHangup(Connection conn, UxRpcMessage msg)
{
GetWorldState(msg.Context).IsHangup = false;
conn.Log.Info("[HANGUP] stop");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskBreakSkillTimeCurve, HandlerPacketKind.Notify)]
private Task AskBreakSkillTimeCurve(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskBreakSkillTimeCurveArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[COMBAT] break-curve releaser={args.releaser} id={args.id} index={args.index}");
return Task.CompletedTask;
}
}