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 }