331 lines
11 KiB
Go
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
|
||
|
}
|