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

207 lines
9.7 KiB
C#
Raw Permalink Normal View History

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 };
}
}
}