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

90 lines
3.9 KiB
Go

package common
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
)
// Role variants retain the native row JSON without converting FixPoint strings
// or text hashes to float64. Only the captured star is available here; this
// does not imply that every star or every hidden shop role has been captured.
type CurrencyWarsRoleVariant struct {
Config CurrencyWarsRoleConfig `json:"Config"`
Price uint32 `json:"Price"`
MaxStar uint32 `json:"MaxStar"`
Star uint32 `json:"Star"`
SellPrice uint32 `json:"SellPrice"`
SaleQuoteKnown *bool `json:"SaleQuoteKnown,omitempty"`
StarConfig json.RawMessage `json:"StarConfig"`
ServantStarConfig json.RawMessage `json:"ServantStarConfig"`
}
func (c *CurrencyWarsCatalog) validateRoleVariants() error {
c.variantByKey = make(map[[2]uint32]CurrencyWarsRoleVariant)
if len(c.CapturedVariants) == 0 && c.Provenance.VariantsSHA256 == "" {
return nil // Older catalogs have no variant capture; lookups reject it.
}
digest, err := hex.DecodeString(c.Provenance.VariantsSHA256)
if err != nil || len(digest) != sha256.Size || len(c.CapturedVariants) == 0 {
return fmt.Errorf("CW role variant capture or provenance is missing")
}
if c.Provenance.StarRowsSHA256 != "" {
d, e := hex.DecodeString(c.Provenance.StarRowsSHA256)
if e != nil || len(d) != sha256.Size {
return fmt.Errorf("CW star row capture identity is invalid")
}
}
for _, role := range c.CapturedVariants {
key := [2]uint32{role.Config.ID, role.Star}
if role.Config.ID == 0 || role.Config.AvatarID == 0 || role.Config.Rarity == 0 ||
role.Config.SeasonID != c.ActiveSeasonID || role.Star == 0 || role.Star > role.MaxStar {
return fmt.Errorf("invalid CW captured role variant %v", key)
}
if _, exists := c.variantByKey[key]; exists {
return fmt.Errorf("duplicate CW captured role variant %v", key)
}
var identity struct{ ID, Star uint32 }
if err := json.Unmarshal(role.StarConfig, &identity); err != nil || identity.ID != key[0] || identity.Star != key[1] {
return fmt.Errorf("CW role star row identity differs for %v", key)
}
c.variantByKey[key] = role
}
for id, role := range c.roleByID {
variant, exists := c.variantByKey[[2]uint32{id, role.DefaultStar}]
if !exists || variant.Price != role.Price || variant.SellPrice != role.DefaultSellPrice || variant.MaxStar != role.MaxStar ||
variant.Config.AvatarID != role.Config.AvatarID || variant.Config.SeasonID != role.Config.SeasonID {
return fmt.Errorf("CW variant capture differs from default role %d", id)
}
}
return nil
}
func (c *CurrencyWarsCatalog) CapturedRoleVariant(roleID, star uint32) (CurrencyWarsRoleVariant, error) {
roleID = c.CurrentRoleID(roleID)
role, exists := c.variantByKey[[2]uint32{roleID, star}]
if !exists {
return CurrencyWarsRoleVariant{}, fmt.Errorf("CW role %d star %d has not been captured", roleID, star)
}
role.Config.TraitList = append([]uint32(nil), role.Config.TraitList...)
role.Config.BackendRankList = append([]uint32(nil), role.Config.BackendRankList...)
role.Config.ChargeType = append([]uint32(nil), role.Config.ChargeType...)
role.StarConfig = append(json.RawMessage(nil), role.StarConfig...)
role.ServantStarConfig = append(json.RawMessage(nil), role.ServantStarConfig...)
return role, nil
}
// Native special roles can have a sale quote different from the rarity array.
// Return only an observed quote, with run modifiers/refunds left to the caller.
func (c *CurrencyWarsCatalog) CapturedSalePrice(roleID, star uint32) (uint32, error) {
role, err := c.CapturedRoleVariant(roleID, star)
if err != nil {
return 0, err
}
if role.SaleQuoteKnown != nil && !*role.SaleQuoteKnown {
return 0, fmt.Errorf("CW role %d star %d sale quote has not been established", roleID, star)
}
return role.SellPrice, nil
}