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

220 lines
13 KiB
Go
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.
package routers
import (
"context"
"sync"
"time"
)
var _ = sync.Mutex{}
var _ = context.Background
var _ = time.Now
//
// 可接管召唤物(android-control 一家子):
// 黑客 APP 蜘蛛机器人(51938181)/ 送货无人机(51938183)→ 40120908 / 40120909
// 巡卫官 APP 机械狗 NCCA Dog(51938184 召唤 / 51938186 驻场态)→ 40120911
// 三条是同一套通道,不是三个功能。2026-09-25 补狗时的取证:
// · AgentSummonCatalog 里 40120908 蜘蛛 / 40120909 无人机 / 40120911 机械狗 紧挨着,
// EnemyClassType=1、GroupEnemy=40211001、AI=40213004 三个字段完全相同
// (狗多一个 InitWeaponId 98006028);EnemyCatalog.json:82 再确认一次。
// · SummonConfig 只有 5 条:1 无人机 / 2 蜘蛛 / **3 机器狗** / 4 鸵鸟马桶 / 5 扫地机器人
// ⇒ 狗是官方召唤物,SyncSwitchControl 的 SummonTag 校验对它成立(硬规则 52)。
// · 客户端有完整的 RobotDogControlsStore.lua(召回/切换操控/普攻/蹲/闪避/拍照…),
// 它调的正是 AskStopControlAgent(true/false) —— 与蜘蛛/无人机逐字相同。
// · 51938184 / 51938186 挂在里希(15021021)的 FightSpiritConfig.TempSkill 上,
// 所以 GameRouter.Combat.cs 的 AllowsAuthoredCharacterSkill 已经放行,
// 技能包到得了这里(Combat.cs 里那句 TryHandleControllableSummonSkill)。
// 为什么以前点了没反应 —— 2026-09-23 抓包确认。
// 客户端其实一直在上报。packets 日志里这两行进得很清楚:
// <code>
// C2S DECODED AskClientUseCommonSkill type=AskUseSkill
// {"data":{"Releaser":100000000012,"Location":{...},"SkillId":51938181,"SkillInstanceId":-211}}
// [COMBAT] accept temp-skill template=15021023 skill=51938181 inst=-211
// [COMBAT] use unit=100000000012 weapon=200141 style=39000005 skill=51938181 target=0
// </code>
// 也就是说:C2S 到了,服务端也认了(走 AllowsAuthoredCharacterSkill 分支),然后——什么
// 都没做。 OnClientUseSkill 只更新了 combat 沙盘记账就返回了,世界上一个实体也没多出来。
// 客户端那边则是全套自备的(lua.zip):
// HackScriptFunc.lua SpiderSkill = function() gCS.BattleManager.UseSkillByPid(pid, 51938181) end
// SkillConfig.json 51938181 召唤蜘蛛机器人 / 51938183 召唤送货无人机,ClientActionAdvance=true
// 所以玩家看到的"放置动作"是客户端先行演出;真正的单位、操控面板、操控本身,都要服务端
// 把单位放进世界之后客户端才成立。
// 分工(用户 2026-09-23 确认):召唤出来之后由玩家接管操控,这套逻辑在客户端。
// 服务端只做三件事:
// 1. 把实体刷出来(不参与战斗 —— 见 engage:false)
// 2. 推一次 SyncSwitchControl(68552741) 把玩家放进操控位
// 3. 接住 AskStopControlAgent(67046103) / AskControlAgent(67291468) 这一对「交还 / 重新接管」
// 之后 DoHackEnemy → ANDROID_CONTROL_SWITCH → RobotSpiderControlStore / DeliveryDroneStore
// 全在客户端,我们不插手。
// ⛔⛔ 2026-09-23 18:02 实机抓到的头号坑:spiritId 不能拿 Profile.InitialUnitId。
// 第一版用 state.WorldEntryControlUnit(= 100000000000 = player.initialUnitId)当
// spiritId,结果是单位出来了、客户端也收到了两个包,但操控没交出去 —— 表现为「机器人站着
// 不动,我还在控角色,而且手机打不开」。客户端日志里是这一行铁证:
// <code>
// [error] SyncHackEnemy enterOrLeave True unit or master is null
// enemyId= 35184372188839 enemy is nullFalse
// spiritId= 100000000000 master is nullTrue
// LX6.Units.Module.BaseUnitModuleUtils:DoHackEnemy(UInt64, UInt64, Boolean)
// </code>
// enemy is nullFalse = 单位找得到;master is nullTrue = 按 spiritId 找不到施法者。
// 玩家真正的 spirit uid 是 100000000012,三处互相印证:
// · AskUseSkill 的 Releaser(客户端自己报的施法者)
// · 我们下发的 SyncPlayerCurrentSpirit {"playerPid":100,"spiritId":100000000012}
// · 客户端 AskUnitMoveActionSimple 上报自己移动时带的那个 UnitId
// 而 100000000000 只是 config/private-server.json 的 player.initialUnitId,
// 是进世界时的占位值,切过角色之后就不再是当前 spirit。
// ⇒ spiritId 一律优先用 req.Releaser。
// 手机打不开也是同一个 bug 的副作用,不是新问题:客户端 SyncSwitchControl 在 DoHackEnemy
// 之后会执行 OnSetSkillBtnState(PhoneCall, "isSummonAgentControl", true) 把手机按钮禁掉
// —— 它以为自己已经在操控召唤物了,而 DoHackEnemy 静默失败,于是「手机锁了但没接管」。
//
func GameRouter_HackerSummon_ControllableSummonAgentId(ctx context.Context, skillId uint32) any {
// Router implementation for GameRouter_HackerSummon_ControllableSummonAgentId
var zero any; return zero
}
// 召唤物的展示名,只用于日志。
func GameRouter_HackerSummon_ControllableSummonName(ctx context.Context, agentId uint32) string {
// Router implementation for GameRouter_HackerSummon_ControllableSummonName
return ""
}
//
// SyncAgentWeaponWheel(68121167) 的载荷。抽成方法是为了让 _ux_wire_check 能反射
// 直接断言生产代码产出的字节,而不是在测试里重写一遍。
// 客户端 reader(RPCDeserializeAuto.lua:171397):
// <code>
// local agentId = reader.ReadUInt64(reader)
// local currentWeaponId = reader.ReadUInt32(reader)
// local weaponSlots = Base.ReadList7Bit(reader, function(r) return r.ReadUInt32(r) end)
// </code>
// ReadList7Bit = 标记字节 + Int7(count) + count 个 u32。
// ⛑ 标记字节按项目硬规则 5 用 0xFF(= 有值);UxWriter.Int7(n) 内部写 n+1,别再加偏置。
//
func GameRouter_HackerSummon_BuildWeaponWheelWire(ctx context.Context, uid uint64, weaponWheelSceneItemId uint32) []byte {
// Router implementation for GameRouter_HackerSummon_BuildWeaponWheelWire
return nil
}
//
// 召唤物的武器轮盘 SceneItem id。0 = 这个模板没有轮盘,不要发这一帧。
// ⛑⛑ 2026-09-26 订正(原注释写错了,按它办的 ⇒ 无人机一直没有 E 技能):
// 原注释拿 AgentSummonCatalog.InitWeaponId 当判据,结论是「只有机械狗有武器,
// 蜘蛛和无人机 InitWeaponId 都是 0 ⇒ 不用发轮盘」。这个判据是错的 —— 轮盘的
// 命名空间是 SceneItem(AgentConfig.InitWeaponSceneItemId),而 HUD 上"有没有
// 技能键"只看 AgentWeaponManager:GetAgentCurrentWeapon 拿不拿得到值,跟单位
// 实际绑了哪把武器无关。
// 客户端的判据(RobotFlyerControlsStore.lua,无人机操控 HUD):
// <code>
// M.RefreshBattleSkillInfo = function(self, weaponId)
// if weaponId then btnHitch:SetActive(true); btnDrop:SetActive(true)
// else btnHitch:SetActive(false); btnDrop:SetActive(false) end
// local cfg = gAgentWeaponManager:GetAgentWeaponInfoCfg(weaponId)
// if cfg then btnHitch:SetTipNameTotally(cfg.Normal) -- 挂钩 = 普通攻击
// btnDrop:SetTipNameTotally(cfg.Basic) end -- 投放 = E 技能
// </code>
// 即:无人机 HUD 上没有 btnSkillPc(那是机械狗/蜘蛛的),它的"原生 E 技能"
// 就是 btnDrop(长按走 SkillBtnType.Basic),配套还有 btnHitch。
// 不发轮盘 ⇒ 这两个键直接 SetActive(false) ⇒ 玩家看到的就是"没有 E 技能"。
// 这是硬规则 173 的同一条链路,只是消费方从 RobotDogControlsStore 换成了
// RobotFlyerControlsStore。
// 蜘蛛 40120908 暂未纳入:它的 HUD(RobotSpiderControlStore)同样按 weaponId 控制
// btnSkillPc,映射也已知(80005973「蜘蛛机器人默认武器」→ 98006056,FixedFightSkill
// 39010099),但本次只改无人机这一处,蜘蛛留作后续。
//
func GameRouter_HackerSummon_ControllableSummonWeaponWheel(ctx context.Context, agentId uint32) uint32 {
// Router implementation for GameRouter_HackerSummon_ControllableSummonWeaponWheel
return 0
}
//
// 每个家族一个独立回滚开关(黑客 / 巡卫犬),别共用 —— 见
// PrivateServerConfig.PoliceDogSummonEnabled 的注释。
//
func GameRouter_HackerSummon_ControllableSummonEnabled(ctx context.Context, skillId uint32, switchName string) bool {
// Router implementation for GameRouter_HackerSummon_ControllableSummonEnabled
return true
}
// =================================================================================
// ① 施放召唤技能
// =================================================================================
//
// 接住一次可接管召唤技能(蜘蛛 / 无人机 / 机械狗)。
// 返回 false 表示这不是召唤技能,调用方应继续走普通战斗流程。
//
func GameRouter_HackerSummon_TryHandleControllableSummonSkill(ctx context.Context, rpcCtx *RpcContext, req any) bool {
// Router implementation for GameRouter_HackerSummon_TryHandleControllableSummonSkill
return true
}
// =================================================================================
// ② 交还操控 / 回收(长按 F)与重新接管(N 键)
// =================================================================================
//
// C2S AskStopControlAgent(67046103) — clearAgent=true 是长按 F 回收(连单位一起销毁),
// false 是 N 键交还操控(单位留在场上,之后还能用 AskControlAgent 切回去)。
// 客户端用 gReliableRpcManager:RegisterRPC 等回执,所以 无论解析成功与否都必须先
// 回一个 empty OK,否则操控面板的 await 永远不落地(按钮按了没反应)。
//
func GameRouter_HackerSummon_AskStopControlAgent(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_HackerSummon_AskStopControlAgent
var zero any; return zero
}
// C2S AskControlAgent(67291468) — 重新接管一个已在场的召唤物(N 键的另一半)。
//
// ⛔⛔ 2026-09-23 18:36 实机抓到的第二个坑:包种类是 NOTIFY,不是 INVOKE。
// 第一版按 HandlerPacketKind.Invoke 注册,结果 packets 日志里全是这一行:
// <code>
// C2S NOTIFY AskControlAgent [0x0402C94C] body=9b
// ! [RPC4229938] unimplemented notify AskControlAgent [0x0402C94C] -&gt; accepted
// </code>
// 也就是说 N 键切回去的包到了服务端,但因为 kind 不匹配被视为未实现的 notify 直接
// accept 掉,handler 一次都没进(控制台里一条 [SUMMON] 都没有)。表现就是"按 N 没反应"。
// 对照:交还操控那条 AskStopControlAgent(67046103) 是 C2S INVOKE(要回执),
// 所以两个必须分开注册 —— 这里用 Both 兜住,将来客户端改成 invoke 也不用改代码。
//
func GameRouter_HackerSummon_AskControlAgent(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_HackerSummon_AskControlAgent
var zero any; return zero
}
func GameRouter_HackerSummon_OnStopControlAgent(ctx context.Context, rpcCtx *RpcContext, clearAgent bool) any {
// Router implementation for GameRouter_HackerSummon_OnStopControlAgent
var zero any; return zero
}
func GameRouter_HackerSummon_OnControlAgent(ctx context.Context, rpcCtx *RpcContext, agentEntityId uint64, reason byte) any {
// Router implementation for GameRouter_HackerSummon_OnControlAgent
var zero any; return zero
}
// =================================================================================
// ③ 机械狗「聚焦模式」—— ⛔ 2026-09-26 05:4x 已删除(实机失败,用户要求撤掉)。
// 原设计:控制狗时按住鼠标左键 ⇒ 准星对准谁就盘查谁。实测不work,
// 盘查入口改走 GM 面板「盘查模式」+ 里希按 R(见 GameRouter.PoliceExamAuto.cs)。
// 回滚记录:日志 2026-09-26。
// =================================================================================
//
// 交出操控位的固定两件套。顺序是硬要求:SyncControllableAgent 先(不查 ClientData),
// SyncSwitchControl 后(它第一件事就是 GetUnit + ClientData.AgentId,单位没建号就 print_error)。
//
func GameRouter_HackerSummon_HandOverSummonControl(ctx context.Context, rpcCtx *RpcContext, summon any, reason byte, why string) any {
// Router implementation for GameRouter_HackerSummon_HandOverSummonControl
var zero any; return zero
}
// 一次召唤的运行时快照。只存交接操控需要的三个 id。
type ControllableSummonState struct {
SpiritId uint64 `json:"spiritId"`
AgentEntityId uint64 `json:"agentEntityId"`
AgentId uint32 `json:"agentId"`
// 服务端视角:操控位此刻是否在召唤物手上(true)还是已交还玩家(false)。
Controlling bool `json:"controlling"`
// 上次下发"接管"的时刻,用于给连发的 AskControlAgent 去抖。
LastHandoverUtc time.Time `json:"lastHandoverUtc"`
}