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/internal/gameserver/routers/game_router_police_arrest.go
T

206 lines
12 KiB
Go
Raw Normal View History

package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// 巡卫官(里希 / 15021021)正向「逮捕 NPC」—— 与 GameRouter.Wanted.cs 那条反向链路
// (玩家犯罪 → 警车追 → 警车抓玩家)共用同一个 S2C,只是把 target 从玩家换成 NPC。
// <code>
// SyncPoliceBeginArrest(68839124) = { u64 agentId(执行者), u64 targetPid(被捕者), byte ArrestType }
// </code>
// 反向链路里 agentId 是警车的 entity id、targetPid 是 Profile.PlayerPid、
// ArrestType=1(GameRouter.Wanted.cs:723)。正向这里 agentId 换成玩家自己的 unit id。
// ⛑⛑ 能确定与不能确定的,分开写清楚:
// · 能确定:68839124 是 LuaOnly、有 lua reader(RPCDeserializeAuto.lua:6986),
// 我方 Wanted.cs 已经在发且客户端吃过(玩家被捕演出成立)⇒ 帧本身是通的。
// · 不能确定:客户端什么时候会发盘查/逮捕的 C2S。盘查面板与审讯一样挂在
// gPoliceJobManager.cs(C#)与 Spoon 上;AskPoliceExamInteract(63233964) 在 lua 里
// 零引用,RpcSurface.json 里也没有 ⇒ 形状未知、触发时机未知。
// ⇒ 所以本文件做的是「把入口全都接住 + 留取证口子」,不假设客户端一定会发。
// 三条取证口子(实机跑一次就有答案):
// 1. [POLICE-ARREST] exam-interact hex=… —— 盘查选项 RPC 的真实字节;
// 2. [POLICE-ARREST] interact-cmd hex=… —— 按 F 交互那条 72 字节 NOTIFY 的内容(前 5 次);
// 3. GM 面板 / POST /api/police/arrest —— 不等客户端,直接验证演出帧能不能出。
// ⛑ 硬规则 94:写完 [Handler] 还要去 GameRouter.cs 的 Enabled4229938MethodIds
// 登记,否则编译通过、功能零生效,而这些都是 INVOKE(客户端会 await 到超时)。
//
//
// 汇总当前会话里服务端已知的全部可抓单位,按到玩家的距离升序。
// ⛑ 三个账本的 state 方法有各自的 SyncRoot,逐个 lock 取快照,不在锁里做 I/O。
// ⛑ 玩家自己的单位(ActiveSpiritUnitId / Profile.InitialUnitId)会被剔除 ——
// 自己抓自己会走到「玩家被捕」那条演出上,与正向逮捕完全相反。
//
func GameRouter_PoliceArrest_CollectPoliceTargets(ctx context.Context, session *GameSession, max int32) []any {
// Router implementation for GameRouter_PoliceArrest_CollectPoliceTargets
return nil
}
// =================================================================================
// ⓪b 「把人拽到面前」—— 反向解法:不挪玩家,挪 NPC
//
// 2026-09-25 用户第三次反馈「面前没 NPC」之后才想明白的:
// **挪玩家那条路是死的**(两条都验过):
// · instant(SyncUnitPositionAndFacing + SwitchSpirit):帧发了(unit=100000000000),
// 玩家没动;客户端后续上报把 LastReportedPlayerPosition 覆盖回原位。
// · loading(SyncPreTeleportOption + SyncTeleport 64030755):客户端直接反序列化失败
// —— `deserialize failed mode=3 len=35 / ReadBuffer invalid size=4 remain=3
// at ReadTeleportOption`。⛑ 我方 TeleportOption **是 30B 且契约与
// RPCDeserializeAuto.lua Auto.Reader[287]、dump.cs TypeDefIndex 33016 逐字段一致**
// (_ux_wire_check 已加断言并 PASS)⇒ 35B 说明多出来的字节不在 DTO 里,
// 而是这条帧根本不该以 notify 形态发(它有 invoke 语义)。不值得再钻。
// **挪 NPC 是活的**:SyncUnitPositionAndFacing 本来就是服务端驱动市民走路用的帧
// (GameRouter.Civilians.cs:391,WorldCodec.Walking),NPC 是 proxy 单位,
// NetworkGhostModule 只挡本地角色 ⇒ 一定能落位。
// ⇒ 所以改成:把目标 NPC 瞬移到玩家正前方 2.2 m,并让服务端账本同步改写,
// 下一次 /api/police/targets 就能看到它贴在脸上。
// =================================================================================
// 把 瞬移到玩家正前方 米处。
func GameRouter_PoliceArrest_PullTargetToPlayerAsync(ctx context.Context, session *GameSession, targetUid uint64, frontMeters float32, token context.Context) any {
// Router implementation for GameRouter_PoliceArrest_PullTargetToPlayerAsync
var zero any; return zero
}
// =================================================================================
// ① C2S:查询类(回执即可,防 await 卡死)
// =================================================================================
//
// C2S AskGetArrestTimes(63170713),INVOKE,返回 UXRPCTask&lt;int&gt;
// (RpcSurface.json:5907)—— 面板上的「今日逮捕次数」。回本局累计值即可。
//
func GameRouter_PoliceArrest_AskGetArrestTimes(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_PoliceArrest_AskGetArrestTimes
var zero any; return zero
}
//
// C2S AskIsArrested(63307468),INVOKE,无返回值 —— 「当前是否处于被捕状态」。
// 我们这边玩家永远不会被自己抓,回空 OK 即可(客户端读的是 SyncPlayerArrestStateNotify)。
//
func GameRouter_PoliceArrest_AskIsArrested(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_PoliceArrest_AskIsArrested
var zero any; return zero
}
//
// C2S AskPoliceEffectiveExam(63851241),INVOKE,无返回值 ——
// 盘查链路里唯一由 lua 直接发的一条(PoliceExamineManager.lua:545,
// 盘查选项命中 4 项以上时发一次,用来触发"成功执法"演出)。
// 回执即可;演出在客户端本地(successPoliceEffect 标志),服务端无帧可推。
//
func GameRouter_PoliceArrest_AskPoliceEffectiveExam(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_PoliceArrest_AskPoliceEffectiveExam
var zero any; return zero
}
// =================================================================================
// ② C2S AskPoliceExamInteract(63233964) —— 盘查面板的选项(形状未知,先接住再取证)
// =================================================================================
//
// 盘查面板里点下某个选项(查身份证 / 搜身 / 酒测 / 毒测 / 罚款 / 逮捕 / 放行)时由
// C# 侧发出来的 INVOKE。
// ⛑ 形状是猜的(见 AskPoliceExamInteract4229938 的注释):用 TryGetArgs,
// 解析失败也照常回执,只把 hex 打出来。等拿到真实字节再收紧契约与 option 过滤。
//
func GameRouter_PoliceArrest_AskPoliceExamInteract(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_PoliceArrest_AskPoliceExamInteract
var zero any; return zero
}
// =================================================================================
// ③ C2S AskInteractCmd(67390408) —— ⛔ 别在这里加 handler,已经有了
//
// GameRouter.Vehicle.cs:268 那条就是它(Notify,带完整解码):
// · CmdType=19 是 2 次/秒的心跳,不打日志;
// · CmdType=33 = 目标选择,stringParam1 是目标单位 id ⇒ 存进
// <c>_lastInteractTargetUnitId</c>;
// · CmdType=34 = 交互心跳,offset 24 是交互状态。
// 2026-09-25 14:08:14 在警卫署点「审讯」抓到的那条 72 字节样本,日志里就是
// <c>[INTERACT] cmd=…</c> 那一行。
// ⇒ 将来要做"对着 NPC 按 F 就逮捕",目标直接用 <c>_lastInteractTargetUnitId</c>,
// 不用重新解析这条包。本次不动它(它是全局交互通道,改了会误伤开门/上车)。
// =================================================================================
// ④ 逮捕动作本体
// =================================================================================
//
// 推一次 SyncPoliceBeginArrest(68839124) 并记账。
// ⛑ 关于 targetPid:客户端那边是 unit.Pid(见
// PoliceExamineManager 里 gPoliceJobManager.cs:ClickOption_Story(self.unit.Pid, …)),
// 而我们服务端生成的单位手里只有 uid。反向链路用的 targetPid 是 Profile.PlayerPid
// (一个 pid 语义的值),所以这里先按 uid 当 pid 用,实机看客户端认不认;
// 不认就得回头找 uid→pid 的映射(SyncManagedLogicAgent 那条通道里有 pid)。
//
func GameRouter_PoliceArrest_PerformArrestAsync(ctx context.Context, rpcCtx *RpcContext, targetUid uint64, source string) bool {
// Router implementation for GameRouter_PoliceArrest_PerformArrestAsync
return true
}
//
// 逮捕本体(与调用方无关的那份)。GM 面板 / /api/police/arrest 直接走这里,
// 因为那边手里是 (session, state),没有 RpcContext。
//
// 执行者单位 id(玩家自己);传 0 ⇒ 退回 Profile.InitialUnitId。
func GameRouter_PoliceArrest_ArrestBySessionAsync(ctx context.Context, session *GameSession, actorUnit uint64, targetUid uint64, source string) bool {
// Router implementation for GameRouter_PoliceArrest_ArrestBySessionAsync
return true
}
//
// 发 SyncPoliceBeginArrest(68839124)。与 GameRouter.Wanted.cs:723 用的是同一帧,
// 序列化方式也保持一致(UxSerializer.Serialize + 三参数 NotifyAsync)。
//
func GameRouter_PoliceArrest_SendPoliceArrestAsync(ctx context.Context, session *GameSession, actorUnitId uint64, targetPid uint64, arrestType byte, token context.Context) any {
// Router implementation for GameRouter_PoliceArrest_SendPoliceArrestAsync
var zero any; return zero
}
func GameRouter_PoliceArrest_GetOrCreatePoliceArrest(ctx context.Context, session *GameSession) any {
// Router implementation for GameRouter_PoliceArrest_GetOrCreatePoliceArrest
var zero any; return zero
}
func GameRouter_PoliceArrest_HexPrefix(ctx context.Context, body []byte, max int32) string {
// Router implementation for GameRouter_PoliceArrest_HexPrefix
return ""
}
type PoliceArrestState struct {
// 本局已逮捕次数(喂 AskGetArrestTimes)。
Count int32 `json:"count"`
// 最近一次被捕目标的 uid(同时当 pid 用,见 SendPoliceArrestAsync)。
LastTarget uint64 `json:"lastTarget"`
}
// =================================================================================
// ⓪ 「抓谁?」—— 服务端账本里的可抓目标清单
//
// 2026-09-25 用户提问的根因在这里,先写死在代码里免得下次再踩:
// **城里绝大多数路人是客户端本地实体,服务端根本没有它们的 id。**
// 实据:AskInteractCmd CmdType=33(目标选择)每秒回报 3~4 次,s1 形如
// `8300000000000000188`(= 0x732F860653BE00BC),既不在我方 uid 段
// (0x200000000000 + ServerSpawnUidFloor 起)也不连续 ⇒ 客户端自己随机分配的单位。
// 这类目标服务端<b>无法点名</b>(没有稳定 id,且下一秒就换了一个)。
//
// 能抓的只有<b>服务端自己生成并推过 SyncManagedLogicAgent / SyncLogicAgentEnter 的单位</b>:
// · Ambient 行人(GameRouter.Ambient 的 Peds 账本,带服务端坐标)
// · 地盘敌人(EnemyTerritory Active:uid -> spot)
// · 功能 NPC(FunctionalNpc Active:uid -> spot)
// · 最近一次 GM 召唤的 NPC(state.LastSummonedNpcEntityId,无坐标,按 0 距离排最前)
// ⇒ 所以这里不做「对着路人按 F」,而是把账本摊开、按到玩家的距离排序点名。
// =================================================================================
// 一个可抓目标。Distance 为米;-1 = 服务端不知道它在哪(GM 召唤的那个)。
type PoliceTarget struct {
Uid uint64 `json:"uid"`
Kind string `json:"kind"`
Label string `json:"label"`
Distance float32 `json:"distance"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
}