85 lines
3.3 KiB
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
|
|
}
|