Files
2026-10-28 18:54:43 +07:00

160 lines
5.2 KiB
Go

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
}