using System.Runtime.CompilerServices; using Ananta.SDK.Network; using Ananta.SDK.Serialization; using Ananta.Server.Protocol.Client4229938; namespace Ananta.Server.Handlers.Game; /// /// ★★ 捐赠端警察链的主线粘合层(2026-10-02)。 /// /// 目的:让 GameRouter.LixiPolice.cs / GameRouter.PoliceVehicle.cs / /// GameRouter.PolicePrison.cs / GameRouter.PolicePrisonAnimation.cs / /// GameRouter.PoliceButtons.cs 能逐字编译,而不去改那些捐赠端文件一个字符。 /// 本文件里的东西分三类: /// /// 捐赠端代码的最小子集:DeliveryText4229938(逐字抄自捐赠端 /// GameRouter.Delivery.cs:225-227)、HackerDevices4229938 账本 /// (逐字抄自捐赠端 GameRouter.HackerDevices.cs:30 与其 runtime 类的被用字段)。 /// ⛑ 只抄被核心链真正引用的成员,机械狗的召唤/控制/武器结算那套没有搬(本次范围外)。 /// 主线命名适配:GetEnemyRuntimeState → 主线的 /// GetLocalEnemyRuntimeState;RunEnemyAttackAiAsync(session, gen, worker) /// → 主线只有单参版本,这里按主线自己的 AttackAiWorkerStarted 闸门转发。 /// 主线自有、捐赠端没有的保留件:TryFindInLedger / /// RewriteLedgerPosition(原 GameRouter.PolicePrison.cs,被 /// GameRouter.PoliceEscort.cs 依赖;捐赠端 PolicePrison.cs 不含这两个方法)。 /// /// /// ⛑ 将来若把机械狗/支援/派遣级联搬进来,本文件的第 1 类应整段删除,换成捐赠端真文件。 /// internal sealed partial class GameRouter { // ================================================================ 主线命名适配 /// /// 捐赠端 GameRouter.Enemy.cs:116 的 GetEnemyRuntimeState。 /// 主线这套敌人账本叫 LocalEnemyRuntimeState(见 GameRouter.LocalEnemyCombat.cs), /// 语义是同一个"每会话一份敌人账本",所以这里只做类型/命名转发。 /// 缺的字段已按捐赠端 EnemyRuntimeState / EnemyRecord 补进那两个类。 /// private static LocalEnemyRuntimeState GetEnemyRuntimeState(TcpSession session) => GetLocalEnemyRuntimeState(session); /// /// 捐赠端 RunEnemyAttackAiAsync(session, generation, worker)。主线只有单参版本, /// 且用 LocalEnemyRuntimeState.AttackAiWorkerStarted 做"同一会话只起一个 worker"的闸门 /// (捐赠端用 AttackAiWorkerGeneration == 0)。这里沿用主线闸门,语义等价。 /// private static Task RunEnemyAttackAiAsync(TcpSession session, int generation, long worker) { var runtime = GetLocalEnemyRuntimeState(session); lock (runtime.SyncRoot) { if (runtime.AttackAiWorkerStarted) return Task.CompletedTask; runtime.AttackAiWorkerStarted = true; } return RunEnemyAttackAiAsync(session); } // ================================================================ 捐赠端代码的最小子集 /// /// 玩家可见提示通道。逐字抄自捐赠端 GameRouter.Delivery.cs:225-227 /// (MethodId.SyncShowMessage,65100728 是捐赠端写死的消息表 id)。 /// 捐赠端在牢房 F 被拒时用它把原因打到屏幕上(LixiPolice.cs:546)。 /// private static Task DeliveryText4229938(TcpSession session, string text) => session.NotifyAsync(MethodId.SyncShowMessage, new UxWriter().U32(65100728).U8(255).Int7(1).UxString(text).ToArray(), session.ClosedToken); /// /// 遥控设备账本。逐字抄自捐赠端 GameRouter.HackerDevices.cs:30。 /// ⛑ 核心链只用到"玩家此刻是不是在骑/在遥控"(Controlled / Riding)和 /// Template 比对;机械狗的召唤/技能/武器结算那套本次没搬, /// 所以本表在主线里恒为空 ⇒ 这两处判定的结果与"没有机械狗"完全一致,不改变行为。 /// private static readonly ConditionalWeakTable HackerDevices4229938 = new(); private sealed class HackerDeviceRuntime4229938 { internal ulong Agent; internal uint Template; internal bool Controlled; internal bool Riding; } /// GetStateIfExists 在 GameRouter 里是 private;GM 桥接(另一个类)要用,开个转发。 internal static WorldEntryState? GetStateIfExistsPublic(TcpSession session) => GetStateIfExists(session); /// /// GM 面板诊断用:S002 root 是否已经发布(PoliceRuntime.RootSent)。 /// ⛑ 这是 F 按钮的三个静默前置之一 —— /// RefreshPoliceNpcInitializationPass4229938 里 /// if (!LixiReady(state) || !r.RootSent || r.Child != 0) return;, /// RootSent 为 false 时 不发 F 也不打日志,面板上表现为"点了没反应"。 /// RootSent 只在 PublishLixiPolice4229938 里置位 ⇒ 没切过里希/没重进就是 false。 /// internal static bool PoliceRootSentForGm4229938(TcpSession session) => PoliceRuntimes.TryGetValue(session, out var r) && r.RootSent; /// /// GM 面板「牢房状态」的只读快照。2026-10-02:移植时把 PolicePrisonStatus 降级成了一堆 /// 硬编码占位(entrance="" / inside="" / entranceMeters=null / sceneOk=true), /// 结果「刷新」按钮显示出来的全是假的 ⇒ 用户根本没法判断自己是不是站在门口 4 m 内。 /// 这里把捐赠端 PolicePrison.cs 的两个坐标和场景判据原样透出(不新增能力,只补齐可读性)。 /// internal static (bool SceneOk, Vec3 Entrance, Vec3 Inside, float? EntranceMeters) PolicePrisonSnapshotForGm4229938(TcpSession session) { var state = GetStateIfExists(session); float? meters = null; if (state is { HasLastReportedPlayerTransform: true }) { var p = state.LastReportedPlayerPosition; var e = PolicePrisonEntrance4229938; var d2 = MathF.Pow(p.X - e.X, 2) + MathF.Pow(p.Y - e.Y, 2) + MathF.Pow(p.Z - e.Z, 2); if (float.IsFinite(d2)) meters = MathF.Sqrt(d2); } return (PolicePrisonScene4229938(state), PolicePrisonEntrance4229938, PolicePrisonInside4229938, meters); } // ================================================================ 主线自有保留件 /// /// 在服务端账本里找这个 uid(三本:Ambient 行人 / 地盘敌人 / 功能 NPC)。 /// 找不到就不能收押 —— 城里绝大多数路人是客户端本地实体,服务端没有它们的 id。 /// ⛑ 这是主线自己的代码(捐赠端没有),被 GameRouter.PoliceEscort.cs 依赖, /// 随捐赠版 PolicePrison.cs 替换而搬到这里保留。 /// private static bool TryFindInLedger(TcpSession session, ulong uid, out string ledger, out Vec3 position) { ledger = ""; position = default; var amb = AmbientState(session); lock (amb.SyncRoot) { var ped = amb.Peds.FirstOrDefault(p => p.Id == uid); if (ped is not null) { ledger = "行人"; position = new Vec3(ped.X, ped.Y, ped.Z); return true; } } var turf = EnemyTerritoryStateOf(session); lock (turf.SyncRoot) { if (turf.Active.TryGetValue(uid, out var spot)) { ledger = "地盘敌人"; position = new Vec3(spot.X, spot.Y, spot.Z); return true; } } var fn = FunctionalNpcStateOf(session); lock (fn.SyncRoot) { if (fn.Active.TryGetValue(uid, out var spot)) { ledger = "功能NPC"; position = new Vec3(spot.X, spot.Y, spot.Z); return true; } } return false; } /// /// 改写服务端账本坐标。⛑ 不改的话下一次服务端路网/AI 一拍就把它拽回原处, /// 表现就是「收押成功、一秒后 NPC 又站回街上」。同样是主线自有、捐赠端没有的保留件。 /// private static void RewriteLedgerPosition(TcpSession session, ulong uid, Vec3 p, bool stopWalking) { var amb = AmbientState(session); lock (amb.SyncRoot) { var ped = amb.Peds.FirstOrDefault(x => x.Id == uid); if (ped is not null) { ped.X = p.X; ped.Y = p.Y; ped.Z = p.Z; if (stopWalking) { ped.Walking = false; ped.Route = null; } return; } } var turf = EnemyTerritoryStateOf(session); lock (turf.SyncRoot) { if (turf.Active.TryGetValue(uid, out var spot)) { turf.Active[uid] = spot with { X = p.X, Y = p.Y, Z = p.Z }; return; } } var fn = FunctionalNpcStateOf(session); lock (fn.SyncRoot) { if (fn.Active.TryGetValue(uid, out var spot)) fn.Active[uid] = spot with { X = p.X, Y = p.Y, Z = p.Z }; } } }