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_enemy_ai_actions.go

206 lines
11 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"
)
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
}