181 lines
7.5 KiB
Go
181 lines
7.5 KiB
Go
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
|
||
}
|
||
|