using System.Buffers.Binary; 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; /// /// 迷雾 / 探索 POI / Aether 氛围层 —— SyncEnterFogMapPoiId(63491307)、 /// GmUnlockAllFogMap(65914419)、GmSetAmbianceEnabled(69166506)、 /// GmGetAmbianceEnabled(69384152)。 /// /// 移植自捐赠端 DremkaPS(2026-09-24)。 /// /// ★★ 这一组是抓包筛出来的,不是照单全收。捐赠端比本服多的 handler 一共有 66 个, /// 拿 16.5 万行 unknown-methods.log 逐个数下来,只有 6 个真的被客户端发过: /// SyncEnterFogMapPoiId 49 次(活跃到 09-23)、GmUnlockAllFogMap 5 次、 /// ForceSubmitTask 5 次、GmSwitchSpiritHere 3 次、GmSubmitEvent 2 次、 /// GmGetAmbianceEnabled 2 次。剩下 60 个零次,注册它们只是增加维护面 —— 已剔除。 /// /// 剔除的三条(虽然日志里出现过): /// · ForceSubmitTask / GmSubmitEvent —— 任务族。本服 /// GameRouter.Tasks.cs 已经四次证明任务面板在 4229938 下渲染不出来 /// (TaskPublishEnabled = false),捐赠端自己也只拿它们记账,搬过来是死代码。 /// · GmSwitchSpiritHere —— 切角色。本服已有 AskSwitchSpirit 这条正规路径, /// GM 版只是控制台的快捷方式,09-16 之后再没被用过。 /// /// ⛑ GmGetAmbianceEnabled 是 0 字节的 INVOKE:客户端在 await 一个返回值。 /// 不接它,控制台按下去会一直卡到超时(规则 94)。这里回一个 bool。 /// internal sealed partial class GameRouter { [Handler(MethodId.SyncEnterFogMapPoiId, HandlerPacketKind.Notify)] private Task EnterFogMapPoiId(Connection conn, UxRpcMessage msg) { var poiId = msg.TryGetArgs(out var args) && args is not null ? args.PoiId : (msg.Body.Length >= 4 ? BinaryPrimitives.ReadUInt32LittleEndian(msg.Body) : 0u); if (poiId == 0) { conn.Log.Warn($"[FOG] enter-poi without an id bytes={msg.Body.Length}"); return Task.CompletedTask; } var state = GetWorldState(msg.Context); bool fresh; int total; lock (state.SyncRoot) { fresh = state.FogMapPoiIds.Add(poiId); total = state.FogMapPoiIds.Count; } // 同一个 POI 玩家会反复踏入(抓包里 49 条几乎全是 id=11),所以只在新增时记 Info。 if (fresh) conn.Log.Info($"[FOG] enter-poi id={poiId} total={total}"); return Task.CompletedTask; } [Handler(MethodId.GmUnlockAllFogMap, HandlerPacketKind.Invoke)] private async Task GmUnlockAllFogMap(Connection conn, UxRpcMessage msg) { var unlock = msg.TryGetArgs(out var args) && args is not null ? args.Unlock : (msg.Body.Length > 0 && msg.Body[0] != 0); var state = GetWorldState(msg.Context); lock (state.SyncRoot) state.FogMapAllUnlocked = unlock; conn.Log.Info($"[FOG] gm-unlock-all unlock={unlock} poi={state.FogMapPoiIds.Count} " + $"raid={state.ActiveRaidId} (recorded: pushing SyncSceneFogMapAllUnlock needs the current sceneId, not wired yet)"); await conn.ReturnEmptyOkAsync(msg); } [Handler(MethodId.GmSetAmbianceEnabled, HandlerPacketKind.Invoke)] private async Task GmSetAmbianceEnabled(Connection conn, UxRpcMessage msg) { bool enabled; int type = 0; if (msg.TryGetArgs(out var args) && args is not null) { enabled = args.Enabled; type = args.Type; } else { enabled = msg.Body.Length > 0 && msg.Body[0] != 0; } var state = GetWorldState(msg.Context); lock (state.SyncRoot) { state.AmbianceEnabled = enabled; state.AmbianceType = type; } conn.Log.Info($"[GM] ambiance set enabled={enabled} type={type}"); await conn.ReturnEmptyOkAsync(msg); } /// /// 0 字节 INVOKE —— 必须回一个 bool,否则客户端这次调用会 await 到超时。 /// [Handler(MethodId.GmGetAmbianceEnabled, HandlerPacketKind.Invoke)] private Task GmGetAmbianceEnabled(Connection conn, UxRpcMessage msg) { var state = GetWorldState(msg.Context); bool enabled; lock (state.SyncRoot) enabled = state.AmbianceEnabled; conn.Log.Info($"[GM] ambiance query -> {enabled}"); return conn.ReturnAsync(msg, enabled); } }