package common import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" ) type CurrencyWarsMergeRules struct { Format uint32 `json:"format_version"` Client string `json:"target_client_version"` Assembly string `json:"target_assembly_sha256"` Capture string `json:"source_capture_sha256"` Copies uint32 `json:"copies_required"` OrdinaryMaxStar uint32 `json:"ordinary_max_star"` } func (c *CurrencyWarsCatalog) MergeRules() (CurrencyWarsMergeRules, error) { var r CurrencyWarsMergeRules raw, e := ReadResourceFile("resources/CurrencyWarsMergeRules.json") if e != nil { return r, e } if e = json.Unmarshal(raw, &r); e != nil { return r, e } digest, e := hex.DecodeString(r.Capture) if e != nil || len(digest) != sha256.Size || r.Format != 1 || r.Client != c.ClientVersion || r.Assembly != c.Provenance.GameAssemblySHA256 || r.Copies < 2 || r.Copies > 64 || r.OrdinaryMaxStar == 0 || r.OrdinaryMaxStar > 16 { return r, fmt.Errorf("CW merge rules missing or revision differs") } return r, nil } // Reserve merged cards as their physical underlying copies. Ordinary full-star // roles are excluded from new draws; returning unsold offers keeps exclusion. func (p *CurrencyWarsCardPool) ReserveOwned(c *CurrencyWarsCatalog, id, star uint32) error { entry, ok := p.Roles[id] if !ok { return nil } base, e := c.CapturedRole(id) if e != nil { return e } rules, e := c.MergeRules() if e != nil { return e } if star < base.DefaultStar || star > base.MaxStar { return fmt.Errorf("CW owned star is invalid") } copies := uint64(1) for s := base.DefaultStar; s < star; s++ { copies *= uint64(rules.Copies) if copies > uint64(entry.Capacity) { return fmt.Errorf("CW owned copies exceed pool capacity") } } if copies > uint64(entry.Remaining) { return fmt.Errorf("CW pool exhausted for %d", id) } entry.Remaining -= uint32(copies) entry.Disabled = entry.Disabled || star >= rules.OrdinaryMaxStar p.Roles[id] = entry return nil }