118 lines
4.5 KiB
Go
118 lines
4.5 KiB
Go
package common
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
)
|
|
|
|
// CapturedRole retains native handbook-default configuration. This is not the
|
|
// complete shop pool: hidden, variant and OtherRoles entries are not captured.
|
|
type CurrencyWarsCapturedRole struct {
|
|
Config CurrencyWarsRoleConfig `json:"Config"`
|
|
Price uint32 `json:"Price"`
|
|
MaxStar uint32 `json:"MaxStar"`
|
|
DefaultStar uint32 `json:"DefaultStar"`
|
|
DefaultSellPrice uint32 `json:"DefaultSellPrice"`
|
|
}
|
|
|
|
type CurrencyWarsRoleConfig struct {
|
|
ID uint32 `json:"ID"`
|
|
SeasonID uint32 `json:"SeasonID"`
|
|
AvatarID uint32 `json:"AvatarID"`
|
|
SpecialAvatarID uint32 `json:"SpecialAvatarID"`
|
|
Rarity uint32 `json:"Rarity"`
|
|
IsInBook bool `json:"IsInBook"`
|
|
IsInPool bool `json:"IsInPool"`
|
|
IsExpert uint32 `json:"IsExpert"`
|
|
IsFakeAvatar uint32 `json:"IsFakeAvatar"`
|
|
FrontBackType uint32 `json:"FrontBackType"`
|
|
EquipmentID uint32 `json:"EquipmentID"`
|
|
TraitList []uint32 `json:"TraitList"`
|
|
BackendRankList []uint32 `json:"BackendRankList"`
|
|
ChargeType []uint32 `json:"ChargeType"`
|
|
}
|
|
|
|
// Stage wrappers were captured on a fresh profile without talents. Future
|
|
// run modifiers must be applied separately; these are not final reward totals.
|
|
type CurrencyWarsCapturedStage struct {
|
|
ID uint32 `json:"ID"`
|
|
EnemyHardLevel uint32 `json:"EnemyHardLevel"`
|
|
Exp uint32 `json:"Exp"`
|
|
WeeklyScore uint32 `json:"WeeklyScore"`
|
|
OverChargeExp uint32 `json:"OverChargeExp"`
|
|
OverChargeWeeklyScore uint32 `json:"OverChargeWeeklyScore"`
|
|
ScoreRule uint32 `json:"ScoreRule"`
|
|
OCScoreRule uint32 `json:"OCScoreRule"`
|
|
EnvironmentBuffList []uint32 `json:"EnvironmentBuffList"`
|
|
}
|
|
|
|
func (c *CurrencyWarsCatalog) validateCapturedGameplay() error {
|
|
digest, err := hex.DecodeString(c.Provenance.GameplaySHA256)
|
|
if err != nil || len(digest) != sha256.Size || len(c.CapturedRoles) == 0 {
|
|
return fmt.Errorf("CW role/stage capture or provenance is missing")
|
|
}
|
|
c.roleByID = make(map[uint32]CurrencyWarsCapturedRole)
|
|
for _, role := range c.CapturedRoles {
|
|
r := role.Config
|
|
if r.ID == 0 || r.AvatarID == 0 || r.SeasonID != c.ActiveSeasonID || role.DefaultStar == 0 || role.DefaultStar > role.MaxStar {
|
|
return fmt.Errorf("invalid CW captured role %d", r.ID)
|
|
}
|
|
if _, duplicate := c.roleByID[r.ID]; duplicate {
|
|
return fmt.Errorf("duplicate CW role %d", r.ID)
|
|
}
|
|
c.roleByID[r.ID] = role
|
|
}
|
|
for _, role := range c.CapturedRoles {
|
|
price, err := c.BaseSalePrice(role.Config.ID, role.DefaultStar)
|
|
if err != nil || price != role.DefaultSellPrice {
|
|
return fmt.Errorf("CW captured default sale price differs from star/rarity table for role %d", role.Config.ID)
|
|
}
|
|
}
|
|
c.stageByID = make(map[uint32]CurrencyWarsCapturedStage)
|
|
for _, stage := range c.CapturedStages {
|
|
division, found := c.divisionByID[stage.ID]
|
|
if !found || division.SeasonID != c.ActiveSeasonID || stage.EnemyHardLevel == 0 {
|
|
return fmt.Errorf("CW stage %d has no matching active division", stage.ID)
|
|
}
|
|
if _, duplicate := c.stageByID[stage.ID]; duplicate {
|
|
return fmt.Errorf("duplicate CW captured stage %d", stage.ID)
|
|
}
|
|
c.stageByID[stage.ID] = stage
|
|
}
|
|
if len(c.stageByID) != len(c.SeasonDivisions(c.ActiveSeasonID)) {
|
|
return fmt.Errorf("CW stage capture does not cover every active division")
|
|
}
|
|
if err := c.validateRoleVariants(); err != nil {
|
|
return err
|
|
}
|
|
if err := c.validatePlayerLevels(); err != nil {
|
|
return err
|
|
}
|
|
if err := c.validateNodeRules(); err != nil {
|
|
return err
|
|
}
|
|
return c.validateMonsterCamps()
|
|
}
|
|
|
|
func (c *CurrencyWarsCatalog) CapturedRole(id uint32) (CurrencyWarsCapturedRole, error) {
|
|
id = c.CurrentRoleID(id)
|
|
role, found := c.roleByID[id]
|
|
if !found {
|
|
return CurrencyWarsCapturedRole{}, fmt.Errorf("CW role %d has not been captured", id)
|
|
}
|
|
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...)
|
|
return role, nil
|
|
}
|
|
|
|
func (c *CurrencyWarsCatalog) CapturedStage(id uint32) (CurrencyWarsCapturedStage, error) {
|
|
stage, found := c.stageByID[id]
|
|
if !found {
|
|
return CurrencyWarsCapturedStage{}, fmt.Errorf("CW stage %d has not been captured", id)
|
|
}
|
|
stage.EnvironmentBuffList = append([]uint32(nil), stage.EnvironmentBuffList...)
|
|
return stage, nil
|
|
}
|