snow/fortnite/season.go
Eccentric 250e85732d feat: update to latest;
new shop system
more config options
arena & hype
per season stats
battle pass
better variant system
complete vbuck & starter pack store
fix bugs related to deleting account
update launcher endpoints
fixed gift loot not deleting
2024-03-10 18:16:42 +00:00

331 lines
11 KiB
Go

package fortnite
import (
"fmt"
"regexp"
"strings"
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/person"
)
type SeasonObjectReference struct {
ObjectName string `json:"ObjectName"`
ObjectPath string `json:"ObjectPath"`
}
type SeasonAssetReference struct {
AssetPathName string `json:"AssetPathName"`
SubPathString string `json:"SubPathString"`
}
type SeasonObjectTag struct {
TagName string `json:"TagName"`
}
type SeasonRewardGiftBox struct {
GiftBoxToUse SeasonAssetReference `json:"GiftBoxToUse"`
GiftBoxFormatData []any `json:"GiftBoxFormatData"`
}
type ObjectSeasonReward struct {
ItemDefinition SeasonAssetReference `json:"ItemDefinition"`
TemplateID string `json:"TemplateId"`
Quantity int `json:"Quantity"`
RewardGiftBox SeasonRewardGiftBox `json:"RewardGiftBox"`
IsChaseReward bool `json:"IsChaseReward"`
RewardType string `json:"RewardType"`
}
type SeasonScheduleLevel struct {
Rewards []ObjectSeasonReward `json:"Rewards"`
}
type SeasonSchedule struct {
Levels []SeasonScheduleLevel `json:"Levels"`
}
type UnrealSeasonProperties struct {
SeasonNumber int `json:"SeasonNumber"`
NumSeasonLevels int `json:"NumSeasonLevels"`
NumBookLevels int `json:"NumBookLevels"`
ChallengesVisibility string `json:"ChallengesVisibility"`
SeasonXpCurve SeasonObjectReference `json:"SeasonXpCurve"`
BookXpCurve SeasonObjectReference `json:"BookXpCurve"`
SeasonStorefront string `json:"SeasonStorefront"`
FreeTokenItemPrimaryAssetId struct {
PrimaryAssetType struct {
Name string `json:"Name"`
} `json:"PrimaryAssetType"`
PrimaryAssetName string `json:"PrimaryAssetName"`
} `json:"FreeTokenItemPrimaryAssetId"`
BattlePassOfferId string `json:"BattlePassOfferId"`
BattlePassBundleOfferId string `json:"BattlePassBundleOfferId"`
BattlePassLevelOfferID string `json:"BattlePassLevelOfferId"`
ChallengeSchedulesAlwaysShown []SeasonObjectReference `json:"ChallengeSchedulesAlwaysShown"`
FreeLevelsThatNavigateToBattlePass []int `json:"FreeLevelsThatNavigateToBattlePass"`
FreeLevelsThatAutoOpenTheAboutScreen []int `json:"FreeLevelsThatAutoOpenTheAboutScreen"`
FreeSeasonItemContentTag SeasonObjectTag `json:"FreeSeasonItemContentTag"`
SeasonFirstWinItemContentTag SeasonObjectTag `json:"SeasonFirstWinItemContentTag"`
SeasonGrantsToEveryoneItemContentTag SeasonObjectTag `json:"SeasonGrantsToEveryoneItemContentTag"`
BattlePassPaidItemContentTag SeasonObjectTag `json:"BattlePassPaidItemContentTag"`
BattlePassFreeItemContentTag SeasonObjectTag `json:"BattlePassFreeItemContentTag"`
SeasonXpScheduleFree SeasonSchedule `json:"SeasonXpScheduleFree"`
BookXpScheduleFree SeasonSchedule `json:"BookXpScheduleFree"`
BookXpSchedulePaid SeasonSchedule `json:"BookXpSchedulePaid"`
SeasonGrantsToEveryone SeasonScheduleLevel `json:"SeasonGrantsToEveryone"`
SeasonFirstWinRewards SeasonScheduleLevel `json:"SeasonFirstWinRewards"`
BattleStarSubstitutionReward SeasonScheduleLevel `json:"BattleStarSubstitutionReward"`
ExpiringRewardTypes []SeasonAssetReference `json:"ExpiringRewardTypes"`
TokensToRemoveAtSeasonEnd []SeasonAssetReference `json:"TokensToRemoveAtSeasonEnd"`
DisplayName struct {
Key string `json:"Key"`
SourceString string `json:"SourceString"`
LocalizedString string `json:"LocalizedString"`
}
}
type UnrealProgressionProperties struct {
RowStruct SeasonObjectReference `json:"RowStruct"`
}
type UnrealEngineObjectProperties interface {
UnrealSeasonProperties | UnrealProgressionProperties
}
type UnrealNoRows struct{}
type UnrealProgressionRows struct {
Level int `json:"Level"`
XpToNextLevel int `json:"XpToNextLevel"`
XpTotal int `json:"XpTotal"`
}
type UnrealEngineObjectRows interface {
UnrealNoRows | UnrealProgressionRows
}
type UnrealEngineObject[T UnrealEngineObjectProperties, K UnrealEngineObjectRows] struct {
Type string `json:"Type"`
Name string `json:"Name"`
Class string `json:"Class"`
Properties T `json:"Properties"`
Rows *map[string]K `json:"Rows"`
}
type SnowSeasonDefinition struct {
DefaultOfferID string
BundleOfferID string
TierOfferID string
LevelProgression []*UnrealProgressionRows
BookProgression []*UnrealProgressionRows
TierRewardsPremium [][]*ItemGrant
TierRewardsFree [][]*ItemGrant
LevelRewards [][]*ItemGrant
VictoryRewards []*ItemGrant
SeasonTokenRemoval []*ItemGrant
BookXPReplacements []*ItemGrant
}
func NewSeasonDefinition() *SnowSeasonDefinition {
return &SnowSeasonDefinition{
LevelProgression: make([]*UnrealProgressionRows, 101),
BookProgression: make([]*UnrealProgressionRows, 101),
TierRewardsPremium: make([][]*ItemGrant, 101),
TierRewardsFree: make([][]*ItemGrant, 101),
LevelRewards: make([][]*ItemGrant, 101),
SeasonTokenRemoval: make([]*ItemGrant, 0),
VictoryRewards: make([]*ItemGrant, 0),
BookXPReplacements: make([]*ItemGrant, 0),
}
}
func convertAssetPathToTemplateId(assetPath string) string {
templateIdParts := make([]string, 2)
regex := regexp.MustCompile(`\.(.*)`)
assetPathParts := regex.FindStringSubmatch(assetPath)
if len(assetPathParts) <= 1 {
return ""
}
templateIdParts[1] = assetPathParts[1]
switch {
case strings.Contains(assetPath, "Game/Items/PersistentResource"):
templateIdParts[0] = "AccountResource"
case strings.Contains(assetPath, "Game/Items/Currency"):
templateIdParts[0] = "Currency"
case strings.Contains(assetPath, "Game/Items/Tokens"):
templateIdParts[0] = "Token"
case strings.Contains(assetPath, "Game/Athena/Items/ChallengeBundleSchedules"):
templateIdParts[0] = "ChallengeBundleSchedule"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Pickaxes"):
templateIdParts[0] = "AthenaPickaxe"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Dances"):
templateIdParts[0] = "AthenaDance"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Sprays"):
templateIdParts[0] = "AthenaDance"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Backpacks"):
templateIdParts[0] = "AthenaBackpack"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/PetCarriers"):
templateIdParts[0] = "AthenaBackpack"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Pets"):
templateIdParts[0] = "AthenaBackpack"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/MusicPacks"):
templateIdParts[0] = "AthenaMusicPack"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Characters"):
templateIdParts[0] = "AthenaCharacter"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Gliders"):
templateIdParts[0] = "AthenaGlider"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/LoadingScreens"):
templateIdParts[0] = "AthenaLoadingScreen"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Toys"):
templateIdParts[0] = "AthenaDance"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/ItemWraps"):
templateIdParts[0] = "AthenaItemWrap"
case strings.Contains(assetPath, "Game/Athena/Items/Cosmetics/Contrails"):
templateIdParts[0] = "AthenaSkydiveContrail"
case strings.Contains(assetPath, "Game/Athena/Items/CosmeticVariantTokens"):
templateIdParts[0] = "CosmeticVariantToken"
default:
aid.Print("Unknown asset path:", assetPath)
}
return strings.Join(templateIdParts, ":")
}
func (s *SnowSeasonDefinition) GetSeasonLevel(stats *person.SeasonStats) int {
level := 0
for i, data := range s.LevelProgression {
if i == 0 {
continue
}
if stats.SeasonXP < data.XpTotal {
break
}
level = i
}
return level
}
func (s *SnowSeasonDefinition) GetRelativeSeasonXP(stats *person.SeasonStats) int {
level := s.GetSeasonLevel(stats)
if level == 0 {
return 0
}
return stats.SeasonXP - s.LevelProgression[level].XpTotal
}
func (s *SnowSeasonDefinition) GetBookLevel(stats *person.SeasonStats) int {
level := 0
for i, data := range s.BookProgression {
if i == 0 {
continue
}
level = i
if stats.BookXP - s.BookProgression[i - 1].XpTotal < data.XpToNextLevel {
break
}
}
return level
}
func (s *SnowSeasonDefinition) GetRelativeBookXP(stats *person.SeasonStats) int {
level := s.GetBookLevel(stats)
if level == 0 {
return 0
}
return stats.BookXP - s.BookProgression[level - 1].XpTotal
}
func (s *SnowSeasonDefinition) GrantUnredeemedBookRewards(p *person.Person, giftBoxId string) bool {
changed := false
gift := person.NewGift(fmt.Sprintf("GiftBox:%s", giftBoxId), 1, "", "")
grantUpTo := s.GetBookLevel(p.CurrentSeasonStats)
freeClaimedUpTo := aid.JSONParseG[int](p.CommonCoreProfile.Attributes.GetAttributeByKey("season.bookFreeClaimedUpTo").ValueJSON)
paidClaimedUpTo := aid.JSONParseG[int](p.CommonCoreProfile.Attributes.GetAttributeByKey("season.bookPaidClaimedUpTo").ValueJSON)
if freeClaimedUpTo >= len(s.TierRewardsFree) - 1 {
return changed
}
if freeClaimedUpTo > grantUpTo {
freeClaimedUpTo = grantUpTo
}
if paidClaimedUpTo > grantUpTo {
paidClaimedUpTo = grantUpTo
}
freeRewards := aid.Flatten[*ItemGrant](s.TierRewardsFree[freeClaimedUpTo+1:grantUpTo+1])
paidRewards := aid.Flatten[*ItemGrant](s.TierRewardsPremium[paidClaimedUpTo+1:grantUpTo+1])
rewards := []*ItemGrant{}
rewards = append(rewards, freeRewards...)
rewards = append(rewards, aid.Ternary[[]*ItemGrant](p.CurrentSeasonStats.BookPurchased, paidRewards, []*ItemGrant{})...)
for _, reward := range rewards {
gift.AddLoot(person.NewItem(reward.TemplateID, reward.Quantity))
}
if p.CurrentSeasonStats.BookPurchased {
p.CommonCoreProfile.Attributes.GetAttributeByKey("season.bookPaidClaimedUpTo").SetValue(grantUpTo).Save()
}
p.CommonCoreProfile.Attributes.GetAttributeByKey("season.bookFreeClaimedUpTo").SetValue(grantUpTo).Save()
if len(gift.Loot) > 0 {
p.CommonCoreProfile.Gifts.AddGift(gift).Save()
changed = true
}
return changed
}
func (s *SnowSeasonDefinition) GrantUnredeemedLevelRewards(p *person.Person) bool {
changed := false
grantUpTo := s.GetSeasonLevel(p.CurrentSeasonStats)
bookLevel := s.GetBookLevel(p.CurrentSeasonStats)
levelClaimedUpTo := aid.JSONParseG[int](p.CommonCoreProfile.Attributes.GetAttributeByKey("season.levelClaimedUpTo").ValueJSON)
if levelClaimedUpTo > grantUpTo {
levelClaimedUpTo = grantUpTo
}
if levelClaimedUpTo >= len(s.LevelRewards) - 1 {
return changed
}
wantedRewards := aid.Flatten[*ItemGrant](s.LevelRewards[levelClaimedUpTo+1:grantUpTo+1])
replacementRewards := []*ItemGrant{}
for _, reward := range wantedRewards {
for _, replacement := range s.BookXPReplacements {
replacementRewards = append(replacementRewards, aid.Ternary[*ItemGrant](reward.TemplateID == "AccountResource:AthenaBattleStar", replacement, reward))
}
}
realRewards := aid.Ternary[[]*ItemGrant](bookLevel > 100, replacementRewards, wantedRewards)
for _, reward := range realRewards {
GrantToPerson(p, reward)
}
p.CommonCoreProfile.Attributes.GetAttributeByKey("season.levelClaimedUpTo").SetValue(grantUpTo).Save()
if len(realRewards) > 0 {
changed = true
}
return changed
}