using Ananta.SDK.Network;
using Ananta.SDK.Rpc;
using Ananta.SDK.Serialization;
using Ananta.Server.Configuration;
using Ananta.Server.Protocol.Client4229938;
using Ananta.Server.RpcTypes.Client4229938;
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
namespace Ananta.Server.Handlers.Game;
///
/// 开放世界通缉系统(Wanted)——「警车版」。
///
/// 2026-09-23 自捐赠端 PrivateServer修改_miyuki_fix 的 GameRouter.Wanted.cs 移植。
///
/// ★ 与捐赠端的关键差异(都是刻意的):
/// 1. **只做警车,不做步行警员**。捐赠端的步行警员靠它自己的 crowd agent 管线
/// (CrowdAgent / PushLogicAgentHandoff / PushCrowdTaskCommand /
/// CrowdAdvanceEstimate),本方没有那套类型,且它那份日志早于它自己的最后一次改动
/// ⇒ 步行警员那半根本没被实机验证过。警车由**客户端自己的车辆 AI** 驱动
/// (NavConfigId=1002 / ChaseDirectlySpeed=26),服务端只负责"派"和"撤"。
/// 2. 因此**自动逃脱/被捕的判据换了**。捐赠端用"最近警员距离"(它自己算的位置),
/// 警车版拿不到警车位置(客户端 AI 不外报),改成两个服务端真信号:
/// · 通缉持续超过 ⇒ 自动逃脱;
/// · 玩家倒地(state.PlayerDowned)⇒ 自动被捕。
/// 3. 没有 resetAfterArrest / RestartCurrentWorld(本方没有那个方法),
/// 被捕就是清场 + 罚款。
/// 4. 不碰 CrimeGameplay(商店抢劫/偷车任务链),那半依赖本方完全没有的
/// GmTasks 任务状态物件。
///
/// ⛑️ 完全绕开官方犯罪通道:不接 SyncAgentCrimeAdd(68831313)、
/// ReportPoliceCrimeEvent(67146776)、GmAddCrimeValue(69946581)。犯罪值是服务端自己加的,
/// 代价是"只有服务端认可的行为会涨值",好处是不会被官方那些未摸清的帧时序炸客户端。
///
/// 回滚:把 wanted.enabled 置 false(或直接删本文件 + 摘掉三处挂钩)。
///
internal sealed partial class GameRouter
{
private const uint WantedMaxLevel4229938 = 5;
private const uint WantedMaxValue4229938 = 9_999;
private const double WantedRewardPerLevel4229938 = 5_000d;
private const double WantedPenaltyPerLevel4229938 = 2_500d;
private const long WantedVehicleHitRearmMs4229938 = 700L;
// 高水位落盘(IdCounterStore §6.10):警车 entityId 重启后不能复用。
private const long WantedVehicleEntityFloor = 430_000_000_000L;
private const string WantedVehicleEntityCounter = "Wanted.VehicleEntity";
/// 警车实体号段 430_000_000_000+(不与本方车辆 8_200e15 / 行人 8_300e15 冲突)。
private const int WantedPursuitTickMs4229938 = 1_000;
private const int WantedPursuitTimeoutSeconds4229938 = 90;
///
/// 三台真警车模板(已在本方 ClientData/4229938/Configs/VehicleConfig.json 复核):
/// 81007029 = T_PoliceCar01a、81007078 = U_NewPolice01a、81004027 = U_PoliceCar02a。
///
private static readonly uint[] WantedPoliceVehicleTemplates4229938 =
[
81007029u,
81007078u,
81004027u,
];
internal sealed record WantedCrimeSettings4229938(
bool Enabled,
uint NpcHitPoints,
uint NpcKillPoints,
uint VehicleHitPoints,
uint VehicleDestroyPoints,
bool AutoEscapeEnabled,
bool ArrestOnPlayerDown,
int PursuitTimeoutSeconds,
int VehiclesPerLevel,
double RewardPerLevel,
double PenaltyPerLevel);
private static readonly object WantedCrimeSettingsLock4229938 = new();
private static WantedCrimeSettings4229938 s_wantedCrimeSettings4229938 = new(
Enabled: true,
NpcHitPoints: 10,
NpcKillPoints: 60,
VehicleHitPoints: 5,
VehicleDestroyPoints: 50,
AutoEscapeEnabled: true,
ArrestOnPlayerDown: true,
PursuitTimeoutSeconds: WantedPursuitTimeoutSeconds4229938,
VehiclesPerLevel: 1,
RewardPerLevel: WantedRewardPerLevel4229938,
PenaltyPerLevel: WantedPenaltyPerLevel4229938);
private sealed class WantedPursuitRuntime
{
internal CancellationTokenSource? Cancellation;
internal Task? Loop;
internal int Generation;
}
private static readonly object WantedPursuitLock4229938 = new();
private static readonly Dictionary WantedPursuitRuntimes4229938 = [];
internal sealed class WantedActionOptions
{
public string Action { get; set; } = string.Empty;
public uint Level { get; set; }
public uint CrimeValue { get; set; }
public uint Points { get; set; } = 25;
public int Seconds { get; set; } = 10;
public double? Amount { get; set; }
public bool? Enabled { get; set; }
public uint NpcHitPoints { get; set; }
public uint NpcKillPoints { get; set; }
public uint VehicleHitPoints { get; set; }
public uint VehicleDestroyPoints { get; set; }
public bool? AutoEscapeEnabled { get; set; }
public bool? ArrestOnPlayerDown { get; set; }
public int PursuitTimeoutSeconds { get; set; }
public int VehiclesPerLevel { get; set; }
public double RewardPerLevel { get; set; }
public double PenaltyPerLevel { get; set; }
}
internal static WantedCrimeSettings4229938 WantedCrimeSettings()
{
lock (WantedCrimeSettingsLock4229938)
return s_wantedCrimeSettings4229938;
}
private static WantedCrimeSettings4229938 SetWantedCrimeSettings(WantedActionOptions options)
{
lock (WantedCrimeSettingsLock4229938)
{
var current = s_wantedCrimeSettings4229938;
return s_wantedCrimeSettings4229938 = new WantedCrimeSettings4229938(
options.Enabled ?? current.Enabled,
Math.Clamp(options.NpcHitPoints, 0u, WantedMaxValue4229938),
Math.Clamp(options.NpcKillPoints, 0u, WantedMaxValue4229938),
Math.Clamp(options.VehicleHitPoints, 0u, WantedMaxValue4229938),
Math.Clamp(options.VehicleDestroyPoints, 0u, WantedMaxValue4229938),
options.AutoEscapeEnabled ?? current.AutoEscapeEnabled,
options.ArrestOnPlayerDown ?? current.ArrestOnPlayerDown,
Math.Clamp(options.PursuitTimeoutSeconds <= 0 ? current.PursuitTimeoutSeconds : options.PursuitTimeoutSeconds, 5, 600),
Math.Clamp(options.VehiclesPerLevel <= 0 ? current.VehiclesPerLevel : options.VehiclesPerLevel, 1, 3),
Math.Clamp(options.RewardPerLevel <= 0d ? current.RewardPerLevel : options.RewardPerLevel, 0d, 1_000_000d),
Math.Clamp(options.PenaltyPerLevel <= 0d ? current.PenaltyPerLevel : options.PenaltyPerLevel, 0d, 1_000_000d));
}
}
internal static object WantedReport(TcpSession? session)
{
var settings = WantedCrimeSettings();
var state = session is null ? null : GetStateIfExists(session);
if (state is null)
return new
{
ok = true,
online = false,
enabled = settings.Enabled,
npcHitPoints = settings.NpcHitPoints,
npcKillPoints = settings.NpcKillPoints,
vehicleHitPoints = settings.VehicleHitPoints,
vehicleDestroyPoints = settings.VehicleDestroyPoints,
autoEscapeEnabled = settings.AutoEscapeEnabled,
arrestOnPlayerDown = settings.ArrestOnPlayerDown,
pursuitTimeoutSeconds = settings.PursuitTimeoutSeconds,
vehiclesPerLevel = settings.VehiclesPerLevel,
rewardPerLevel = settings.RewardPerLevel,
penaltyPerLevel = settings.PenaltyPerLevel,
note = "等待游戏会话进入世界;参数可以先设置,出警与结算需要进入世界后验证",
};
lock (state.SyncRoot)
{
var remainingMs = state.WantedEscapeActive
? Math.Max(0, state.WantedEscapeDeadlineTicks - Environment.TickCount64)
: 0;
var elapsedMs = state.WantedStartedTicks == 0
? 0
: Math.Max(0, Environment.TickCount64 - state.WantedStartedTicks);
return new
{
ok = true,
online = true,
ready = state.Ready,
crimeValue = state.WantedCrimeValue,
wantedLevel = state.WantedLevel,
chaseActive = state.WantedChaseActive,
policeVehicles = state.WantedPoliceVehicleIds.Order().ToArray(),
policeVehicleCount = state.WantedPoliceVehicleIds.Count,
pursuitElapsedSeconds = (int)(elapsedMs / 1000),
escapeActive = state.WantedEscapeActive,
escapeRemainingSeconds = (int)Math.Ceiling(remainingMs / 1000d),
escapeCount = state.WantedEscapeCount,
arrestCount = state.WantedArrestCount,
rewardTotal = state.WantedRewardTotal,
penaltyTotal = state.WantedPenaltyTotal,
enabled = settings.Enabled,
npcHitPoints = settings.NpcHitPoints,
npcKillPoints = settings.NpcKillPoints,
vehicleHitPoints = settings.VehicleHitPoints,
vehicleDestroyPoints = settings.VehicleDestroyPoints,
autoEscapeEnabled = settings.AutoEscapeEnabled,
arrestOnPlayerDown = settings.ArrestOnPlayerDown,
pursuitTimeoutSeconds = settings.PursuitTimeoutSeconds,
vehiclesPerLevel = settings.VehiclesPerLevel,
rewardPerLevel = settings.RewardPerLevel,
penaltyPerLevel = settings.PenaltyPerLevel,
npcHitCount = state.WantedNpcHitCount,
npcKillCount = state.WantedNpcKillCount,
vehicleHitCount = state.WantedVehicleHitCount,
vehicleDestroyCount = state.WantedVehicleDestroyCount,
lastIncident = state.WantedLastIncident,
lastOutcome = state.WantedLastOutcome,
lastSync = state.WantedLastSync,
note = "警车由客户端车辆 AI 驱动,服务端只负责派/撤;自动逃脱按通缉时长判定,自动被捕按玩家倒地判定",
};
}
}
internal static async Task