89 lines
3.8 KiB
Go
89 lines
3.8 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) {
|
|
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
|
|
}
|