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