package catalogs import ( "encoding/json" "os" "path/filepath" "sync" ) var _ = json.RawMessage{} var _ = sync.Mutex{} var _ = os.ReadFile var _ = filepath.Join // // Client talent tables (天赋树) imported from the 4229938 config dump into // paths.clientConfigs: // · TalentTreeGamePlayConfig.json — the gameplay ids the talent panel is split into (1..5). // · TalentTreeConfig.json — one row per tree: how the client keys it (gameplay / job // class / spirit) — see TalentTree. // · TalentTreeTalentConfig.json — one row per talent node: owning tree, point cost, max layer. // Optional, like the economy tables: a missing file only disables talent point accounting instead // of refusing to start. The server needs these numbers because the client's AskActiveGameplayTalentLayer // only carries (gameplayId, talentId, addLayer) — no cost — so a server that wants to debit points // has to know what the node costs itself. // var ( TalentTreeRepository_cacheLock sync.RWMutex TalentTreeRepository_cached = false TalentTreeRepository_entries []any ) // LoadTalentTreeRepository loads catalog records from client config directory func LoadTalentTreeRepository(configDir string) ([]any, error) { TalentTreeRepository_cacheLock.RLock() if TalentTreeRepository_cached { defer TalentTreeRepository_cacheLock.RUnlock() return TalentTreeRepository_entries, nil } TalentTreeRepository_cacheLock.RUnlock() TalentTreeRepository_cacheLock.Lock() defer TalentTreeRepository_cacheLock.Unlock() if TalentTreeRepository_cached { return TalentTreeRepository_entries, nil } filePath := filepath.Join(configDir, "TalentTreeRepository.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) } } TalentTreeRepository_entries = result TalentTreeRepository_cached = true return result, nil } // Forces the (optional) tables to load so the startup log reports what was imported. func TalentTreeRepository_Warmup() { // Catalog helper TalentTreeRepository_Warmup } func TalentTreeRepository_TryTalent(id uint32, node TalentNode) bool { // Catalog helper TalentTreeRepository_TryTalent return false } // // Nodes of every tree matching , de-duplicated by node id and // ascending. A node can sit in several trees (33 of them do), so the union has to be distinct. // func TalentTreeRepository_NodesOfTrees(predicate any) []TalentNode { // Catalog helper TalentTreeRepository_NodesOfTrees return nil } // // Every node belonging to one talent tree (TalentTreeid row), ascending by id. // Needed by the career-talent seeding (TalentTreeSettings.SeedCareerTalents): nodes have // prerequisites, so activating one node in isolation can still read as locked on the client. // Publishing a whole tree sidesteps that dependency order entirely. // func TalentTreeRepository_NodesOfTree(treeId uint32) []TalentNode { // Catalog helper TalentTreeRepository_NodesOfTree return nil } func TalentTreeRepository_ReadRecords(path string, warnings []string) []any { // Catalog helper TalentTreeRepository_ReadRecords return nil } // Dump records may carry fully qualified keys ("LT.ConfigGen.IFoo.Id"), so match on the leaf name. func TalentTreeRepository_TryProperty(row any, name string, value any) bool { // Catalog helper TalentTreeRepository_TryProperty return false } func TalentTreeRepository_TryUInt32(row any, name string, result uint32) bool { // Catalog helper TalentTreeRepository_TryUInt32 return false } // One talent node. is the first TalentTreeid row (a node can // belong to several trees in the dump; the panel key we echo back only needs one). type TalentNode struct { Id uint32 `json:"id"` TreeId uint32 `json:"treeId"` CostPoint uint32 `json:"costPoint"` LayerNum uint32 `json:"layerNum"` } // // One talent tree, i.e. one TalentTreeConfig row. The three ids are mutually exclusive // in stock 4229938 and decide which client container the tree's nodes have to be published in // (TalentTreeMgr.OnStartCurrent): // // GameplayId != 0 — keyed by gameplay id, read from GameplayTalentInfos. // JobClassId != 0 — keyed by job class, read from that career's // SpiritJob.TalentInfo (so a tree only shows up for characters that own the career). // both 0 — dumped into SpiritInfo.TalentInfo, further narrowed by // SpiritIds (Kingpin lists the two protagonists; some rows pin a single spirit). // // type TalentTree struct { Id uint32 `json:"id"` Name string `json:"name"` GameplayId uint32 `json:"gameplayId"` JobClassId uint32 `json:"jobClassId"` SpiritIds []uint32 `json:"spiritIds"` } type TalentTreeRepository_Data struct { GameplayIds []uint32 `json:"gameplayIds"` Trees []TalentTree `json:"trees"` Nodes map[uint32]TalentNode `json:"nodes"` ByTree map[uint32][]TalentNode `json:"byTree"` }