154 lines
5.1 KiB
Go
154 lines
5.1 KiB
Go
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):
|
|
// <list type="bullet">
|
|
// <item>GameplayId != 0 — keyed by gameplay id, read from GameplayTalentInfos.</item>
|
|
// <item>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).</item>
|
|
// <item>both 0 — dumped into SpiritInfo.TalentInfo, further narrowed by
|
|
// SpiritIds (Kingpin lists the two protagonists; some rows pin a single spirit).</item>
|
|
// </list>
|
|
//
|
|
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"`
|
|
}
|
|
|