package common import ( "encoding/json" "fmt" "sort" ) type PreviewActivitySchedule struct { ModuleID, PanelID uint32 DataHandler string } func LoadPreviewActivitySchedules() ([]PreviewActivitySchedule, error) { raw, err := ReadResourceFile("resources/ActivityPreviewPolicy.json") if err != nil { return nil, err } var policy struct { Source string `json:"source"` Panels []struct { Prefab string `json:"prefab"` DataHandler string `json:"data_handler"` } `json:"panels"` // Display-item panels are withheld by default because their reward // previews previously triggered invalid-item errors. A panel listed // here is published deliberately so its real data callbacks can be // identified from the Client's own requests. AllowDisplayItemPanels []uint32 `json:"allow_display_item_panels"` } if err = json.Unmarshal(raw, &policy); err != nil { return nil, err } if policy.Source == "" { return nil, fmt.Errorf("activity preview policy missing") } if len(policy.Panels) == 0 { return nil, nil } raw, err = ReadResourceFile("resources/PreviewGameplayCatalog.json") if err != nil { return nil, err } var catalog struct { Evidence string `json:"evidence_id"` Tables struct { Panels []struct { ID uint32 `json:"PanelID"` Prefab string `json:"UIPrefab"` Icon string `json:"TabIcon"` DisplayItems []json.RawMessage `json:"DisplayItemList"` } `json:"panels"` Activities []struct { ID uint32 `json:"ActivityID"` PanelID uint32 `json:"ActivityPanelID"` Residents []uint32 `json:"ResidentModuleList"` Modules []uint32 `json:"ActivityModuleIDList"` } `json:"activities"` } `json:"tables"` } if err = json.Unmarshal(raw, &catalog); err != nil { return nil, err } if catalog.Evidence == "" { return nil, fmt.Errorf("activity preview catalog provenance missing") } availabilityRaw, err := ReadResourceFile("resources/PreviewAssetAvailability.json") if err != nil { return nil, err } var availability struct { Source string `json:"source"` Paths map[string]bool `json:"paths"` } if err = json.Unmarshal(availabilityRaw, &availability); err != nil { return nil, err } if availability.Source == "" || availability.Paths == nil { return nil, fmt.Errorf("event asset verification missing") } prefabs := map[string]string{} for _, p := range policy.Panels { if p.DataHandler != "GetFiveDimMiniGameData" && p.DataHandler != "GetActiveActivityData" && p.DataHandler != "QuestState" && p.DataHandler != "GetMultipleDropInfo" && p.DataHandler != "ComicDessertPreview" { return nil, fmt.Errorf("unsupported preview data handler %s", p.DataHandler) } if p.DataHandler == "ComicDessertPreview" { if _, err := LoadComicDessertCatalog(); err != nil { return nil, err } } prefabs[p.Prefab] = p.DataHandler } allowDisplayItems := map[uint32]bool{} for _, id := range policy.AllowDisplayItemPanels { allowDisplayItems[id] = true } panels := map[uint32]struct{ Handler, Prefab string }{} for _, p := range catalog.Tables.Panels { if handler := prefabs[p.Prefab]; handler != "" && availability.Paths[p.Prefab] && availability.Paths[p.Icon] && (len(p.DisplayItems) == 0 || allowDisplayItems[p.ID]) { panels[p.ID] = struct{ Handler, Prefab string }{handler, p.Prefab} } } // Rerun activities can reuse one module ID across panel generations, so a // module may resolve to several panels. Publish one schedule per module and // only when every candidate panel agrees on handler and prefab; otherwise // the association would be an accidental first match. modulePanels := map[uint32][]uint32{} for _, activity := range catalog.Tables.Activities { panel := activity.PanelID if panel == 0 { panel = activity.ID } if panels[panel].Handler == "" { continue } for _, list := range [][]uint32{activity.Modules, activity.Residents} { for _, id := range list { if id == 0 { continue } if _, duplicate := modulePanels[id]; !duplicate { modulePanels[id] = []uint32{panel} continue } already := false for _, existing := range modulePanels[id] { if existing == panel { already = true break } } if !already { modulePanels[id] = append(modulePanels[id], panel) } } } } if len(modulePanels) == 0 { return nil, fmt.Errorf("no supported activity-preview modules in captured catalog") } moduleIDs := make([]uint32, 0, len(modulePanels)) for id := range modulePanels { moduleIDs = append(moduleIDs, id) } sort.Slice(moduleIDs, func(i, j int) bool { return moduleIDs[i] < moduleIDs[j] }) var schedules []PreviewActivitySchedule for _, id := range moduleIDs { panelsForModule := modulePanels[id] sort.Slice(panelsForModule, func(i, j int) bool { return panelsForModule[i] < panelsForModule[j] }) first := panels[panelsForModule[0]] for _, panel := range panelsForModule[1:] { if panels[panel] != first { return nil, fmt.Errorf("activity module %d maps to conflicting panels %v", id, panelsForModule) } } schedules = append(schedules, PreviewActivitySchedule{ModuleID: id, PanelID: panelsForModule[0], DataHandler: first.Handler}) } return schedules, nil }