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) }