Files
GaCy-GO/internal/common/currency_wars_orb_recipes.go
2026-10-28 18:54:43 +07:00

121 lines
3.3 KiB
Go

package common
import (
"encoding/hex"
"encoding/json"
"fmt"
)
// CurrencyWarsOrbReward describes a reference-authored declarative reward.
// These are not asserted to be original Client roll rates.
type CurrencyWarsOrbReward struct {
Reward string
Amount, ItemID, Count, Rarity, Star uint32
Category string
}
type CurrencyWarsOrbRecipe struct {
ID uint32
Status, Engine string
Chapters []uint32
Choices [][]CurrencyWarsOrbReward
}
type CurrencyWarsOrbRecipes struct {
Format uint32 `json:"format_version"`
Source string `json:"source_kind"`
Binary string `json:"binary_sha256"`
Excel string `json:"excel_json_sha256"`
Custom string `json:"custom_json_sha256"`
Orbs []struct {
ID uint32 `json:"id"`
Bonus uint32 `json:"bonus_id"`
} `json:"orbs"`
Basic []struct {
ID uint32 `json:"id"`
Kind string `json:"kind"`
Amount uint32 `json:"amount"`
} `json:"basic_bonuses"`
Recipes []CurrencyWarsOrbRecipe `json:"recipes"`
Equipment []struct {
ID uint32 `json:"id"`
Category string `json:"category"`
} `json:"equipment"`
}
func LoadCurrencyWarsOrbRecipes() (*CurrencyWarsOrbRecipes, error) {
raw, err := ReadResourceFile("resources/CurrencyWarsOrbRecipes.json")
if err != nil {
return nil, err
}
var out CurrencyWarsOrbRecipes
if err = json.Unmarshal(raw, &out); err != nil {
return nil, err
}
if out.Format != 1 || out.Source != "reference-server-authored-orb-recipes" {
return nil, fmt.Errorf("CW Orb recipe format/source differs")
}
for _, value := range []string{out.Binary, out.Excel, out.Custom} {
h, err := hex.DecodeString(value)
if err != nil || len(h) != 32 {
return nil, fmt.Errorf("CW Orb recipe provenance missing")
}
}
seen := map[uint32]bool{}
for _, orb := range out.Orbs {
if orb.ID == 0 || orb.Bonus == 0 || seen[orb.ID] {
return nil, fmt.Errorf("CW Orb recipe identity invalid")
}
seen[orb.ID] = true
}
seen = map[uint32]bool{}
for _, recipe := range out.Recipes {
if recipe.ID == 0 || seen[recipe.ID] || len(recipe.Choices) == 0 {
return nil, fmt.Errorf("CW Orb custom recipe identity/choices invalid")
}
seen[recipe.ID] = true
}
return &out, nil
}
func (r *CurrencyWarsOrbRecipes) Choices(orbID, chapter uint32) ([][]CurrencyWarsOrbReward, error) {
for _, recipe := range r.Recipes {
if recipe.ID != orbID {
continue
}
if recipe.Status != "Implemented" || recipe.Engine != "Orb" {
return nil, fmt.Errorf("CW Orb %d recipe is not implemented", orbID)
}
if len(recipe.Chapters) > 0 {
found := false
for _, ch := range recipe.Chapters {
found = found || ch == chapter
}
if !found {
return nil, fmt.Errorf("CW Orb %d is not applicable to chapter %d", orbID, chapter)
}
}
return recipe.Choices, nil
}
var bonus uint32
for _, orb := range r.Orbs {
if orb.ID == orbID {
bonus = orb.Bonus
break
}
}
for _, basic := range r.Basic {
if basic.ID != bonus {
continue
}
switch basic.Kind {
case "Gold", "Exp", "Refresh", "GlobalHp":
if basic.Amount == 0 {
return nil, fmt.Errorf("CW Orb %d has a zero/implicit basic amount", orbID)
}
return [][]CurrencyWarsOrbReward{{{Reward: basic.Kind, Amount: basic.Amount}}}, nil
}
}
return nil, fmt.Errorf("CW Orb %d requires an unresolved bonus-pool recipe", orbID)
}