feat(currency_wars): complete systems, settlement Rank SS, damage report, camera and recovery fixes
This commit is contained in:
1 parent
c008243f11
commit
0995055296
78 files changed
+33840
-9525
No files matched your search
@@ -0,0 +1,204 @@
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user