-
Notifications
You must be signed in to change notification settings - Fork 3
🚀 Refactor mission data loading with dynamic config support #2180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,11 @@ package ei | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||||
| "log" | ||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||
| "sort" | ||||||||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const missionJSON = `{"ships":[ | ||||||||||||||||||||||||||||||||||
|
|
@@ -18,21 +23,194 @@ const missionJSON = `{"ships":[ | |||||||||||||||||||||||||||||||||
| {"name": "Atreggies Henliner","art":"atreggies","duration":["2d","3d","4d"]} | ||||||||||||||||||||||||||||||||||
| ]}` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // MissionConfigPath is the on-disk config used for mission and artifact data. | ||||||||||||||||||||||||||||||||||
| const MissionConfigPath = "ttbb-data/ei-afx-config.json" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // ShipData holds data for each mission ship | ||||||||||||||||||||||||||||||||||
| type ShipData struct { | ||||||||||||||||||||||||||||||||||
| Name string `json:"Name"` | ||||||||||||||||||||||||||||||||||
| Art string `json:"Art"` | ||||||||||||||||||||||||||||||||||
| ArtDev string `json:"ArtDev"` | ||||||||||||||||||||||||||||||||||
| Duration []string `json:"Duration"` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| type missionData struct { | ||||||||||||||||||||||||||||||||||
| Ships []ShipData `json:"ships"` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| type missionConfig struct { | ||||||||||||||||||||||||||||||||||
| MissionParameters []missionParameter `json:"missionParameters"` | ||||||||||||||||||||||||||||||||||
| ArtifactParameters []artifactParameter `json:"artifactParameters"` | ||||||||||||||||||||||||||||||||||
| CraftingLevelInfos []craftingLevelInfo `json:"craftingLevelInfos"` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| type craftingLevelInfo struct { | ||||||||||||||||||||||||||||||||||
| XpRequired int `json:"xpRequired"` | ||||||||||||||||||||||||||||||||||
| RarityMult float64 `json:"rarityMult"` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| type artifactParameter struct { | ||||||||||||||||||||||||||||||||||
| Spec artifactSpec `json:"spec"` | ||||||||||||||||||||||||||||||||||
| BaseQuality float64 `json:"baseQuality"` | ||||||||||||||||||||||||||||||||||
| Value float64 `json:"value"` | ||||||||||||||||||||||||||||||||||
| OddsMultiplier float64 `json:"oddsMultiplier"` | ||||||||||||||||||||||||||||||||||
| CraftingPrice float64 `json:"craftingPrice"` | ||||||||||||||||||||||||||||||||||
| CraftingPriceLow float64 `json:"craftingPriceLow"` | ||||||||||||||||||||||||||||||||||
| CraftingPriceDomain float64 `json:"craftingPriceDomain"` | ||||||||||||||||||||||||||||||||||
| CraftingPriceCurve float64 `json:"craftingPriceCurve"` | ||||||||||||||||||||||||||||||||||
| CraftingXp float64 `json:"craftingXp"` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| type artifactSpec struct { | ||||||||||||||||||||||||||||||||||
| Name string `json:"name"` | ||||||||||||||||||||||||||||||||||
| Level string `json:"level"` | ||||||||||||||||||||||||||||||||||
| Rarity string `json:"rarity"` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| type missionParameter struct { | ||||||||||||||||||||||||||||||||||
| Ship string `json:"ship"` | ||||||||||||||||||||||||||||||||||
| Durations []missionDuration `json:"durations"` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| type missionDuration struct { | ||||||||||||||||||||||||||||||||||
| DurationType string `json:"durationType"` | ||||||||||||||||||||||||||||||||||
| Seconds int `json:"seconds"` | ||||||||||||||||||||||||||||||||||
| Quality float64 `json:"quality"` | ||||||||||||||||||||||||||||||||||
| MinQuality float64 `json:"minQuality"` | ||||||||||||||||||||||||||||||||||
| MaxQuality float64 `json:"maxQuality"` | ||||||||||||||||||||||||||||||||||
| Capacity int `json:"capacity"` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // MissionArt holds the mission art and durations loaded from JSON | ||||||||||||||||||||||||||||||||||
| var MissionArt missionData | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // ArtifactParameters holds artifact parameters loaded from JSON | ||||||||||||||||||||||||||||||||||
| var ArtifactParameters []artifactParameter | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // CraftingLevelInfos holds crafting level info loaded from JSON | ||||||||||||||||||||||||||||||||||
| var CraftingLevelInfos []craftingLevelInfo | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+86
to
+90
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| var missionShipInfo = map[string]ShipData{ | ||||||||||||||||||||||||||||||||||
| "CHICKEN_ONE": {Name: "Chicken One", Art: "chicken1"}, | ||||||||||||||||||||||||||||||||||
| "CHICKEN_NINE": {Name: "Chicken Nine", Art: "chicken9"}, | ||||||||||||||||||||||||||||||||||
| "CHICKEN_HEAVY": {Name: "Chicken Heavy", Art: "chickenheavy"}, | ||||||||||||||||||||||||||||||||||
| "BCR": {Name: "BCR", Art: "bcr"}, | ||||||||||||||||||||||||||||||||||
| "MILLENIUM_CHICKEN": {Name: "Quintillion Chicken", Art: "milleniumchicken"}, | ||||||||||||||||||||||||||||||||||
| "CORELLIHEN_CORVETTE": {Name: "Cornish-Hen Corvette", Art: "corellihencorvette"}, | ||||||||||||||||||||||||||||||||||
| "GALEGGTICA": {Name: "Galeggtica", Art: "galeggtica"}, | ||||||||||||||||||||||||||||||||||
| "CHICKFIANT": {Name: "Defihent", Art: "defihent"}, | ||||||||||||||||||||||||||||||||||
| "VOYEGGER": {Name: "Voyegger", Art: "voyegger"}, | ||||||||||||||||||||||||||||||||||
| "HENERPRISE": {Name: "Henerprise", Art: "henerprise"}, | ||||||||||||||||||||||||||||||||||
| "ATREGGIES": {Name: "Atreggies Henliner", Art: "atreggies"}, | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| func init() { | ||||||||||||||||||||||||||||||||||
| _ = json.Unmarshal([]byte(missionJSON), &MissionArt) | ||||||||||||||||||||||||||||||||||
| if !loadMissionDataFromConfig(MissionConfigPath) { | ||||||||||||||||||||||||||||||||||
| _ = json.Unmarshal([]byte(missionJSON), &MissionArt) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // ReloadMissionConfig reloads mission, artifact, and crafting data from disk. | ||||||||||||||||||||||||||||||||||
| func ReloadMissionConfig() bool { | ||||||||||||||||||||||||||||||||||
| return loadMissionDataFromConfig(MissionConfigPath) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+112
to
+115
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| func loadMissionDataFromConfig(path string) bool { | ||||||||||||||||||||||||||||||||||
| data, err := os.ReadFile(path) | ||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||
| log.Printf("Mission config read failed: %v", err) | ||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| var cfg missionConfig | ||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(data, &cfg); err != nil { | ||||||||||||||||||||||||||||||||||
| log.Printf("Mission config parse failed: %v", err) | ||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ArtifactParameters = cfg.ArtifactParameters | ||||||||||||||||||||||||||||||||||
| CraftingLevelInfos = cfg.CraftingLevelInfos | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
Comment on lines
+130
to
+132
|
||||||||||||||||||||||||||||||||||
| var md missionData | ||||||||||||||||||||||||||||||||||
| for _, param := range cfg.MissionParameters { | ||||||||||||||||||||||||||||||||||
| info, ok := missionShipInfo[param.Ship] | ||||||||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| info.Duration = pickMissionDurations(param.Durations) | ||||||||||||||||||||||||||||||||||
| md.Ships = append(md.Ships, info) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| md.Ships = append(md.Ships, info) | |
| // Place each ship at its shipTypeID index rather than appending in config order. | |
| shipID := int(param.Ship) | |
| if shipID < 0 { | |
| continue | |
| } | |
| // Grow the slice as needed to accommodate this ship ID, preserving existing entries. | |
| if shipID >= len(md.Ships) { | |
| newShips := make([]missionShip, shipID+1) | |
| copy(newShips, md.Ships) | |
| md.Ships = newShips | |
| } | |
| md.Ships[shipID] = info |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding unit tests for pickMissionDurations/formatMissionDuration since this new logic affects user-facing output and depends on durationType selection and formatting edge cases (e.g., 90m => 1h30m, day+hour combos, fallback behavior when preferred types are missing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
watcher.Add(ei.MissionConfigPath) calls log.Fatal on error. Since ei_missions.go explicitly falls back to embedded missionJSON when the file doesn’t exist, treating a missing optional config file as fatal will prevent the app from starting on fresh installs. Consider checking os.IsNotExist and skipping the watch (or watching the directory) until the file is created.