This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.PoliceDonorGlue.cs

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