// SPDX-License-Identifier: AGPL-3.0-only // Adapted from Currency-War-SR by xaodev (2026); see third_party/currency-war-sr. package common import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" ) type CurrencyWarsBranchRule struct { ID uint32 `json:"id"` Family uint32 `json:"family"` Quality uint32 `json:"quality"` PerformLevel uint32 `json:"perform_level"` } type CurrencyWarsXaodev struct { Format uint32 `json:"format_version"` Client string `json:"target_client_version"` Assembly string `json:"target_assembly_sha256"` Provenance struct { Commit string `json:"commit"` Inputs map[string]string `json:"inputs_sha256"` Native struct { Assembly string `json:"game_assembly_sha256"` } `json:"native_source"` } `json:"provenance"` NodeRules []CurrencyWarsBranchRule `json:"node_rules"` DiffAdd []struct{ Rule, Quality, Add uint32 } `json:"diff_add"` Difficulty []struct{ Chapter, Level uint32 } `json:"difficulty_levels"` Divisions []struct{ DivisionID, EnemyDifficultyLevel, BinaryNodeDiffAddRule uint32 } `json:"divisions"` Policy struct { Source string `json:"source"` MonsterCount uint32 `json:"monster_count"` MonsterTier uint32 `json:"monster_tier"` StarCap uint32 `json:"star_cap"` NormalQuality uint32 `json:"normal_quality"` PowerfulQualities []uint32 `json:"powerful_qualities"` RewardPolicy string `json:"reward_policy"` ExtraRewards bool `json:"extra_rewards_supported"` } `json:"generation_policy"` } func LoadCurrencyWarsXaodev(c *CurrencyWarsCatalog) (*CurrencyWarsXaodev, error) { raw, err := ReadResourceFile("resources/CurrencyWarsXaodev.json") if err != nil { return nil, err } v := new(CurrencyWarsXaodev) if err = json.Unmarshal(raw, v); err != nil { return nil, err } if c == nil || v.Format != 1 || v.Client != c.ClientVersion || v.Assembly != c.Provenance.GameAssemblySHA256 || v.Provenance.Native.Assembly != v.Assembly { return nil, fmt.Errorf("CW xaodev resource revision differs") } commit, err := hex.DecodeString(v.Provenance.Commit) if err != nil || len(commit) != 20 || len(v.Provenance.Inputs) == 0 { return nil, fmt.Errorf("CW xaodev source revision missing") } for _, digest := range v.Provenance.Inputs { h, err := hex.DecodeString(digest) if err != nil || len(h) != sha256.Size { return nil, fmt.Errorf("CW xaodev source digest invalid") } } if v.Policy.Source == "" || v.Policy.MonsterCount == 0 || v.Policy.MonsterCount > uint32(^uint16(0)) || v.Policy.MonsterTier == 0 || v.Policy.StarCap == 0 || v.Policy.NormalQuality == 0 || len(v.Policy.PowerfulQualities) == 0 || v.Policy.RewardPolicy != "preview_base_gold_already_paid_by_settlement" || v.Policy.ExtraRewards { return nil, fmt.Errorf("CW xaodev generation policy unsupported") } qualities := map[uint32]bool{v.Policy.NormalQuality: true} for _, q := range v.Policy.PowerfulQualities { if q <= v.Policy.NormalQuality || qualities[q] { return nil, fmt.Errorf("CW xaodev powerful quality invalid") } qualities[q] = true } seen := map[uint32]bool{} keys := map[[2]uint32]bool{} for _, r := range v.NodeRules { key := [2]uint32{r.Family, r.Quality} if r.ID == 0 || r.Quality == 0 || seen[r.ID] || keys[key] { return nil, fmt.Errorf("CW xaodev branch rules duplicate or invalid") } seen[r.ID], keys[key] = true, true } if len(seen) == 0 { return nil, fmt.Errorf("CW xaodev branch rules empty") } seen = map[uint32]bool{} for _, d := range v.Divisions { s, err := c.CapturedStage(d.DivisionID) if err != nil || seen[d.DivisionID] || s.EnemyHardLevel != d.EnemyDifficultyLevel { return nil, fmt.Errorf("CW xaodev division differs from catalog") } seen[d.DivisionID] = true } if len(seen) != len(c.CapturedStages) { return nil, fmt.Errorf("CW xaodev divisions incomplete") } keys = map[[2]uint32]bool{} for _, r := range v.DiffAdd { key := [2]uint32{r.Rule, r.Quality} if r.Rule == 0 || r.Quality == 0 || keys[key] { return nil, fmt.Errorf("CW xaodev difficulty rule duplicate or invalid") } keys[key] = true } keys = map[[2]uint32]bool{} for _, r := range v.Difficulty { key := [2]uint32{r.Chapter, r.Level} if r.Chapter == 0 || keys[key] { return nil, fmt.Errorf("CW xaodev difficulty level duplicate or invalid") } keys[key] = true } if len(keys) == 0 { return nil, fmt.Errorf("CW xaodev difficulty levels empty") } return v, nil } func (v *CurrencyWarsXaodev) Branch(family, quality uint32) (CurrencyWarsBranchRule, error) { for _, row := range v.NodeRules { if row.Family == family && row.Quality == quality { return row, nil } } return CurrencyWarsBranchRule{}, fmt.Errorf("CW xaodev branch family=%d quality=%d missing", family, quality) } func (v *CurrencyWarsXaodev) BranchDifficulty(division, option uint32) (uint32, error) { var rule, quality uint32 var foundDivision, foundOption bool for _, d := range v.Divisions { if d.DivisionID == division { rule = d.BinaryNodeDiffAddRule foundDivision = true break } } if !foundDivision { return 0, fmt.Errorf("CW xaodev division %d is not mapped", division) } for _, r := range v.NodeRules { if r.ID == option { quality = r.Quality foundOption = true break } } if !foundOption { return 0, fmt.Errorf("CW xaodev branch option %d is not mapped", option) } if rule == 0 { return 0, nil } for _, r := range v.DiffAdd { if r.Rule == rule && r.Quality == quality { return r.Add, nil } } return 0, nil } func (v *CurrencyWarsXaodev) SupportsDifficulty(chapter, level uint32) bool { for _, r := range v.Difficulty { if r.Chapter == chapter && r.Level == level { return true } } return false } // Node-specific gain follows xaodev roundExp, using our current Client constants. // Missing constants/chapters are errors; no fixed default is substituted. func (c *CurrencyWarsCatalog) BattleNodeExperience(overclock bool, chapter uint32, kind string) (uint32, error) { if chapter == 0 { return 0, fmt.Errorf("CW EXP chapter missing") } name := "GridFight_GainExpWhenWaveEnd" if kind == "Boss" { name = "GridFight_GainExpWhenBossWaveEnd" } if overclock { name = "GridFight_OCGainExpWhenWaveEndList" if kind == "Boss" { name = "GridFight_OCGainExpWhenBossWaveEndList" } values, err := c.IntegerArrayRule(name) if err != nil { return 0, err } if uint64(chapter) > uint64(len(values)) { return 0, fmt.Errorf("CW EXP chapter outside native rule") } return values[chapter-1], nil } gain, err := c.IntegerRule(name) if err != nil || gain < 0 || uint64(gain) > uint64(^uint32(0)) { return 0, fmt.Errorf("CW EXP gain rule invalid: %s", name) } return uint32(gain), nil }