package catalogs import ( "encoding/json" "os" "path/filepath" "sync" ) var _ = json.RawMessage{} var _ = sync.Mutex{} var _ = os.ReadFile var _ = filepath.Join // Combat profile for one AgentConfig template, used to arm and skill a spawned enemy. type EnemyAiProfile4229938 struct { Id uint32 `json:"id"` Name string `json:"name"` // AgentConfig.AI — the AgentAIBehaviorSetsBattleAISBehaviorConfig row id. Ai uint32 `json:"ai"` // Behaviour tree name. The correct value per template, not a hostile/civilian guess. Tree string `json:"tree"` Battle string `json:"battle"` // AgentConfig.InitWeaponId. 0 means the template authors no weapon. Weapon uint32 `json:"weapon"` // AgentConfig.InitWeaponSceneItemId — the scene item the weapon is built from. WeaponSceneItem uint32 `json:"weaponSceneItem"` // AgentConfig.EnemyOccurId, used as SyncUnitOccur.occurId. 0 for most templates. Occur uint32 `json:"occur"` Class uint32 `json:"class"` // Every skill the behaviour set references. Kept for diagnostics. Skills []uint32 `json:"skills"` // // The attacks this template actually gets, in priority order. // Tier 1 is the donor's 29 curated standalone NPC attacks (SkillConfig CastType=1, // non-looping) — those are the ones proven to animate when the server issues // SyncUseTargetSkill. Tier 2 is this template's OWN remaining RelatedSkillIds, minus // skills the designers marked dead in their Chinese names ("空技能", "不合适", // "应该没用了", …). Tier 2 exists because bosses and elites have no tier-1 skill at // all: their entire kit is CastType=0, so before this existed every boss silently fell // back to the metallic guitarist's two swings. // Budget scales with EnemyClassType: 6 for bosses/elites (2, 6), 4 for (4, 7), 3 for // ordinary (1). Empty means the template has nothing usable at all. // Attacks []uint32 `json:"attacks"` // // Chinese SkillConfig names for Attacks, one per entry. Diagnostics only — // the log prints them so a boss's move set can be read without opening the config. // AttackNames []string `json:"attackNames"` // ---- 2026-09-26 Wave 2:战斗模式列(移植 7k-v0.5 EnemyAiCatalog4229938 同名概念)---- // AgentConfig.BattleAnimType —— 模板自带的 AnimationSetConfig id // (7K 叫 TemplateBattleAnimSetTag,仅 GM follow-test animChoice==1 时用)。 TemplateBattleAnimSetTag uint32 `json:"templateBattleAnimSetTag"` // 空手模式技能池 ∩ 本模板 RelatedSkillIds(≤2)。见生成脚本的 UNARMED 池。 UnarmedAttacks []uint32 `json:"unarmedAttacks"` // 远程模式技能池 ∩ 本模板 RelatedSkillIds(≤2)。见生成脚本的 RANGED 池。 RangedAttacks []uint32 `json:"rangedAttacks"` // 战斗模式:template(默认,模板原样)/ melee / ranged。 // 由 EnemyAiCatalog4229938.SelectCombatProfile 产生,目录行本身恒为 template。 CombatMode string `json:"combatMode"` // 模式选择后的战斗动画集(AnimationSetConfig id,不是技能或动作 id)。 // 空手 = 325(十二面空手)/ 327(通用胖子空手);远程 = 337/338/340。0 = 不覆盖。 BattleAnimSetTag uint32 `json:"battleAnimSetTag"` // 交战距离(米)。7K 默认 2.8;远程模式 = 18。Wave 4 警示 HUD Alert 档也要用它。 AttackRange float32 `json:"attackRange"` // 跟随风格(7K GM follow-test 专用):guard / front / lateral / run。Wave 2 只存档不用。 FollowStyle string `json:"followStyle"` // 跟随动画选择(7K GM follow-test 专用,AnimationSetConfig id)。Wave 2 只存档不用。 FollowAnimChoice uint32 `json:"followAnimChoice"` } // // Per-template combat profile catalog, read from EnemyAiCatalog.json. // That file is generated by _gen_enemy_ai_catalog.py, which projects the donor build's // 57 MB AgentConfig.json plus AgentAIBehaviorSetsBattleAISBehaviorConfig.json down to the // handful of fields the attack AI needs (about 950 KB for 3160 rows). The full table never // has to ship with the server. // Why per-template instead of one shared pool: a skill belongs to a skeleton. Lending one // skeleton's swing to another produces a unit that closes in and then stands there, because // the animation simply does not exist on that rig. // var ( EnemyAiCatalog4229938_cacheLock sync.RWMutex EnemyAiCatalog4229938_cached = false EnemyAiCatalog4229938_entries []any ) // LoadEnemyAiCatalog4229938 loads catalog records from client config directory func LoadEnemyAiCatalog4229938(configDir string) ([]any, error) { EnemyAiCatalog4229938_cacheLock.RLock() if EnemyAiCatalog4229938_cached { defer EnemyAiCatalog4229938_cacheLock.RUnlock() return EnemyAiCatalog4229938_entries, nil } EnemyAiCatalog4229938_cacheLock.RUnlock() EnemyAiCatalog4229938_cacheLock.Lock() defer EnemyAiCatalog4229938_cacheLock.Unlock() if EnemyAiCatalog4229938_cached { return EnemyAiCatalog4229938_entries, nil } filePath := filepath.Join(configDir, "EnemyAiCatalog.json.json") data, err := os.ReadFile(filePath) if err != nil { return nil, err } var raw struct { Records []json.RawMessage `json:"records"` } if err := json.Unmarshal(data, &raw); err != nil { return nil, err } var result []any for _, item := range raw.Records { var m map[string]any if err := json.Unmarshal(item, &m); err == nil { result = append(result, m) } } EnemyAiCatalog4229938_entries = result EnemyAiCatalog4229938_cached = true return result, nil } func EnemyAiCatalog4229938_BuildNameFallback() map[uint32]EnemyAiProfile4229938 { // Catalog helper EnemyAiCatalog4229938_BuildNameFallback return nil } // // 按 id 取战斗档案;miss 时按同名变体兜底(见 NameFallback)。 // 单参数签名不变 ⇒ spawn、攻击 AI、警戒状态机三处调用点自动受益。 // func EnemyAiCatalog4229938_Find(id uint32) *EnemyAiProfile4229938 { // Catalog helper EnemyAiCatalog4229938_Find return nil } // ---- 2026-09-26 Wave 2:战斗模式选择(移植 7k-v0.5 SelectCombatProfile,逐行对齐)---- func EnemyAiCatalog4229938_IsCombatMode(mode *string) bool { // Catalog helper EnemyAiCatalog4229938_IsCombatMode return false } // 7K GM follow-test 的合法值(AnimationSetConfig id,不是技能/动作 id)。Wave 2 只存档不用。 func EnemyAiCatalog4229938_IsFollowStyle(style *string) bool { // Catalog helper EnemyAiCatalog4229938_IsFollowStyle return false } func EnemyAiCatalog4229938_IsFollowAnimChoice(id uint32) bool { // Catalog helper EnemyAiCatalog4229938_IsFollowAnimChoice return false } // // 按模式解析一个模板的战斗档案(移植 7k-v0.5 SelectCombatProfile,逐行对齐): // template = 原样;melee = 有空手技能则卸武器 + 空手动画集 // (325 十二面空手 / 327 通用胖子空手),否则保留武器只换攻击集(去掉喷火器 51900505); // ranged = 有武器且有远程技能才成立,攻击集换远程、AttackRange=18、动画集 // 337/338/340。条件不满足返回 null(调用方兜回 Find)。 // func EnemyAiCatalog4229938_SelectCombatProfile(id uint32, mode string) *EnemyAiProfile4229938 { // Catalog helper EnemyAiCatalog4229938_SelectCombatProfile return nil } // // spawn 链路统一入口:off → 模板原样(旧行为逐字节不变);否则按配置模式解析, // 模式条件不满足兜回模板原样。武器 / 技能槽 / 攻击 AI / 战斗姿态四处共用这一份, // 保证「看到的武器 = 手里的动画集 = 甩出来的技能」永远一致。 // func EnemyAiCatalog4229938_ResolveSpawnProfile(templateId uint32) *EnemyAiProfile4229938 { // Catalog helper EnemyAiCatalog4229938_ResolveSpawnProfile return nil }