package common import ( "encoding/json" "fmt" "os" "sync" "time" ) // LogInfo prints a timestamped info line (kept dependency-free). func LogInfo(format string, args ...any) { fmt.Printf("%s %s\n", time.Now().Format("2006-01-02 15:04:05"), fmt.Sprintf(format, args...)) } // --------------------------------------------------------------------------- // res.json (camelCase, produced by the AstralOS dump pipeline) // --------------------------------------------------------------------------- type Vector struct { X int32 `json:"x"` Y int32 `json:"y"` Z int32 `json:"z"` } type SceneMonsterInfo struct { Pos Vector `json:"pos"` Rot Vector `json:"rot"` GroupID uint32 `json:"groupId"` InstID uint32 `json:"instId"` MonsterID uint32 `json:"monsterId"` EventID uint32 `json:"eventId"` } type SceneNpcInfo struct { Pos Vector `json:"pos"` Rot Vector `json:"rot"` GroupID uint32 `json:"groupId"` InstID uint32 `json:"instId"` NpcID uint32 `json:"npcId"` } type ScenePropInfo struct { Pos Vector `json:"pos"` Rot Vector `json:"rot"` GroupID uint32 `json:"groupId"` InstID uint32 `json:"instId"` PropState uint32 `json:"propState"` PropID uint32 `json:"propId"` } type TeleportInfo struct { Pos Vector `json:"pos"` Rot Vector `json:"rot"` } type SceneData struct { Npcs []SceneNpcInfo `json:"npcs"` Props []ScenePropInfo `json:"props"` Monsters []SceneMonsterInfo `json:"monsters"` Teleports map[uint32]TeleportInfo `json:"teleports"` FinishedSubMissions []uint32 `json:"finishedSubMissions"` FinishedMainMissions []uint32 `json:"finishedMainMissions"` Chests []uint32 `json:"chests"` } type LevelOutputConfig struct { IsEnteredSceneInfo bool `json:"isEnteredSceneInfo"` Scenes map[uint32]SceneData `json:"scenes"` PlaneType uint32 `json:"planeType"` WorldID uint32 `json:"worldId"` Sections []uint32 `json:"sections"` SavedValues map[string]int32 `json:"savedValues"` } type AvatarConfig struct { WeaknessBuffID uint32 `json:"weaknessBuffId"` } type JsonConfig struct { // entryId -> "P_F" -> config LevelOutputConfigs map[uint32]map[string]*LevelOutputConfig `json:"levelOutputConfigs"` AvatarConfigs map[uint32]AvatarConfig `json:"avatarConfigs"` MapDefaultEntranceMap map[uint32]uint32 `json:"mapDefaultEntranceMap"` RelicAvatarRecommend map[uint32][]uint32 `json:"relicAvatarRecommend"` SkinIDs []uint32 `json:"skinIds"` } var ( gameResOnce sync.Once gameRes *JsonConfig gameResErr error teleportOnce sync.Once teleportDB map[uint32]map[uint32]TeleportInfo ) // GameRes lazily parses res.json (13MB; parsed once). func GameRes() (*JsonConfig, error) { gameResOnce.Do(func() { gameRes = &JsonConfig{} raw, err := os.ReadFile("res.json") if err != nil { gameResErr = err return } if err := json.Unmarshal(raw, gameRes); err != nil { gameResErr = fmt.Errorf("res.json is malformed: %w", err) return } if gameRes.LevelOutputConfigs == nil { gameRes.LevelOutputConfigs = map[uint32]map[string]*LevelOutputConfig{} } if gameRes.AvatarConfigs == nil { gameRes.AvatarConfigs = map[uint32]AvatarConfig{} } if gameRes.MapDefaultEntranceMap == nil { gameRes.MapDefaultEntranceMap = map[uint32]uint32{} } }) return gameRes, gameResErr } // Teleports lazily parses teleports.json ({floor_id: {teleport_id: info}}). func Teleports() map[uint32]map[uint32]TeleportInfo { teleportOnce.Do(func() { teleportDB = map[uint32]map[uint32]TeleportInfo{} raw, err := os.ReadFile("teleports.json") if err != nil { return } _ = json.Unmarshal(raw, &teleportDB) }) return teleportDB }