package catalogs import ( "encoding/json" "os" "path/filepath" "sync" ) var _ = json.RawMessage{} var _ = sync.Mutex{} var _ = os.ReadFile var _ = filepath.Join // // One NPC cultivation ("好感度 / NPC card") entry, projected from NpcCultivationConfig.json. // Id is the 910500xx NpcCultivationId and is what the client stores as // NpcCardInfo.TemplateId. // AgentTag is the bridge into the daily-life system: it is what a // LifeScheduleTimetableConfig row and every AgentDataSetsActivityConfig row are keyed by, and it // is NOT the agent id (FightSpiritConfig.AgentId) — 辰宿/昭昭 are tags 2/3, Garm is 4, Richie 12. // type NpcCultivationEntry4229938 struct { Id uint32 `json:"id"` FightSpiritId uint32 `json:"fightSpiritId"` Name string `json:"name"` AgentTag uint32 `json:"agentTag"` } // // One phone menu skin part, projected from MobileMenuSkinPartConfig.json. // type MobileSkinPartEntry4229938 struct { Id uint32 `json:"id"` Type uint32 `json:"type"` IsShow bool `json:"isShow"` } // // Build-4229938 NPC cultivation + phone skin-part catalogs. // Both are read straight out of the login payload by the client: // lua/LX6/Data/Player/PlayerData/PlayerInfoMinorNpcCultivationData.lua:9 // InitPlayerInfo -> info.InfoMinor.InfoNpcCultivation -> NpcCardInfos -> CreateNpcCardInfo // lua/LX6/Data/Player/PlayerData/PlayerInfoSpiritData.lua:18 // InitPlayerInfo -> info.InfoSpirit.AvailableSkinParts // Neither source table ships in the v0.3 Configs folder; both come from // 补丁和工具\ConfigDump.zip. Regenerate with _gen_npccult_skin_cfg.py. // Layout of the cultivation struct is pinned by the client's own reader // (RPCDeserializeAuto.lua Auto.Reader[1311]) and matches Client4229938Contracts.Auto.cs // field-for-field: NpcCardInfos, LockedCardInfos, NpcChats, NpcGroupChats, // AvailableGiftSendCount, InteractPoint, NpcEventQueueList, ChatGroupRenameDict. // const ( // // Favour ceiling stamped on every card. The client's favour ladder is // NpcCultivationNpcFavorLevelConfig.UnlockFavor = 0/30/90/180/290/410/540/680/830/1000, // so 1000 unlocks every favour level (and the dialogs/voices gated behind them). // Lower this single value to make progression start from zero instead. // NpcCultivationCatalog4229938_MaxFavor float64 = 1000 // Client InteractPointMaxLimit in ClientConfig.lua. NpcCultivationCatalog4229938_MaxInteractPoint uint32 = 5 // Gifts the player may hand out; the client re-syncs this whenever a gift is sent. NpcCultivationCatalog4229938_GiftSendCount uint32 = 10 // // Default phone-skin suit. These three ids are the client's own fallbacks, taken from // ConfigDump_v3/Static/MobileMenuSkinPartConfig.static.json // (DefaultWallPaperCfg=12003000 / DefaultDecorationCfg=12003001 / DefaultPendantCfg=12003002). // They matter because the wallpaper actually worn is NOT in AvailableSkinParts — it is // SpiritInfo.MobileSkinInfo (Wallpaper/Decoration/Pendant), a per-character struct carried // by the LOGIN payload. Leaving it zeroed makes MainPhoneUtils.GetCurrentSpiritSkinInfo() // degrade to these same defaults, so an "applied" wallpaper snaps back on panel close. // NpcCultivationCatalog4229938_DefaultWallpaperPart uint32 = 12003000 NpcCultivationCatalog4229938_DefaultDecorationPart uint32 = 12003001 NpcCultivationCatalog4229938_DefaultPendantPart uint32 = 12003002 ) var ( NpcCultivationCatalog4229938_cacheLock sync.RWMutex NpcCultivationCatalog4229938_cached = false NpcCultivationCatalog4229938_entries []any ) // LoadNpcCultivationCatalog4229938 loads catalog records from client config directory func LoadNpcCultivationCatalog4229938(configDir string) ([]any, error) { NpcCultivationCatalog4229938_cacheLock.RLock() if NpcCultivationCatalog4229938_cached { defer NpcCultivationCatalog4229938_cacheLock.RUnlock() return NpcCultivationCatalog4229938_entries, nil } NpcCultivationCatalog4229938_cacheLock.RUnlock() NpcCultivationCatalog4229938_cacheLock.Lock() defer NpcCultivationCatalog4229938_cacheLock.Unlock() if NpcCultivationCatalog4229938_cached { return NpcCultivationCatalog4229938_entries, nil } filePath := filepath.Join(configDir, "NpcCultivationCatalog.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) } } NpcCultivationCatalog4229938_entries = result NpcCultivationCatalog4229938_cached = true return result, nil } // // spirit -> AgentTag, via NpcCultivationConfig.FightSpiritID. Returns 0 for a spirit with no // cultivation row (and for 辰宿/昭昭 it returns 2/3, which have no timetable — callers must // still check that an activity actually exists before using it). // func NpcCultivationCatalog4229938_AgentTagForSpirit(spiritId uint32) uint32 { // Catalog helper NpcCultivationCatalog4229938_AgentTagForSpirit return 0 } func NpcCultivationCatalog4229938_Open(file string, doc any, records any) bool { // Catalog helper NpcCultivationCatalog4229938_Open return false } func NpcCultivationCatalog4229938_LoadCultivation() []NpcCultivationEntry4229938 { // Catalog helper NpcCultivationCatalog4229938_LoadCultivation return nil } func NpcCultivationCatalog4229938_LoadSkinParts() []MobileSkinPartEntry4229938 { // Catalog helper NpcCultivationCatalog4229938_LoadSkinParts return nil }