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

207 lines
12 KiB
Go
Raw 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.
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"`
}