package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // Attack AI — action layer for SERVER-SPAWNED enemies (GameAdminBridge.SpawnAgentAsync). // Why this exists: BattleAiS only switches the unit onto its combat behaviour tree; it is // not a decision loop, and the client's tree on this build does not close the distance or // open an attack on its own for a server-spawned unit. So the server issues bounded native // commands — move / turn / cast — and the client owns the animation, the movement and the // hit detection. Nothing here teleports an enemy or fabricates a hit: every packet is a // request the client's own systems execute. // Compatibility scope, stated up front: this is NOT a reconstruction of the retail behaviour // trees. Complex navigation, vehicle/seat skills, grabs/executions and co-op sequences are // out of scope. Short-range approach can be blocked by geometry. // Damage: intentionally zero. Enemy hits on the player are visual feedback only (see // ApplyEnemyPlayerHitFeedbackAsync) — no HP, no death, no resource packets. // // ---- 移动节奏(2026-09-23:修「走一步停一下」的抽搐)---- // // SyncEnemyBattleMoveTowardUnit 是**有限位移**:客户端只走 MoveTime 秒就停在那儿等下一条。 // 原来 MoveTime=0.65s 而派发间隔 1000ms ⇒ 走 0.65s、干站 0.35s,看起来就是一步一顿。 // 所以这两个值必须配对:间隔 ≥ 时长(短了会打断),又不能长太多(长了就是停顿)。 // 都从 PrivateServerConfig 读,便于实机微调,不用重编译。 // 单条移动命令让客户端走多久(秒)。 func GameRouter_EnemyAiActions_EnemyAiMoveSecondsNow(ctx context.Context, ) float32 { // Router implementation for GameRouter_EnemyAiActions_EnemyAiMoveSecondsNow return 0 } // 派发间隔:默认贴着时长 +50ms;配置值更大时以配置为准。 func GameRouter_EnemyAiActions_EnemyAiMoveIntervalMsNow(ctx context.Context, moveSeconds float32) int32 { // Router implementation for GameRouter_EnemyAiActions_EnemyAiMoveIntervalMsNow return 0 } // // 每次派发把估算锚点推进多少米。1.2 m 是 MoveTime=0.65s 时调出来的,按时长等比放大, // 免得「估算还在 5m 外、实体其实已经贴脸」导致它一直发移动命令原地挤。 // func GameRouter_EnemyAiActions_EnemyAiMoveStepNow(ctx context.Context, moveSeconds float32) float32 { // Router implementation for GameRouter_EnemyAiActions_EnemyAiMoveStepNow return 0 } // // 攻击后的冻结时长改为可配。原来是硬编码 6000/6500 ms —— 打一下原地站 6.5 秒, // 这也是"走一步停一下"里那段"停"的来源之一(玩家看到的就是打完杵着不动)。 // 配置缺项时退回硬编码默认值,避免老配置直接把敌人变成连击机关枪。 // func GameRouter_EnemyAiActions_EnemyAiCastBusyMsNow(ctx context.Context, ) int32 { // Router implementation for GameRouter_EnemyAiActions_EnemyAiCastBusyMsNow return 0 } func GameRouter_EnemyAiActions_EnemyAiCastIntervalMsNow(ctx context.Context, ) int32 { // Router implementation for GameRouter_EnemyAiActions_EnemyAiCastIntervalMsNow return 0 } // // 日志密度:前 3 条必打,之后每 N 条打一条(N 来自配置 EnemyAiVerboseLogEvery)。 // 原来只打前 3 条 —— 实机上敌人一次接敌要走十几条移动命令,后面全被吞掉, // 排查「一步一停」时完全看不到节奏。 // func GameRouter_EnemyAiActions_EnemyAiShouldLog(ctx context.Context, count int32) bool { // Router implementation for GameRouter_EnemyAiActions_EnemyAiShouldLog return true } // Resolved once per template: the behaviour set's own attacks when it has any. // 2026-09-26 Wave 2:改走 EnemyAiCatalog4229938.ResolveSpawnProfile —— // 战斗模式开启时这里拿到的就是模式套件(melee 空手 / ranged 远程),与 spawn 时 // 绑的技能槽、换的武器、推的动画集是同一份解析,不会出现「手拿空手动画集、 // 甩的却是模板武器技能」的错位。off 时 = Find 本体,旧行为不变。 func GameRouter_EnemyAiActions_EnemyAiAttacksFor(ctx context.Context, templateId uint32) []any { // Router implementation for GameRouter_EnemyAiActions_EnemyAiAttacksFor return nil } // Chinese name of one skill in a template's pool, for logs. "" when unknown. // ⛑ Wave 2:anames 只覆盖模板原配攻击集;模式套件(空手/远程池)查不到名字, // 日志里就是空串 —— 只影响可读性,不影响行为。 func GameRouter_EnemyAiActions_EnemyAiAttackName(ctx context.Context, templateId uint32, skillId uint32) string { // Router implementation for GameRouter_EnemyAiActions_EnemyAiAttackName return "" } func GameRouter_EnemyAiActions_RunEnemyAttackAiAsync(ctx context.Context, session *GameSession) any { // Router implementation for GameRouter_EnemyAiActions_RunEnemyAttackAiAsync var zero any; return zero } // // Fires the queued control-state releases that have come due. Several casts can queue // deadlines that land in the same poll, so every due deadline collapses into one batch — // otherwise a boss swinging twice in a row would send the same 24 packets four times. // func GameRouter_EnemyAiActions_DrainPendingUnlocksAsync(ctx context.Context, session *GameSession, state any, now int64) any { // Router implementation for GameRouter_EnemyAiActions_DrainPendingUnlocksAsync var zero any; return zero } // // ★ 蹲伏暗杀播种(硬规则 192)—— 主动下发 SyncEnemyCanAssassinate(68218494)。 // 为什么要这一段:客户端的候选筛选是自举死锁。 // FeiSuoCrouchManager.CheckCanCrouchAss(0x14CB11E0) → // PreCheckCanAss(0x131F6700) 要求目标的 BattleDataModule.canAssassinate // (私有字段,偏移 0xB1)== true;该字段全 .text 唯一写入点就是这条 S2C 的接收函数 // (0x127D102F mov byte ptr [rax+0xB1], bl,set_CanAssassinate 零调用者); // 而这条 S2C 名义上是 ReportCanAssassinateEnemies(67161256) 的回应, // 上报却必须先过 canAssassinate ⇒ 没有任何敌人天生带标记 ⇒ 循环永远启动不了 // (实机:AskEnterFeiSuoCrouch 19 次 / ReportCanAssassinateEnemies 0 次)。 // 官方用 Spoon 剧本动作 L18.Spoon.EnemySetCanAssassinate(dump.cs:3194822) 播种, // 我们没有 Spoon 系统 ⇒ 用这条主动下发替代。 // ⛑⛑ 只在蹲伏期间播:离开蹲伏态由 RevokeAllAssassinationGrants 全部撤销。 // ⛑ 每个敌人每次蹲伏只播一次(AssassinationSeededAgents):播完客户端若判定 // 它不合格,会上报一个不含它的名单 ⇒ 上报路径撤销它;只看 // AssassinationGrantedAgents 的话播种会以为「没授权过」而重播, // 撤销→播种→撤销来回甩。离开半径后重新靠近才会再播。 // func GameRouter_EnemyAiActions_SeedAssassinationCandidatesAsync(ctx context.Context, session *GameSession, state any, now int64) any { // Router implementation for GameRouter_EnemyAiActions_SeedAssassinationCandidatesAsync var zero any; return zero } // 0 when the player is too far, unknown, or the enemy is busy dying. func GameRouter_EnemyAiActions_EnemyAiSelectTarget(ctx context.Context, enemy any, state any) uint64 { // Router implementation for GameRouter_EnemyAiActions_EnemyAiSelectTarget return 0 } // Engagement batch, re-sent only when the lock actually changes — spamming it // resets the client's action layer and cuts every animation short. func GameRouter_EnemyAiActions_RefreshEnemyEngageAsync(ctx context.Context, session *GameSession, uid uint64, state any, now int64) any { // Router implementation for GameRouter_EnemyAiActions_RefreshEnemyEngageAsync var zero any; return zero } func GameRouter_EnemyAiActions_RefreshEnemyActionAsync(ctx context.Context, session *GameSession, uid uint64, state any, now int64) any { // Router implementation for GameRouter_EnemyAiActions_RefreshEnemyActionAsync var zero any; return zero } // // 出招前让单位面向目标。三种方式由 gameplay.aether.enemyAiTurnPacket 选 // (AetherSettings.EnemyAiTurnPacket): // target(默认)= SyncUnitSmoothFaceToTarget,把目标单位甩给客户端算 —— // 转得准,因为客户端知道自己的真实朝向; // smooth = 旧的 SyncUnitSmoothFace(fromAngle, changeAngle),服务端按估算 // 朝向算增量 —— 估算一旦过期就会转歪(实机上就是"转 90° 打空气"); // off = 完全不发转向包(对照用:若技能都带 NoRotation=true,此时应当朝哪打哪)。 // ⛑ 单位是度(记忆 2026-09-21.md:1066:Facing 与 EulerAngles.y 同值), // 一开始怀疑过的"度/弧度搞反"已经排除了。 // func GameRouter_EnemyAiActions_TurnEnemyToAsync(ctx context.Context, session *GameSession, uid uint64, targetId uint64, smooth any) any { // Router implementation for GameRouter_EnemyAiActions_TurnEnemyToAsync var zero any; return zero } // 踱步两段之间随机停顿多久(毫秒),避免所有敌人同步齐步走。 func GameRouter_EnemyAiActions_RandomPause(ctx context.Context, cfg any) int32 { // Router implementation for GameRouter_EnemyAiActions_RandomPause return 0 } // // 哨兵踱步:玩家不在仇恨范围(30m)时让敌人「走一段 → 停一会 → 倒着走回来」。 // 原来这段时间是完全不动的(EnemyAiSelectTarget 给不出目标 ⇒ RefreshEnemyActionAsync 直接 // return),敌人从生成到玩家走近一直杵着。这里复用同一条 BattleMove 通道,靠 // WalkFront / WalkBack 交替让位移互相抵消 —— 它只会原地来回踱步,不会一路漂到玩家身上。 // ⛑️ 服务端拿不到真实坐标(客户端不回传),所以 AiPosition 是估算;估算漂太远会让实体和 // 锚点对不上,因此用 HomePosition 做保险绳:离出生点超过 leash 就强制往回走。 // func GameRouter_EnemyAiActions_DispatchEnemyPatrolStepAsync(ctx context.Context, session *GameSession, uid uint64, state any, now int64, cfg any, moveSeconds float32, moveInterval int32, moveStep float32) any { // Router implementation for GameRouter_EnemyAiActions_DispatchEnemyPatrolStepAsync var zero any; return zero } // // Waits out the cast animation and then settles the hit on the player. // Fire-and-forget on purpose: the AI worker must keep polling, and a session that disappears // during the wait must not surface as an AI failure. // func GameRouter_EnemyAiActions_ApplyEnemyHitToPlayerDelayedAsync(ctx context.Context, session *GameSession, state any, uid uint64, skillId uint32) any { // Router implementation for GameRouter_EnemyAiActions_ApplyEnemyHitToPlayerDelayedAsync var zero any; return zero } // // C2S end-of-move report. Carries a pid and nothing else — no token, no success flag — // so it is logged as a diagnostic acknowledgement and never treated as proof of arrival // or as a reason to shorten the move interval. // func GameRouter_EnemyAiActions_ReportEnemyBattleMoveEnd(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_EnemyAiActions_ReportEnemyBattleMoveEnd var zero any; return zero }