Files
GaCy-GO/internal/common/currency_wars_levels.go

85 lines
3.3 KiB
Go

package common
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"regexp"
)
// Native PlayerLevelConfigRow, before run-specific modifiers are applied.
type CurrencyWarsPlayerLevel struct {
GeneralPropertyList []CurrencyWarsNativePropertyValue `json:"GeneralPropertyList"`
Level uint32 `json:"PlayerLevel"`
LevelUpExp uint32 `json:"LevelUpExp"`
AvatarMaxNumber uint32 `json:"AvatarMaxNumber"`
Rarity1Weight uint32 `json:"Rarity1Weight"`
Rarity2Weight uint32 `json:"Rarity2Weight"`
Rarity3Weight uint32 `json:"Rarity3Weight"`
Rarity4Weight uint32 `json:"Rarity4Weight"`
Rarity5Weight uint32 `json:"Rarity5Weight"`
}
// Keep native FixPoint decimal values as text rather than float64.
type CurrencyWarsNativePropertyValue struct {
PropertyType uint32 `json:"PropertyType"`
Value string `json:"Value"`
}
var currencyWarsDecimal = regexp.MustCompile(`^[+-]?[0-9]+(?:\.[0-9]+)?$`)
func (p CurrencyWarsPlayerLevel) RarityWeights() [5]uint32 {
return [5]uint32{p.Rarity1Weight, p.Rarity2Weight, p.Rarity3Weight, p.Rarity4Weight, p.Rarity5Weight}
}
func (c *CurrencyWarsCatalog) validatePlayerLevels() error {
c.playerLevelByID = make(map[uint32]CurrencyWarsPlayerLevel)
if len(c.PlayerLevels) == 0 && c.Provenance.PlayerLevelsSHA256 == "" {
return nil
}
digest, err := hex.DecodeString(c.Provenance.PlayerLevelsSHA256)
if err != nil || len(digest) != sha256.Size {
return fmt.Errorf("CW player-level capture provenance is missing")
}
maxLevel, err := c.IntegerRule("GridFight_DefaultPlayerLevelMax")
if err != nil || maxLevel <= 0 || uint64(maxLevel) != uint64(len(c.PlayerLevels)) {
return fmt.Errorf("CW player-level capture does not cover the default range")
}
for _, row := range c.PlayerLevels {
if row.Level == 0 || uint64(row.Level) > uint64(maxLevel) || row.AvatarMaxNumber == 0 {
return fmt.Errorf("invalid CW player-level row %d", row.Level)
}
if _, duplicate := c.playerLevelByID[row.Level]; duplicate {
return fmt.Errorf("duplicate CW player level %d", row.Level)
}
if row.GeneralPropertyList == nil {
return fmt.Errorf("CW level %d is missing property rows", row.Level)
}
for _, prop := range row.GeneralPropertyList {
if prop.PropertyType == 0 || !currencyWarsDecimal.MatchString(prop.Value) {
return fmt.Errorf("invalid CW property value at level %d", row.Level)
}
}
var total uint64
for _, weight := range row.RarityWeights() {
total += uint64(weight)
}
if total == 0 {
return fmt.Errorf("CW shop rarity weights are empty at level %d", row.Level)
}
c.playerLevelByID[row.Level] = row
}
return nil
}
// Native GetLevelUpNeedExp uses a run modifier when present, otherwise this
// table's LevelUpExp. Reading a row does not apply EXP or draw shop cards.
func (c *CurrencyWarsCatalog) PlayerLevel(level uint32) (CurrencyWarsPlayerLevel, error) {
row, exists := c.playerLevelByID[level]
if !exists {
return CurrencyWarsPlayerLevel{}, fmt.Errorf("CW player level %d has not been captured", level)
}
row.GeneralPropertyList = append([]CurrencyWarsNativePropertyValue(nil), row.GeneralPropertyList...)
return row, nil
}