package catalogs import ( "encoding/json" "os" "path/filepath" "sync" ) var _ = json.RawMessage{} var _ = sync.Mutex{} var _ = os.ReadFile var _ = filepath.Join // // One row of PoliceDispatchConfig.json (4 rows) — a dispatch slot of the police Duty Terminal. // ShowInApp is the client-side visibility gate // (PolicePanelManager.CheckDispatchEnable, PolicePanelManager.lua:834). // type PoliceDispatchEntry4229938 struct { Id uint32 `json:"id"` Name string `json:"name"` SubTitle string `json:"subTitle"` SwitchSubTitle string `json:"switchSubTitle"` SkillId uint32 `json:"skillId"` SwitchSkillId uint32 `json:"switchSkillId"` Icon uint32 `json:"icon"` Number uint32 `json:"number"` ShowInApp bool `json:"showInApp"` ShowInCircle bool `json:"showInCircle"` NeedActive bool `json:"needActive"` AtWorkUse bool `json:"atWorkUse"` CloseApp bool `json:"closeApp"` MaxProgress uint32 `json:"maxProgress"` DialogId uint32 `json:"dialogId"` GuideId string `json:"guideId"` JobId uint32 `json:"jobId"` Tag string `json:"tag"` Des string `json:"des"` UnlockDes string `json:"unlockDes"` } // One row of PoliceFineConfig.json (31 rows) — a charge that can land on a case. type PoliceFineEntry4229938 struct { Id uint32 `json:"id"` Title string `json:"title"` Type uint32 `json:"type"` CrimeType uint32 `json:"crimeType"` Factor uint32 `json:"factor"` IsDriver bool `json:"isDriver"` IsShow bool `json:"isShow"` Time uint32 `json:"time"` Drop uint32 `json:"drop"` } // One row of PoliceSuspectCatalog.json — a real AgentConfig row usable as a case NpcId. type PoliceSuspectEntry4229938 struct { Id uint32 `json:"id"` Name string `json:"name"` HeadIcon uint32 `json:"headIcon"` } // One row of PoliceFakeFileCatalog.json (27 rows) — an entry of the Mimic Database // archive (伪人档案). Rows are authored per character (FightSpiritId). type PoliceFakeFileEntry4229938 struct { Id uint32 `json:"id"` FightSpiritId uint32 `json:"fightSpiritId"` AgentId uint32 `json:"agentId"` Group string `json:"group"` ParentFileId uint32 `json:"parentFileId"` NeedActive bool `json:"needActive"` MaxProgress uint32 `json:"maxProgress"` } // // Build-4229938 police catalog set, projected out of ConfigDump.zip by _gen_police_cfg.py: // PoliceDispatchCatalog.json <- PoliceDispatchConfig.json (4 rows) // PoliceFineCatalog.json <- PoliceFineConfig.json (31 rows) // PoliceStaticCatalog.json <- PoliceConfig.static.json (named field constants) // PoliceSuspectCatalog.json <- AgentConfig.json (SpeciesType==2 rows, 50) // PoliceFakeFileCatalog.json <- PoliceFakeFileConfig.json (27 rows, _gen_police_fakefile_cfg.py) // Why this exists: the police phone app is a pure data consumer. Every list it draws comes from // either the server payload (SyncSpiritPoliceJobInfo) or a config table the client already // ships, and the config tables never carry per-player state. So "the panel is empty" is always a // payload problem, never a missing table problem — but which payload rows make sense can only be // answered from the tables, which is what these four projections are for. // ⛑️ The one non-obvious trap is PoliceCaseListPanelStore.OnRenderCaseItem: it does // AgentConfig.GetConfig(castInfo.NpcId) and returns when that misses, rendering nothing // at all. A fabricated case therefore needs a NpcId that exists in AgentConfig, not just any number. // var ( PoliceCatalog4229938_cacheLock sync.RWMutex PoliceCatalog4229938_cached = false PoliceCatalog4229938_entries []any ) // LoadPoliceCatalog4229938 loads catalog records from client config directory func LoadPoliceCatalog4229938(configDir string) ([]any, error) { PoliceCatalog4229938_cacheLock.RLock() if PoliceCatalog4229938_cached { defer PoliceCatalog4229938_cacheLock.RUnlock() return PoliceCatalog4229938_entries, nil } PoliceCatalog4229938_cacheLock.RUnlock() PoliceCatalog4229938_cacheLock.Lock() defer PoliceCatalog4229938_cacheLock.Unlock() if PoliceCatalog4229938_cached { return PoliceCatalog4229938_entries, nil } filePath := filepath.Join(configDir, "PoliceCatalog.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) } } PoliceCatalog4229938_entries = result PoliceCatalog4229938_cached = true return result, nil } // // The archive rows authored for one character. All 27 rows belong to Richie (15021021), so a // different template legitimately resolves to nothing — the panel then just stays empty. // func PoliceCatalog4229938_FakeFilesFor(templateId uint32) []PoliceFakeFileEntry4229938 { // Catalog helper PoliceCatalog4229938_FakeFilesFor return nil } func PoliceCatalog4229938_Rows(root string, file string) []any { // Catalog helper PoliceCatalog4229938_Rows return nil } func PoliceCatalog4229938_LogMissing(file string) { // Catalog helper PoliceCatalog4229938_LogMissing } func PoliceCatalog4229938_UInt(row any, name string) uint32 { // Catalog helper PoliceCatalog4229938_UInt return 0 } func PoliceCatalog4229938_Bool(row any, name string) bool { // Catalog helper PoliceCatalog4229938_Bool return false } func PoliceCatalog4229938_Str(row any, name string) string { // Catalog helper PoliceCatalog4229938_Str return "" } type Catalog struct { Dispatches []PoliceDispatchEntry4229938 `json:"dispatches"` Fines []PoliceFineEntry4229938 `json:"fines"` Constants map[string]uint32 `json:"constants"` Suspects []PoliceSuspectEntry4229938 `json:"suspects"` FakeFiles []PoliceFakeFileEntry4229938 `json:"fakeFiles"` }