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
This commit is contained in:
parent
aaeacb663a
commit
250e85732d
22
aid/aid.go
22
aid/aid.go
|
@ -1,6 +1,7 @@
|
|||
package aid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
|
@ -26,6 +27,13 @@ func FormatNumber(number int) string {
|
|||
return ReverseString(str)
|
||||
}
|
||||
|
||||
func FormatPrice(number int) string {
|
||||
last := number % 100
|
||||
number /= 100
|
||||
str := fmt.Sprintf("%d.%02d", number, last)
|
||||
return str
|
||||
}
|
||||
|
||||
func ReverseString(input string) string {
|
||||
str := ""
|
||||
for _, char := range input {
|
||||
|
@ -53,9 +61,23 @@ func Regex(str, regex string) *string {
|
|||
return nil
|
||||
}
|
||||
|
||||
// if condition is true, return a, else return b
|
||||
func Ternary[T any](condition bool, a, b T) T {
|
||||
if condition {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func ToInt(str string) int {
|
||||
i, _ := strconv.Atoi(str)
|
||||
return i
|
||||
}
|
||||
|
||||
func Flatten[T any](arr [][]T) []T {
|
||||
var flat []T
|
||||
for _, a := range arr {
|
||||
flat = append(flat, a...)
|
||||
}
|
||||
return flat
|
||||
}
|
|
@ -22,6 +22,7 @@ type CS struct {
|
|||
Secret string
|
||||
Token string
|
||||
Guild string
|
||||
CallbackURL string
|
||||
}
|
||||
Amazon struct {
|
||||
Enabled bool
|
||||
|
@ -38,6 +39,10 @@ type CS struct {
|
|||
Port string
|
||||
FrontendPort string
|
||||
Debug bool
|
||||
XMPP struct {
|
||||
Host string
|
||||
Port string
|
||||
}
|
||||
}
|
||||
JWT struct {
|
||||
Secret string
|
||||
|
@ -106,6 +111,11 @@ func LoadConfig(file []byte) {
|
|||
panic("Discord Guild ID is empty")
|
||||
}
|
||||
|
||||
Config.Discord.CallbackURL = cfg.Section("api").Key("discord_url").String()
|
||||
if Config.Discord.CallbackURL == "" {
|
||||
panic("Discord Callback URL is empty")
|
||||
}
|
||||
|
||||
Config.Amazon.Enabled = true
|
||||
Config.Amazon.BucketURI = cfg.Section("amazon").Key("uri").String()
|
||||
if Config.Amazon.BucketURI == "" {
|
||||
|
@ -144,6 +154,16 @@ func LoadConfig(file []byte) {
|
|||
|
||||
Config.API.Debug = cfg.Section("api").Key("debug").MustBool(false)
|
||||
|
||||
Config.API.XMPP.Host = cfg.Section("api").Key("xmpp_host").String()
|
||||
if Config.API.XMPP.Host == "" {
|
||||
panic("API XMPP Host is empty")
|
||||
}
|
||||
|
||||
Config.API.XMPP.Port = cfg.Section("api").Key("xmpp_port").String()
|
||||
if Config.API.XMPP.Port == "" {
|
||||
panic("API XMPP Port is empty")
|
||||
}
|
||||
|
||||
Config.JWT.Secret = cfg.Section("jwt").Key("secret").String()
|
||||
if Config.JWT.Secret == "" {
|
||||
panic("JWT Secret is empty")
|
||||
|
|
20
aid/fiber.go
20
aid/fiber.go
|
@ -1,6 +1,7 @@
|
|||
package aid
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
@ -13,7 +14,21 @@ func FiberLogger() fiber.Handler {
|
|||
return logger.New(logger.Config{
|
||||
Format: "(${method}) (${status}) (${latency}) ${path}\n",
|
||||
Next: func(c *fiber.Ctx) bool {
|
||||
return c.Response().StatusCode() == 302
|
||||
if (slices.Contains[[]int](
|
||||
[]int{302, 101},
|
||||
c.Response().StatusCode(),
|
||||
)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (slices.Contains[[]string](
|
||||
[]string{"/snow/log", "/purchase/assets/", " /favicon.ico"},
|
||||
c.Path(),
|
||||
)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -42,4 +57,5 @@ func FiberGetQueries(c *fiber.Ctx, queryKeys ...string) map[string][]string {
|
|||
}
|
||||
}
|
||||
return argsMaps
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,4 +29,10 @@ func JSONParse(input string) interface{} {
|
|||
var output interface{}
|
||||
json.Unmarshal([]byte(input), &output)
|
||||
return output
|
||||
}
|
||||
|
||||
func JSONParseG[T interface{}](input string) T {
|
||||
var output T
|
||||
json.Unmarshal([]byte(input), &output)
|
||||
return output
|
||||
}
|
|
@ -45,17 +45,29 @@ guild="1234567890..."
|
|||
level="info"
|
||||
|
||||
[api]
|
||||
; this will enable some routes to show information about the backend
|
||||
; this is useful for debugging
|
||||
; this should be disabled in production
|
||||
debug=true
|
||||
; port to listen on
|
||||
port=":3000"
|
||||
; host that the api is running on
|
||||
; e.g. if you are running the api on your local machine, you would set this to 127.0.0.1
|
||||
; if you are running the api on a server, you would set this to the ip of the server or the domain name
|
||||
; localhost will not work with the xmpp from testing
|
||||
host="127.0.0.1"
|
||||
; this will enable some routes to show information about the backend
|
||||
; this is useful for debugging
|
||||
; this should be disabled in production
|
||||
debug=true
|
||||
; host that the xmpp on the fortnite client will try and connect to
|
||||
; if you are running the api on your local machine, you would set this to the same as the host
|
||||
; if you are running the api on a server, you would set this to the ip of the server or the domain name
|
||||
; localhost will not work with the xmpp from testing
|
||||
xmpp_host="127.0.0.1"
|
||||
; port that the xmpp on the fortnite client will try and connect to
|
||||
; if you are running the api on your local machine, you would set this to the same as the port
|
||||
; if you are running the api on a server, you would set this to the port that you are running the api on
|
||||
xmpp_port=":3000"
|
||||
; this this is the beginning of the url that the discord bot will use to send messages to the api
|
||||
; this includes the protocol and the host + port
|
||||
; for a public api, this could be "https://snows.rocks" for example
|
||||
discord_url="http://127.0.0.1:3000"
|
||||
|
||||
[jwt]
|
||||
; secret for jwt signing
|
||||
|
|
|
@ -32,10 +32,10 @@ func informationHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||
Data: &discordgo.InteractionResponseData{
|
||||
Embeds: []*discordgo.MessageEmbed{
|
||||
NewEmbedBuilder().
|
||||
SetTitle("Information").
|
||||
SetTitle("Snow Information").
|
||||
SetColor(0x2b2d31).
|
||||
AddField("Players Registered", aid.FormatNumber(playerCount), true).
|
||||
AddField("Players Online", aid.FormatNumber(0), true).
|
||||
AddField("Players Online", aid.FormatNumber(socket.JabberSockets.Len()), true).
|
||||
AddField("VBucks in Circulation", aid.FormatNumber(totalVbucks), false).
|
||||
Build(),
|
||||
},
|
||||
|
@ -287,15 +287,7 @@ func addItemHandler(s *discordgo.Session, i *discordgo.InteractionCreate, looker
|
|||
}
|
||||
|
||||
snapshot := player.GetProfileFromType(profile).Snapshot()
|
||||
foundItem := player.GetProfileFromType(profile).Items.GetItemByTemplateID(item)
|
||||
switch (foundItem) {
|
||||
case nil:
|
||||
foundItem = person.NewItem(item, int(qty))
|
||||
player.GetProfileFromType(profile).Items.AddItem(foundItem)
|
||||
default:
|
||||
foundItem.Quantity += int(qty)
|
||||
}
|
||||
foundItem.Save()
|
||||
fortnite.GrantToPerson(player, fortnite.NewItemGrant(item, int(qty)))
|
||||
player.GetProfileFromType(profile).Diff(snapshot)
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
|
|
|
@ -55,7 +55,7 @@ func IntialiseClient() {
|
|||
|
||||
err := StaticClient.Client.Open()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
aid.Print("(discord) failed to connect; will be disabled")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
416
fortnite/arena.go
Normal file
416
fortnite/arena.go
Normal file
|
@ -0,0 +1,416 @@
|
|||
package fortnite
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
)
|
||||
|
||||
type ArenaScoringRule struct {
|
||||
StatName string
|
||||
MatchRule string
|
||||
RewardTiers []struct{
|
||||
Value int
|
||||
Points int
|
||||
Multiply bool
|
||||
}
|
||||
}
|
||||
|
||||
func NewScoringRule(stat, rule string) *ArenaScoringRule {
|
||||
return &ArenaScoringRule{
|
||||
StatName: stat,
|
||||
MatchRule: rule,
|
||||
RewardTiers: new(ArenaScoringRule).RewardTiers,
|
||||
}
|
||||
}
|
||||
|
||||
func (sr *ArenaScoringRule) AddTier(value, points int, multiply bool) *ArenaScoringRule {
|
||||
sr.RewardTiers = append(sr.RewardTiers, struct{
|
||||
Value int
|
||||
Points int
|
||||
Multiply bool
|
||||
}{
|
||||
Value: value,
|
||||
Points: points,
|
||||
Multiply: multiply,
|
||||
})
|
||||
|
||||
return sr
|
||||
}
|
||||
|
||||
func (sr *ArenaScoringRule) GenerateFortniteScoringRule() aid.JSON {
|
||||
tiers := make([]aid.JSON, 0)
|
||||
|
||||
for _, tier := range sr.RewardTiers {
|
||||
tiers = append(tiers, aid.JSON{
|
||||
"keyValue": tier.Value,
|
||||
"pointsEarned": tier.Points,
|
||||
"multiplicative": tier.Multiply,
|
||||
})
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"trackedStat": sr.StatName,
|
||||
"matchRule": sr.MatchRule,
|
||||
"rewardTiers": tiers,
|
||||
}
|
||||
}
|
||||
|
||||
type ArenaEventTemplate struct {
|
||||
ID string
|
||||
MatchLimit int
|
||||
PlaylistID string
|
||||
ScoringRules []*ArenaScoringRule
|
||||
}
|
||||
|
||||
func NewEventTemplate(id string, limit int) *ArenaEventTemplate {
|
||||
return &ArenaEventTemplate{
|
||||
ID: id,
|
||||
MatchLimit: limit,
|
||||
ScoringRules: make([]*ArenaScoringRule, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (et *ArenaEventTemplate) AddScoringRule(rule ...*ArenaScoringRule) {
|
||||
et.ScoringRules = append(et.ScoringRules, rule...)
|
||||
}
|
||||
|
||||
func (et *ArenaEventTemplate) GenerateFortniteEventTemplate() aid.JSON {
|
||||
rules := make([]aid.JSON, 0)
|
||||
|
||||
for _, rule := range et.ScoringRules {
|
||||
rules = append(rules, rule.GenerateFortniteScoringRule())
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"gameId": "Fortnite",
|
||||
"eventTemplateId": et.ID,
|
||||
"playlistId": et.PlaylistID,
|
||||
"persistentScoreId": "Hype",
|
||||
"matchCap": et.MatchLimit,
|
||||
"scoringRules": rules,
|
||||
}
|
||||
}
|
||||
|
||||
type ArenaEventWindow struct {
|
||||
ID string
|
||||
ParentEvent *Event
|
||||
Template *ArenaEventTemplate
|
||||
Round int
|
||||
ToBeDetermined bool
|
||||
CanLiveSpectate bool
|
||||
Meta struct {
|
||||
DivisionRank int
|
||||
ThresholdToAdvanceDivision int
|
||||
}
|
||||
}
|
||||
|
||||
func NewEventWindow(id string, template *ArenaEventTemplate) *ArenaEventWindow {
|
||||
return &ArenaEventWindow{
|
||||
ID: id,
|
||||
Meta: new(ArenaEventWindow).Meta,
|
||||
Template: template,
|
||||
}
|
||||
}
|
||||
|
||||
func (ew *ArenaEventWindow) GenerateFortniteEventWindow() aid.JSON {
|
||||
meta := aid.JSON{
|
||||
"divisionRank": ew.Meta.DivisionRank,
|
||||
"ThresholdToAdvanceDivision": ew.Meta.ThresholdToAdvanceDivision,
|
||||
"RoundType": "Arena",
|
||||
}
|
||||
|
||||
allTokens := []string{
|
||||
"ARENA_S8_Division1",
|
||||
"ARENA_S8_Division2",
|
||||
"ARENA_S8_Division3",
|
||||
"ARENA_S8_Division4",
|
||||
"ARENA_S8_Division5",
|
||||
"ARENA_S8_Division6",
|
||||
"ARENA_S8_Division7",
|
||||
}
|
||||
requireAll := []string{}
|
||||
requireNone := []string{}
|
||||
|
||||
for index, token := range allTokens {
|
||||
if index == ew.Meta.DivisionRank {
|
||||
requireAll = append(requireAll, token)
|
||||
continue
|
||||
}
|
||||
|
||||
requireNone = append(requireNone, token)
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"eventWindowId": ew.ID,
|
||||
"eventTemplateId": ew.Template.ID,
|
||||
"countdownBeginTime": "2023-06-15T15:00:00.000Z",
|
||||
"beginTime": time.Now().Add(time.Hour * -24).Format(time.RFC3339),
|
||||
"endTime": "9999-12-31T23:59:59.000Z",
|
||||
"payoutDelay": 30,
|
||||
"round": ew.Round,
|
||||
"isTBD": ew.ToBeDetermined,
|
||||
"canLiveSpectate": ew.CanLiveSpectate,
|
||||
"visibility": "public",
|
||||
"scoreLocations": []aid.JSON{},
|
||||
"blackoutPeriods": []string{},
|
||||
"requireAnyTokens": []string{},
|
||||
"requireAllTokens": requireAll,
|
||||
"requireAllTokensCaller": []string{},
|
||||
"requireNoneTokensCaller": requireNone,
|
||||
"requireAnyTokensCaller": []string{},
|
||||
"additionalRequirements": []string{},
|
||||
"teammateEligibility": "any",
|
||||
"metadata": meta,
|
||||
}
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
ID string
|
||||
DisplayID string
|
||||
Windows []*ArenaEventWindow
|
||||
}
|
||||
|
||||
func NewEvent(id string, displayId string) *Event {
|
||||
return &Event{
|
||||
ID: id,
|
||||
DisplayID: displayId,
|
||||
Windows: make([]*ArenaEventWindow, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) AddWindow(window *ArenaEventWindow) {
|
||||
window.ParentEvent = e
|
||||
e.Windows = append(e.Windows, window)
|
||||
}
|
||||
|
||||
func (e *Event) GenerateFortniteEvent() aid.JSON {
|
||||
eventWindows := make([]aid.JSON, 0)
|
||||
|
||||
for _, window := range e.Windows {
|
||||
eventWindows = append(eventWindows, window.GenerateFortniteEventWindow())
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"gameId": "Fortnite",
|
||||
"eventId": e.ID,
|
||||
"eventGroup": "",
|
||||
"regions": []string{ "NAE", "ME", "NAW", "OCE", "ASIA", "EU", "BR", },
|
||||
"regionMappings": aid.JSON{},
|
||||
"platforms": []string{ "PS4", "XboxOne", "Switch", "Android", "IOS", "Windows", },
|
||||
"platformMappings": aid.JSON{},
|
||||
"displayDataId": e.DisplayID,
|
||||
"eventWindows": eventWindows,
|
||||
"appId": nil,
|
||||
"link": nil,
|
||||
"metadata": aid.JSON{
|
||||
"minimumAccountLevel": 1,
|
||||
"TrackedStats": []string{
|
||||
"PLACEMENT_STAT_INDEX",
|
||||
"TEAM_ELIMS_STAT_INDEX",
|
||||
"MATCH_PLAYED_STAT",
|
||||
},
|
||||
},
|
||||
"environment": nil,
|
||||
"announcementTime": time.Now().Format(time.RFC3339),
|
||||
"beginTime": time.Now().Add(time.Hour * -24).Format(time.RFC3339),
|
||||
"endTime": "9999-12-31T23:59:59.000Z",
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ArenaEvents = make([]*Event, 0)
|
||||
)
|
||||
|
||||
func PreloadEvents() {
|
||||
if aid.Config.Fortnite.Season < 8 {
|
||||
return
|
||||
}
|
||||
|
||||
ArenaEvents = []*Event{
|
||||
createDuoEvent(),
|
||||
createSoloEvent(),
|
||||
}
|
||||
}
|
||||
|
||||
func createSoloEvent() *Event {
|
||||
ArenaSolo := NewEvent("epicgames_Arena_S8_Solo", "SnowArenaSolo")
|
||||
|
||||
defaultPlacement := NewScoringRule("PLACEMENT_STAT_INDEX", "lte")
|
||||
defaultPlacement.AddTier(1, 3, false)
|
||||
defaultPlacement.AddTier(5, 2, false)
|
||||
defaultPlacement.AddTier(15, 2, false)
|
||||
defaultPlacement.AddTier(25, 3, false)
|
||||
defaultEliminations := NewScoringRule("TEAM_ELIMS_STAT_INDEX", "gte")
|
||||
defaultEliminations.AddTier(1, 1, true)
|
||||
|
||||
soloOpen1T := NewEventTemplate("eventTemplate_Arena_S8_Division1_Solo", 100)
|
||||
soloOpen1T.PlaylistID = "Playlist_ShowdownAlt_Solo"
|
||||
soloOpen1T.AddScoringRule(defaultPlacement, defaultEliminations)
|
||||
soloOpen1W := NewEventWindow("Arena_S8_Division1_Solo", soloOpen1T)
|
||||
soloOpen1W.ToBeDetermined = false
|
||||
soloOpen1W.CanLiveSpectate = false
|
||||
soloOpen1W.Round = 0
|
||||
soloOpen1W.Meta.DivisionRank = 0
|
||||
soloOpen1W.Meta.ThresholdToAdvanceDivision = 25
|
||||
ArenaSolo.AddWindow(soloOpen1W)
|
||||
|
||||
soloOpen2T := NewEventTemplate("eventTemplate_Arena_S8_Division2_Solo", 100)
|
||||
soloOpen2T.PlaylistID = "Playlist_ShowdownAlt_Solo"
|
||||
soloOpen2T.AddScoringRule(defaultPlacement, defaultEliminations)
|
||||
soloOpen2W := NewEventWindow("Arena_S8_Division2_Solo", soloOpen2T)
|
||||
soloOpen2W.ToBeDetermined = false
|
||||
soloOpen2W.CanLiveSpectate = false
|
||||
soloOpen2W.Round = 1
|
||||
soloOpen2W.Meta.DivisionRank = 1
|
||||
soloOpen2W.Meta.ThresholdToAdvanceDivision = 75
|
||||
ArenaSolo.AddWindow(soloOpen2W)
|
||||
|
||||
soloOpen3T := NewEventTemplate("eventTemplate_Arena_S8_Division3_Solo", 100)
|
||||
soloOpen3T.PlaylistID = "Playlist_ShowdownAlt_Solo"
|
||||
soloOpen3T.AddScoringRule(defaultPlacement, defaultEliminations)
|
||||
soloOpen3W := NewEventWindow("Arena_S8_Division3_Solo", soloOpen3T)
|
||||
soloOpen3W.Round = 2
|
||||
soloOpen3W.ToBeDetermined = false
|
||||
soloOpen3W.CanLiveSpectate = false
|
||||
soloOpen3W.Meta.DivisionRank = 2
|
||||
soloOpen3W.Meta.ThresholdToAdvanceDivision = 125
|
||||
ArenaSolo.AddWindow(soloOpen3W)
|
||||
|
||||
soloContender4T := NewEventTemplate("eventTemplate_Arena_S8_Division4_Solo", 100)
|
||||
soloContender4T.PlaylistID = "Playlist_ShowdownAlt_Solo"
|
||||
soloContender4T.AddScoringRule(defaultPlacement, NewScoringRule("MATCH_PLAYED_STAT", "gtw").AddTier(1, -2, false), defaultEliminations)
|
||||
soloConteder4W := NewEventWindow("Arena_S8_Division4_Solo", soloContender4T)
|
||||
soloConteder4W.Round = 3
|
||||
soloConteder4W.ToBeDetermined = false
|
||||
soloConteder4W.CanLiveSpectate = false
|
||||
soloConteder4W.Meta.DivisionRank = 3
|
||||
soloConteder4W.Meta.ThresholdToAdvanceDivision = 175
|
||||
ArenaSolo.AddWindow(soloConteder4W)
|
||||
|
||||
soloContender5T := NewEventTemplate("eventTemplate_Arena_S8_Division5_Solo", 100)
|
||||
soloContender5T.PlaylistID = "Playlist_ShowdownAlt_Solo"
|
||||
soloContender5T.AddScoringRule(defaultPlacement, NewScoringRule("MATCH_PLAYED_STAT", "gtw").AddTier(1, -4, false), defaultEliminations)
|
||||
soloConteder5W := NewEventWindow("Arena_S8_Division5_Solo", soloContender5T)
|
||||
soloConteder5W.Round = 4
|
||||
soloConteder5W.ToBeDetermined = false
|
||||
soloConteder5W.CanLiveSpectate = false
|
||||
soloConteder5W.Meta.DivisionRank = 4
|
||||
soloConteder5W.Meta.ThresholdToAdvanceDivision = 225
|
||||
ArenaSolo.AddWindow(soloConteder5W)
|
||||
|
||||
soloContender6T := NewEventTemplate("eventTemplate_Arena_S8_Division6_Solo", 100)
|
||||
soloContender6T.PlaylistID = "Playlist_ShowdownAlt_Solo"
|
||||
soloContender6T.AddScoringRule(defaultPlacement, NewScoringRule("MATCH_PLAYED_STAT", "gtw").AddTier(1, -6, false), defaultEliminations)
|
||||
soloConteder6W := NewEventWindow("Arena_S8_Division6_Solo", soloContender6T)
|
||||
soloConteder6W.Round = 5
|
||||
soloConteder6W.ToBeDetermined = false
|
||||
soloConteder6W.CanLiveSpectate = false
|
||||
soloConteder6W.Meta.DivisionRank = 5
|
||||
soloConteder6W.Meta.ThresholdToAdvanceDivision = 300
|
||||
ArenaSolo.AddWindow(soloConteder6W)
|
||||
|
||||
soloChampions7T := NewEventTemplate("eventTemplate_Arena_S8_Division7_Solo", 100)
|
||||
soloChampions7T.PlaylistID = "Playlist_ShowdownAlt_Solo"
|
||||
soloChampions7T.AddScoringRule(defaultPlacement, NewScoringRule("MATCH_PLAYED_STAT", "gtw").AddTier(1, -8, false), defaultEliminations)
|
||||
soloChampions7W := NewEventWindow("Arena_S8_Division7_Solo", soloChampions7T)
|
||||
soloChampions7W.Round = 6
|
||||
soloChampions7W.ToBeDetermined = true
|
||||
soloChampions7W.CanLiveSpectate = false
|
||||
soloChampions7W.Meta.DivisionRank = 6
|
||||
soloChampions7W.Meta.ThresholdToAdvanceDivision = 9999999999
|
||||
ArenaSolo.AddWindow(soloChampions7W)
|
||||
|
||||
return ArenaSolo
|
||||
}
|
||||
|
||||
func createDuoEvent() *Event {
|
||||
ArenaDuo := NewEvent("epicgames_Arena_S8_Duos", "SnowArenaDuos")
|
||||
|
||||
defaultPlacement := NewScoringRule("PLACEMENT_STAT_INDEX", "lte")
|
||||
defaultPlacement.AddTier(1, 3, false)
|
||||
defaultPlacement.AddTier(3, 2, false)
|
||||
defaultPlacement.AddTier(7, 2, false)
|
||||
defaultPlacement.AddTier(12, 3, false)
|
||||
defaultEliminations := NewScoringRule("TEAM_ELIMS_STAT_INDEX", "gte")
|
||||
defaultEliminations.AddTier(1, 1, true)
|
||||
|
||||
duoOpen1T := NewEventTemplate("eventTemplate_Arena_S8_Division1_Duos", 100)
|
||||
duoOpen1T.PlaylistID = "Playlist_ShowdownAlt_Duos"
|
||||
duoOpen1T.AddScoringRule(defaultPlacement, defaultEliminations)
|
||||
duoOpen1W := NewEventWindow("Arena_S8_Division1_Duos", duoOpen1T)
|
||||
duoOpen1W.ToBeDetermined = false
|
||||
duoOpen1W.CanLiveSpectate = false
|
||||
duoOpen1W.Round = 0
|
||||
duoOpen1W.Meta.DivisionRank = 0
|
||||
duoOpen1W.Meta.ThresholdToAdvanceDivision = 25
|
||||
ArenaDuo.AddWindow(duoOpen1W)
|
||||
|
||||
duoOpen2T := NewEventTemplate("eventTemplate_Arena_S8_Division2_Duos", 100)
|
||||
duoOpen2T.PlaylistID = "Playlist_ShowdownAlt_Duos"
|
||||
duoOpen2T.AddScoringRule(defaultPlacement, defaultEliminations)
|
||||
duoOpen2W := NewEventWindow("Arena_S8_Division2_Duos", duoOpen2T)
|
||||
duoOpen2W.ToBeDetermined = false
|
||||
duoOpen2W.CanLiveSpectate = false
|
||||
duoOpen2W.Round = 1
|
||||
duoOpen2W.Meta.DivisionRank = 1
|
||||
duoOpen2W.Meta.ThresholdToAdvanceDivision = 75
|
||||
ArenaDuo.AddWindow(duoOpen2W)
|
||||
|
||||
duoOpen3T := NewEventTemplate("eventTemplate_Arena_S8_Division3_Duos", 100)
|
||||
duoOpen3T.PlaylistID = "Playlist_ShowdownAlt_Duos"
|
||||
duoOpen3T.AddScoringRule(defaultPlacement, defaultEliminations)
|
||||
duoOpen3W := NewEventWindow("Arena_S8_Division3_Duos", duoOpen3T)
|
||||
duoOpen3W.Round = 2
|
||||
duoOpen3W.ToBeDetermined = false
|
||||
duoOpen3W.CanLiveSpectate = false
|
||||
duoOpen3W.Meta.DivisionRank = 2
|
||||
duoOpen3W.Meta.ThresholdToAdvanceDivision = 125
|
||||
ArenaDuo.AddWindow(duoOpen3W)
|
||||
|
||||
duoContender4T := NewEventTemplate("eventTemplate_Arena_S8_Division4_Duos", 100)
|
||||
duoContender4T.PlaylistID = "Playlist_ShowdownAlt_Duos"
|
||||
duoContender4T.AddScoringRule(defaultPlacement, NewScoringRule("MATCH_PLAYED_STAT", "gtw").AddTier(1, -2, false), defaultEliminations)
|
||||
duoConteder4W := NewEventWindow("Arena_S8_Division4_Duos", duoContender4T)
|
||||
duoConteder4W.Round = 3
|
||||
duoConteder4W.ToBeDetermined = false
|
||||
duoConteder4W.CanLiveSpectate = false
|
||||
duoConteder4W.Meta.DivisionRank = 3
|
||||
duoConteder4W.Meta.ThresholdToAdvanceDivision = 175
|
||||
ArenaDuo.AddWindow(duoConteder4W)
|
||||
|
||||
duoContender5T := NewEventTemplate("eventTemplate_Arena_S8_Division5_Duos", 100)
|
||||
duoContender5T.PlaylistID = "Playlist_ShowdownAlt_Duos"
|
||||
duoContender5T.AddScoringRule(defaultPlacement, NewScoringRule("MATCH_PLAYED_STAT", "gtw").AddTier(1, -4, false), defaultEliminations)
|
||||
duoConteder5W := NewEventWindow("Arena_S8_Division5_Duos", duoContender5T)
|
||||
duoConteder5W.Round = 4
|
||||
duoConteder5W.ToBeDetermined = false
|
||||
duoConteder5W.CanLiveSpectate = false
|
||||
duoConteder5W.Meta.DivisionRank = 4
|
||||
duoConteder5W.Meta.ThresholdToAdvanceDivision = 225
|
||||
ArenaDuo.AddWindow(duoConteder5W)
|
||||
|
||||
duoContender6T := NewEventTemplate("eventTemplate_Arena_S8_Division6_Duos", 100)
|
||||
duoContender6T.PlaylistID = "Playlist_ShowdownAlt_Duos"
|
||||
duoContender6T.AddScoringRule(defaultPlacement, NewScoringRule("MATCH_PLAYED_STAT", "gtw").AddTier(1, -6, false), defaultEliminations)
|
||||
duoConteder6W := NewEventWindow("Arena_S8_Division6_Duos", duoContender6T)
|
||||
duoConteder6W.Round = 5
|
||||
duoConteder6W.ToBeDetermined = false
|
||||
duoConteder6W.CanLiveSpectate = false
|
||||
duoConteder6W.Meta.DivisionRank = 5
|
||||
duoConteder6W.Meta.ThresholdToAdvanceDivision = 300
|
||||
ArenaDuo.AddWindow(duoConteder6W)
|
||||
|
||||
duoChampions7T := NewEventTemplate("eventTemplate_Arena_S8_Division7_Duos", 100)
|
||||
duoChampions7T.PlaylistID = "Playlist_ShowdownAlt_Duos"
|
||||
duoChampions7T.AddScoringRule(defaultPlacement, NewScoringRule("MATCH_PLAYED_STAT", "gtw").AddTier(1, -8, false), defaultEliminations)
|
||||
duoChampions7W := NewEventWindow("Arena_S8_Division7_Duos", duoChampions7T)
|
||||
duoChampions7W.Round = 6
|
||||
duoChampions7W.ToBeDetermined = true
|
||||
duoChampions7W.CanLiveSpectate = false
|
||||
duoChampions7W.Meta.DivisionRank = 6
|
||||
duoChampions7W.Meta.ThresholdToAdvanceDivision = 9999999999
|
||||
ArenaDuo.AddWindow(duoChampions7W)
|
||||
|
||||
return ArenaDuo
|
||||
}
|
|
@ -18,29 +18,30 @@ var (
|
|||
|
||||
type dataClient struct {
|
||||
h *http.Client
|
||||
FortniteSets map[string]*FortniteSet `json:"sets"`
|
||||
FortniteItems map[string]*FortniteItem `json:"items"`
|
||||
FortniteItemsWithDisplayAssets map[string]*FortniteItem `json:"-"`
|
||||
FortniteItemsWithFeaturedImage []*FortniteItem `json:"-"`
|
||||
TypedFortniteItems map[string][]*FortniteItem `json:"-"`
|
||||
TypedFortniteItemsWithDisplayAssets map[string][]*FortniteItem `json:"-"`
|
||||
FortniteSets map[string]*APISetDefinition `json:"sets"`
|
||||
FortniteItems map[string]*APICosmeticDefinition `json:"items"`
|
||||
FortniteItemsWithDisplayAssets map[string]*APICosmeticDefinition `json:"-"`
|
||||
FortniteItemsWithFeaturedImage []*APICosmeticDefinition `json:"-"`
|
||||
TypedFortniteItems map[string][]*APICosmeticDefinition `json:"-"`
|
||||
TypedFortniteItemsWithDisplayAssets map[string][]*APICosmeticDefinition `json:"-"`
|
||||
SnowVariantTokens map[string]*FortniteVariantToken `json:"variants"`
|
||||
StorefrontCosmeticOfferPriceLookup map[string]map[string]int `json:"-"`
|
||||
StorefrontDailyItemCountLookup []struct{Season int;Items int} `json:"-"`
|
||||
StorefrontWeeklySetCountLookup []struct{Season int;Sets int} `json:"-"`
|
||||
StorefrontCurrencyOfferPriceLookup map[string]map[int]int `json:"-"`
|
||||
StorefrontCurrencyMultiplier map[string]float64 `json:"-"`
|
||||
SnowSeason *SnowSeasonDefinition `json:"season"`
|
||||
}
|
||||
|
||||
func NewDataClient() *dataClient {
|
||||
return &dataClient{
|
||||
h: &http.Client{},
|
||||
FortniteItems: make(map[string]*FortniteItem),
|
||||
FortniteSets: make(map[string]*FortniteSet),
|
||||
FortniteItemsWithDisplayAssets: make(map[string]*FortniteItem),
|
||||
FortniteItemsWithFeaturedImage: []*FortniteItem{},
|
||||
TypedFortniteItems: make(map[string][]*FortniteItem),
|
||||
TypedFortniteItemsWithDisplayAssets: make(map[string][]*FortniteItem),
|
||||
FortniteItems: make(map[string]*APICosmeticDefinition),
|
||||
FortniteSets: make(map[string]*APISetDefinition),
|
||||
FortniteItemsWithDisplayAssets: make(map[string]*APICosmeticDefinition),
|
||||
FortniteItemsWithFeaturedImage: []*APICosmeticDefinition{},
|
||||
TypedFortniteItems: make(map[string][]*APICosmeticDefinition),
|
||||
TypedFortniteItemsWithDisplayAssets: make(map[string][]*APICosmeticDefinition),
|
||||
SnowVariantTokens: make(map[string]*FortniteVariantToken),
|
||||
StorefrontDailyItemCountLookup: []struct{Season int;Items int}{
|
||||
{2, 4},
|
||||
|
@ -55,7 +56,7 @@ func NewDataClient() *dataClient {
|
|||
StorefrontCosmeticOfferPriceLookup: map[string]map[string]int{
|
||||
"EFortRarity::Legendary": {
|
||||
"AthenaCharacter": 2000,
|
||||
"AthenaBackpack": 1500,
|
||||
"AthenaBackpack": 300,
|
||||
"AthenaPickaxe": 1500,
|
||||
"AthenaGlider": 1800,
|
||||
"AthenaDance": 500,
|
||||
|
@ -63,7 +64,7 @@ func NewDataClient() *dataClient {
|
|||
},
|
||||
"EFortRarity::Epic": {
|
||||
"AthenaCharacter": 1500,
|
||||
"AthenaBackpack": 1200,
|
||||
"AthenaBackpack": 250,
|
||||
"AthenaPickaxe": 1200,
|
||||
"AthenaGlider": 1500,
|
||||
"AthenaDance": 800,
|
||||
|
@ -71,7 +72,7 @@ func NewDataClient() *dataClient {
|
|||
},
|
||||
"EFortRarity::Rare": {
|
||||
"AthenaCharacter": 1200,
|
||||
"AthenaBackpack": 800,
|
||||
"AthenaBackpack": 200,
|
||||
"AthenaPickaxe": 800,
|
||||
"AthenaGlider": 800,
|
||||
"AthenaDance": 500,
|
||||
|
@ -133,14 +134,14 @@ func (c *dataClient) LoadExternalData() {
|
|||
return
|
||||
}
|
||||
|
||||
content := &FortniteCosmeticsResponse{}
|
||||
content := &APICosmeticsResponse{}
|
||||
err = json.Unmarshal(bodyBytes, content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range content.Data {
|
||||
c.LoadItem(&item)
|
||||
c.LoadItemDefinition(&item)
|
||||
}
|
||||
|
||||
for _, item := range c.TypedFortniteItems["AthenaBackpack"] {
|
||||
|
@ -156,7 +157,7 @@ func (c *dataClient) LoadExternalData() {
|
|||
c.AddDisplayAssetToItem(displayAsset)
|
||||
}
|
||||
|
||||
variantTokens := storage.HttpAsset[map[string]SnowCosmeticVariantToken]("variants.snow.json")
|
||||
variantTokens := storage.HttpAsset[map[string]SnowCosmeticVariantDefinition]("variants.snow.json")
|
||||
if variantTokens == nil {
|
||||
return
|
||||
}
|
||||
|
@ -188,23 +189,142 @@ func (c *dataClient) LoadExternalData() {
|
|||
c.AddNumericStylesToItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
athenaSeasonObj := storage.HttpAsset[[]UnrealEngineObject[UnrealSeasonProperties, UnrealNoRows]]("season.snow.json")
|
||||
if athenaSeasonObj == nil {
|
||||
return
|
||||
}
|
||||
|
||||
levelProgressionObj := storage.HttpAsset[[]UnrealEngineObject[UnrealProgressionProperties, UnrealProgressionRows]]("progression.levels.snow.json")
|
||||
if levelProgressionObj == nil {
|
||||
return
|
||||
}
|
||||
|
||||
bookProgressionObj := storage.HttpAsset[[]UnrealEngineObject[UnrealProgressionProperties, UnrealProgressionRows]]("progression.book.snow.json")
|
||||
if bookProgressionObj == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.SnowSeason = NewSeasonDefinition()
|
||||
|
||||
for index, tier := range (*athenaSeasonObj)[0].Properties.BookXpSchedulePaid.Levels {
|
||||
c.SnowSeason.TierRewardsPremium[index] = []*ItemGrant{}
|
||||
for _, reward := range tier.Rewards {
|
||||
templateId := aid.Ternary[string](reward.ItemDefinition.AssetPathName != "None", convertAssetPathToTemplateId(reward.ItemDefinition.AssetPathName), reward.TemplateID)
|
||||
c.SnowSeason.TierRewardsPremium[index] = append(c.SnowSeason.TierRewardsPremium[index], NewItemGrant(templateId, reward.Quantity))
|
||||
}
|
||||
}
|
||||
c.SnowSeason.TierRewardsPremium[0] = []*ItemGrant{}
|
||||
|
||||
for index, tier := range (*athenaSeasonObj)[0].Properties.BookXpScheduleFree.Levels {
|
||||
c.SnowSeason.TierRewardsFree[index] = []*ItemGrant{}
|
||||
for _, reward := range tier.Rewards {
|
||||
templateId := aid.Ternary[string](reward.ItemDefinition.AssetPathName != "None", convertAssetPathToTemplateId(reward.ItemDefinition.AssetPathName), reward.TemplateID)
|
||||
c.SnowSeason.TierRewardsFree[index] = append(c.SnowSeason.TierRewardsFree[index], NewItemGrant(templateId, reward.Quantity))
|
||||
}
|
||||
}
|
||||
c.SnowSeason.TierRewardsFree[0] = []*ItemGrant{}
|
||||
|
||||
for index, level := range (*athenaSeasonObj)[0].Properties.SeasonXpScheduleFree.Levels {
|
||||
c.SnowSeason.LevelRewards[index] = []*ItemGrant{}
|
||||
for _, reward := range level.Rewards {
|
||||
templateId := aid.Ternary[string](reward.ItemDefinition.AssetPathName != "None", convertAssetPathToTemplateId(reward.ItemDefinition.AssetPathName), reward.TemplateID)
|
||||
c.SnowSeason.LevelRewards[index] = append(c.SnowSeason.LevelRewards[index], NewItemGrant(templateId, reward.Quantity))
|
||||
}
|
||||
}
|
||||
c.SnowSeason.LevelRewards[0] = []*ItemGrant{}
|
||||
|
||||
for _, token := range (*athenaSeasonObj)[0].Properties.TokensToRemoveAtSeasonEnd {
|
||||
c.SnowSeason.SeasonTokenRemoval = append(c.SnowSeason.SeasonTokenRemoval, NewItemGrant(convertAssetPathToTemplateId(token.AssetPathName), 1))
|
||||
}
|
||||
|
||||
for _, reward := range (*athenaSeasonObj)[0].Properties.SeasonFirstWinRewards.Rewards {
|
||||
templateId := aid.Ternary[string](reward.ItemDefinition.AssetPathName != "None", convertAssetPathToTemplateId(reward.ItemDefinition.AssetPathName), reward.TemplateID)
|
||||
c.SnowSeason.VictoryRewards = append(c.SnowSeason.VictoryRewards, NewItemGrant(templateId, reward.Quantity))
|
||||
}
|
||||
|
||||
for _, token := range (*athenaSeasonObj)[0].Properties.ExpiringRewardTypes {
|
||||
c.SnowSeason.SeasonTokenRemoval = append(c.SnowSeason.SeasonTokenRemoval, NewItemGrant(convertAssetPathToTemplateId(token.AssetPathName), 1))
|
||||
}
|
||||
|
||||
for _, reward := range (*athenaSeasonObj)[0].Properties.SeasonGrantsToEveryone.Rewards {
|
||||
templateId := aid.Ternary[string](reward.ItemDefinition.AssetPathName != "None", convertAssetPathToTemplateId(reward.ItemDefinition.AssetPathName), reward.TemplateID)
|
||||
c.SnowSeason.TierRewardsFree[0] = append(c.SnowSeason.TierRewardsFree[0], NewItemGrant(templateId, reward.Quantity))
|
||||
}
|
||||
|
||||
for _, replacement := range (*athenaSeasonObj)[0].Properties.BattleStarSubstitutionReward.Rewards {
|
||||
templateId := aid.Ternary[string](replacement.ItemDefinition.AssetPathName != "None", convertAssetPathToTemplateId(replacement.ItemDefinition.AssetPathName), replacement.TemplateID)
|
||||
c.SnowSeason.BookXPReplacements = append(c.SnowSeason.BookXPReplacements, NewItemGrant(templateId, replacement.Quantity))
|
||||
}
|
||||
|
||||
for indexString, row := range *(*levelProgressionObj)[0].Rows {
|
||||
index := aid.ToInt(indexString)
|
||||
c.SnowSeason.LevelProgression[index] = &row
|
||||
}
|
||||
c.SnowSeason.LevelProgression[0] = &UnrealProgressionRows{
|
||||
Level: 0,
|
||||
XpToNextLevel: 0,
|
||||
XpTotal: 0,
|
||||
}
|
||||
c.SnowSeason.LevelProgression[100] = &UnrealProgressionRows{
|
||||
Level: 100,
|
||||
XpToNextLevel: 0,
|
||||
XpTotal: c.SnowSeason.LevelProgression[99].XpTotal + c.SnowSeason.LevelProgression[99].XpToNextLevel,
|
||||
}
|
||||
|
||||
for indexString, row := range *(*bookProgressionObj)[0].Rows {
|
||||
index := aid.ToInt(indexString)
|
||||
c.SnowSeason.BookProgression[index] = &row
|
||||
}
|
||||
c.SnowSeason.BookProgression[0] = &UnrealProgressionRows{
|
||||
Level: 0,
|
||||
XpToNextLevel: 0,
|
||||
XpTotal: 0,
|
||||
}
|
||||
c.SnowSeason.BookProgression[100] = &UnrealProgressionRows{
|
||||
Level: 100,
|
||||
XpToNextLevel: 0,
|
||||
XpTotal: c.SnowSeason.BookProgression[99].XpTotal + c.SnowSeason.BookProgression[99].XpToNextLevel,
|
||||
}
|
||||
|
||||
c.SnowSeason.DefaultOfferID = (*athenaSeasonObj)[0].Properties.BattlePassOfferId
|
||||
c.SnowSeason.BundleOfferID = (*athenaSeasonObj)[0].Properties.BattlePassBundleOfferId
|
||||
c.SnowSeason.TierOfferID = (*athenaSeasonObj)[0].Properties.BattlePassLevelOfferID
|
||||
}
|
||||
|
||||
func (c *dataClient) LoadItem(item *FortniteItem) {
|
||||
func (c *dataClient) LoadItemDefinition(item *APICosmeticDefinition) {
|
||||
if item.Introduction.BackendValue > aid.Config.Fortnite.Season || item.Introduction.BackendValue == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
typeLookup := map[string]string{
|
||||
"AthenaCharacter": "AthenaCharacter",
|
||||
"AthenaBackpack": "AthenaBackpack",
|
||||
"AthenaPickaxe": "AthenaPickaxe",
|
||||
"AthenaGlider": "AthenaGlider",
|
||||
"AthenaDance": "AthenaDance",
|
||||
"AthenaToy": "AthenaDance",
|
||||
"AthenaEmoji": "AthenaEmoji",
|
||||
"AthenaItemWrap": "AthenaItemWrap",
|
||||
"AthenaMusicPack": "AthenaMusicPack",
|
||||
"AthenaPet": "AthenaBackpack",
|
||||
"AthenaPetCarrier": "AthenaBackpack",
|
||||
"AthenaLoadingScreen": "AthenaLoadingScreen",
|
||||
"AthenaSkyDiveContrail": "AthenaSkyDiveContrail",
|
||||
}
|
||||
|
||||
item.Type.BackendValue = aid.Ternary[string](typeLookup[item.Type.BackendValue] != "", typeLookup[item.Type.BackendValue], item.Type.BackendValue)
|
||||
|
||||
if c.FortniteSets[item.Set.BackendValue] == nil {
|
||||
c.FortniteSets[item.Set.BackendValue] = &FortniteSet{
|
||||
c.FortniteSets[item.Set.BackendValue] = &APISetDefinition{
|
||||
BackendName: item.Set.Value,
|
||||
DisplayName: item.Set.Text,
|
||||
Items: []*FortniteItem{},
|
||||
Items: []*APICosmeticDefinition{},
|
||||
}
|
||||
}
|
||||
|
||||
if c.TypedFortniteItems[item.Type.BackendValue] == nil {
|
||||
c.TypedFortniteItems[item.Type.BackendValue] = []*FortniteItem{}
|
||||
c.TypedFortniteItems[item.Type.BackendValue] = []*APICosmeticDefinition{}
|
||||
}
|
||||
|
||||
c.FortniteItems[item.ID] = item
|
||||
|
@ -228,7 +348,7 @@ func (c *dataClient) LoadItem(item *FortniteItem) {
|
|||
c.FortniteItemsWithFeaturedImage = append(c.FortniteItemsWithFeaturedImage, item)
|
||||
}
|
||||
|
||||
func (c *dataClient) AddBackpackToItem(backpack *FortniteItem) {
|
||||
func (c *dataClient) AddBackpackToItem(backpack *APICosmeticDefinition) {
|
||||
if backpack.ItemPreviewHeroPath == "" {
|
||||
return
|
||||
}
|
||||
|
@ -239,7 +359,7 @@ func (c *dataClient) AddBackpackToItem(backpack *FortniteItem) {
|
|||
return
|
||||
}
|
||||
|
||||
character.Backpack = backpack
|
||||
character.BackpackDefinition = backpack
|
||||
}
|
||||
|
||||
func (c *dataClient) AddDisplayAssetToItem(displayAsset string) {
|
||||
|
@ -257,20 +377,20 @@ func (c *dataClient) AddDisplayAssetToItem(displayAsset string) {
|
|||
return
|
||||
}
|
||||
|
||||
found.DisplayAssetPath2 = displayAsset
|
||||
found.NewDisplayAssetPath = displayAsset
|
||||
c.FortniteItemsWithDisplayAssets[found.ID] = found
|
||||
c.TypedFortniteItemsWithDisplayAssets[found.Type.BackendValue] = append(c.TypedFortniteItemsWithDisplayAssets[found.Type.BackendValue], found)
|
||||
}
|
||||
|
||||
func (c *dataClient) AddNumericStylesToItem(item *FortniteItem) {
|
||||
ownedStyles := []FortniteVariantChannel{}
|
||||
func (c *dataClient) AddNumericStylesToItem(item *APICosmeticDefinition) {
|
||||
ownedStyles := []APICosmeticDefinitionVariant{}
|
||||
for i := 0; i < 100; i++ {
|
||||
ownedStyles = append(ownedStyles, FortniteVariantChannel{
|
||||
ownedStyles = append(ownedStyles, APICosmeticDefinitionVariant{
|
||||
Tag: fmt.Sprint(i),
|
||||
})
|
||||
}
|
||||
|
||||
item.Variants = append(item.Variants, FortniteVariant{
|
||||
item.Variants = append(item.Variants, APICosmeticDefinitionVariantChannel{
|
||||
Channel: "Numeric",
|
||||
Type: "int",
|
||||
Options: ownedStyles,
|
||||
|
@ -290,12 +410,12 @@ func (c *dataClient) GetStorefrontDailyItemCount(season int) int {
|
|||
|
||||
func (c *dataClient) GetStorefrontWeeklySetCount(season int) int {
|
||||
currentValue := 2
|
||||
for _, item := range c.StorefrontWeeklySetCountLookup {
|
||||
if item.Season > season {
|
||||
continue
|
||||
}
|
||||
currentValue = item.Sets
|
||||
}
|
||||
// for _, item := range c.StorefrontWeeklySetCountLookup {
|
||||
// if item.Season > season {
|
||||
// continue
|
||||
// }
|
||||
// currentValue = item.Sets
|
||||
// }
|
||||
return currentValue
|
||||
}
|
||||
|
||||
|
@ -307,7 +427,7 @@ func (c *dataClient) GetStorefrontCurrencyOfferPrice(currency string, amount int
|
|||
return c.StorefrontCurrencyOfferPriceLookup[currency][amount]
|
||||
}
|
||||
|
||||
func (c *dataClient) GetLocalizedPrice(currency string, amount int) int {
|
||||
func (c *dataClient) GetStorefrontLocalizedOfferPrice(currency string, amount int) int {
|
||||
return int(float64(amount) * c.StorefrontCurrencyMultiplier[currency])
|
||||
}
|
||||
|
||||
|
@ -319,7 +439,7 @@ func PreloadCosmetics() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetItemByShallowID(shallowID string) *FortniteItem {
|
||||
func GetItemByShallowID(shallowID string) *APICosmeticDefinition {
|
||||
for _, item := range DataClient.TypedFortniteItems["AthenaCharacter"] {
|
||||
if strings.Contains(item.ID, shallowID) {
|
||||
return item
|
||||
|
@ -329,26 +449,26 @@ func GetItemByShallowID(shallowID string) *FortniteItem {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetRandomItemWithDisplayAsset() *FortniteItem {
|
||||
func GetRandomItemWithDisplayAsset() *APICosmeticDefinition {
|
||||
items := DataClient.FortniteItemsWithDisplayAssets
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
flat := []FortniteItem{}
|
||||
flat := []APICosmeticDefinition{}
|
||||
for _, item := range items {
|
||||
flat = append(flat, *item)
|
||||
}
|
||||
|
||||
slices.SortFunc[[]FortniteItem](flat, func(a, b FortniteItem) int {
|
||||
slices.SortFunc[[]APICosmeticDefinition](flat, func(a, b APICosmeticDefinition) int {
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
return &flat[aid.RandomInt(0, len(flat))]
|
||||
}
|
||||
|
||||
func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem {
|
||||
flat := []FortniteItem{}
|
||||
func GetRandomItemWithDisplayAssetOfNotType(notType string) *APICosmeticDefinition {
|
||||
flat := []APICosmeticDefinition{}
|
||||
|
||||
for t, items := range DataClient.TypedFortniteItemsWithDisplayAssets {
|
||||
if t == notType {
|
||||
|
@ -360,15 +480,15 @@ func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem {
|
|||
}
|
||||
}
|
||||
|
||||
slices.SortFunc[[]FortniteItem](flat, func(a, b FortniteItem) int {
|
||||
slices.SortFunc[[]APICosmeticDefinition](flat, func(a, b APICosmeticDefinition) int {
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
return &flat[aid.RandomInt(0, len(flat))]
|
||||
}
|
||||
|
||||
func GetRandomSet() *FortniteSet {
|
||||
sets := []FortniteSet{}
|
||||
func GetRandomSet() *APISetDefinition {
|
||||
sets := []APISetDefinition{}
|
||||
for _, set := range DataClient.FortniteSets {
|
||||
if set.BackendName == "" {
|
||||
continue
|
||||
|
@ -376,7 +496,7 @@ func GetRandomSet() *FortniteSet {
|
|||
sets = append(sets, *set)
|
||||
}
|
||||
|
||||
slices.SortFunc[[]FortniteSet](sets, func(a, b FortniteSet) int {
|
||||
slices.SortFunc[[]APISetDefinition](sets, func(a, b APISetDefinition) int {
|
||||
return strings.Compare(a.BackendName, b.BackendName)
|
||||
})
|
||||
|
||||
|
|
182
fortnite/granting.go
Normal file
182
fortnite/granting.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package fortnite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/person"
|
||||
)
|
||||
|
||||
var (
|
||||
grantLookupTable = map[string]func(*person.Person, *LootResult, *ItemGrant) error {
|
||||
"AthenaCharacter": grantAthenaCosmetic,
|
||||
"AthenaBackpack": grantAthenaCosmetic,
|
||||
"AthenaPickaxe": grantAthenaCosmetic,
|
||||
"AthenaDance": grantAthenaCosmetic,
|
||||
"AthenaGlider": grantAthenaCosmetic,
|
||||
"AthenaLoadingScreen": grantAthenaCosmetic,
|
||||
"AthenaMusicPack": grantAthenaCosmetic,
|
||||
"AthenaPet": grantAthenaCosmetic,
|
||||
"AthenaSkyDiveContrail": grantAthenaCosmetic,
|
||||
"AthenaSpray": grantAthenaCosmetic,
|
||||
"AthenaToy": grantAthenaCosmetic,
|
||||
"AthenaEmoji": grantAthenaCosmetic,
|
||||
"AthenaItemWrap": grantAthenaCosmetic,
|
||||
"Currency": grantCurrency,
|
||||
"Token": grantCommonCoreCosmetic,
|
||||
"HomebaseBannerIcon":grantCommonCoreCosmetic,
|
||||
"HomebaseBannerColor": grantCommonCoreCosmetic,
|
||||
"CosmeticVariantToken": grantCosmeticVariantToken,
|
||||
"PersistentResource": grantPersistentResource,
|
||||
"AccountResource": grantPersistentResource,
|
||||
"Snow": grantSnowCustomReward,
|
||||
}
|
||||
)
|
||||
|
||||
// This will either update the quantity of an
|
||||
// already exisiting item or create a new item.
|
||||
func GrantToPerson(p *person.Person, grants ...*ItemGrant) (*LootResult, error) {
|
||||
loot := NewLootResult()
|
||||
|
||||
for _, grant := range grants {
|
||||
templateData := strings.Split(grant.TemplateID, ":")
|
||||
if len(templateData) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
handler, ok := grantLookupTable[templateData[0]]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
err := handler(p, loot, grant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return loot, nil
|
||||
}
|
||||
|
||||
func grantAthenaCosmetic(p *person.Person, loot *LootResult, grant *ItemGrant) error {
|
||||
parts := strings.Split(grant.TemplateID, ":")
|
||||
|
||||
newTemplateId := ""
|
||||
switch parts[0] {
|
||||
case "AthenaPet":
|
||||
newTemplateId = "AthenaBackpack:" + parts[1]
|
||||
case "AthenaSpray":
|
||||
newTemplateId = "AthenaDance:" + parts[1]
|
||||
case "AthenaEmoji":
|
||||
newTemplateId = "AthenaDance:" + parts[1]
|
||||
case "AthenaToy":
|
||||
newTemplateId = "AthenaDance:" + parts[1]
|
||||
default:
|
||||
newTemplateId = parts[0] + ":" + parts[1]
|
||||
}
|
||||
|
||||
if item := p.AthenaProfile.Items.GetItemByTemplateID(newTemplateId); item != nil {
|
||||
item.Quantity++
|
||||
item.Save()
|
||||
return nil
|
||||
}
|
||||
|
||||
item := person.NewItem(newTemplateId, grant.Quantity)
|
||||
p.AthenaProfile.Items.AddItem(item).Save()
|
||||
loot.AddItem(item)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func grantCurrency(p *person.Person, loot *LootResult, grant *ItemGrant) error {
|
||||
p.GiveAndSyncVbucks(grant.Quantity)
|
||||
return nil
|
||||
}
|
||||
|
||||
func grantCommonCoreCosmetic(p *person.Person, loot *LootResult, grant *ItemGrant) error {
|
||||
if item := p.CommonCoreProfile.Items.GetItemByTemplateID(grant.TemplateID); item != nil {
|
||||
item.Quantity++
|
||||
item.Save()
|
||||
return nil
|
||||
}
|
||||
|
||||
item := person.NewItem(grant.TemplateID, grant.Quantity)
|
||||
p.CommonCoreProfile.Items.AddItem(item).Save()
|
||||
loot.AddItem(item)
|
||||
return nil
|
||||
}
|
||||
|
||||
func grantCosmeticVariantToken(p *person.Person, loot *LootResult, grant *ItemGrant) error {
|
||||
parts := strings.Split(grant.TemplateID, ":")
|
||||
newTemplateId := "CosmeticVariantToken:" + parts[1]
|
||||
if variantToken := p.AthenaProfile.VariantTokens.GetVariantToken(newTemplateId); variantToken != nil {
|
||||
return fmt.Errorf("variant token already owned")
|
||||
}
|
||||
|
||||
tokenData, ok := DataClient.SnowVariantTokens[parts[1]]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid variant token data")
|
||||
}
|
||||
|
||||
found := p.AthenaProfile.Items.GetItemByTemplateID(tokenData.Item.Type.BackendValue + ":" + tokenData.Item.ID)
|
||||
if found == nil {
|
||||
aid.Print("tried to give variant for nil item" + tokenData.Item.Type.BackendValue + ":" + tokenData.Item.ID)
|
||||
return fmt.Errorf("tried to give variant for nil item" + tokenData.Item.Type.BackendValue + ":" + tokenData.Item.ID)
|
||||
}
|
||||
|
||||
g := map[string][]string{}
|
||||
for _, variant := range tokenData.Grants {
|
||||
if _, ok := g[variant.Channel]; !ok {
|
||||
g[variant.Channel] = []string{}
|
||||
}
|
||||
|
||||
g[variant.Channel] = append(g[variant.Channel], variant.Value)
|
||||
}
|
||||
|
||||
for c, tags := range g {
|
||||
channel := found.GetChannel(c)
|
||||
if channel == nil {
|
||||
channel = found.NewChannel(c, tags, tags[0])
|
||||
found.AddChannel(channel)
|
||||
continue
|
||||
}
|
||||
|
||||
channel.Owned = append(channel.Owned, tags...)
|
||||
}
|
||||
found.Save()
|
||||
|
||||
p.AthenaProfile.CreateItemAttributeChangedChange(found, "Variants")
|
||||
return nil
|
||||
}
|
||||
|
||||
func grantPersistentResource(p *person.Person, loot *LootResult, grant *ItemGrant) error {
|
||||
parts := strings.Split(grant.TemplateID, ":")
|
||||
switch parts[1] {
|
||||
case "AthenaSeasonalXP":
|
||||
p.CurrentSeasonStats.SeasonXP += grant.Quantity
|
||||
p.CurrentSeasonStats.Save()
|
||||
p.AthenaProfile.Attributes.GetAttributeByKey("level").SetValue(DataClient.SnowSeason.GetSeasonLevel(p.CurrentSeasonStats)).Save()
|
||||
p.AthenaProfile.Attributes.GetAttributeByKey("xp").SetValue(DataClient.SnowSeason.GetRelativeSeasonXP(p.CurrentSeasonStats)).Save()
|
||||
case "AthenaBattleStar":
|
||||
p.CurrentSeasonStats.BookXP += grant.Quantity
|
||||
p.CurrentSeasonStats.Save()
|
||||
p.AthenaProfile.Attributes.GetAttributeByKey("book_level").SetValue(DataClient.SnowSeason.GetBookLevel(p.CurrentSeasonStats)).Save()
|
||||
p.AthenaProfile.Attributes.GetAttributeByKey("book_xp").SetValue(DataClient.SnowSeason.GetRelativeBookXP(p.CurrentSeasonStats)).Save()
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func grantSnowCustomReward(p *person.Person, loot *LootResult, grant *ItemGrant) error {
|
||||
parts := strings.Split(grant.TemplateID, ":")
|
||||
switch parts[1] {
|
||||
case "BattlePass":
|
||||
p.CurrentSeasonStats.BookPurchased = true
|
||||
p.CurrentSeasonStats.Save()
|
||||
p.AthenaProfile.Attributes.GetAttributeByKey("book_purchased").SetValue(true).Save()
|
||||
}
|
||||
|
||||
DataClient.SnowSeason.GrantUnredeemedBookRewards(p, "GB_BattlePassPurchased")
|
||||
return nil
|
||||
}
|
|
@ -2,11 +2,9 @@ package fortnite
|
|||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
p "github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
|
@ -29,39 +27,14 @@ func NewFortnitePerson(displayName string, everything bool) *p.Person {
|
|||
}
|
||||
|
||||
func GiveEverything(person *p.Person) {
|
||||
items := make([]storage.DB_Item, 0)
|
||||
|
||||
for _, item := range DataClient.FortniteItems {
|
||||
if strings.Contains(strings.ToLower(item.ID), "random") {
|
||||
continue
|
||||
}
|
||||
|
||||
has := person.AthenaProfile.Items.GetItemByTemplateID(item.ID)
|
||||
if has != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
new := p.NewItem(item.Type.BackendValue + ":" + item.ID, 1)
|
||||
new.HasSeen = true
|
||||
|
||||
grouped := map[string][]string{}
|
||||
for _, variant := range item.Variants {
|
||||
grouped[variant.Channel] = []string{}
|
||||
|
||||
for _, option := range variant.Options {
|
||||
grouped[variant.Channel] = append(grouped[variant.Channel], option.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
for channel, tags := range grouped {
|
||||
new.AddChannel(new.NewChannel(channel, tags, tags[0]))
|
||||
}
|
||||
|
||||
person.AthenaProfile.Items.AddItem(new)
|
||||
items = append(items, *new.ToDatabase(person.AthenaProfile.ID))
|
||||
GrantToPerson(person, NewItemGrant(item.Type.BackendValue+":"+item.ID, 1))
|
||||
}
|
||||
|
||||
for key := range DataClient.SnowVariantTokens {
|
||||
GrantToPerson(person, NewItemGrant("CosmeticVariantToken:"+key, 1))
|
||||
}
|
||||
|
||||
storage.Repo.BulkCreateItems(&items)
|
||||
person.Save()
|
||||
}
|
||||
|
||||
|
@ -78,25 +51,21 @@ func NewFortnitePersonWithId(id string, displayName string, everything bool) *p.
|
|||
for _, item := range defaultCommonCoreItems {
|
||||
if item == "HomebaseBannerIcon:StandardBanner" {
|
||||
for i := 1; i < 32; i++ {
|
||||
item := p.NewItem(item+strconv.Itoa(i), 1)
|
||||
item.HasSeen = true
|
||||
person.CommonCoreProfile.Items.AddItem(item).Save()
|
||||
GrantToPerson(person, NewItemGrant(item+strconv.Itoa(i), 1))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if item == "HomebaseBannerColor:DefaultColor" {
|
||||
for i := 1; i < 22; i++ {
|
||||
item := p.NewItem(item+strconv.Itoa(i), 1)
|
||||
item.HasSeen = true
|
||||
person.CommonCoreProfile.Items.AddItem(item).Save()
|
||||
GrantToPerson(person, NewItemGrant(item+strconv.Itoa(i), 1))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if item == "Currency:MtxPurchased" {
|
||||
person.CommonCoreProfile.Items.AddItem(p.NewItem(item, 0)).Save()
|
||||
person.Profile0Profile.Items.AddItem(p.NewItem(item, 0)).Save()
|
||||
person.CommonCoreProfile.Items.AddItem(p.NewItem(item, 9999999)).Save()
|
||||
person.Profile0Profile.Items.AddItem(p.NewItem(item, 99999999)).Save()
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -111,10 +80,11 @@ func NewFortnitePersonWithId(id string, displayName string, everything bool) *p.
|
|||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("inventory_limit_bonus", 0)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("daily_rewards", []aid.JSON{})).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("competitive_identity", aid.JSON{})).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("permissions", []aid.JSON{})).Save()
|
||||
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("season_update", 0)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("season_num", aid.Config.Fortnite.Season)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("permissions", []aid.JSON{})).Save()
|
||||
|
||||
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("accountLevel", 1)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("level", 1)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("xp", 0)).Save()
|
||||
|
@ -122,11 +92,15 @@ func NewFortnitePersonWithId(id string, displayName string, everything bool) *p.
|
|||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("rested_xp", 0)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("rested_xp_mult", 0)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("rested_xp_exchange", 0)).Save()
|
||||
|
||||
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("book_purchased", false)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("book_level", 1)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("book_xp", 0)).Save()
|
||||
|
||||
seasonStats := p.NewSeasonStats(aid.Config.Fortnite.Season)
|
||||
seasonStats.PersonID = person.ID
|
||||
seasonStats.Save()
|
||||
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("favorite_character", person.AthenaProfile.Items.GetItemByTemplateID("AthenaCharacter:CID_001_Athena_Commando_F_Default").ID)).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("favorite_backpack", "")).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("favorite_pickaxe", person.AthenaProfile.Items.GetItemByTemplateID("AthenaPickaxe:DefaultPickaxe").ID)).Save()
|
||||
|
@ -136,8 +110,8 @@ func NewFortnitePersonWithId(id string, displayName string, everything bool) *p.
|
|||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("favorite_itemwraps", make([]string, 7))).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("favorite_loadingscreen", "")).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("favorite_musicpack", "")).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("banner_icon", "")).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("banner_color", "")).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("banner_icon", "StandardBanner1")).Save()
|
||||
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("banner_color", "DefaultColor1")).Save()
|
||||
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("mfa_enabled", true)).Save()
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("mtx_affiliate", "")).Save()
|
||||
|
@ -151,10 +125,19 @@ func NewFortnitePersonWithId(id string, displayName string, everything bool) *p.
|
|||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("allowed_to_receive_gifts", true)).Save()
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("allowed_to_send_gifts", true)).Save()
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("gift_history", aid.JSON{})).Save()
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("in_app_purchases", aid.JSON{
|
||||
"receipts": []string{},
|
||||
"ignoredReceipts": []string{},
|
||||
"fulfillmentCounts": map[string]int{},
|
||||
"refreshTimers": aid.JSON{},
|
||||
})).Save()
|
||||
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("party.recieveIntents", "ALL")).Save()
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("party.recieveInvites", "ALL")).Save()
|
||||
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("season.bookFreeClaimedUpTo", 0)).Save()
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("season.bookPaidClaimedUpTo", 0)).Save()
|
||||
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("season.levelClaimedUpTo", 0)).Save()
|
||||
|
||||
loadout := p.NewLoadout("PRESET 1", person.AthenaProfile)
|
||||
person.AthenaProfile.Loadouts.AddLoadout(loadout).Save()
|
||||
|
|
331
fortnite/season.go
Normal file
331
fortnite/season.go
Normal file
|
@ -0,0 +1,331 @@
|
|||
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
|
||||
}
|
852
fortnite/shop.go
852
fortnite/shop.go
|
@ -1,852 +0,0 @@
|
|||
package fortnite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type FortniteCatalogStarterPackGrant struct {
|
||||
TemplateID string
|
||||
Quantity int
|
||||
}
|
||||
|
||||
func NewFortniteCatalogStarterPackGrant(templateID string, quantity int) *FortniteCatalogStarterPackGrant {
|
||||
return &FortniteCatalogStarterPackGrant{
|
||||
TemplateID: templateID,
|
||||
Quantity: quantity,
|
||||
}
|
||||
}
|
||||
|
||||
type FortniteCatalogStarterPack struct {
|
||||
ID string
|
||||
DevName string
|
||||
Grants []*FortniteCatalogStarterPackGrant
|
||||
Meta struct {
|
||||
IconSize string
|
||||
BannerOverride string
|
||||
DisplayAssetPath string
|
||||
NewDisplayAssetPath string
|
||||
OriginalOffer int
|
||||
ExtraBonus int
|
||||
}
|
||||
Price struct {
|
||||
PriceType string
|
||||
PriceToPay int
|
||||
}
|
||||
Title string
|
||||
Description string
|
||||
LongDescription string
|
||||
Priority int
|
||||
SeasonsAllowed []int
|
||||
}
|
||||
|
||||
func NewFortniteCatalogStarterPack(price int) *FortniteCatalogStarterPack {
|
||||
return &FortniteCatalogStarterPack{
|
||||
ID: "v2:/" + aid.RandomString(32),
|
||||
Price: struct {
|
||||
PriceType string
|
||||
PriceToPay int
|
||||
}{"RealMoney", price},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogStarterPack) GenerateFortniteCatalogStarterPackResponse() aid.JSON {
|
||||
grantsResponse := []aid.JSON{}
|
||||
for _, grant := range f.Grants {
|
||||
grantsResponse = append(grantsResponse, aid.JSON{
|
||||
"templateId": grant.TemplateID,
|
||||
"quantity": grant.Quantity,
|
||||
})
|
||||
}
|
||||
|
||||
prices := []aid.JSON{}
|
||||
switch f.Price.PriceType {
|
||||
case "RealMoney":
|
||||
prices = append(prices, aid.JSON{
|
||||
"currencyType": "RealMoney",
|
||||
"currencySubType": "",
|
||||
"regularPrice": 0,
|
||||
"dynamicRegularPrice": -1,
|
||||
"finalPrice": 0,
|
||||
"saleExpiration": "9999-12-31T23:59:59.999Z",
|
||||
"basePrice": 0,
|
||||
})
|
||||
case "MtxCurrency":
|
||||
prices = append(prices, aid.JSON{
|
||||
"currencyType": "MtxCurrency",
|
||||
"currencySubType": "",
|
||||
"regularPrice": f.Price.PriceToPay,
|
||||
"dynamicRegularPrice": f.Price.PriceToPay,
|
||||
"finalPrice": f.Price.PriceToPay,
|
||||
"saleExpiration": "9999-12-31T23:59:59.999Z",
|
||||
"basePrice": f.Price.PriceToPay,
|
||||
})
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"offerId": f.ID,
|
||||
"devName": f.DevName,
|
||||
"offerType": "StaticPrice",
|
||||
"prices": prices,
|
||||
"categories": []string{},
|
||||
"dailyLimit": -1,
|
||||
"weeklyLimit": -1,
|
||||
"monthlyLimit": -1,
|
||||
"refundable": false,
|
||||
"appStoreId": []string{
|
||||
"",
|
||||
"app-" + f.ID,
|
||||
},
|
||||
"requirements": []aid.JSON{},
|
||||
"metaInfo": []aid.JSON{
|
||||
{
|
||||
"key": "SectionId",
|
||||
"value": "LimitedTime",
|
||||
},
|
||||
{
|
||||
"key": "IconSize",
|
||||
"value": f.Meta.IconSize,
|
||||
},
|
||||
{
|
||||
"key": "BannerOverride",
|
||||
"value": f.Meta.BannerOverride,
|
||||
},
|
||||
{
|
||||
"key": "DisplayAssetPath",
|
||||
"value": f.Meta.DisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"key": "NewDisplayAssetPath",
|
||||
"value": f.Meta.NewDisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"key": "MtxQuantity",
|
||||
"value": f.Meta.OriginalOffer + f.Meta.ExtraBonus,
|
||||
},
|
||||
{
|
||||
"key": "MtxBonus",
|
||||
"value": f.Meta.ExtraBonus,
|
||||
},
|
||||
},
|
||||
"meta": aid.JSON{
|
||||
"IconSize": f.Meta.IconSize,
|
||||
"BannerOverride": f.Meta.BannerOverride,
|
||||
"SectionID": "LimitedTime",
|
||||
"DisplayAssetPath": f.Meta.DisplayAssetPath,
|
||||
"NewDisplayAssetPath": f.Meta.NewDisplayAssetPath,
|
||||
"MtxQuantity": f.Meta.OriginalOffer + f.Meta.ExtraBonus,
|
||||
"MtxBonus": f.Meta.ExtraBonus,
|
||||
},
|
||||
"catalogGroup": "",
|
||||
"catalogGroupPriority": 0,
|
||||
"sortPriority": f.Priority,
|
||||
"bannerOverride": f.Meta.BannerOverride,
|
||||
"title": f.Title,
|
||||
"shortDescription": "",
|
||||
"description": f.Description,
|
||||
"displayAssetPath": f.Meta.DisplayAssetPath,
|
||||
"itemGrants": []aid.JSON{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogStarterPack) GenerateFortniteCatalogBulkOfferResponse() aid.JSON {
|
||||
return aid.JSON{
|
||||
"id": "app-" + f.ID,
|
||||
"title": f.Title,
|
||||
"description": f.Description,
|
||||
"longDescription": f.LongDescription,
|
||||
"technicalDetails": "",
|
||||
"keyImages": []aid.JSON{},
|
||||
"categories": []aid.JSON{},
|
||||
"namespace": "fn",
|
||||
"status": "ACTIVE",
|
||||
"creationDate": time.Now().Format(time.RFC3339),
|
||||
"lastModifiedDate": time.Now().Format(time.RFC3339),
|
||||
"customAttributes": aid.JSON{},
|
||||
"internalName": f.Title,
|
||||
"recurrence": "ONCE",
|
||||
"items": []aid.JSON{},
|
||||
"price": DataClient.GetLocalizedPrice("GBP", f.Price.PriceToPay),
|
||||
"currentPrice": DataClient.GetLocalizedPrice("GBP", f.Price.PriceToPay),
|
||||
"currencyCode": "GBP",
|
||||
"basePrice": DataClient.GetLocalizedPrice("USD", f.Price.PriceToPay),
|
||||
"basePriceCurrencyCode": "USD",
|
||||
"recurringPrice": 0,
|
||||
"freeDays": 0,
|
||||
"maxBillingCycles": 0,
|
||||
"seller": aid.JSON{},
|
||||
"viewableDate": time.Now().Format(time.RFC3339),
|
||||
"effectiveDate": time.Now().Format(time.RFC3339),
|
||||
"expiryDate": "9999-12-31T23:59:59.999Z",
|
||||
"vatIncluded": true,
|
||||
"isCodeRedemptionOnly": false,
|
||||
"isFeatured": false,
|
||||
"taxSkuId": "FN_Currency",
|
||||
"merchantGroup": "FN_MKT",
|
||||
"priceTier": fmt.Sprintf("%d", DataClient.GetLocalizedPrice("USD", f.Price.PriceToPay)),
|
||||
"urlSlug": "fortnite--" + f.Title,
|
||||
"roleNamesToGrant": []aid.JSON{},
|
||||
"tags": []aid.JSON{},
|
||||
"purchaseLimit": -1,
|
||||
"ignoreOrder": false,
|
||||
"fulfillToGroup": false,
|
||||
"fraudItemType": "V-Bucks",
|
||||
"shareRevenue": false,
|
||||
"offerType": "OTHERS",
|
||||
"unsearchable": false,
|
||||
"releaseDate": time.Now().Format(time.RFC3339),
|
||||
"releaseOffer": "",
|
||||
"title4Sort": f.Title,
|
||||
"countriesBlacklist": []string{},
|
||||
"selfRefundable": false,
|
||||
"refundType": "NON_REFUNDABLE",
|
||||
"pcReleaseDate": time.Now().Format(time.RFC3339),
|
||||
"priceCalculationMode": "FIXED",
|
||||
"assembleMode": "SINGLE",
|
||||
"publisherDisplayName": "Epic Games",
|
||||
"developerDisplayName": "Epic Games",
|
||||
"visibilityType": "IS_LISTED",
|
||||
"currencyDecimals": 2,
|
||||
"allowPurchaseForPartialOwned": true,
|
||||
"shareRevenueWithUnderageAffiliates": false,
|
||||
"platformWhitelist": []string{},
|
||||
"platformBlacklist": []string{},
|
||||
"partialItemPrerequisiteCheck": false,
|
||||
"upgradeMode": "UPGRADED_WITH_PRICE_FULL",
|
||||
}
|
||||
}
|
||||
|
||||
func (startPack *FortniteCatalogStarterPack) AddGrant(g *FortniteCatalogStarterPackGrant) {
|
||||
startPack.Grants = append(startPack.Grants, g)
|
||||
}
|
||||
|
||||
type FortniteCatalogCurrencyOffer struct {
|
||||
ID string
|
||||
DevName string
|
||||
Price struct {
|
||||
OriginalOffer int
|
||||
ExtraBonus int
|
||||
}
|
||||
Meta struct {
|
||||
IconSize string
|
||||
CurrencyAnalyticsName string
|
||||
BannerOverride string
|
||||
}
|
||||
Title string
|
||||
Description string
|
||||
LongDescription string
|
||||
Priority int
|
||||
}
|
||||
|
||||
func NewFortniteCatalogCurrencyOffer(original, bonus int) *FortniteCatalogCurrencyOffer {
|
||||
return &FortniteCatalogCurrencyOffer{
|
||||
ID: "v2:/"+aid.RandomString(32),
|
||||
Price: struct {
|
||||
OriginalOffer int
|
||||
ExtraBonus int
|
||||
}{original, bonus},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogCurrencyOffer) GenerateFortniteCatalogCurrencyOfferResponse() aid.JSON {
|
||||
return aid.JSON{
|
||||
"offerId": f.ID,
|
||||
"devName": f.DevName,
|
||||
"offerType": "StaticPrice",
|
||||
"prices": []aid.JSON{{
|
||||
"currencyType": "RealMoney",
|
||||
"currencySubType": "",
|
||||
"regularPrice": 0,
|
||||
"dynamicRegularPrice": -1,
|
||||
"finalPrice": 0,
|
||||
"saleExpiration": "9999-12-31T23:59:59.999Z",
|
||||
"basePrice": 0,
|
||||
}},
|
||||
"categories": []string{},
|
||||
"dailyLimit": -1,
|
||||
"weeklyLimit": -1,
|
||||
"monthlyLimit": -1,
|
||||
"refundable": false,
|
||||
"appStoreId": []string{
|
||||
"",
|
||||
"app-" + f.ID,
|
||||
},
|
||||
"requirements": []aid.JSON{},
|
||||
"metaInfo": []aid.JSON{
|
||||
{
|
||||
"key": "MtxQuantity",
|
||||
"value": f.Price.OriginalOffer + f.Price.ExtraBonus,
|
||||
},
|
||||
{
|
||||
"key": "MtxBonus",
|
||||
"value": f.Price.ExtraBonus,
|
||||
},
|
||||
{
|
||||
"key": "IconSize",
|
||||
"value": f.Meta.IconSize,
|
||||
},
|
||||
{
|
||||
"key": "BannerOverride",
|
||||
"value": f.Meta.BannerOverride,
|
||||
},
|
||||
{
|
||||
"Key": "CurrencyAnalyticsName",
|
||||
"Value": f.Meta.CurrencyAnalyticsName,
|
||||
},
|
||||
},
|
||||
"meta": aid.JSON{
|
||||
"IconSize": f.Meta.IconSize,
|
||||
"CurrencyAnalyticsName": f.Meta.CurrencyAnalyticsName,
|
||||
"BannerOverride": f.Meta.BannerOverride,
|
||||
"MtxQuantity": f.Price.OriginalOffer + f.Price.ExtraBonus,
|
||||
"MtxBonus": f.Price.ExtraBonus,
|
||||
},
|
||||
"catalogGroup": "",
|
||||
"catalogGroupPriority": 0,
|
||||
"sortPriority": f.Priority,
|
||||
"bannerOverride": f.Meta.BannerOverride,
|
||||
"title": f.Title,
|
||||
"shortDescription": "",
|
||||
"description": f.Description,
|
||||
"displayAssetPath": "/Game/Catalog/DisplayAssets/DA_" + f.Meta.CurrencyAnalyticsName + ".DA_" + f.Meta.CurrencyAnalyticsName,
|
||||
"itemGrants": []aid.JSON{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogCurrencyOffer) GenerateFortniteCatalogBulkOfferResponse() aid.JSON{
|
||||
return aid.JSON{
|
||||
"id": "app-" + f.ID,
|
||||
"title": f.Title,
|
||||
"description": f.Description,
|
||||
"longDescription": f.LongDescription,
|
||||
"technicalDetails": "",
|
||||
"keyImages": []aid.JSON{},
|
||||
"categories": []aid.JSON{},
|
||||
"namespace": "fn",
|
||||
"status": "ACTIVE",
|
||||
"creationDate": time.Now().Format(time.RFC3339),
|
||||
"lastModifiedDate": time.Now().Format(time.RFC3339),
|
||||
"customAttributes": aid.JSON{},
|
||||
"internalName": f.Title,
|
||||
"recurrence": "ONCE",
|
||||
"items": []aid.JSON{},
|
||||
"price": DataClient.GetStorefrontCurrencyOfferPrice("GBP", f.Price.OriginalOffer + f.Price.ExtraBonus),
|
||||
"currentPrice": DataClient.GetStorefrontCurrencyOfferPrice("GBP", f.Price.OriginalOffer + f.Price.ExtraBonus),
|
||||
"currencyCode": "GBP",
|
||||
"basePrice": DataClient.GetStorefrontCurrencyOfferPrice("USD", f.Price.OriginalOffer + f.Price.ExtraBonus),
|
||||
"basePriceCurrencyCode": "USD",
|
||||
"recurringPrice": 0,
|
||||
"freeDays": 0,
|
||||
"maxBillingCycles": 0,
|
||||
"seller": aid.JSON{},
|
||||
"viewableDate": time.Now().Format(time.RFC3339),
|
||||
"effectiveDate": time.Now().Format(time.RFC3339),
|
||||
"expiryDate": "9999-12-31T23:59:59.999Z",
|
||||
"vatIncluded": true,
|
||||
"isCodeRedemptionOnly": false,
|
||||
"isFeatured": false,
|
||||
"taxSkuId": "FN_Currency",
|
||||
"merchantGroup": "FN_MKT",
|
||||
"priceTier": fmt.Sprintf("%d", DataClient.GetStorefrontCurrencyOfferPrice("USD", f.Price.OriginalOffer + f.Price.ExtraBonus)),
|
||||
"urlSlug": "fortnite--" + f.Title,
|
||||
"roleNamesToGrant": []aid.JSON{},
|
||||
"tags": []aid.JSON{},
|
||||
"purchaseLimit": -1,
|
||||
"ignoreOrder": false,
|
||||
"fulfillToGroup": false,
|
||||
"fraudItemType": "V-Bucks",
|
||||
"shareRevenue": false,
|
||||
"offerType": "OTHERS",
|
||||
"unsearchable": false,
|
||||
"releaseDate": time.Now().Format(time.RFC3339),
|
||||
"releaseOffer": "",
|
||||
"title4Sort": f.Title,
|
||||
"countriesBlacklist": []string{},
|
||||
"selfRefundable": false,
|
||||
"refundType": "NON_REFUNDABLE",
|
||||
"pcReleaseDate": time.Now().Format(time.RFC3339),
|
||||
"priceCalculationMode": "FIXED",
|
||||
"assembleMode": "SINGLE",
|
||||
"publisherDisplayName": "Epic Games",
|
||||
"developerDisplayName": "Epic Games",
|
||||
"visibilityType": "IS_LISTED",
|
||||
"currencyDecimals": 2,
|
||||
"allowPurchaseForPartialOwned": true,
|
||||
"shareRevenueWithUnderageAffiliates": false,
|
||||
"platformWhitelist": []string{},
|
||||
"platformBlacklist": []string{},
|
||||
"partialItemPrerequisiteCheck": false,
|
||||
"upgradeMode": "UPGRADED_WITH_PRICE_FULL",
|
||||
}
|
||||
}
|
||||
|
||||
type FortniteCatalogCosmeticOffer struct {
|
||||
ID string
|
||||
Grants []*FortniteItem
|
||||
TotalPrice int
|
||||
Meta struct {
|
||||
DisplayAssetPath string
|
||||
NewDisplayAssetPath string
|
||||
SectionId string
|
||||
TileSize string
|
||||
Category string
|
||||
ProfileId string
|
||||
}
|
||||
Frontend struct {
|
||||
Title string
|
||||
Description string
|
||||
ShortDescription string
|
||||
}
|
||||
Giftable bool
|
||||
BundleInfo struct {
|
||||
IsBundle bool
|
||||
PricePercent float32
|
||||
}
|
||||
}
|
||||
|
||||
func NewFortniteCatalogSectionOffer() *FortniteCatalogCosmeticOffer {
|
||||
return &FortniteCatalogCosmeticOffer{}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogCosmeticOffer) GenerateID() {
|
||||
for _, item := range f.Grants {
|
||||
f.ID += item.Type.BackendValue + ":" + item.ID + ","
|
||||
}
|
||||
|
||||
f.ID = "v2:/" + aid.Hash([]byte(f.ID))
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogCosmeticOffer) GenerateTotalPrice() {
|
||||
if !f.BundleInfo.IsBundle {
|
||||
f.TotalPrice = DataClient.GetStorefrontCosmeticOfferPrice(f.Grants[0].Rarity.BackendValue, f.Grants[0].Type.BackendValue)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range f.Grants {
|
||||
f.TotalPrice += DataClient.GetStorefrontCosmeticOfferPrice(item.Rarity.BackendValue, item.Rarity.BackendValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogCosmeticOffer) GenerateFortniteCatalogCosmeticOfferResponse() aid.JSON {
|
||||
f.GenerateTotalPrice()
|
||||
|
||||
itemGrantResponse := []aid.JSON{}
|
||||
purchaseRequirementsResponse := []aid.JSON{}
|
||||
|
||||
for _, item := range f.Grants {
|
||||
itemGrantResponse = append(itemGrantResponse, aid.JSON{
|
||||
"templateId": item.Type.BackendValue + ":" + item.ID,
|
||||
"quantity": 1,
|
||||
})
|
||||
|
||||
purchaseRequirementsResponse = append(purchaseRequirementsResponse, aid.JSON{
|
||||
"requirementType": "DenyOnItemOwnership",
|
||||
"requiredId": item.Type.BackendValue + ":" + item.ID,
|
||||
"minQuantity": 1,
|
||||
})
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"devName": uuid.New().String(),
|
||||
"offerId": f.ID,
|
||||
"offerType": "StaticPrice",
|
||||
"prices": []aid.JSON{{
|
||||
"currencyType": "MtxCurrency",
|
||||
"currencySubType": "",
|
||||
"regularPrice": f.TotalPrice,
|
||||
"dynamicRegularPrice": f.TotalPrice,
|
||||
"finalPrice": f.TotalPrice,
|
||||
"basePrice": f.TotalPrice,
|
||||
"saleExpiration": "9999-12-31T23:59:59.999Z",
|
||||
}},
|
||||
"itemGrants": itemGrantResponse,
|
||||
"meta": aid.JSON{
|
||||
"TileSize": f.Meta.TileSize,
|
||||
"SectionId": f.Meta.SectionId,
|
||||
"NewDisplayAssetPath": f.Meta.NewDisplayAssetPath,
|
||||
"DisplayAssetPath": f.Meta.DisplayAssetPath,
|
||||
},
|
||||
"metaInfo": []aid.JSON{
|
||||
{
|
||||
"Key": "TileSize",
|
||||
"Value": f.Meta.TileSize,
|
||||
},
|
||||
{
|
||||
"Key": "SectionId",
|
||||
"Value": f.Meta.SectionId,
|
||||
},
|
||||
{
|
||||
"Key": "NewDisplayAssetPath",
|
||||
"Value": f.Meta.NewDisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"Key": "DisplayAssetPath",
|
||||
"Value": f.Meta.DisplayAssetPath,
|
||||
},
|
||||
},
|
||||
"giftInfo": aid.JSON{
|
||||
"bIsEnabled": f.Giftable,
|
||||
"forcedGiftBoxTemplateId": "",
|
||||
"purchaseRequirements": purchaseRequirementsResponse,
|
||||
"giftRecordIds": []string{},
|
||||
},
|
||||
"purchaseRequirements": purchaseRequirementsResponse,
|
||||
"categories": []string{f.Meta.Category},
|
||||
"title": f.Frontend.Title,
|
||||
"description": f.Frontend.Description,
|
||||
"shortDescription": f.Frontend.ShortDescription,
|
||||
"displayAssetPath": f.Meta.DisplayAssetPath,
|
||||
"appStoreId": []string{},
|
||||
"fufillmentIds": []string{},
|
||||
"dailyLimit": -1,
|
||||
"weeklyLimit": -1,
|
||||
"monthlyLimit": -1,
|
||||
"sortPriority": 0,
|
||||
"catalogGroupPriority": 0,
|
||||
"filterWeight": 0,
|
||||
"refundable": true,
|
||||
}
|
||||
}
|
||||
|
||||
type FortniteCatalogSection struct {
|
||||
Name string
|
||||
Offers []*FortniteCatalogCosmeticOffer
|
||||
}
|
||||
|
||||
func NewFortniteCatalogSection(name string) *FortniteCatalogSection {
|
||||
return &FortniteCatalogSection{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogSection) GenerateFortniteCatalogSectionResponse() aid.JSON {
|
||||
catalogEntiresResponse := []aid.JSON{}
|
||||
for _, offer := range f.Offers {
|
||||
catalogEntiresResponse = append(catalogEntiresResponse, offer.GenerateFortniteCatalogCosmeticOfferResponse())
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"name": f.Name,
|
||||
"catalogEntries": catalogEntiresResponse,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalogSection) GetGroupedOffers() map[string][]*FortniteCatalogCosmeticOffer {
|
||||
groupedOffers := map[string][]*FortniteCatalogCosmeticOffer{}
|
||||
|
||||
for _, offer := range f.Offers {
|
||||
if groupedOffers[offer.Meta.Category] == nil {
|
||||
groupedOffers[offer.Meta.Category] = []*FortniteCatalogCosmeticOffer{}
|
||||
}
|
||||
|
||||
groupedOffers[offer.Meta.Category] = append(groupedOffers[offer.Meta.Category], offer)
|
||||
}
|
||||
|
||||
return groupedOffers
|
||||
}
|
||||
|
||||
type FortniteCatalog struct {
|
||||
Sections []*FortniteCatalogSection
|
||||
MoneyOffers []*FortniteCatalogCurrencyOffer
|
||||
StarterPacks []*FortniteCatalogStarterPack
|
||||
}
|
||||
|
||||
func NewFortniteCatalog() *FortniteCatalog {
|
||||
return &FortniteCatalog{
|
||||
Sections: []*FortniteCatalogSection{},
|
||||
MoneyOffers: []*FortniteCatalogCurrencyOffer{},
|
||||
StarterPacks: []*FortniteCatalogStarterPack{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalog) AddSection(section *FortniteCatalogSection) {
|
||||
f.Sections = append(f.Sections, section)
|
||||
}
|
||||
|
||||
func (f *FortniteCatalog) AddMoneyOffer(offer *FortniteCatalogCurrencyOffer) {
|
||||
offer.Priority = -len(f.MoneyOffers)
|
||||
f.MoneyOffers = append(f.MoneyOffers, offer)
|
||||
}
|
||||
|
||||
func (f *FortniteCatalog) AddStarterPack(pack *FortniteCatalogStarterPack) {
|
||||
pack.Priority = -len(f.StarterPacks)
|
||||
f.StarterPacks = append(f.StarterPacks, pack)
|
||||
}
|
||||
|
||||
func (f *FortniteCatalog) GenerateFortniteCatalogResponse() aid.JSON {
|
||||
catalogSectionsResponse := []aid.JSON{}
|
||||
|
||||
for _, section := range f.Sections {
|
||||
catalogSectionsResponse = append(catalogSectionsResponse, section.GenerateFortniteCatalogSectionResponse())
|
||||
}
|
||||
|
||||
currencyOffersResponse := []aid.JSON{}
|
||||
for _, offer := range f.MoneyOffers {
|
||||
currencyOffersResponse = append(currencyOffersResponse, offer.GenerateFortniteCatalogCurrencyOfferResponse())
|
||||
}
|
||||
catalogSectionsResponse = append(catalogSectionsResponse, aid.JSON{
|
||||
"name": "CurrencyStorefront",
|
||||
"catalogEntries": currencyOffersResponse,
|
||||
})
|
||||
|
||||
starterPacksResponse := []aid.JSON{}
|
||||
for _, pack := range f.StarterPacks {
|
||||
for _, season := range pack.SeasonsAllowed {
|
||||
if season == aid.Config.Fortnite.Season {
|
||||
starterPacksResponse = append(starterPacksResponse, pack.GenerateFortniteCatalogStarterPackResponse())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
catalogSectionsResponse = append(catalogSectionsResponse, aid.JSON{
|
||||
"name": "BRStarterKits",
|
||||
"catalogEntries": starterPacksResponse,
|
||||
})
|
||||
|
||||
return aid.JSON{
|
||||
"storefronts": catalogSectionsResponse,
|
||||
"refreshIntervalHrs": 24,
|
||||
"dailyPurchaseHrs": 24,
|
||||
"expiration": "9999-12-31T23:59:59.999Z",
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FortniteCatalog) FindCosmeticOfferById(id string) *FortniteCatalogCosmeticOffer {
|
||||
for _, section := range f.Sections {
|
||||
for _, offer := range section.Offers {
|
||||
if offer.ID == id {
|
||||
return offer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FortniteCatalog) FindCurrencyOfferById(id string) *FortniteCatalogCurrencyOffer {
|
||||
for _, offer := range f.MoneyOffers {
|
||||
if offer.ID == id {
|
||||
return offer
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FortniteCatalog) FindStarterPackById(id string) *FortniteCatalogStarterPack {
|
||||
for _, pack := range f.StarterPacks {
|
||||
if pack.ID == id {
|
||||
return pack
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRandomFortniteCatalog() *FortniteCatalog {
|
||||
aid.SetRandom(rand.New(rand.NewSource(int64(aid.Config.Fortnite.ShopSeed) + aid.CurrentDayUnix())))
|
||||
catalog := NewFortniteCatalog()
|
||||
|
||||
daily := NewFortniteCatalogSection("BRDailyStorefront")
|
||||
for len(daily.Offers) < DataClient.GetStorefrontDailyItemCount(aid.Config.Fortnite.Season) {
|
||||
entry := newCosmeticOfferFromFortniteitem(GetRandomItemWithDisplayAssetOfNotType("AthenaCharacter"), false)
|
||||
entry.Meta.SectionId = "Daily"
|
||||
daily.Offers = append(daily.Offers, entry)
|
||||
}
|
||||
catalog.AddSection(daily)
|
||||
|
||||
weekly := NewFortniteCatalogSection("BRWeeklyStorefront")
|
||||
for len(weekly.GetGroupedOffers()) < DataClient.GetStorefrontWeeklySetCount(aid.Config.Fortnite.Season) {
|
||||
set := GetRandomSet()
|
||||
for _, item := range set.Items {
|
||||
if item.DisplayAssetPath == "" || item.DisplayAssetPath2 == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
entry := newCosmeticOfferFromFortniteitem(item, true)
|
||||
entry.Meta.Category = set.BackendName
|
||||
entry.Meta.SectionId = "Featured"
|
||||
weekly.Offers = append(weekly.Offers, entry)
|
||||
}
|
||||
}
|
||||
catalog.AddSection(weekly)
|
||||
|
||||
if aid.Config.Fortnite.EnableVBucks {
|
||||
smallCurrencyOffer := newCurrencyOfferFromName("Small Currency Pack", 1000, 0)
|
||||
smallCurrencyOffer.Meta.IconSize = "XSmall"
|
||||
smallCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack1000"
|
||||
catalog.AddMoneyOffer(smallCurrencyOffer)
|
||||
|
||||
mediumCurrencyOffer := newCurrencyOfferFromName("Medium Currency Pack", 2000, 800)
|
||||
mediumCurrencyOffer.Meta.IconSize = "Small"
|
||||
mediumCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack2800"
|
||||
mediumCurrencyOffer.Meta.BannerOverride = "12PercentExtra"
|
||||
catalog.AddMoneyOffer(mediumCurrencyOffer)
|
||||
|
||||
intermediateCurrencyOffer := newCurrencyOfferFromName("Intermediate Currency Pack", 6000, 1500)
|
||||
intermediateCurrencyOffer.Meta.IconSize = "Medium"
|
||||
intermediateCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack7500"
|
||||
intermediateCurrencyOffer.Meta.BannerOverride = "25PercentExtra"
|
||||
catalog.AddMoneyOffer(intermediateCurrencyOffer)
|
||||
|
||||
jumboCurrencyOffer := newCurrencyOfferFromName("Jumbo Currency Pack", 10000, 3500)
|
||||
jumboCurrencyOffer.Meta.IconSize = "XLarge"
|
||||
jumboCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack13500"
|
||||
jumboCurrencyOffer.Meta.BannerOverride = "35PercentExtra"
|
||||
catalog.AddMoneyOffer(jumboCurrencyOffer)
|
||||
|
||||
rogueAgentStarterPack := newStarterPackOfferFromName("The Rogue Agent Pack", 499, []*FortniteCatalogStarterPackGrant{
|
||||
NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_090_Athena_Commando_M_Tactical", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaBackpack:BID_030_TacticalRogue", 1),
|
||||
NewFortniteCatalogStarterPackGrant("Currency:MtxPurchased", 600),
|
||||
}...)
|
||||
rogueAgentStarterPack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_090_Athena_Commando_M_Tactical.DA_Featured_CID_090_Athena_Commando_M_Tactical"
|
||||
rogueAgentStarterPack.SeasonsAllowed = []int{4}
|
||||
catalog.AddStarterPack(rogueAgentStarterPack)
|
||||
|
||||
wingmanStarterPack := newStarterPackOfferFromName("The Wingman Pack", 499, []*FortniteCatalogStarterPackGrant{
|
||||
NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_139_Athena_Commando_M_FighterPilot", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaBackpack:BID_056_FighterPilot", 1),
|
||||
NewFortniteCatalogStarterPackGrant("Currency:MtxPurchased", 600),
|
||||
}...)
|
||||
wingmanStarterPack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_139_Athena_Commando_M_FighterPilot.DA_Featured_CID_139_Athena_Commando_M_FighterPilot"
|
||||
wingmanStarterPack.SeasonsAllowed = []int{4, 5}
|
||||
catalog.AddStarterPack(wingmanStarterPack)
|
||||
|
||||
aceStarterPack := newStarterPackOfferFromName("The Ace Pack", 499, []*FortniteCatalogStarterPackGrant{
|
||||
NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_195_Athena_Commando_F_Bling", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaBackpack:BID_101_BlingFemale", 1),
|
||||
NewFortniteCatalogStarterPackGrant("Currency:MtxPurchased", 600),
|
||||
}...)
|
||||
aceStarterPack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_195_Athena_Commando_F_Bling.DA_Featured_CID_195_Athena_Commando_F_Bling"
|
||||
aceStarterPack.SeasonsAllowed = []int{5, 6}
|
||||
catalog.AddStarterPack(aceStarterPack)
|
||||
|
||||
summitStarterPack := newStarterPackOfferFromName("The Summit Striker Pack", 499, []*FortniteCatalogStarterPackGrant{
|
||||
NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_253_Athena_Commando_M_MilitaryFashion2", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaBackpack:BID_134_MilitaryFashion", 1),
|
||||
NewFortniteCatalogStarterPackGrant("Currency:MtxPurchased", 600),
|
||||
}...)
|
||||
summitStarterPack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_253_Athena_Commando_M_MilitaryFashion2.DA_Featured_CID_253_Athena_Commando_M_MilitaryFashion2"
|
||||
summitStarterPack.SeasonsAllowed = []int{6, 7}
|
||||
catalog.AddStarterPack(summitStarterPack)
|
||||
|
||||
cobaltStarterPack := newStarterPackOfferFromName("The Cobalt Pack", 499, []*FortniteCatalogStarterPackGrant{
|
||||
NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_327_Athena_Commando_M_BlueMystery", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaBackpack:BID_203_BlueMystery", 1),
|
||||
NewFortniteCatalogStarterPackGrant("Currency:MtxPurchased", 600),
|
||||
}...)
|
||||
cobaltStarterPack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_327_Athena_Commando_M_BlueMystery.DA_Featured_CID_327_Athena_Commando_M_BlueMystery"
|
||||
cobaltStarterPack.SeasonsAllowed = []int{7}
|
||||
catalog.AddStarterPack(cobaltStarterPack)
|
||||
|
||||
lagunaStarterPack := newStarterPackOfferFromName("The Laguna Pack", 499, []*FortniteCatalogStarterPackGrant{
|
||||
NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_367_Athena_Commando_F_Tropical", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaBackpack:BID_231_TropicalFemale", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaItemWrap:Wrap_033_TropicalGirl", 1),
|
||||
NewFortniteCatalogStarterPackGrant("Currency:MtxPurchased", 600),
|
||||
}...)
|
||||
lagunaStarterPack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_367_Athena_Commando_F_Tropical.DA_Featured_CID_367_Athena_Commando_F_Tropical"
|
||||
lagunaStarterPack.SeasonsAllowed = []int{8}
|
||||
catalog.AddStarterPack(lagunaStarterPack)
|
||||
|
||||
wildeStarterPack := newStarterPackOfferFromName("The Wilde Pack", 499, []*FortniteCatalogStarterPackGrant{
|
||||
NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_420_Athena_Commando_F_WhiteTiger", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaBackpack:BID_277_WhiteTiger", 1),
|
||||
NewFortniteCatalogStarterPackGrant("Currency:MtxPurchased", 600),
|
||||
}...)
|
||||
wildeStarterPack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_420_Athena_Commando_F_WhiteTiger.DA_Featured_CID_420_Athena_Commando_F_WhiteTiger"
|
||||
wildeStarterPack.SeasonsAllowed = []int{9}
|
||||
catalog.AddStarterPack(wildeStarterPack)
|
||||
|
||||
redStrikePack := newStarterPackOfferFromName("The Red Strike Pack", 499, []*FortniteCatalogStarterPackGrant{
|
||||
NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_384_Athena_Commando_M_StreetAssassin", 1),
|
||||
NewFortniteCatalogStarterPackGrant("AthenaBackpack:BID_247_StreetAssassin", 1),
|
||||
NewFortniteCatalogStarterPackGrant("Currency:MtxPurchased", 600),
|
||||
}...)
|
||||
redStrikePack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_384_Athena_Commando_M_StreetAssasin.DA_Featured_CID_384_Athena_Commando_M_StreetAssasin"
|
||||
redStrikePack.SeasonsAllowed = []int{10}
|
||||
catalog.AddStarterPack(redStrikePack)
|
||||
|
||||
// Below is an example of a custom starter pack
|
||||
// Uncomment to use.
|
||||
// snowCustomPack := newStarterPackOfferFromName("Snow Gift", 0, []*FortniteCatalogStarterPackGrant{
|
||||
// NewFortniteCatalogStarterPackGrant("AthenaCharacter:CID_384_Athena_Commando_M_StreetAssassin", 1),
|
||||
// }...)
|
||||
// snowCustomPack.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_TBD_Athena_Commando_M_RaptorArcticCamo_Bundle.DA_Featured_CID_TBD_Athena_Commando_M_RaptorArcticCamo_Bundle"
|
||||
// snowCustomPack.SeasonsAllowed = []int{1,2,3,4,5,6,7,8,9,10}
|
||||
// snowCustomPack.Meta.OriginalOffer = 1000
|
||||
// snowCustomPack.Meta.ExtraBonus = 500
|
||||
// snowCustomPack.Description = ""
|
||||
// snowCustomPack.LongDescription = "Thank you for using Snow! Here's a special offer for you!"
|
||||
// catalog.AddStarterPack(snowCustomPack)
|
||||
}
|
||||
return catalog
|
||||
}
|
||||
|
||||
func newCosmeticOfferFromFortniteitem(fortniteItem *FortniteItem, addAssets bool) *FortniteCatalogCosmeticOffer {
|
||||
displayAsset := regexp.MustCompile(`[^/]+$`).FindString(fortniteItem.DisplayAssetPath)
|
||||
|
||||
entry := NewFortniteCatalogSectionOffer()
|
||||
entry.Meta.TileSize = "Small"
|
||||
if fortniteItem.Type.BackendValue == "AthenaCharacter" {
|
||||
entry.Meta.TileSize = "Normal"
|
||||
}
|
||||
if addAssets {
|
||||
entry.Meta.NewDisplayAssetPath = "/Game/Catalog/NewDisplayAssets/" + fortniteItem.DisplayAssetPath2 + "." + fortniteItem.DisplayAssetPath2
|
||||
if displayAsset != "" {
|
||||
entry.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/" + displayAsset + "." + displayAsset
|
||||
}
|
||||
}
|
||||
entry.Meta.ProfileId = "athena"
|
||||
entry.Giftable = true
|
||||
entry.Grants = append(entry.Grants, fortniteItem)
|
||||
entry.GenerateTotalPrice()
|
||||
entry.GenerateID()
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
func newCurrencyOfferFromName(name string, original, bonus int) *FortniteCatalogCurrencyOffer {
|
||||
formattedPrice := aid.FormatNumber(original + bonus)
|
||||
offer := NewFortniteCatalogCurrencyOffer(original, bonus)
|
||||
offer.Meta.IconSize = "Small"
|
||||
offer.Meta.CurrencyAnalyticsName = name
|
||||
offer.DevName = name
|
||||
offer.Title = formattedPrice + " V-Bucks"
|
||||
offer.Description = "Buy " + formattedPrice + " Fortnite V-Bucks, the in-game currency that can be spent in Fortnite Battle Royale and Creative modes. You can purchase new customization items like Outfits, Gliders, Pickaxes, Emotes, Wraps and the latest season's Battle Pass! Gliders and Contrails may not be used in Save the World mode."
|
||||
offer.LongDescription = "Buy " + formattedPrice + " Fortnite V-Bucks, the in-game currency that can be spent in Fortnite Battle Royale and Creative modes. You can purchase new customization items like Outfits, Gliders, Pickaxes, Emotes, Wraps and the latest season's Battle Pass! Gliders and Contrails may not be used in Save the World mode.\n\nAll V-Bucks purchased on the Epic Games Store are not redeemable or usable on Nintendo Switch™."
|
||||
|
||||
return offer
|
||||
}
|
||||
|
||||
func newStarterPackOfferFromName(name string, totalPrice int, grants ...*FortniteCatalogStarterPackGrant) *FortniteCatalogStarterPack {
|
||||
mainString := "Jump into Fortnite Battle Royale with the " + strings.ReplaceAll(name, "The ", "") + ". Includes:\n\n- 600 V-Bucks"
|
||||
|
||||
for _, grant := range grants {
|
||||
fortniteItem := DataClient.FortniteItems[strings.Split(grant.TemplateID, ":")[1]]
|
||||
if fortniteItem != nil {
|
||||
mainString += "\n- " + fortniteItem.Name + " " + fortniteItem.Type.DisplayValue + " - Battle Royale Only"
|
||||
}
|
||||
}
|
||||
|
||||
offer := NewFortniteCatalogStarterPack(totalPrice)
|
||||
offer.DevName = name + "StarterPack"
|
||||
offer.Title = name
|
||||
offer.Description = mainString
|
||||
offer.LongDescription = mainString + "\n\nV-Bucks are an in-game currency that can be spent in both the Battle Royale PvP mode and the Save the World PvE campaign. In Battle Royale, you can use V-bucks to purchase new customization items like outfits, emotes, pickaxes, gliders, and more! In Save the World you can purchase Llama Pinata card packs that contain weapon, trap and gadget schematics as well as new Heroes and more! \n\nNote: Items do not transfer between the Battle Royale mode and the Save the World campaign."
|
||||
offer.Meta.OriginalOffer = 500
|
||||
offer.Meta.ExtraBonus = 100
|
||||
|
||||
for _, grant := range grants {
|
||||
offer.AddGrant(grant)
|
||||
}
|
||||
|
||||
return offer
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package fortnite
|
||||
|
||||
type FortniteVariantChannel struct {
|
||||
Tag string `json:"tag"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
type FortniteVariant struct {
|
||||
Channel string `json:"channel"`
|
||||
Type string `json:"type"`
|
||||
Options []FortniteVariantChannel `json:"options"`
|
||||
}
|
||||
|
||||
type FortniteItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type struct {
|
||||
Value string `json:"value"`
|
||||
DisplayValue string `json:"displayValue"`
|
||||
BackendValue string `json:"backendValue"`
|
||||
} `json:"type"`
|
||||
Rarity struct {
|
||||
Value string `json:"value"`
|
||||
DisplayValue string `json:"displayValue"`
|
||||
BackendValue string `json:"backendValue"`
|
||||
} `json:"rarity"`
|
||||
Series struct {
|
||||
Value string `json:"value"`
|
||||
Image string `json:"image"`
|
||||
BackendValue string `json:"backendValue"`
|
||||
} `json:"series"`
|
||||
Set struct {
|
||||
Value string `json:"value"`
|
||||
Text string `json:"text"`
|
||||
BackendValue string `json:"backendValue"`
|
||||
} `json:"set"`
|
||||
Introduction struct {
|
||||
Chapter string `json:"chapter"`
|
||||
Season string `json:"season"`
|
||||
Text string `json:"text"`
|
||||
BackendValue int `json:"backendValue"`
|
||||
} `json:"introduction"`
|
||||
Images struct {
|
||||
Icon string `json:"icon"`
|
||||
Featured string `json:"featured"`
|
||||
SmallIcon string `json:"smallIcon"`
|
||||
Other map[string]string `json:"other"`
|
||||
} `json:"images"`
|
||||
Variants []FortniteVariant `json:"variants"`
|
||||
GameplayTags []string `json:"gameplayTags"`
|
||||
SearchTags []string `json:"searchTags"`
|
||||
MetaTags []string `json:"metaTags"`
|
||||
ShowcaseVideo string `json:"showcaseVideo"`
|
||||
DynamicPakID string `json:"dynamicPakId"`
|
||||
DisplayAssetPath string `json:"displayAssetPath"`
|
||||
DisplayAssetPath2 string
|
||||
ItemPreviewHeroPath string `json:"itemPreviewHeroPath"`
|
||||
Backpack *FortniteItem `json:"backpack"`
|
||||
Path string `json:"path"`
|
||||
Added string `json:"added"`
|
||||
ShopHistory []string `json:"shopHistory"`
|
||||
BattlePass bool `json:"battlePass"`
|
||||
}
|
||||
|
||||
type FortniteSet struct {
|
||||
BackendName string `json:"backendName"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Items []*FortniteItem `json:"items"`
|
||||
}
|
||||
|
||||
type FortniteCosmeticsResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data []FortniteItem `json:"data"`
|
||||
}
|
||||
|
||||
type SnowCosmeticVariantToken struct {
|
||||
Grants []struct {
|
||||
Channel string `json:"channel"`
|
||||
Value string `json:"value"`
|
||||
} `json:"grants"`
|
||||
Item string `json:"item"`
|
||||
Name string `json:"name"`
|
||||
Gift bool `json:"gift"`
|
||||
Equip bool `json:"equip"`
|
||||
Unseen bool `json:"unseen"`
|
||||
}
|
||||
|
||||
type FortniteVariantToken struct {
|
||||
Grants []struct {
|
||||
Channel string `json:"channel"`
|
||||
Value string `json:"value"`
|
||||
} `json:"grants"`
|
||||
Item *FortniteItem `json:"item"`
|
||||
Name string `json:"name"`
|
||||
Gift bool `json:"gift"`
|
||||
Equip bool `json:"equip"`
|
||||
Unseen bool `json:"unseen"`
|
||||
}
|
159
fortnite/typings.go
Normal file
159
fortnite/typings.go
Normal file
|
@ -0,0 +1,159 @@
|
|||
package fortnite
|
||||
|
||||
import (
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/person"
|
||||
)
|
||||
|
||||
type APICosmeticDefinitionVariant struct {
|
||||
Tag string `json:"tag"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
type APICosmeticDefinitionVariantChannel struct {
|
||||
Channel string `json:"channel"`
|
||||
Type string `json:"type"`
|
||||
Options []APICosmeticDefinitionVariant `json:"options"`
|
||||
}
|
||||
|
||||
type APICosmeticDefinition struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type struct {
|
||||
Value string `json:"value"`
|
||||
DisplayValue string `json:"displayValue"`
|
||||
BackendValue string `json:"backendValue"`
|
||||
} `json:"type"`
|
||||
Rarity struct {
|
||||
Value string `json:"value"`
|
||||
DisplayValue string `json:"displayValue"`
|
||||
BackendValue string `json:"backendValue"`
|
||||
} `json:"rarity"`
|
||||
Series struct {
|
||||
Value string `json:"value"`
|
||||
Image string `json:"image"`
|
||||
BackendValue string `json:"backendValue"`
|
||||
} `json:"series"`
|
||||
Set struct {
|
||||
Value string `json:"value"`
|
||||
Text string `json:"text"`
|
||||
BackendValue string `json:"backendValue"`
|
||||
} `json:"set"`
|
||||
Introduction struct {
|
||||
Chapter string `json:"chapter"`
|
||||
Season string `json:"season"`
|
||||
Text string `json:"text"`
|
||||
BackendValue int `json:"backendValue"`
|
||||
} `json:"introduction"`
|
||||
Images struct {
|
||||
Icon string `json:"icon"`
|
||||
Featured string `json:"featured"`
|
||||
SmallIcon string `json:"smallIcon"`
|
||||
Other map[string]string `json:"other"`
|
||||
} `json:"images"`
|
||||
Variants []APICosmeticDefinitionVariantChannel `json:"variants"`
|
||||
GameplayTags []string `json:"gameplayTags"`
|
||||
SearchTags []string `json:"searchTags"`
|
||||
MetaTags []string `json:"metaTags"`
|
||||
ShowcaseVideo string `json:"showcaseVideo"`
|
||||
DynamicPakID string `json:"dynamicPakId"`
|
||||
DisplayAssetPath string `json:"displayAssetPath"`
|
||||
NewDisplayAssetPath string
|
||||
ItemPreviewHeroPath string `json:"itemPreviewHeroPath"`
|
||||
BackpackDefinition *APICosmeticDefinition `json:"backpack"`
|
||||
Path string `json:"path"`
|
||||
Added string `json:"added"`
|
||||
ShopHistory []string `json:"shopHistory"`
|
||||
BattlePass bool `json:"battlePass"`
|
||||
}
|
||||
|
||||
type APISetDefinition struct {
|
||||
BackendName string `json:"backendName"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Items []*APICosmeticDefinition `json:"items"`
|
||||
}
|
||||
|
||||
type APICosmeticsResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data []APICosmeticDefinition `json:"data"`
|
||||
}
|
||||
|
||||
type SnowCosmeticVariantDefinition struct {
|
||||
Grants []struct {
|
||||
Channel string `json:"channel"`
|
||||
Value string `json:"value"`
|
||||
} `json:"grants"`
|
||||
Item string `json:"item"`
|
||||
Name string `json:"name"`
|
||||
Gift bool `json:"gift"`
|
||||
Equip bool `json:"equip"`
|
||||
Unseen bool `json:"unseen"`
|
||||
}
|
||||
|
||||
type FortniteVariantToken struct {
|
||||
Grants []struct {
|
||||
Channel string `json:"channel"`
|
||||
Value string `json:"value"`
|
||||
} `json:"grants"`
|
||||
Item *APICosmeticDefinition `json:"item"`
|
||||
Name string `json:"name"`
|
||||
Gift bool `json:"gift"`
|
||||
Equip bool `json:"equip"`
|
||||
Unseen bool `json:"unseen"`
|
||||
}
|
||||
|
||||
type ItemGrant struct {
|
||||
TemplateID string
|
||||
Quantity int
|
||||
ProfileType string
|
||||
}
|
||||
|
||||
func NewItemGrant(templateId string, quantity int) *ItemGrant {
|
||||
return &ItemGrant{
|
||||
TemplateID: templateId,
|
||||
Quantity: quantity,
|
||||
}
|
||||
}
|
||||
|
||||
type LootResultLoot struct {
|
||||
TemplateID string
|
||||
ItemID string
|
||||
Quantity int
|
||||
ItemProfileType string
|
||||
}
|
||||
|
||||
type LootResult struct {
|
||||
Items []*LootResultLoot
|
||||
}
|
||||
|
||||
func NewLootResult() *LootResult {
|
||||
return &LootResult{
|
||||
Items: make([]*LootResultLoot, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LootResult) AddItem(i *person.Item) {
|
||||
l.Items = append(l.Items, &LootResultLoot{
|
||||
TemplateID: i.TemplateID,
|
||||
ItemID: i.ID,
|
||||
Quantity: i.Quantity,
|
||||
ItemProfileType: i.ProfileType,
|
||||
})
|
||||
}
|
||||
|
||||
func (l *LootResult) GenerateFortniteLootResultEntry() []aid.JSON {
|
||||
loot := []aid.JSON{}
|
||||
|
||||
for _, item := range l.Items {
|
||||
loot = append(loot, aid.JSON{
|
||||
"itemType": item.TemplateID,
|
||||
"itemGuid": item.ItemID,
|
||||
"itemProfile": item.ItemProfileType,
|
||||
"quantity": item.Quantity,
|
||||
})
|
||||
}
|
||||
|
||||
return loot
|
||||
}
|
4
go.sum
4
go.sum
|
@ -85,8 +85,6 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/karrick/tparse v2.4.2+incompatible h1:+cW306qKAzrASC5XieHkgN7/vPaGKIuK62Q7nI7DIRc=
|
||||
github.com/karrick/tparse v2.4.2+incompatible/go.mod h1:ASPA+vrIcN1uEW6BZg8vfWbzm69ODPSYZPU6qJyfdK0=
|
||||
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
|
||||
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
|
@ -98,8 +96,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
|
|
@ -100,7 +100,7 @@ func PostTokenExchangeCode(c *fiber.Ctx, body *FortniteTokenBody) error {
|
|||
return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Exchange Code"))
|
||||
}
|
||||
|
||||
if expire.Add(time.Hour).Before(time.Now()) {
|
||||
if expire.Add(time.Minute).Before(time.Now()) {
|
||||
return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Exchange Code"))
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,13 @@ package handlers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/fortnite"
|
||||
p "github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/shop"
|
||||
"github.com/ectrc/snow/socket"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
@ -33,6 +33,11 @@ var (
|
|||
"RemoveGiftBox": clientRemoveGiftBoxAction,
|
||||
"SetAffiliateName": clientSetAffiliateNameAction,
|
||||
"SetReceiveGiftsEnabled": clientSetReceiveGiftsEnabledAction,
|
||||
"VerifyRealMoneyPurchase": clientVerifyRealMoneyPurchaseAction,
|
||||
}
|
||||
|
||||
repeatingActions = []func(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error{
|
||||
clientCalculateTierAndLevel,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -66,6 +71,12 @@ func PostClientProfileAction(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
for _, action := range repeatingActions {
|
||||
if err := action(c, person, profile, ¬ifications); err != nil {
|
||||
return c.Status(400).JSON(aid.ErrorBadRequest(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
for key, profileSnapshot := range profileSnapshots {
|
||||
profile := person.GetProfileFromType(key)
|
||||
if profile == nil {
|
||||
|
@ -78,13 +89,8 @@ func PostClientProfileAction(c *fiber.Ctx) error {
|
|||
|
||||
profile.Diff(profileSnapshot)
|
||||
}
|
||||
|
||||
revision, _ := strconv.Atoi(c.Query("rvn"))
|
||||
if revision == -1 {
|
||||
revision = profile.Revision
|
||||
}
|
||||
revision++
|
||||
profile.Revision = revision
|
||||
|
||||
profile.Revision = aid.Ternary[int](c.QueryInt("rvn") == -1, profile.Revision, c.QueryInt("rvn"))+1
|
||||
go profile.Save()
|
||||
delete(profileSnapshots, profile.Type)
|
||||
|
||||
|
@ -94,11 +100,11 @@ func PostClientProfileAction(c *fiber.Ctx) error {
|
|||
if profile == nil {
|
||||
continue
|
||||
}
|
||||
profile.Revision++
|
||||
|
||||
|
||||
if len(profile.Changes) == 0 {
|
||||
continue
|
||||
}
|
||||
profile.Revision++
|
||||
|
||||
multiUpdate = append(multiUpdate, aid.JSON{
|
||||
"profileId": profile.Type,
|
||||
|
@ -126,11 +132,32 @@ func PostClientProfileAction(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func clientQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
accountLevel := 0
|
||||
person.AllSeasonsStats.Range(func(key string, value *p.SeasonStats) bool {
|
||||
accountLevel += fortnite.DataClient.SnowSeason.GetSeasonLevel(value)
|
||||
return true
|
||||
})
|
||||
|
||||
profile.CreateFullProfileUpdateChange()
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientClientQuestLoginAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
// _g := p.NewGift("GiftBox:GB_FortnitemaresChallenges", 1, "", "")
|
||||
// _g.AddLoot(p.NewItemWithType("Token:FoundersPackDailyRewardToken", 1, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("Token:MysteryToken", 1, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("Token:AccountInventoryBonus", 1, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("Token:DeniedOrDisabledCosmeticPlaceholderToken", 1, "athena"))
|
||||
// _g.AddLoot(p.NewItemWithType("Token:WorldInventoryBonus", 23, "athena"))
|
||||
// _g.AddLoot(p.NewItemWithType("Token:CTF_Dom_Key", 1, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("FortIngredient:Ingredient_Crystal_ShadowShard", 32, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("Ingredient:Ingredient_Crystal_ShadowShard", 4232, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("AccountResource:AthenaBattleStar", 3, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("AccountResource:AthenaSeasonalXP", 2, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("HomebaseNode:QuestReward_BuildingUpgradeLevel2", 5, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("FortHomebaseNode:QuestReward_BuildingUpgradeLevel2", 8, "common_core"))
|
||||
// _g.AddLoot(p.NewItemWithType("Token:NeighborhoodCurrency", 2, "common_core"))
|
||||
// person.CommonCoreProfile.Gifts.AddGift(_g).Save()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -601,66 +628,73 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
|
|||
return fmt.Errorf("invalid Body")
|
||||
}
|
||||
|
||||
shop := fortnite.NewRandomFortniteCatalog()
|
||||
offer := shop.FindCosmeticOfferById(body.OfferID)
|
||||
if offer == nil {
|
||||
storefront := shop.GetShop()
|
||||
offerRaw, type_ := storefront.GetOfferByID(body.OfferID)
|
||||
if offerRaw == nil {
|
||||
return fmt.Errorf("offer not found")
|
||||
}
|
||||
|
||||
if offer.TotalPrice != body.ExpectedTotalPrice {
|
||||
return fmt.Errorf("invalid price")
|
||||
switch type_ {
|
||||
case shop.StorefrontCatalogOfferEnumItem:
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeItem)
|
||||
if (offer.Price.FinalPrice * body.PurchaseQuantity) != body.ExpectedTotalPrice {
|
||||
return fmt.Errorf("invalid price")
|
||||
}
|
||||
case shop.StorefrontCatalogOfferEnumBattlePass:
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeBattlePass)
|
||||
if (offer.Price.FinalPrice * body.PurchaseQuantity) != body.ExpectedTotalPrice {
|
||||
return fmt.Errorf("invalid price")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid offer type")
|
||||
}
|
||||
|
||||
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if vbucks == nil {
|
||||
return fmt.Errorf("vbucks not found")
|
||||
purchaseLookup := map[shop.StorefrontCatalogOfferEnum]func(quantity int, offerRaw interface{}, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error{
|
||||
shop.StorefrontCatalogOfferEnumItem: clientPurchaseCatalogItemEntryAction,
|
||||
shop.StorefrontCatalogOfferEnumBattlePass: clientPurchaseCatalogBattlePassEntryAction,
|
||||
}
|
||||
|
||||
profile0Vbucks := person.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if profile0Vbucks == nil {
|
||||
return fmt.Errorf("profile0vbucks not found")
|
||||
if purchaseFunc, ok := purchaseLookup[type_]; ok {
|
||||
return purchaseFunc(body.PurchaseQuantity, offerRaw, person, profile, notifications)
|
||||
}
|
||||
|
||||
if vbucks.Quantity < body.ExpectedTotalPrice {
|
||||
return fmt.Errorf("not enough vbucks")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
vbucks.Quantity -= body.ExpectedTotalPrice
|
||||
profile0Vbucks.Quantity = vbucks.Quantity
|
||||
vbucks.Save()
|
||||
profile0Vbucks.Save()
|
||||
|
||||
if offer.Meta.ProfileId != "athena" {
|
||||
return fmt.Errorf("save the world not implemeted yet")
|
||||
func clientPurchaseCatalogItemEntryAction(quantity int, offerRaw interface{}, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeItem)
|
||||
for _, grant := range offer.Rewards {
|
||||
if grant.ProfileType != shop.ShopGrantProfileTypeAthena {
|
||||
return fmt.Errorf("save the world not implemeted yet")
|
||||
}
|
||||
}
|
||||
person.TakeAndSyncVbucks(offer.Price.FinalPrice * quantity)
|
||||
|
||||
loot := []aid.JSON{}
|
||||
purchase := p.NewPurchase(body.OfferID, body.ExpectedTotalPrice)
|
||||
for i := 0; i < body.PurchaseQuantity; i++ {
|
||||
for _, grant := range offer.Grants {
|
||||
templateId := grant.Type.BackendValue + ":" + grant.ID
|
||||
if profile.Items.GetItemByTemplateID(templateId) != nil {
|
||||
item := profile.Items.GetItemByTemplateID(templateId)
|
||||
item.Quantity++
|
||||
go item.Save()
|
||||
purchase := p.NewPurchase(offer.OfferID, offer.Price.FinalPrice)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
item := p.NewItem(templateId, 1)
|
||||
person.AthenaProfile.Items.AddItem(item)
|
||||
purchase.AddLoot(item)
|
||||
|
||||
loot = append(loot, aid.JSON{
|
||||
"itemType": item.TemplateID,
|
||||
"itemGuid": item.ID,
|
||||
"quantity": item.Quantity,
|
||||
"itemProfile": offer.Meta.ProfileId,
|
||||
})
|
||||
groupedRewards := map[string]int{}
|
||||
for i := 0; i < quantity; i++ {
|
||||
for _, grant := range offer.Rewards {
|
||||
groupedRewards[grant.TemplateID] += grant.Quantity
|
||||
}
|
||||
}
|
||||
|
||||
person.AthenaProfile.Purchases.AddPurchase(purchase).Save()
|
||||
for templateID, quantity := range groupedRewards {
|
||||
r, err := fortnite.GrantToPerson(person, fortnite.NewItemGrant(templateID, quantity))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
loot = append(loot, r.GenerateFortniteLootResultEntry()...)
|
||||
}
|
||||
|
||||
for _, item := range loot {
|
||||
purchaseItem := p.NewItem(item["itemType"].(string), 1)
|
||||
purchaseItem.ID = item["itemGuid"].(string)
|
||||
purchaseItem.ProfileType = item["itemProfile"].(string)
|
||||
purchase.AddLoot(purchaseItem)
|
||||
}
|
||||
|
||||
*notifications = append(*notifications, aid.JSON{
|
||||
"type": "CatalogPurchase",
|
||||
|
@ -669,21 +703,56 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
|
|||
},
|
||||
"primary": true,
|
||||
})
|
||||
person.AthenaProfile.Purchases.AddPurchase(purchase).Save()
|
||||
|
||||
affiliate := person.CommonCoreProfile.Attributes.GetAttributeByKey("mtx_affiliate")
|
||||
if affiliate == nil {
|
||||
return c.Status(400).JSON(aid.ErrorBadRequest("Invalid affiliate attribute"))
|
||||
return nil
|
||||
}
|
||||
|
||||
creator := p.Find(p.AttributeConvert[string](affiliate))
|
||||
if creator != nil {
|
||||
creator.CommonCoreProfile.Items.GetItemByTemplateID("Currency:MtxPurchased").Quantity += body.ExpectedTotalPrice
|
||||
creator.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased").Quantity += body.ExpectedTotalPrice
|
||||
creator.CommonCoreProfile.Items.GetItemByTemplateID("Currency:MtxPurchased").Quantity += int(float64(offer.Price.FinalPrice) * 0.10)
|
||||
creator.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased").Quantity += int(float64(offer.Price.FinalPrice) * 0.10)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientPurchaseCatalogBattlePassEntryAction(quantity int, offerRaw interface{}, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeBattlePass)
|
||||
person.TakeAndSyncVbucks(offer.Price.FinalPrice * quantity)
|
||||
|
||||
groupedRewards := map[string]int{}
|
||||
for i := 0; i < quantity; i++ {
|
||||
for _, grant := range offer.Rewards {
|
||||
groupedRewards[grant.TemplateID] += grant.Quantity
|
||||
}
|
||||
}
|
||||
|
||||
for templateID, quantity := range groupedRewards {
|
||||
_, err := fortnite.GrantToPerson(person, fortnite.NewItemGrant(templateID, quantity))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
receipt := p.NewReceipt(offer.GetOfferID(), 0)
|
||||
receipt.SetState("OK")
|
||||
person.Receipts.AddReceipt(receipt).Save()
|
||||
affiliate := person.CommonCoreProfile.Attributes.GetAttributeByKey("mtx_affiliate")
|
||||
if affiliate == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
creator := p.Find(p.AttributeConvert[string](affiliate))
|
||||
if creator != nil {
|
||||
creator.CommonCoreProfile.Items.GetItemByTemplateID("Currency:MtxPurchased").Quantity += int(float64(offer.Price.FinalPrice) * 0.10)
|
||||
creator.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased").Quantity += int(float64(offer.Price.FinalPrice) * 0.10)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientRefundMtxPurchaseAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
var body struct {
|
||||
PurchaseID string `json:"purchaseId" binding:"required"`
|
||||
|
@ -703,35 +772,18 @@ func clientRefundMtxPurchaseAction(c *fiber.Ctx, person *p.Person, profile *p.Pr
|
|||
}
|
||||
|
||||
person.RefundTickets--
|
||||
for _, item := range purchase.Loot {
|
||||
person.GetProfileFromType(item.ProfileType).Items.DeleteItem(item.ID)
|
||||
person.GetProfileFromType(item.ProfileType).CreateItemRemovedChange(item.ID)
|
||||
for _, lootItem := range purchase.Loot {
|
||||
person.GetProfileFromType(lootItem.ProfileType).Items.DeleteItem(lootItem.ID)
|
||||
person.GetProfileFromType(lootItem.ProfileType).CreateItemRemovedChange(lootItem.ID)
|
||||
}
|
||||
|
||||
purchase.RefundedAt = time.Now()
|
||||
purchase.Save()
|
||||
|
||||
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if vbucks == nil {
|
||||
return fmt.Errorf("vbucks not found")
|
||||
}
|
||||
|
||||
vbucks.Quantity += purchase.TotalPaid
|
||||
vbucks.Save()
|
||||
|
||||
profile0Vbucks := person.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if profile0Vbucks == nil {
|
||||
return fmt.Errorf("profile0vbucks not found")
|
||||
}
|
||||
|
||||
profile0Vbucks.Quantity = vbucks.Quantity
|
||||
profile0Vbucks.Save()
|
||||
person.GiveAndSyncVbucks(purchase.TotalPaid)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
var body struct {
|
||||
OfferID string `json:"offerId" binding:"required"`
|
||||
Currency string `json:"currency" binding:"required"`
|
||||
CurrencySubType string `json:"currencySubType" binding:"required"`
|
||||
ExpectedTotalPrice int `json:"expectedTotalPrice" binding:"required"`
|
||||
|
@ -739,20 +791,23 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
|
|||
GiftWrapTemplateId string `json:"giftWrapTemplateId" binding:"required"`
|
||||
PersonalMessage string `json:"personalMessage" binding:"required"`
|
||||
ReceiverAccountIds []string `json:"receiverAccountIds" binding:"required"`
|
||||
OfferId string `json:"offerId" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return fmt.Errorf("invalid Body")
|
||||
}
|
||||
|
||||
shop := fortnite.NewRandomFortniteCatalog()
|
||||
offer := shop.FindCosmeticOfferById(body.OfferId)
|
||||
if offer == nil {
|
||||
storefront := shop.GetShop()
|
||||
offerRaw, type_ := storefront.GetOfferByID(body.OfferID)
|
||||
if offerRaw == nil {
|
||||
return fmt.Errorf("offer not found")
|
||||
}
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeItem)
|
||||
if type_ != shop.StorefrontCatalogOfferEnumItem {
|
||||
return fmt.Errorf("invalid offer type")
|
||||
}
|
||||
|
||||
if offer.TotalPrice != body.ExpectedTotalPrice {
|
||||
if offer.Price.FinalPrice != body.ExpectedTotalPrice {
|
||||
return fmt.Errorf("invalid price")
|
||||
}
|
||||
|
||||
|
@ -762,40 +817,22 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
|
|||
return fmt.Errorf("one or more receivers not found")
|
||||
}
|
||||
|
||||
for _, grant := range offer.Grants {
|
||||
if receiverPerson.AthenaProfile.Items.GetItemByTemplateID(grant.Type.BackendValue + ":" + grant.ID) != nil {
|
||||
for _, grant := range offer.Rewards {
|
||||
if receiverPerson.AthenaProfile.Items.GetItemByTemplateID(grant.TemplateID) != nil {
|
||||
return fmt.Errorf("one or more receivers has one of the items")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
price := offer.TotalPrice * len(body.ReceiverAccountIds)
|
||||
|
||||
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if vbucks == nil {
|
||||
return fmt.Errorf("vbucks not found")
|
||||
}
|
||||
|
||||
profile0Vbucks := person.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if profile0Vbucks == nil {
|
||||
return fmt.Errorf("profile0vbucks not found")
|
||||
}
|
||||
|
||||
if vbucks.Quantity < price {
|
||||
return fmt.Errorf("not enough vbucks")
|
||||
}
|
||||
|
||||
vbucks.Quantity -= price
|
||||
profile0Vbucks.Quantity = price
|
||||
vbucks.Save()
|
||||
profile0Vbucks.Save()
|
||||
price := offer.Price.FinalPrice * len(body.ReceiverAccountIds)
|
||||
person.TakeAndSyncVbucks(price)
|
||||
|
||||
for _, receiverAccountId := range body.ReceiverAccountIds {
|
||||
receiverPerson := p.Find(receiverAccountId)
|
||||
gift := p.NewGift(body.GiftWrapTemplateId, 1, person.ID, body.PersonalMessage)
|
||||
for _, grant := range offer.Grants {
|
||||
item := p.NewItem(grant.Type.BackendValue + ":" + grant.ID, 1)
|
||||
item.ProfileType = offer.Meta.ProfileId
|
||||
for _, grant := range offer.Rewards {
|
||||
item := p.NewItem(grant.TemplateID, grant.Quantity)
|
||||
item.ProfileType = string(grant.ProfileType)
|
||||
gift.AddLoot(item)
|
||||
}
|
||||
|
||||
|
@ -808,7 +845,7 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
|
|||
|
||||
func clientRemoveGiftBoxAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
var body struct {
|
||||
GiftBoxItemId string `json:"giftBoxItemId" binding:"required"`
|
||||
GiftBoxItemId string `json:"giftBoxItemId" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
|
@ -820,13 +857,27 @@ func clientRemoveGiftBoxAction(c *fiber.Ctx, person *p.Person, profile *p.Profil
|
|||
return fmt.Errorf("gift not found")
|
||||
}
|
||||
|
||||
aid.Print(gift.TemplateID)
|
||||
|
||||
loot := []aid.JSON{}
|
||||
for _, item := range gift.Loot {
|
||||
person.GetProfileFromType(item.ProfileType).Items.AddItem(item).Save()
|
||||
result, err := fortnite.GrantToPerson(person, fortnite.NewItemGrant(item.TemplateID, item.Quantity))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
loot = append(loot, result.GenerateFortniteLootResultEntry()...)
|
||||
item.DeleteLoot()
|
||||
}
|
||||
|
||||
person.CommonCoreProfile.Gifts.DeleteGift(gift.ID)
|
||||
|
||||
*notifications = append(*notifications, aid.JSON{
|
||||
"type": "CatalogPurchase",
|
||||
"lootResult": aid.JSON{
|
||||
"items": loot,
|
||||
},
|
||||
"primary": true,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -867,13 +918,63 @@ func clientSetReceiveGiftsEnabledAction(c *fiber.Ctx, person *p.Person, profile
|
|||
return fmt.Errorf("invalid Body")
|
||||
}
|
||||
|
||||
attribute := profile.Attributes.GetAttributeByKey("allowed_to_receive_gifts")
|
||||
if attribute == nil {
|
||||
return fmt.Errorf("attribute not found")
|
||||
profile.Attributes.GetAttributeByKey("allowed_to_receive_gifts").SetValue(body.ReceiveGifts).Save()
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientVerifyRealMoneyPurchaseAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
var body struct {
|
||||
AppStore string `json:"appStore" binding:"required"`
|
||||
AppStoreId string `json:"appStoreId" binding:"required"`
|
||||
PurchaseCorrelationId string `json:"purchaseCorrelationId" binding:"required"`
|
||||
ReceiptId string `json:"receiptId" binding:"required"`
|
||||
ReceiptInfo string `json:"receiptInfo" binding:"required"`
|
||||
}
|
||||
|
||||
attribute.ValueJSON = aid.JSONStringify(body.ReceiveGifts)
|
||||
go attribute.Save()
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return fmt.Errorf("invalid Body")
|
||||
}
|
||||
|
||||
receipt := person.Receipts.GetReceipt(body.ReceiptId)
|
||||
if receipt == nil {
|
||||
return fmt.Errorf("receipt does not exist")
|
||||
}
|
||||
|
||||
if receipt.OfferID != body.AppStoreId {
|
||||
return fmt.Errorf("receipt does not match offer")
|
||||
}
|
||||
|
||||
gift := p.NewGift("GiftBox:GB_MakeGood", 1, "", "Thank you for your purchase!")
|
||||
for _, grant := range receipt.Loot {
|
||||
item := p.NewItem(grant.TemplateID, grant.Quantity)
|
||||
item.ProfileType = grant.ProfileType
|
||||
gift.AddLoot(item)
|
||||
}
|
||||
|
||||
person.CommonCoreProfile.Gifts.AddGift(gift).Save()
|
||||
person.SetInAppPurchasesAttribute()
|
||||
person.SyncVBucks("common_core")
|
||||
receipt.SetState("OK")
|
||||
receipt.Save()
|
||||
return nil
|
||||
}
|
||||
|
||||
func clientCalculateTierAndLevel(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
|
||||
for {
|
||||
tierChanged := fortnite.DataClient.SnowSeason.GrantUnredeemedBookRewards(person, "GB_BattlePass")
|
||||
levelChanged := fortnite.DataClient.SnowSeason.GrantUnredeemedLevelRewards(person)
|
||||
|
||||
if !tierChanged && !levelChanged {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
person.AthenaProfile.Attributes.GetAttributeByKey("season_num").SetValue(person.CurrentSeasonStats.Season).Save()
|
||||
person.AthenaProfile.Attributes.GetAttributeByKey("level").SetValue(fortnite.DataClient.SnowSeason.GetSeasonLevel(person.CurrentSeasonStats)).Save()
|
||||
person.AthenaProfile.Attributes.GetAttributeByKey("xp").SetValue(fortnite.DataClient.SnowSeason.GetRelativeSeasonXP(person.CurrentSeasonStats)).Save()
|
||||
person.AthenaProfile.Attributes.GetAttributeByKey("book_purchased").SetValue(person.CurrentSeasonStats.BookPurchased).Save()
|
||||
person.AthenaProfile.Attributes.GetAttributeByKey("book_level").SetValue(fortnite.DataClient.SnowSeason.GetBookLevel(person.CurrentSeasonStats)).Save()
|
||||
person.AthenaProfile.Attributes.GetAttributeByKey("book_xp").SetValue(fortnite.DataClient.SnowSeason.GetRelativeBookXP(person.CurrentSeasonStats)).Save()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -3,6 +3,7 @@ package handlers
|
|||
import (
|
||||
"github.com/ectrc/snow/aid"
|
||||
p "github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
|
@ -27,7 +28,19 @@ func PostGameAccess(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func GetFortniteReceipts(c *fiber.Ctx) error {
|
||||
return c.Status(200).JSON([]string{})
|
||||
person := c.Locals("person").(*p.Person)
|
||||
receipts := []aid.JSON{}
|
||||
|
||||
person.Receipts.RangeReceipts(func(key string, value *p.Receipt) bool {
|
||||
if value.State == "OK" {
|
||||
return true
|
||||
}
|
||||
|
||||
receipts = append(receipts, value.GenerateFortniteReceiptEntry())
|
||||
return true
|
||||
})
|
||||
|
||||
return c.Status(200).JSON(receipts)
|
||||
}
|
||||
|
||||
func GetMatchmakingSession(c *fiber.Ctx) error {
|
||||
|
@ -69,4 +82,15 @@ func GetRegion(c *fiber.Ctx) error {
|
|||
},
|
||||
"subdivisions": []aid.JSON{},
|
||||
})
|
||||
}
|
||||
|
||||
func SendJSONResponseFromAsset(c *fiber.Ctx, asset string) error {
|
||||
bytes := storage.Asset(asset)
|
||||
if bytes == nil {
|
||||
return c.Status(404).JSON(aid.JSON{})
|
||||
}
|
||||
|
||||
stringBytes := string(*bytes)
|
||||
c.Set("Content-Type", "application/json")
|
||||
return c.Status(200).SendString(stringBytes)
|
||||
}
|
|
@ -16,7 +16,7 @@ import (
|
|||
func GetDiscordOAuthURL(c *fiber.Ctx) error {
|
||||
code := c.Query("code")
|
||||
if code == "" {
|
||||
return c.Status(200).SendString("https://discord.com/oauth2/authorize?client_id="+ aid.Config.Discord.ID +"&redirect_uri="+ url.QueryEscape("http://" + aid.Config.API.Host + aid.Config.API.Port +"/snow/discord") + "&response_type=code&scope=identify")
|
||||
return c.Status(200).SendString("https://discord.com/oauth2/authorize?client_id="+ aid.Config.Discord.ID +"&redirect_uri="+ url.QueryEscape(aid.Config.Discord.CallbackURL +"/snow/discord") + "&response_type=code&scope=identify")
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
|
@ -26,7 +26,7 @@ func GetDiscordOAuthURL(c *fiber.Ctx) error {
|
|||
"client_secret": {aid.Config.Discord.Secret},
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
"redirect_uri": {"http://" + aid.Config.API.Host + aid.Config.API.Port +"/snow/discord"},
|
||||
"redirect_uri": {aid.Config.Discord.CallbackURL +"/snow/discord"},
|
||||
})
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(aid.JSON{"error":err.Error()})
|
||||
|
|
|
@ -7,157 +7,36 @@ import (
|
|||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func createContentPanel(title string, id string) aid.JSON {
|
||||
return aid.JSON{
|
||||
"NumPages": 1,
|
||||
"AnalyticsId": id,
|
||||
"PanelType": "AnalyticsList",
|
||||
"AnalyticsListName": title,
|
||||
"CuratedListOfLinkCodes": []aid.JSON{},
|
||||
"ModelName": "",
|
||||
"PageSize": 7,
|
||||
"PlatformBlacklist": []aid.JSON{},
|
||||
"PanelName": id,
|
||||
"MetricInterval": "",
|
||||
"SkippedEntriesCount": 0,
|
||||
"SkippedEntriesPercent": 0,
|
||||
"SplicedEntries": []aid.JSON{},
|
||||
"PlatformWhitelist": []aid.JSON{},
|
||||
"EntrySkippingMethod": "None",
|
||||
"PanelDisplayName": aid.JSON{
|
||||
"Category": "Game",
|
||||
"NativeCulture": "",
|
||||
"Namespace": "CreativeDiscoverySurface_Frontend",
|
||||
"LocalizedStrings": []aid.JSON{{
|
||||
"key": "en",
|
||||
"value": title,
|
||||
}},
|
||||
"bIsMinimalPatch": false,
|
||||
"NativeString": title,
|
||||
"Key": "",
|
||||
},
|
||||
"PlayHistoryType": "RecentlyPlayed",
|
||||
"bLowestToHighest": false,
|
||||
"PanelLinkCodeBlacklist": []aid.JSON{},
|
||||
"PanelLinkCodeWhitelist": []aid.JSON{},
|
||||
"FeatureTags": []aid.JSON{},
|
||||
"MetricName": "",
|
||||
}
|
||||
}
|
||||
|
||||
func createPlaylist(mnemonic string, image string) aid.JSON {
|
||||
return aid.JSON{
|
||||
"linkData": aid.JSON{
|
||||
"namespace": "fn",
|
||||
"mnemonic": mnemonic,
|
||||
"linkType": "BR:Playlist",
|
||||
"active": true,
|
||||
"disabled": false,
|
||||
"version": 1,
|
||||
"moderationStatus": "Unmoderated",
|
||||
"accountId": "epic",
|
||||
"creatorName": "Epic",
|
||||
"descriptionTags": []string{},
|
||||
"metadata": aid.JSON{
|
||||
"image_url": image,
|
||||
"matchmaking": aid.JSON{
|
||||
"override_playlist": mnemonic,
|
||||
},
|
||||
},
|
||||
},
|
||||
"lastVisited": nil,
|
||||
"linkCode": mnemonic,
|
||||
"isFavorite": false,
|
||||
}
|
||||
}
|
||||
|
||||
func PostDiscovery(c *fiber.Ctx) error {
|
||||
results := []aid.JSON{}
|
||||
results = append(results, createPlaylist("Playlist_DefaultSolo", "https://bucket.retrac.site/55737fa15677cd57fab9e7f4499d62f89cfde320.png"))
|
||||
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"Panels": []aid.JSON{
|
||||
{
|
||||
"PanelName": "1",
|
||||
"Pages": []aid.JSON{{
|
||||
"results": results,
|
||||
"hasMore": false,
|
||||
}},
|
||||
},
|
||||
},
|
||||
"TestCohorts": []string{
|
||||
"playlists",
|
||||
},
|
||||
"ModeSets": aid.JSON{},
|
||||
})
|
||||
}
|
||||
|
||||
func PostAssets(c *fiber.Ctx) error {
|
||||
var body struct {
|
||||
DAD_CosmeticItemUserOptions int `json:"DAD_CosmeticItemUserOptions"`
|
||||
FortCreativeDiscoverySurface int `json:"FortCreativeDiscoverySurface"`
|
||||
FortPlaylistAthena int `json:"FortPlaylistAthena"`
|
||||
}
|
||||
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
return c.Status(400).JSON(aid.JSON{"error":err.Error()})
|
||||
}
|
||||
|
||||
testCohort := aid.JSON{
|
||||
"AnalyticsId": "0",
|
||||
"CohortSelector": "PlayerDeterministic",
|
||||
"PlatformBlacklist": []aid.JSON{},
|
||||
"ContentPanels": []aid.JSON{
|
||||
createContentPanel("Featured", "1"),
|
||||
},
|
||||
"PlatformWhitelist": []aid.JSON{},
|
||||
"SelectionChance": 0.1,
|
||||
"TestName": "playlists",
|
||||
}
|
||||
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"FortCreativeDiscoverySurface": aid.JSON{
|
||||
"meta": aid.JSON{
|
||||
"promotion": 1,
|
||||
},
|
||||
"assets": aid.JSON{
|
||||
"CreativeDiscoverySurface_Frontend": aid.JSON{
|
||||
"meta": aid.JSON{
|
||||
"revision": 1,
|
||||
"headRevision": 1,
|
||||
"promotion": 1,
|
||||
"revisedAt": "0000-00-00T00:00:00.000Z",
|
||||
"promotedAt": "0000-00-00T00:00:00.000Z",
|
||||
},
|
||||
"assetData": aid.JSON{
|
||||
"AnalyticsId": "t412",
|
||||
"TestCohorts": []aid.JSON{
|
||||
testCohort,
|
||||
},
|
||||
"GlobalLinkCodeBlacklist": []aid.JSON{},
|
||||
"SurfaceName": "CreativeDiscoverySurface_Frontend",
|
||||
"TestName": "20.10_4/11/2022_hero_combat_popularConsole",
|
||||
"primaryAssetId": "FortCreativeDiscoverySurface:CreativeDiscoverySurface_Frontend",
|
||||
"GlobalLinkCodeWhitelist": []aid.JSON{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func GetContentPages(c *fiber.Ctx) error {
|
||||
seasonString := strconv.Itoa(aid.Config.Fortnite.Season)
|
||||
|
||||
playlists := []aid.JSON{}
|
||||
// for playlist := range fortnite.PlaylistImages {
|
||||
// playlists = append(playlists, aid.JSON{
|
||||
// "image": "http://" +aid.Config.API.Host + aid.Config.API.Port + "/snow/image/" + playlist + ".png?cache="+strconv.Itoa(rand.Intn(9999)),
|
||||
// "playlist_name": playlist,
|
||||
// "hidden": false,
|
||||
// })
|
||||
// }
|
||||
playlists := []aid.JSON{
|
||||
{
|
||||
"image": "https://cdn.snows.rocks/squads.png",
|
||||
"playlist_name": "Playlist_DefaultSquad",
|
||||
"hidden": false,
|
||||
},
|
||||
{
|
||||
"image": "https://cdn.snows.rocks/duos.png",
|
||||
"playlist_name": "Playlist_DefaultDuo",
|
||||
"hidden": false,
|
||||
},
|
||||
{
|
||||
"image": "https://cdn.snows.rocks/solo.png",
|
||||
"playlist_name": "Playlist_DefaultSolo",
|
||||
"hidden": false,
|
||||
},
|
||||
{
|
||||
"image": "https://cdn.snows.rocks/arena_solo.png",
|
||||
"playlist_name": "Playlist_ShowdownAlt_Solo",
|
||||
"hidden": false,
|
||||
},
|
||||
{
|
||||
"image": "https://cdn.snows.rocks/arena_duos.png",
|
||||
"playlist_name": "Playlist_ShowdownAlt_Duos",
|
||||
"hidden": false,
|
||||
},
|
||||
}
|
||||
|
||||
backgrounds := []aid.JSON{}
|
||||
switch aid.Config.Fortnite.Season {
|
||||
|
@ -182,6 +61,17 @@ func GetContentPages(c *fiber.Ctx) error {
|
|||
"spotlight": false,
|
||||
"hidden": true,
|
||||
"messagetype": "normal",
|
||||
"image": "https://cdn.snows.rocks/loading_stw.png",
|
||||
},
|
||||
},
|
||||
"saveTheWorld": aid.JSON{
|
||||
"message": aid.JSON{
|
||||
"title": "Co-op PvE",
|
||||
"body": "Cooperative PvE storm-fighting adventure!",
|
||||
"spotlight": false,
|
||||
"hidden": true,
|
||||
"messagetype": "normal",
|
||||
"image": "https://cdn.snows.rocks/loading_stw.png",
|
||||
},
|
||||
},
|
||||
"battleRoyale": aid.JSON{
|
||||
|
@ -191,6 +81,7 @@ func GetContentPages(c *fiber.Ctx) error {
|
|||
"spotlight": false,
|
||||
"hidden": true,
|
||||
"messagetype": "normal",
|
||||
"image": "https://cdn.snows.rocks/loading_br.png",
|
||||
},
|
||||
},
|
||||
"creative": aid.JSON{
|
||||
|
@ -282,6 +173,23 @@ func GetContentPages(c *fiber.Ctx) error {
|
|||
"frontend_matchmaking_header_text": "ECS Qualifiers",
|
||||
"lastModified": "0000-00-00T00:00:00.000Z",
|
||||
},
|
||||
"tournamentinformation": aid.JSON{
|
||||
"tournament_info": aid.JSON{
|
||||
"tournaments": []aid.JSON{
|
||||
{
|
||||
"tournament_display_id": "SnowArenaSolo",
|
||||
"playlist_tile_image": "https://cdn.snows.rocks/arena_solo.png",
|
||||
"title_line_2" : "ARENA",
|
||||
},
|
||||
{
|
||||
"tournament_display_id": "SnowArenaDuos",
|
||||
"playlist_tile_image": "https://cdn.snows.rocks/arena_duos.png",
|
||||
"title_line_2" : "ARENA",
|
||||
},
|
||||
},
|
||||
},
|
||||
"lastModified": "0000-00-00T00:00:00.000Z",
|
||||
},
|
||||
"lastModified": "0000-00-00T00:00:00.000Z",
|
||||
})
|
||||
}
|
65
handlers/events.go
Normal file
65
handlers/events.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
// structs from https://github.com/FabianFG/Fortnite-Api/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/fortnite"
|
||||
"github.com/ectrc/snow/person"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
hypeTokens = map[int]string{
|
||||
0: "ARENA_S8_Division1",
|
||||
25: "ARENA_S8_Division2",
|
||||
75: "ARENA_S8_Division3",
|
||||
125: "ARENA_S8_Division4",
|
||||
175: "ARENA_S8_Division5",
|
||||
225: "ARENA_S8_Division6",
|
||||
300: "ARENA_S8_Division7",
|
||||
}
|
||||
)
|
||||
|
||||
func GetEvents(c *fiber.Ctx) error {
|
||||
person := c.Locals("person").(*person.Person)
|
||||
|
||||
events := []aid.JSON{}
|
||||
templates := []aid.JSON{}
|
||||
tokens := []string{}
|
||||
|
||||
for _, event := range fortnite.ArenaEvents {
|
||||
events = append(events, event.GenerateFortniteEvent())
|
||||
|
||||
for _, window := range event.Windows {
|
||||
templates = append(templates, window.Template.GenerateFortniteEventTemplate())
|
||||
}
|
||||
}
|
||||
|
||||
for limit, token := range hypeTokens {
|
||||
if person.CurrentSeasonStats.Hype >= limit {
|
||||
tokens = []string{token}
|
||||
}
|
||||
}
|
||||
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"player": aid.JSON{
|
||||
"gameId": "Fortnite",
|
||||
"accountId": person.ID,
|
||||
"tokens": tokens,
|
||||
"teams": aid.JSON{},
|
||||
"pendingPayouts": []string{},
|
||||
"pendingPenalties": aid.JSON{},
|
||||
"persistentScores": aid.JSON{
|
||||
"Hype": person.CurrentSeasonStats.Hype,
|
||||
},
|
||||
"groupIdentity": aid.JSON{},
|
||||
},
|
||||
"events": events,
|
||||
"templates": templates,
|
||||
})
|
||||
}
|
||||
|
||||
func GetEventsBulkHistory(c *fiber.Ctx) error {
|
||||
return c.Status(200).JSON([]aid.JSON{})
|
||||
}
|
|
@ -14,6 +14,10 @@ func GetFriendList(c *fiber.Ctx) error {
|
|||
result := []aid.JSON{}
|
||||
|
||||
person.Relationships.Range(func(key string, value *p.Relationship) bool {
|
||||
if value.Towards == nil || value.From == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch value.Direction {
|
||||
case p.RelationshipInboundDirection:
|
||||
result = append(result, value.GenerateFortniteFriendEntry(p.GenerateTypeTowardsPerson))
|
||||
|
|
|
@ -94,7 +94,7 @@ func GetFortniteTimeline(c *fiber.Ctx) error {
|
|||
"seasonNumber": season,
|
||||
"seasonTemplateId": "AthenaSeason:AthenaSeason" + strings.Split(build, ".")[0],
|
||||
"seasonBegin": time.Now().Add(-time.Hour * 24 * 7).Format("2006-01-02T15:04:05.000Z"),
|
||||
"seasonEnd": time.Now().Add(time.Hour * 24 * 7).Format("2006-01-02T15:04:05.000Z"),
|
||||
"seasonEnd": time.Now().Add(time.Hour * 24 * 65).Format("2006-01-02T15:04:05.000Z"),
|
||||
"seasonDisplayedEnd": time.Now().Add(time.Hour * 24 * 7).Format("2006-01-02T15:04:05.000Z"),
|
||||
"activeStorefronts": []aid.JSON{},
|
||||
"dailyStoreEnd": aid.TimeEndOfDay(),
|
||||
|
|
177
handlers/purchases.go
Normal file
177
handlers/purchases.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/person"
|
||||
p "github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/shop"
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func GetHtmlPurchasePage(c *fiber.Ctx) error {
|
||||
c.Set("X-UEL", "DEFAULT")
|
||||
c.Set("X-Download-Options", "noopen")
|
||||
c.Set("X-DNS-Prefetch-Control", "off")
|
||||
c.Set("x-epic-correlation-id", uuid.New().String())
|
||||
c.Set("X-Frame-Options", "SAMEORIGIN")
|
||||
|
||||
var cookies struct {
|
||||
Token string `cookie:"EPIC_BEARER_TOKEN"`
|
||||
}
|
||||
|
||||
if err := c.CookieParser(&cookies); err != nil {
|
||||
return c.SendStatus(401)
|
||||
}
|
||||
|
||||
if cookies.Token == "" {
|
||||
return c.SendStatus(401)
|
||||
}
|
||||
|
||||
person, err := aid.GetSnowFromToken(cookies.Token)
|
||||
if err != nil {
|
||||
return c.SendStatus(401)
|
||||
}
|
||||
c.Locals("person", person)
|
||||
|
||||
fileBytes := storage.Asset("purchase.html")
|
||||
if fileBytes == nil {
|
||||
return c.SendStatus(404)
|
||||
}
|
||||
|
||||
c.Set("content-type", "text/html")
|
||||
return c.SendString(string(*fileBytes))
|
||||
}
|
||||
|
||||
func GetPurchaseAsset(c *fiber.Ctx) error {
|
||||
asset := c.Query("asset")
|
||||
|
||||
type_ := strings.Split(asset, ".")
|
||||
fileBytes := storage.Asset(asset)
|
||||
if fileBytes == nil {
|
||||
return c.SendStatus(404)
|
||||
}
|
||||
|
||||
c.Set("content-type", "text/" + type_[1])
|
||||
return c.SendString(string(*fileBytes))
|
||||
}
|
||||
|
||||
func GetPurchaseOffer(c *fiber.Ctx) error {
|
||||
player := c.Locals("person").(*person.Person)
|
||||
offerId := c.Query("offerId")
|
||||
if offerId == "" {
|
||||
return c.SendStatus(400)
|
||||
}
|
||||
|
||||
store := shop.GetShop()
|
||||
offerRaw, type_ := store.GetOfferByID(offerId)
|
||||
if offerRaw == nil {
|
||||
return c.SendStatus(404)
|
||||
}
|
||||
|
||||
response := aid.JSON{
|
||||
"user": aid.JSON{
|
||||
"displayName": player.DisplayName,
|
||||
},
|
||||
}
|
||||
|
||||
switch type_ {
|
||||
case shop.StorefrontCatalogOfferEnumCurrency:
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeCurrency)
|
||||
response["offer"] = aid.JSON{
|
||||
"id": offer.GetOfferID(),
|
||||
"price": aid.FormatPrice(int(offer.Price.LocalPrice)),
|
||||
"name": offer.Diplay.Title,
|
||||
"imageUrl": offer.Meta.FeaturedImageURL,
|
||||
"type": "currency",
|
||||
}
|
||||
case shop.StorefrontCatalogOfferEnumStarterKit:
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeStarterKit)
|
||||
response["offer"] = aid.JSON{
|
||||
"id": offer.GetOfferID(),
|
||||
"price": aid.FormatPrice(int(offer.Price.LocalPrice)),
|
||||
"name": offer.Diplay.Title,
|
||||
"imageUrl": offer.Meta.FeaturedImageURL,
|
||||
"type": "starterpack",
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return c.Status(200).JSON(response)
|
||||
}
|
||||
|
||||
func PostPurchaseOffer(c *fiber.Ctx) error {
|
||||
person := c.Locals("person").(*p.Person)
|
||||
|
||||
var body struct {
|
||||
OfferId string `json:"offerId" binding:"required"`
|
||||
Type string `json:"type" binding:"required"` // "currency" or "starterpack"
|
||||
}
|
||||
|
||||
aid.PrintJSON(body)
|
||||
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return c.SendStatus(400)
|
||||
}
|
||||
|
||||
lookup := map[string]func(*fiber.Ctx, *p.Person, string) error{
|
||||
"currency": purchaseCurrency,
|
||||
"starterpack": purchaseStarterPack,
|
||||
}
|
||||
|
||||
if handler, ok := lookup[body.Type]; ok {
|
||||
return handler(c, person, body.OfferId)
|
||||
}
|
||||
|
||||
return c.SendStatus(400)
|
||||
}
|
||||
|
||||
func purchaseCurrency(c *fiber.Ctx, person *p.Person, offerId string) error {
|
||||
offerRaw, type_ := shop.GetShop().GetOfferByID(offerId)
|
||||
if offerRaw == nil {
|
||||
return c.Status(404).JSON(aid.ErrorNotFound)
|
||||
}
|
||||
if type_ != shop.StorefrontCatalogOfferEnumCurrency {
|
||||
return c.Status(400).JSON(aid.ErrorBadRequest)
|
||||
}
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeCurrency)
|
||||
|
||||
receipt := p.NewReceipt(offerId, int(offer.Price.BasePrice))
|
||||
for _, grant := range offer.Rewards {
|
||||
item := p.NewItem(grant.TemplateID, grant.Quantity)
|
||||
item.ProfileType = string(grant.ProfileType)
|
||||
receipt.AddLoot(item)
|
||||
}
|
||||
person.Receipts.AddReceipt(receipt).Save()
|
||||
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"receipt": receipt.GenerateUnrealReceiptEntry(),
|
||||
})
|
||||
}
|
||||
|
||||
func purchaseStarterPack(c *fiber.Ctx, person *p.Person, offerId string) error {
|
||||
offerRaw, type_ := shop.GetShop().GetOfferByID(offerId)
|
||||
if offerRaw == nil {
|
||||
return c.Status(404).JSON(aid.ErrorNotFound)
|
||||
}
|
||||
if type_ != shop.StorefrontCatalogOfferEnumStarterKit {
|
||||
return c.Status(400).JSON(aid.ErrorBadRequest)
|
||||
}
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeStarterKit)
|
||||
|
||||
receipt := p.NewReceipt(offerId, int(offer.Price.BasePrice))
|
||||
for _, grant := range offer.Rewards {
|
||||
item := p.NewItem(grant.TemplateID, grant.Quantity)
|
||||
item.ProfileType = string(grant.ProfileType)
|
||||
receipt.AddLoot(item)
|
||||
}
|
||||
person.Receipts.AddReceipt(receipt).Save()
|
||||
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"receipt": receipt.GenerateUnrealReceiptEntry(),
|
||||
})
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/fortnite"
|
||||
p "github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/shop"
|
||||
"github.com/ectrc/snow/socket"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
|
@ -42,17 +46,52 @@ func GetSnowParties(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func GetSnowShop(c *fiber.Ctx) error {
|
||||
shop := fortnite.NewRandomFortniteCatalog()
|
||||
shop := shop.GetShop()
|
||||
return c.JSON(shop.GenerateFortniteCatalogResponse())
|
||||
}
|
||||
|
||||
//
|
||||
func PostSnowLog(c *fiber.Ctx) error {
|
||||
var body struct {
|
||||
JSON aid.JSON `json:"json"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return c.Status(400).JSON(err.Error())
|
||||
}
|
||||
|
||||
aid.PrintJSON(body.JSON)
|
||||
return c.JSON(body)
|
||||
}
|
||||
|
||||
func GetPlayer(c *fiber.Ctx) error {
|
||||
person := c.Locals("person").(*p.Person)
|
||||
return c.Status(200).JSON(person.Snapshot())
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"snapshot": person.Snapshot(),
|
||||
"season": aid.JSON{
|
||||
"level": fortnite.DataClient.SnowSeason.GetSeasonLevel(person.CurrentSeasonStats),
|
||||
"xp": fortnite.DataClient.SnowSeason.GetRelativeSeasonXP(person.CurrentSeasonStats),
|
||||
"bookLevel": fortnite.DataClient.SnowSeason.GetBookLevel(person.CurrentSeasonStats),
|
||||
"bookXp": fortnite.DataClient.SnowSeason.GetRelativeBookXP(person.CurrentSeasonStats),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func GetPlayerOkay(c *fiber.Ctx) error {
|
||||
return c.Status(200).SendString("okay")
|
||||
}
|
||||
|
||||
func PostPlayerCreateCode(c *fiber.Ctx) error {
|
||||
person := c.Locals("person").(*p.Person)
|
||||
code := person.ID + "=" + time.Now().Format("2006-01-02T15:04:05.999Z")
|
||||
encrypted, sig := aid.KeyPair.EncryptAndSignB64([]byte(code))
|
||||
return c.Status(200).SendString(encrypted + "." + sig)
|
||||
}
|
||||
|
||||
func GetLauncherStatus(c *fiber.Ctx) error {
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"CurrentSeason": aid.Config.Fortnite.Season,
|
||||
"CurrentBuild": aid.Config.Fortnite.Build,
|
||||
"PlayersOnline": aid.FormatNumber(socket.JabberSockets.Len()),
|
||||
})
|
||||
}
|
|
@ -1,18 +1,16 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/fortnite"
|
||||
"github.com/ectrc/snow/shop"
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func GetStorefrontCatalog(c *fiber.Ctx) error {
|
||||
shop := fortnite.NewRandomFortniteCatalog()
|
||||
shop := shop.GetShop()
|
||||
return c.Status(200).JSON(shop.GenerateFortniteCatalogResponse())
|
||||
}
|
||||
|
||||
|
@ -27,7 +25,7 @@ func GetStorefrontKeychain(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func GetStorefrontCatalogBulkOffers(c *fiber.Ctx) error {
|
||||
shop := fortnite.NewRandomFortniteCatalog()
|
||||
store := shop.GetShop()
|
||||
|
||||
appStoreIdBytes := c.Request().URI().QueryArgs().PeekMulti("id")
|
||||
appStoreIds := make([]string, len(appStoreIdBytes))
|
||||
|
@ -37,21 +35,21 @@ func GetStorefrontCatalogBulkOffers(c *fiber.Ctx) error {
|
|||
|
||||
response := aid.JSON{}
|
||||
for _, id := range appStoreIds {
|
||||
offer := shop.FindCurrencyOfferById(strings.ReplaceAll(id, "app-", ""))
|
||||
if offer == nil {
|
||||
offerRaw, type_ := store.GetOfferByID(id)
|
||||
if offerRaw == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
response[id] = offer.GenerateFortniteCatalogBulkOfferResponse()
|
||||
}
|
||||
|
||||
for _, id := range appStoreIds {
|
||||
offer := shop.FindStarterPackById(strings.ReplaceAll(id, "app-", ""))
|
||||
if offer == nil {
|
||||
continue
|
||||
switch type_ {
|
||||
case shop.StorefrontCatalogOfferEnumCurrency:
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeCurrency)
|
||||
response[id] = offer.GenerateFortniteBulkOffersResponse()
|
||||
case shop.StorefrontCatalogOfferEnumStarterKit:
|
||||
offer := offerRaw.(*shop.StorefrontCatalogOfferTypeStarterKit)
|
||||
response[id] = offer.GenerateFortniteBulkOffersResponse()
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
response[id] = offer.GenerateFortniteCatalogBulkOfferResponse()
|
||||
}
|
||||
|
||||
return c.Status(200).JSON(response)
|
||||
|
|
48
main.go
48
main.go
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/ectrc/snow/fortnite"
|
||||
"github.com/ectrc/snow/handlers"
|
||||
"github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/shop"
|
||||
"github.com/ectrc/snow/storage"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -20,7 +21,7 @@ import (
|
|||
var configFile []byte
|
||||
|
||||
func init() {
|
||||
aid.LoadConfig(configFile)
|
||||
aid.LoadConfig(configFile)
|
||||
var device storage.Storage
|
||||
switch aid.Config.Database.Type {
|
||||
case "postgres":
|
||||
|
@ -45,13 +46,20 @@ func init() {
|
|||
func init() {
|
||||
discord.IntialiseClient()
|
||||
fortnite.PreloadCosmetics()
|
||||
fortnite.NewRandomFortniteCatalog()
|
||||
fortnite.PreloadEvents()
|
||||
shop.GetShop()
|
||||
|
||||
for _, username := range aid.Config.Accounts.Gods {
|
||||
found := person.FindByDisplay(username)
|
||||
if found == nil {
|
||||
found = fortnite.NewFortnitePersonWithId(username, username, aid.Config.Fortnite.Everything)
|
||||
}
|
||||
found.Discord = &storage.DB_DiscordPerson{
|
||||
ID: found.ID,
|
||||
PersonID: found.ID,
|
||||
Username: username,
|
||||
}
|
||||
found.Save()
|
||||
|
||||
found.AddPermission(person.PermissionAllWithRoles)
|
||||
aid.Print("(snow) max account " + username + " loaded")
|
||||
|
@ -76,20 +84,16 @@ func main() {
|
|||
})
|
||||
|
||||
r.Use(aid.FiberLogger())
|
||||
r.Use(aid.FiberLimiter(100))
|
||||
r.Use(aid.FiberLimiter(1000))
|
||||
r.Use(aid.FiberCors())
|
||||
|
||||
r.Get("/region", handlers.GetRegion)
|
||||
r.Get("/content/api/pages/fortnite-game", handlers.GetContentPages)
|
||||
r.Get("/waitingroom/api/waitingroom", handlers.GetWaitingRoomStatus)
|
||||
r.Get("/affiliate/api/public/affiliates/slug/:slug", handlers.GetAffiliate)
|
||||
|
||||
r.Get("/api/v1/search/:accountId", handlers.GetPersonSearch)
|
||||
r.Post("/api/v1/assets/Fortnite/:versionId/:assetName", handlers.PostAssets)
|
||||
|
||||
r.Get("/profile/privacy_settings", handlers.MiddlewareFortnite, handlers.GetPrivacySettings)
|
||||
r.Put("/profile/play_region", handlers.AnyNoContent)
|
||||
|
||||
r.Get("/api/v1/search/:accountId", handlers.GetPersonSearch)
|
||||
r.Get("/", handlers.RedirectSocket)
|
||||
r.Get("/socket", handlers.MiddlewareWebsocket, websocket.New(handlers.WebsocketConnection))
|
||||
|
||||
|
@ -103,7 +107,7 @@ func main() {
|
|||
account.Delete("/oauth/sessions/kill", handlers.DeleteToken)
|
||||
|
||||
fortnite := r.Group("/fortnite/api")
|
||||
fortnite.Get("/receipts/v1/account/:accountId/receipts", handlers.GetFortniteReceipts)
|
||||
fortnite.Get("/receipts/v1/account/:accountId/receipts", handlers.MiddlewareFortnite, handlers.GetFortniteReceipts)
|
||||
fortnite.Get("/v2/versioncheck/:version", handlers.GetFortniteVersion)
|
||||
fortnite.Get("/calendar/v1/timeline", handlers.GetFortniteTimeline)
|
||||
|
||||
|
@ -134,11 +138,15 @@ func main() {
|
|||
friends.Post("/:version/:accountId/friends/:wanted", handlers.PostCreateFriend)
|
||||
friends.Delete("/:version/:accountId/friends/:wanted", handlers.DeleteFriend)
|
||||
|
||||
events := r.Group("/api/v1/events/Fortnite")
|
||||
events.Use(handlers.MiddlewareFortnite)
|
||||
events.Get("/download/:accountId", handlers.GetEvents)
|
||||
events.Get("/:eventId/history/:accountId", handlers.GetEventsBulkHistory)
|
||||
|
||||
game := fortnite.Group("/game/v2")
|
||||
game.Get("/enabled_features", handlers.GetGameEnabledFeatures)
|
||||
game.Post("/tryPlayOnPlatform/account/:accountId", handlers.PostGamePlatform)
|
||||
game.Post("/grant_access/:accountId", handlers.PostGameAccess)
|
||||
game.Post("/creative/discovery/surface/:accountId", handlers.PostDiscovery)
|
||||
game.Post("/profileToken/verify/:accountId", handlers.AnyNoContent)
|
||||
|
||||
profile := game.Group("/profile/:accountId")
|
||||
|
@ -150,6 +158,12 @@ func main() {
|
|||
lightswitch.Use(handlers.MiddlewareFortnite)
|
||||
lightswitch.Get("/service/bulk/status", handlers.GetLightswitchBulkStatus)
|
||||
|
||||
purchasing := r.Group("/purchase")
|
||||
purchasing.Get("/", handlers.GetHtmlPurchasePage)
|
||||
purchasing.Get("/offer", handlers.MiddlewareFortnite, handlers.GetPurchaseOffer)
|
||||
purchasing.Post("/offer", handlers.MiddlewareFortnite, handlers.PostPurchaseOffer)
|
||||
purchasing.Get("/assets", handlers.GetPurchaseAsset)
|
||||
|
||||
party := r.Group("/party/api/v1/Fortnite")
|
||||
party.Use(handlers.MiddlewareFortnite)
|
||||
party.Get("/user/:accountId", handlers.GetPartiesForUser)
|
||||
|
@ -169,13 +183,19 @@ func main() {
|
|||
party.Post("/members/:friendId/intentions/:accountId", handlers.PostPartyCreateIntention)
|
||||
|
||||
snow := r.Group("/snow")
|
||||
snow.Post("/log", handlers.PostSnowLog)
|
||||
|
||||
discord := snow.Group("/discord")
|
||||
discord.Get("/", handlers.GetDiscordOAuthURL)
|
||||
|
||||
launcher := snow.Group("/launcher")
|
||||
launcher.Get("/", handlers.GetLauncherStatus)
|
||||
|
||||
player := snow.Group("/player")
|
||||
player.Use(handlers.MiddlewareWeb)
|
||||
player.Get("/", handlers.GetPlayer)
|
||||
player.Get("/okay", handlers.GetPlayerOkay)
|
||||
player.Post("/code", handlers.PostPlayerCreateCode)
|
||||
|
||||
debug := snow.Group("/")
|
||||
debug.Use(handlers.MiddlewareOnlyDebug)
|
||||
|
@ -191,10 +211,10 @@ func main() {
|
|||
})
|
||||
r.All("*", func(c *fiber.Ctx) error { return c.Status(fiber.StatusNotFound).JSON(aid.ErrorNotFound) })
|
||||
|
||||
if aid.Config.Fortnite.Season <= 2 {
|
||||
t := handlers.NewServer()
|
||||
go t.Listen()
|
||||
}
|
||||
// if aid.Config.Fortnite.Season <= 2 {
|
||||
// t := handlers.NewServer()
|
||||
// go t.Listen()
|
||||
// }
|
||||
|
||||
err := r.Listen("0.0.0.0" + aid.Config.API.Port)
|
||||
if err != nil {
|
||||
|
|
|
@ -56,6 +56,11 @@ func (a *Attribute) Save() {
|
|||
storage.Repo.SaveAttribute(a.ToDatabase(a.ProfileID))
|
||||
}
|
||||
|
||||
func (a *Attribute) SetValue(value interface{}) *Attribute {
|
||||
a.ValueJSON = aid.JSONStringify(value)
|
||||
return a
|
||||
}
|
||||
|
||||
func AttributeConvertToSlice[T any](attribute *Attribute) []T {
|
||||
valuesRaw := aid.JSONParse(attribute.ValueJSON).([]interface{})
|
||||
values := make([]T, len(valuesRaw))
|
||||
|
@ -67,5 +72,5 @@ func AttributeConvertToSlice[T any](attribute *Attribute) []T {
|
|||
}
|
||||
|
||||
func AttributeConvert[T any](attribute *Attribute) T {
|
||||
return aid.JSONParse(attribute.ValueJSON).(T)
|
||||
return aid.JSONParseG[T](attribute.ValueJSON)
|
||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
type Gift struct {
|
||||
ID string
|
||||
ProfileID string
|
||||
ProfileID string
|
||||
TemplateID string
|
||||
Quantity int
|
||||
FromID string
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package person
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/google/uuid"
|
||||
|
@ -82,16 +84,29 @@ func FromDatabasePurchaseLoot(item *storage.DB_PurchaseLoot) *Item {
|
|||
}
|
||||
}
|
||||
|
||||
func (i *Item) GenerateFortniteItemEntry() aid.JSON {
|
||||
attributes := aid.JSON{
|
||||
"variants": i.GenerateFortniteItemVariantChannels(),
|
||||
"favorite": i.Favorite,
|
||||
"item_seen": i.HasSeen,
|
||||
func FromDatabaseReceiptLoot(item *storage.DB_ReceiptLoot) *Item {
|
||||
return &Item{
|
||||
ID: item.ID,
|
||||
TemplateID: item.TemplateID,
|
||||
Quantity: item.Quantity,
|
||||
Favorite: false,
|
||||
HasSeen: false,
|
||||
Variants: []*VariantChannel{},
|
||||
ProfileType: item.ProfileType,
|
||||
}
|
||||
}
|
||||
|
||||
if i.TemplateID == "Currency:MtxPurchased" {
|
||||
func (i *Item) GenerateFortniteItemEntry() aid.JSON {
|
||||
attributes := aid.JSON{}
|
||||
|
||||
switch strings.Split(i.TemplateID, ":")[0] {
|
||||
case "Currency":
|
||||
attributes["platform"] = "Shared"
|
||||
default:
|
||||
attributes = aid.JSON{
|
||||
"platform": "Shared",
|
||||
"variants": i.GenerateFortniteItemVariantChannels(),
|
||||
"favorite": i.Favorite,
|
||||
"item_seen": i.HasSeen,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,6 +152,10 @@ func (i *Item) DeleteLoot() {
|
|||
storage.Repo.DeleteLoot(i.ID)
|
||||
}
|
||||
|
||||
func (i *Item) DeleteReceiptLoot() {
|
||||
storage.Repo.DeleteReceiptLoot(i.ID)
|
||||
}
|
||||
|
||||
func (i *Item) NewChannel(channel string, owned []string, active string) *VariantChannel {
|
||||
return &VariantChannel{
|
||||
ID: uuid.New().String(),
|
||||
|
@ -149,7 +168,6 @@ func (i *Item) NewChannel(channel string, owned []string, active string) *Varian
|
|||
|
||||
func (i *Item) AddChannel(channel *VariantChannel) {
|
||||
i.Variants = append(i.Variants, channel)
|
||||
//storage.Repo.SaveItemVariant(i.ID, channel)
|
||||
}
|
||||
|
||||
func (i *Item) RemoveChannel(channel *VariantChannel) {
|
||||
|
@ -211,6 +229,10 @@ func (i *Item) Save() {
|
|||
return
|
||||
}
|
||||
|
||||
for _, variant := range i.Variants {
|
||||
variant.Save()
|
||||
}
|
||||
|
||||
storage.Repo.SaveItem(i.ToDatabase(i.ProfileID))
|
||||
}
|
||||
|
||||
|
@ -234,8 +256,17 @@ func (i *Item) ToPurchaseLootDatabase(purchaseId string) *storage.DB_PurchaseLoo
|
|||
}
|
||||
}
|
||||
|
||||
func (i *Item) ToReceiptLootDatabase(receiptId string) *storage.DB_ReceiptLoot {
|
||||
return &storage.DB_ReceiptLoot{
|
||||
ID: i.ID,
|
||||
ReceiptID: receiptId,
|
||||
ProfileType: i.ProfileType,
|
||||
TemplateID: i.TemplateID,
|
||||
Quantity: i.Quantity,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Item) SaveLoot(giftId string) {
|
||||
//storage.Repo.SaveLoot(i.ToLootDatabase(giftId))
|
||||
}
|
||||
|
||||
func (i *Item) Snapshot() ItemSnapshot {
|
||||
|
|
147
person/person.go
147
person/person.go
|
@ -1,6 +1,8 @@
|
|||
package person
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
|
@ -19,7 +21,10 @@ type Person struct {
|
|||
Profile0Profile *Profile
|
||||
CollectionsProfile *Profile
|
||||
CreativeProfile *Profile
|
||||
CurrentSeasonStats *SeasonStats
|
||||
AllSeasonsStats aid.GenericSyncMap[SeasonStats]
|
||||
Discord *storage.DB_DiscordPerson
|
||||
Receipts *ReceiptMutex
|
||||
BanHistory aid.GenericSyncMap[storage.DB_BanStatus]
|
||||
Relationships aid.GenericSyncMap[Relationship]
|
||||
Parties aid.GenericSyncMap[Party]
|
||||
|
@ -28,8 +33,9 @@ type Person struct {
|
|||
}
|
||||
|
||||
func NewPerson() *Person {
|
||||
id := uuid.New().String()
|
||||
return &Person{
|
||||
ID: uuid.New().String(),
|
||||
ID: id,
|
||||
DisplayName: uuid.New().String(),
|
||||
Permissions: 0,
|
||||
RefundTickets: 3,
|
||||
|
@ -39,6 +45,8 @@ func NewPerson() *Person {
|
|||
Profile0Profile: NewProfile("profile0"),
|
||||
CollectionsProfile: NewProfile("collections"),
|
||||
CreativeProfile: NewProfile("creative"),
|
||||
Receipts: NewReceiptMutex(id),
|
||||
AllSeasonsStats: aid.GenericSyncMap[SeasonStats]{},
|
||||
BanHistory: aid.GenericSyncMap[storage.DB_BanStatus]{},
|
||||
Relationships: aid.GenericSyncMap[Relationship]{},
|
||||
Parties: aid.GenericSyncMap[Party]{},
|
||||
|
@ -59,6 +67,8 @@ func NewPersonWithCustomID(id string) *Person {
|
|||
Profile0Profile: NewProfile("profile0"),
|
||||
CollectionsProfile: NewProfile("collections"),
|
||||
CreativeProfile: NewProfile("creative"),
|
||||
Receipts: NewReceiptMutex(id),
|
||||
AllSeasonsStats: aid.GenericSyncMap[SeasonStats]{},
|
||||
BanHistory: aid.GenericSyncMap[storage.DB_BanStatus]{},
|
||||
Relationships: aid.GenericSyncMap[Relationship]{},
|
||||
Parties: aid.GenericSyncMap[Party]{},
|
||||
|
@ -164,6 +174,7 @@ func findHelper(databasePerson *storage.DB_Person, shallow bool, save bool) *Per
|
|||
profile0 := NewProfile("profile0")
|
||||
collectionsProfile := NewProfile("collections")
|
||||
creativeProfile := NewProfile("creative")
|
||||
receipts := NewReceiptMutex(databasePerson.ID)
|
||||
|
||||
for _, profile := range databasePerson.Profiles {
|
||||
if profile.Type == "athena" {
|
||||
|
@ -197,11 +208,14 @@ func findHelper(databasePerson *storage.DB_Person, shallow bool, save bool) *Per
|
|||
}
|
||||
}
|
||||
|
||||
for _, receipt := range databasePerson.Receipts {
|
||||
receipts.AddReceipt(FromDatabaseReceipt(&receipt))
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
ID: databasePerson.ID,
|
||||
DisplayName: databasePerson.DisplayName,
|
||||
Permissions: Permission(databasePerson.Permissions),
|
||||
BanHistory: aid.GenericSyncMap[storage.DB_BanStatus]{},
|
||||
AthenaProfile: athenaProfile,
|
||||
CommonCoreProfile: commonCoreProfile,
|
||||
CommonPublicProfile: commonPublicProfile,
|
||||
|
@ -210,6 +224,9 @@ func findHelper(databasePerson *storage.DB_Person, shallow bool, save bool) *Per
|
|||
CreativeProfile: creativeProfile,
|
||||
Discord: &databasePerson.Discord,
|
||||
RefundTickets: databasePerson.RefundTickets,
|
||||
Receipts: receipts,
|
||||
AllSeasonsStats: aid.GenericSyncMap[SeasonStats]{},
|
||||
BanHistory: aid.GenericSyncMap[storage.DB_BanStatus]{},
|
||||
Relationships: aid.GenericSyncMap[Relationship]{},
|
||||
Parties: aid.GenericSyncMap[Party]{},
|
||||
Invites: aid.GenericSyncMap[PartyInvite]{},
|
||||
|
@ -220,6 +237,21 @@ func findHelper(databasePerson *storage.DB_Person, shallow bool, save bool) *Per
|
|||
person.BanHistory.Set(ban.ID, &ban)
|
||||
}
|
||||
|
||||
for _, stat := range databasePerson.Stats {
|
||||
person.AllSeasonsStats.Set(fmt.Sprint(stat.Season), FromDatabaseSeasonStats(stat))
|
||||
|
||||
if stat.Season == aid.Config.Fortnite.Season {
|
||||
person.CurrentSeasonStats = FromDatabaseSeasonStats(stat)
|
||||
}
|
||||
}
|
||||
|
||||
if person.CurrentSeasonStats == nil {
|
||||
person.CurrentSeasonStats = NewSeasonStats(aid.Config.Fortnite.Season)
|
||||
person.CurrentSeasonStats.PersonID = person.ID
|
||||
person.AllSeasonsStats.Set(fmt.Sprint(aid.Config.Fortnite.Season), person.CurrentSeasonStats)
|
||||
person.CurrentSeasonStats.Save()
|
||||
}
|
||||
|
||||
if !shallow {
|
||||
person.LoadRelationships()
|
||||
}
|
||||
|
@ -340,10 +372,6 @@ func (p *Person) RemovePermission(permission Permission) {
|
|||
}
|
||||
|
||||
func (p *Person) HasPermission(permission Permission) bool {
|
||||
// if permission == PermissionAll && permission != PermissionOwner {
|
||||
// return p.Permissions == PermissionAll
|
||||
// }
|
||||
|
||||
return p.Permissions & permission != 0
|
||||
}
|
||||
|
||||
|
@ -352,8 +380,9 @@ func (p *Person) ToDatabase() *storage.DB_Person {
|
|||
ID: p.ID,
|
||||
DisplayName: p.DisplayName,
|
||||
Permissions: int64(p.Permissions),
|
||||
BanHistory: []storage.DB_BanStatus{},
|
||||
RefundTickets: p.RefundTickets,
|
||||
Receipts: []storage.DB_Receipt{},
|
||||
BanHistory: []storage.DB_BanStatus{},
|
||||
Profiles: []storage.DB_Profile{},
|
||||
Stats: []storage.DB_SeasonStat{},
|
||||
Discord: storage.DB_DiscordPerson{},
|
||||
|
@ -377,6 +406,16 @@ func (p *Person) ToDatabase() *storage.DB_Person {
|
|||
return true
|
||||
})
|
||||
|
||||
p.Receipts.RangeReceipts(func(key string, receipt *Receipt) bool {
|
||||
dbPerson.Receipts = append(dbPerson.Receipts, *receipt.ToDatabase())
|
||||
return true
|
||||
})
|
||||
|
||||
p.AllSeasonsStats.Range(func(key string, stat *SeasonStats) bool {
|
||||
dbPerson.Stats = append(dbPerson.Stats, *stat.ToDatabase(p.ID))
|
||||
return true
|
||||
})
|
||||
|
||||
for profileType, profile := range profilesToConvert {
|
||||
dbProfile := storage.DB_Profile{
|
||||
ID: profile.ID,
|
||||
|
@ -426,8 +465,9 @@ func (p *Person) ToDatabaseShallow() *storage.DB_Person {
|
|||
ID: p.ID,
|
||||
DisplayName: p.DisplayName,
|
||||
Permissions: int64(p.Permissions),
|
||||
BanHistory: []storage.DB_BanStatus{},
|
||||
RefundTickets: p.RefundTickets,
|
||||
Receipts: []storage.DB_Receipt{},
|
||||
BanHistory: []storage.DB_BanStatus{},
|
||||
Profiles: []storage.DB_Profile{},
|
||||
Stats: []storage.DB_SeasonStat{},
|
||||
Discord: storage.DB_DiscordPerson{},
|
||||
|
@ -442,6 +482,16 @@ func (p *Person) ToDatabaseShallow() *storage.DB_Person {
|
|||
return true
|
||||
})
|
||||
|
||||
p.Receipts.RangeReceipts(func(key string, receipt *Receipt) bool {
|
||||
dbPerson.Receipts = append(dbPerson.Receipts, *receipt.ToDatabase())
|
||||
return true
|
||||
})
|
||||
|
||||
p.AllSeasonsStats.Range(func(key string, stat *SeasonStats) bool {
|
||||
dbPerson.Stats = append(dbPerson.Stats, *stat.ToDatabase(p.ID))
|
||||
return true
|
||||
})
|
||||
|
||||
return &dbPerson
|
||||
}
|
||||
|
||||
|
@ -457,7 +507,10 @@ func (p *Person) Snapshot() *PersonSnapshot {
|
|||
Profile0Profile: *p.Profile0Profile.Snapshot(),
|
||||
CollectionsProfile: *p.CollectionsProfile.Snapshot(),
|
||||
CreativeProfile: *p.CreativeProfile.Snapshot(),
|
||||
CurrentSeasonStats: *p.CurrentSeasonStats,
|
||||
AllSeasonsStats: []SeasonStats{},
|
||||
BanHistory: []storage.DB_BanStatus{},
|
||||
Receipts: []storage.DB_Receipt{},
|
||||
Discord: *p.Discord,
|
||||
Relationships: *p.Relationships.Snapshot(),
|
||||
Parties: *p.Parties.Snapshot(),
|
||||
|
@ -470,6 +523,16 @@ func (p *Person) Snapshot() *PersonSnapshot {
|
|||
return true
|
||||
})
|
||||
|
||||
p.Receipts.RangeReceipts(func(key string, receipt *Receipt) bool {
|
||||
snapshot.Receipts = append(snapshot.Receipts, *receipt.ToDatabase())
|
||||
return true
|
||||
})
|
||||
|
||||
p.AllSeasonsStats.Range(func(key string, stat *SeasonStats) bool {
|
||||
snapshot.AllSeasonsStats = append(snapshot.AllSeasonsStats, *stat)
|
||||
return true
|
||||
})
|
||||
|
||||
return snapshot
|
||||
}
|
||||
|
||||
|
@ -493,4 +556,72 @@ func (p *Person) SetPurchaseHistoryAttribute() {
|
|||
"purchases": purchases,
|
||||
})
|
||||
purchaseAttribute.Save()
|
||||
}
|
||||
|
||||
func (p *Person) SetInAppPurchasesAttribute() {
|
||||
receipts := []string{}
|
||||
fulfillmentCounts := map[string]int{}
|
||||
|
||||
p.Receipts.RangeReceipts(func(key string, r *Receipt) bool {
|
||||
pureOfferId := strings.ReplaceAll(r.OfferID, "app-", "")
|
||||
receipts = append(receipts, r.ID)
|
||||
fulfillmentCounts[pureOfferId]++
|
||||
return true
|
||||
})
|
||||
|
||||
inAppPurchaseAttribute := p.CommonCoreProfile.Attributes.GetAttributeByKey("in_app_purchases")
|
||||
inAppPurchaseAttribute.ValueJSON = aid.JSONStringify(aid.JSON{
|
||||
"ignoredReceipts": []string{},
|
||||
"refreshTimers": aid.JSON{},
|
||||
"receipts": receipts,
|
||||
"fulfillmentCounts": fulfillmentCounts,
|
||||
})
|
||||
inAppPurchaseAttribute.Save()
|
||||
}
|
||||
|
||||
func (p *Person) SyncVBucks(sourceProfileType string) {
|
||||
antiSourceLookup := map[string]string{
|
||||
"profile0": "common_core",
|
||||
"common_core": "profile0",
|
||||
}
|
||||
sourceProfile := p.GetProfileFromType(sourceProfileType)
|
||||
antiSourceProfile := p.GetProfileFromType(antiSourceLookup[sourceProfileType])
|
||||
if sourceProfile == nil || antiSourceProfile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sourceCurrency := sourceProfile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
antiSourceCurrency := antiSourceProfile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if sourceCurrency == nil || antiSourceCurrency == nil {
|
||||
return
|
||||
}
|
||||
|
||||
antiSourceCurrency.Quantity = sourceCurrency.Quantity
|
||||
antiSourceCurrency.Save()
|
||||
}
|
||||
|
||||
func (p *Person) TakeAndSyncVbucks(quant int) {
|
||||
currency := p.CommonCoreProfile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if currency == nil {
|
||||
aid.Print("currency not found")
|
||||
return
|
||||
}
|
||||
|
||||
currency.Quantity -= quant
|
||||
currency.Save()
|
||||
|
||||
p.SyncVBucks("common_core")
|
||||
}
|
||||
|
||||
func (p *Person) GiveAndSyncVbucks(quant int) {
|
||||
currency := p.CommonCoreProfile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||
if currency == nil {
|
||||
aid.Print("currency not found")
|
||||
return
|
||||
}
|
||||
|
||||
currency.Quantity += quant
|
||||
currency.Save()
|
||||
|
||||
p.SyncVBucks("common_core")
|
||||
}
|
|
@ -18,6 +18,7 @@ type Profile struct {
|
|||
Attributes *AttributeMutex
|
||||
Loadouts *LoadoutMutex
|
||||
Purchases *PurchaseMutex
|
||||
VariantTokens *VariantTokenMutex
|
||||
Type string
|
||||
Revision int
|
||||
Changes []interface{}
|
||||
|
@ -34,6 +35,7 @@ func NewProfile(profile string) *Profile {
|
|||
Attributes: NewAttributeMutex(&storage.DB_Profile{ID: id, Type: profile}),
|
||||
Loadouts: NewLoadoutMutex(&storage.DB_Profile{ID: id, Type: profile}),
|
||||
Purchases: NewPurchaseMutex(&storage.DB_Profile{ID: id, Type: profile}),
|
||||
VariantTokens: NewVariantTokenMutex(&storage.DB_Profile{ID: id, Type: profile}),
|
||||
Type: profile,
|
||||
Revision: 0,
|
||||
Changes: []interface{}{},
|
||||
|
@ -47,6 +49,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
|
|||
attributes := NewAttributeMutex(profile)
|
||||
loadouts := NewLoadoutMutex(profile)
|
||||
purchases := NewPurchaseMutex(profile)
|
||||
variantTokens := NewVariantTokenMutex(profile)
|
||||
|
||||
for _, item := range profile.Items {
|
||||
items.AddItem(FromDatabaseItem(&item))
|
||||
|
@ -56,6 +59,10 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
|
|||
gifts.AddGift(FromDatabaseGift(&gift))
|
||||
}
|
||||
|
||||
for _, variantToken := range profile.VariantTokens {
|
||||
variantTokens.AddVariantToken(FromDatabaseVariantToken(&variantToken))
|
||||
}
|
||||
|
||||
for _, quest := range profile.Quests {
|
||||
quests.AddQuest(FromDatabaseQuest(&quest))
|
||||
}
|
||||
|
@ -87,6 +94,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
|
|||
Attributes: attributes,
|
||||
Loadouts: loadouts,
|
||||
Purchases: purchases,
|
||||
VariantTokens: variantTokens,
|
||||
Type: profile.Type,
|
||||
Revision: profile.Revision,
|
||||
Changes: []interface{}{},
|
||||
|
@ -112,6 +120,11 @@ func (p *Profile) GenerateFortniteProfileEntry() aid.JSON {
|
|||
return true
|
||||
})
|
||||
|
||||
p.VariantTokens.RangeVariantTokens(func(id string, variantToken *VariantToken) bool {
|
||||
items[id] = variantToken.GenerateFortniteVariantTokenEntry()
|
||||
return true
|
||||
})
|
||||
|
||||
p.Attributes.RangeAttributes(func(id string, attribute *Attribute) bool {
|
||||
attributes[attribute.Key] = aid.JSONParse(attribute.ValueJSON)
|
||||
return true
|
||||
|
@ -146,6 +159,7 @@ func (p *Profile) Snapshot() *ProfileSnapshot {
|
|||
quests := map[string]Quest{}
|
||||
attributes := map[string]Attribute{}
|
||||
loadouts := map[string]Loadout{}
|
||||
variantTokens := map[string]VariantToken{}
|
||||
|
||||
p.Items.RangeItems(func(id string, item *Item) bool {
|
||||
items[id] = item.Snapshot()
|
||||
|
@ -172,6 +186,11 @@ func (p *Profile) Snapshot() *ProfileSnapshot {
|
|||
return true
|
||||
})
|
||||
|
||||
p.VariantTokens.RangeVariantTokens(func(id string, variantToken *VariantToken) bool {
|
||||
variantTokens[id] = *variantToken
|
||||
return true
|
||||
})
|
||||
|
||||
return &ProfileSnapshot{
|
||||
ID: p.ID,
|
||||
Items: items,
|
||||
|
@ -212,10 +231,12 @@ func (p *Profile) Diff(b *ProfileSnapshot) []diff.Change {
|
|||
item := p.Items.GetItem(change.Path[1])
|
||||
p.CreateItemAttributeChangedChange(item, change.Path[2])
|
||||
|
||||
slotType := loadout.GetSlotFromItemTemplateID(item.TemplateID)
|
||||
slotValue := loadout.GetItemFromSlot(slotType)
|
||||
if slotValue != nil && slotValue.ID == item.ID {
|
||||
p.CreateLoadoutChangedChange(loadout, slotType + "ID")
|
||||
if loadout != nil {
|
||||
slotType := loadout.GetSlotFromItemTemplateID(item.TemplateID)
|
||||
slotValue := loadout.GetItemFromSlot(slotType)
|
||||
if slotValue != nil && slotValue.ID == item.ID {
|
||||
p.CreateLoadoutChangedChange(loadout, slotType + "ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
case "Quests":
|
||||
|
@ -231,6 +252,14 @@ func (p *Profile) Diff(b *ProfileSnapshot) []diff.Change {
|
|||
p.CreateGiftAddedChange(p.Gifts.GetGift(change.Path[1]))
|
||||
}
|
||||
|
||||
if change.Type == "delete" && change.Path[2] == "ID" {
|
||||
p.CreateItemRemovedChange(change.Path[1])
|
||||
}
|
||||
case "VariantTokens":
|
||||
if change.Type == "create" && change.Path[2] == "ID" {
|
||||
p.CreateVariantTokenAddedChange(p.VariantTokens.GetVariantToken(change.Path[1]))
|
||||
}
|
||||
|
||||
if change.Type == "delete" && change.Path[2] == "ID" {
|
||||
p.CreateItemRemovedChange(change.Path[1])
|
||||
}
|
||||
|
@ -310,6 +339,19 @@ func (p *Profile) CreateGiftAddedChange(gift *Gift) {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *Profile) CreateVariantTokenAddedChange(variantToken *VariantToken) {
|
||||
if variantToken == nil {
|
||||
fmt.Println("error getting variant token from profile", variantToken.ID)
|
||||
return
|
||||
}
|
||||
|
||||
p.Changes = append(p.Changes, ItemAdded{
|
||||
ChangeType: "itemAdded",
|
||||
ItemId: variantToken.ID,
|
||||
Item: variantToken.GenerateFortniteVariantTokenEntry(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Profile) CreateQuestAddedChange(quest *Quest) {
|
||||
if quest == nil {
|
||||
fmt.Println("error getting quest from profile", quest.ID)
|
||||
|
@ -443,6 +485,7 @@ func (p *Profile) ToDatabase() *storage.DB_Profile {
|
|||
Type: p.Type,
|
||||
Items: []storage.DB_Item{},
|
||||
Gifts: []storage.DB_Gift{},
|
||||
VariantTokens: []storage.DB_VariantToken{},
|
||||
Quests: []storage.DB_Quest{},
|
||||
Loadouts: []storage.DB_Loadout{},
|
||||
Purchases: []storage.DB_Purchase{},
|
||||
|
@ -450,16 +493,21 @@ func (p *Profile) ToDatabase() *storage.DB_Profile {
|
|||
Revision: p.Revision,
|
||||
}
|
||||
|
||||
// p.Items.RangeItems(func(id string, item *Item) bool {
|
||||
// dbProfile.Items = append(dbProfile.Items, *item.ToDatabase(dbProfile.PersonID))
|
||||
// return true
|
||||
// })
|
||||
p.Items.RangeItems(func(id string, item *Item) bool {
|
||||
dbProfile.Items = append(dbProfile.Items, *item.ToDatabase(dbProfile.PersonID))
|
||||
return true
|
||||
}) // slow
|
||||
|
||||
p.Gifts.RangeGifts(func(id string, gift *Gift) bool {
|
||||
dbProfile.Gifts = append(dbProfile.Gifts, *gift.ToDatabase(dbProfile.PersonID))
|
||||
return true
|
||||
})
|
||||
|
||||
p.VariantTokens.RangeVariantTokens(func(id string, variantToken *VariantToken) bool {
|
||||
dbProfile.VariantTokens = append(dbProfile.VariantTokens, *variantToken.ToDatabase(dbProfile.PersonID))
|
||||
return true
|
||||
})
|
||||
|
||||
p.Quests.RangeQuests(func(id string, quest *Quest) bool {
|
||||
dbProfile.Quests = append(dbProfile.Quests, *quest.ToDatabase(dbProfile.PersonID))
|
||||
return true
|
||||
|
|
115
person/receipt.go
Normal file
115
person/receipt.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package person
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Receipt struct {
|
||||
ID string
|
||||
PersonID string
|
||||
OfferID string
|
||||
PurchaseDate int64
|
||||
TotalPaid int
|
||||
State string
|
||||
Loot []*Item
|
||||
}
|
||||
|
||||
func NewReceipt(offerID string, totalPaid int) *Receipt {
|
||||
return &Receipt{
|
||||
ID: uuid.New().String(),
|
||||
OfferID: offerID,
|
||||
PurchaseDate: time.Now().Unix(),
|
||||
TotalPaid: totalPaid,
|
||||
Loot: []*Item{},
|
||||
State: "PENDING",
|
||||
}
|
||||
}
|
||||
|
||||
func FromDatabaseReceipt(receipt *storage.DB_Receipt) *Receipt {
|
||||
loot := []*Item{}
|
||||
|
||||
for _, item := range receipt.Loot {
|
||||
loot = append(loot, FromDatabaseReceiptLoot(&item))
|
||||
}
|
||||
|
||||
return &Receipt{
|
||||
ID: receipt.ID,
|
||||
PersonID: receipt.PersonID,
|
||||
OfferID: receipt.OfferID,
|
||||
PurchaseDate: receipt.PurchaseDate,
|
||||
TotalPaid: receipt.TotalPaid,
|
||||
State: receipt.State,
|
||||
Loot: loot,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Receipt) GenerateUnrealReceiptEntry() aid.JSON {
|
||||
return aid.JSON{
|
||||
"TransactionId": r.ID,
|
||||
"TransactionState": string(r.State),
|
||||
"Offers": []aid.JSON{{
|
||||
"OfferNamespace": "fn",
|
||||
"OfferId": r.OfferID,
|
||||
"Items": []aid.JSON{{
|
||||
"EntitlementId": r.ID,
|
||||
"EntitlementName": "",
|
||||
"ItemId": r.OfferID,
|
||||
"ItemNamespace": "fn",
|
||||
}},
|
||||
}},
|
||||
"grantedVoucher": aid.JSON{},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Receipt) GenerateFortniteReceiptEntry() aid.JSON {
|
||||
return aid.JSON{
|
||||
"receiptId": r.ID,
|
||||
"appStoreId": r.OfferID,
|
||||
"receiptInfo": r.State,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Receipt) AddLoot(item *Item) {
|
||||
r.Loot = append(r.Loot, item)
|
||||
}
|
||||
|
||||
func (r *Receipt) SetState(state string) {
|
||||
r.State = state
|
||||
}
|
||||
|
||||
func (r *Receipt) Delete() {
|
||||
for _, item := range r.Loot {
|
||||
item.DeleteReceiptLoot()
|
||||
}
|
||||
|
||||
storage.Repo.DeleteReceipt(r.ID)
|
||||
}
|
||||
|
||||
func (r *Receipt) Save() {
|
||||
for _, item := range r.Loot {
|
||||
storage.Repo.SaveReceiptLoot(item.ToReceiptLootDatabase(r.ID))
|
||||
}
|
||||
storage.Repo.SaveReceipt(r.ToDatabase())
|
||||
}
|
||||
|
||||
func (r *Receipt) ToDatabase() *storage.DB_Receipt {
|
||||
loot := []storage.DB_ReceiptLoot{}
|
||||
|
||||
for _, item := range r.Loot {
|
||||
loot = append(loot, *item.ToReceiptLootDatabase(r.ID))
|
||||
}
|
||||
|
||||
return &storage.DB_Receipt{
|
||||
ID: r.ID,
|
||||
PersonID: r.PersonID,
|
||||
OfferID: r.OfferID,
|
||||
PurchaseDate: r.PurchaseDate,
|
||||
TotalPaid: r.TotalPaid,
|
||||
State: r.State,
|
||||
Loot: loot,
|
||||
}
|
||||
}
|
55
person/season.go
Normal file
55
person/season.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package person
|
||||
|
||||
import (
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type SeasonStats struct {
|
||||
ID string
|
||||
PersonID string
|
||||
Season int
|
||||
SeasonXP int
|
||||
BookXP int
|
||||
BookPurchased bool
|
||||
Hype int
|
||||
}
|
||||
|
||||
func NewSeasonStats(season int) *SeasonStats {
|
||||
return &SeasonStats{
|
||||
ID: uuid.New().String(),
|
||||
Season: season,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SeasonStats) ToDatabase(personId string) *storage.DB_SeasonStat {
|
||||
return &storage.DB_SeasonStat{
|
||||
ID: s.ID,
|
||||
PersonID: personId,
|
||||
Season: s.Season,
|
||||
SeasonXP: s.SeasonXP,
|
||||
BookXP: s.BookXP,
|
||||
BookPurchased: s.BookPurchased,
|
||||
Hype: s.Hype,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SeasonStats) Save() {
|
||||
storage.Repo.SaveSeasonStats(s.ToDatabase(s.PersonID))
|
||||
}
|
||||
|
||||
func (s *SeasonStats) Delete() {
|
||||
storage.Repo.DeleteSeasonStats(s.ID)
|
||||
}
|
||||
|
||||
func FromDatabaseSeasonStats(db storage.DB_SeasonStat) *SeasonStats {
|
||||
return &SeasonStats{
|
||||
ID: db.ID,
|
||||
PersonID: db.PersonID,
|
||||
Season: db.Season,
|
||||
SeasonXP: db.SeasonXP,
|
||||
BookXP: db.BookXP,
|
||||
BookPurchased: db.BookPurchased,
|
||||
Hype: db.Hype,
|
||||
}
|
||||
}
|
|
@ -13,6 +13,9 @@ type PersonSnapshot struct {
|
|||
Profile0Profile ProfileSnapshot
|
||||
CollectionsProfile ProfileSnapshot
|
||||
CreativeProfile ProfileSnapshot
|
||||
CurrentSeasonStats SeasonStats
|
||||
AllSeasonsStats []SeasonStats
|
||||
Receipts []storage.DB_Receipt
|
||||
BanHistory []storage.DB_BanStatus
|
||||
Discord storage.DB_DiscordPerson
|
||||
Relationships map[string]*Relationship
|
||||
|
@ -25,6 +28,7 @@ type ProfileSnapshot struct {
|
|||
ID string
|
||||
Items map[string]ItemSnapshot
|
||||
Gifts map[string]GiftSnapshot
|
||||
Variants map[string]VariantChannel
|
||||
Quests map[string]Quest
|
||||
Attributes map[string]Attribute
|
||||
Loadouts map[string]Loadout
|
||||
|
|
101
person/sync.go
101
person/sync.go
|
@ -364,4 +364,105 @@ func (m *PurchaseMutex) CountRefunded() int {
|
|||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
type ReceiptMutex struct {
|
||||
sync.Map
|
||||
PersonID string
|
||||
}
|
||||
|
||||
func NewReceiptMutex(personID string) *ReceiptMutex {
|
||||
return &ReceiptMutex{
|
||||
PersonID: personID,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ReceiptMutex) AddReceipt(receipt *Receipt) *Receipt {
|
||||
receipt.PersonID = m.PersonID
|
||||
m.Store(receipt.ID, receipt)
|
||||
// storage.Repo.SaveReceipt(receipt.ToDatabase())
|
||||
return receipt
|
||||
}
|
||||
|
||||
func (m *ReceiptMutex) DeleteReceipt(id string) {
|
||||
receipt := m.GetReceipt(id)
|
||||
if receipt == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Delete(id)
|
||||
receipt.Delete()
|
||||
}
|
||||
|
||||
func (m *ReceiptMutex) GetReceipt(id string) *Receipt {
|
||||
receipt, ok := m.Load(id)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return receipt.(*Receipt)
|
||||
}
|
||||
|
||||
func (m *ReceiptMutex) RangeReceipts(f func(key string, value *Receipt) bool) {
|
||||
m.Range(func(key, value interface{}) bool {
|
||||
return f(key.(string), value.(*Receipt))
|
||||
})
|
||||
}
|
||||
|
||||
func (m *ReceiptMutex) Count() int {
|
||||
count := 0
|
||||
m.Range(func(key, value interface{}) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
type VariantTokenMutex struct {
|
||||
sync.Map
|
||||
ProfileID string
|
||||
ProfileType string
|
||||
}
|
||||
|
||||
func NewVariantTokenMutex(profile *storage.DB_Profile) *VariantTokenMutex {
|
||||
return &VariantTokenMutex{
|
||||
ProfileID: profile.ID,
|
||||
ProfileType: profile.Type,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *VariantTokenMutex) AddVariantToken(token *VariantToken) *VariantToken {
|
||||
token.ProfileID = m.ProfileID
|
||||
m.Store(token.ID, token)
|
||||
// storage.Repo.SaveVariantToken(token.ToDatabase(m.ProfileID))
|
||||
return token
|
||||
}
|
||||
|
||||
func (m *VariantTokenMutex) DeleteVariantToken(id string) {
|
||||
m.Delete(id)
|
||||
storage.Repo.DeleteVariantToken(id)
|
||||
}
|
||||
|
||||
func (m *VariantTokenMutex) GetVariantToken(id string) *VariantToken {
|
||||
token, ok := m.Load(id)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return token.(*VariantToken)
|
||||
}
|
||||
|
||||
func (m *VariantTokenMutex) RangeVariantTokens(f func(key string, value *VariantToken) bool) {
|
||||
m.Range(func(key, value interface{}) bool {
|
||||
return f(key.(string), value.(*VariantToken))
|
||||
})
|
||||
}
|
||||
|
||||
func (m *VariantTokenMutex) Count() int {
|
||||
count := 0
|
||||
m.Range(func(key, value interface{}) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
130
person/variant.go
Normal file
130
person/variant.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package person
|
||||
|
||||
import (
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type VariantToken struct {
|
||||
ID string
|
||||
ProfileID string
|
||||
TemplateID string
|
||||
Name string
|
||||
AutoEquipOnGrant bool
|
||||
CreateGiftboxOnGrant bool
|
||||
MarkItemUnseenOnGrant bool
|
||||
VariantGrants []*VariantTokenGrant
|
||||
}
|
||||
|
||||
func NewVariantToken(profileID, templateID string) *VariantToken {
|
||||
return &VariantToken{
|
||||
ID: uuid.New().String(),
|
||||
ProfileID: profileID,
|
||||
TemplateID: templateID,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VariantToken) AddVariantGrant(channel, value string) {
|
||||
vtGrant := &VariantTokenGrant{
|
||||
ID: uuid.New().String(),
|
||||
VariantTokenID: v.ID,
|
||||
Channel: channel,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
v.VariantGrants = append(v.VariantGrants, vtGrant)
|
||||
}
|
||||
|
||||
func FromDatabaseVariantToken(token *storage.DB_VariantToken) *VariantToken {
|
||||
variantGrants := []*VariantTokenGrant{}
|
||||
|
||||
for _, grant := range token.VariantGrants {
|
||||
variantGrants = append(variantGrants, FromDatabaseVariantTokenGrant(&grant))
|
||||
}
|
||||
|
||||
return &VariantToken{
|
||||
ID: token.ID,
|
||||
ProfileID: token.ProfileID,
|
||||
TemplateID: token.TemplateID,
|
||||
Name: token.Name,
|
||||
AutoEquipOnGrant: token.AutoEquipOnGrant,
|
||||
CreateGiftboxOnGrant: token.CreateGiftboxOnGrant,
|
||||
MarkItemUnseenOnGrant: token.MarkItemUnseenOnGrant,
|
||||
VariantGrants: variantGrants,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VariantToken) GenerateFortniteVariantTokenEntry() aid.JSON {
|
||||
return aid.JSON{
|
||||
"templateId": v.TemplateID,
|
||||
"attributes": aid.JSON{
|
||||
"auto_equip_variant": v.AutoEquipOnGrant,
|
||||
"create_giftbox": v.CreateGiftboxOnGrant,
|
||||
"mark_item_unseen": v.MarkItemUnseenOnGrant,
|
||||
"variant_name": v.Name,
|
||||
},
|
||||
"quantity": 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VariantToken) ToDatabase(profileID string) *storage.DB_VariantToken {
|
||||
variantGrants := []storage.DB_VariantTokenGrant{}
|
||||
|
||||
for _, grant := range v.VariantGrants {
|
||||
variantGrants = append(variantGrants, *grant.ToDatabase())
|
||||
}
|
||||
|
||||
return &storage.DB_VariantToken{
|
||||
ID: v.ID,
|
||||
ProfileID: profileID,
|
||||
TemplateID: v.TemplateID,
|
||||
Name: v.Name,
|
||||
AutoEquipOnGrant: v.AutoEquipOnGrant,
|
||||
CreateGiftboxOnGrant: v.CreateGiftboxOnGrant,
|
||||
MarkItemUnseenOnGrant: v.MarkItemUnseenOnGrant,
|
||||
VariantGrants: variantGrants,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VariantToken) Save() {
|
||||
storage.Repo.SaveVariantToken(v.ToDatabase(v.ProfileID))
|
||||
}
|
||||
|
||||
func (v *VariantToken) Delete() {
|
||||
storage.Repo.DeleteVariantToken(v.ID)
|
||||
}
|
||||
|
||||
type VariantTokenGrant struct {
|
||||
ID string
|
||||
VariantTokenID string
|
||||
Channel string
|
||||
Value string
|
||||
}
|
||||
|
||||
func NewVariantTokenGrant(vtID, channel, value string) *VariantTokenGrant {
|
||||
return &VariantTokenGrant{
|
||||
ID: uuid.New().String(),
|
||||
VariantTokenID: vtID,
|
||||
Channel: channel,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func FromDatabaseVariantTokenGrant(grant *storage.DB_VariantTokenGrant) *VariantTokenGrant {
|
||||
return &VariantTokenGrant{
|
||||
ID: grant.ID,
|
||||
VariantTokenID: grant.VariantTokenID,
|
||||
Channel: grant.Channel,
|
||||
Value: grant.Value,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VariantTokenGrant) ToDatabase() *storage.DB_VariantTokenGrant {
|
||||
return &storage.DB_VariantTokenGrant{
|
||||
ID: v.ID,
|
||||
VariantTokenID: v.VariantTokenID,
|
||||
Channel: v.Channel,
|
||||
Value: v.Value,
|
||||
}
|
||||
}
|
15
readme.md
15
readme.md
|
@ -4,18 +4,14 @@
|
|||
|
||||
> Performance first, feature-rich universal Fortnite private server backend written in Go.
|
||||
|
||||
Snow will no longer be updated. I have reached a point where I would need a game server to make it worth adding new features, e.g. Leaderboards, Challenges etc. In the future I may continue but for the time being no updates will occur. If you would like to contribute I will still review each request!
|
||||
|
||||
## Overview
|
||||
|
||||
- **Single File** It will embed all of the external files inside of one executable! This allows the backend to be ran anywhere with no setup _(after initial config)_!
|
||||
- **Blazingly Fast** Written in Go and built upon Fast HTTP, it is extremely fast and can handle any profile action in milliseconds with its caching system.
|
||||
- **Automatic Profile Changes** Automatically keeps track of profile changes exactly so any external changes are displayed in-game on the next action.
|
||||
- **Universal Database** It is possible to add new database types to satisfy your needs. Currently, it only supports `postgresql`.
|
||||
|
||||
## What's up next?
|
||||
|
||||
- Purchasing the **Battle Pass**. This will require the Battle Pass Storefront ID for every build. I am yet to think of a solution for this.
|
||||
- Interaction with a Game Server to handle **Event Tracking** for player statistics and challenges. This will be a very large task as a new specialised game server will need to be created.
|
||||
- After the game server addition, a **Matchmaking System** will be added to match players together for a game. It will use a bin packing algorithm to ensure that games are filled as much as possible.
|
||||
|
||||
|
@ -25,22 +21,23 @@ And once battle royale is completed ...
|
|||
|
||||
## Feature List
|
||||
|
||||
- **Battle Pass** Claim a free battle pass and level it up with challenges or buy tiers with V-Bucks.
|
||||
- **Store Purchasing** Buy V-Bucks and Starter Packs right from the in-game store!
|
||||
- **Item Refunding** Of previous shop purchases, will use a refund ticket if refunded in time.
|
||||
- **Automatic Item Shop** Will automatically update the item shop for the day, for all builds.
|
||||
- **Support A Creator 5%** Use any display name and each purchase will give them 5% of the vbucks spent.
|
||||
- **XMPP** For interacting with friends, parties and gifting.
|
||||
- **Friends** On every build, this will allow for adding, removing and blocking friends.
|
||||
- **Party System V2** This replaces the legacy xmpp driven party system.
|
||||
- **Automatic Item Shop** Will automatically update the item shop for the day, for all builds.
|
||||
- **Gifting** Of any item shop entry to any friend.
|
||||
- **Locker Loadouts** On seasons 12 onwards, this allows for the saving and loading of multiple locker presets.
|
||||
- **Item Refunding** Of previous shop purchases, will use a refund ticket if refunded in time.
|
||||
- **V-Bucks Purchasing** Buy V-Bucks and Starter Packs right from the in-game store!
|
||||
- **Client Settings Storage** Uses amazon buckets to store client settings.
|
||||
- **Giftable Bundles** Players can recieve bundles, e.g. Twitch Prime, and gift them to friends.
|
||||
- **Support A Creator 5%** Use any display name and each purchase will give them 5% of the vbucks spent.
|
||||
- **Discord Bot** Very useful to control players, their inventory and their settings
|
||||
|
||||
## Supported MCP Actions
|
||||
|
||||
`QueryProfile`, `ClientQuestLogin`, `MarkItemSeen`, `SetItemFavoriteStatusBatch`, `EquipBattleRoyaleCustomization`, `SetBattleRoyaleBanner`, `SetCosmeticLockerSlot`, `SetCosmeticLockerBanner`, `SetCosmeticLockerName`, `CopyCosmeticLoadout`, `DeleteCosmeticLoadout`, `PurchaseCatalogEntry`, `GiftCatalogEntry`, `RemoveGiftBox`, `RefundMtxPurchase`, `SetAffiliateName`, `SetReceiveGiftsEnabled`
|
||||
`QueryProfile`, `ClientQuestLogin`, `MarkItemSeen`, `SetItemFavoriteStatusBatch`, `EquipBattleRoyaleCustomization`, `SetBattleRoyaleBanner`, `SetCosmeticLockerSlot`, `SetCosmeticLockerBanner`, `SetCosmeticLockerName`, `CopyCosmeticLoadout`, `DeleteCosmeticLoadout`, `PurchaseCatalogEntry`, `GiftCatalogEntry`, `RemoveGiftBox`, `RefundMtxPurchase`, `SetAffiliateName`, `SetReceiveGiftsEnabled`, `VerifyRealMoneyPurchase`
|
||||
|
||||
## Support
|
||||
|
||||
|
|
130
shop/book.go
Normal file
130
shop/book.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package shop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
)
|
||||
|
||||
type StorefrontCatalogOfferMetaTypeBattlePass struct {
|
||||
TileSize string
|
||||
SectionID string
|
||||
DisplayAssetPath string
|
||||
NewDisplayAssetPath string
|
||||
Priority int
|
||||
OnlyOnce bool
|
||||
}
|
||||
|
||||
type StorefrontCatalogOfferTypeBattlePass struct {
|
||||
OfferID string
|
||||
OfferType StorefrontCatalogOfferEnum
|
||||
Rewards []*StorefrontCatalogOfferGrant
|
||||
Price *StorefrontCatalogOfferPriceMtxCurrency
|
||||
Diplay *OfferDisplay
|
||||
Categories []string
|
||||
Meta *StorefrontCatalogOfferMetaTypeBattlePass
|
||||
}
|
||||
|
||||
func NewBattlePassCatalogOffer() *StorefrontCatalogOfferTypeBattlePass {
|
||||
return &StorefrontCatalogOfferTypeBattlePass{
|
||||
OfferID: aid.RandomString(32),
|
||||
OfferType: StorefrontCatalogOfferEnumBattlePass,
|
||||
Rewards: make([]*StorefrontCatalogOfferGrant, 0),
|
||||
Price: &StorefrontCatalogOfferPriceMtxCurrency{},
|
||||
Diplay: &OfferDisplay{},
|
||||
Categories: make([]string, 0),
|
||||
Meta: &StorefrontCatalogOfferMetaTypeBattlePass{},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeBattlePass) GetOffer() *StorefrontCatalogOfferTypeBattlePass {
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeBattlePass) GetOfferID() string {
|
||||
return o.OfferID
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeBattlePass) GetOfferType() StorefrontCatalogOfferEnum {
|
||||
return o.OfferType
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeBattlePass) GetOfferPrice() *StorefrontCatalogOfferPriceMtxCurrency {
|
||||
return o.Price
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeBattlePass) GetRewards() []*StorefrontCatalogOfferGrant {
|
||||
return o.Rewards
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeBattlePass) GenerateFortniteCatalogOfferResponse() aid.JSON {
|
||||
return aid.JSON{
|
||||
"offerId": o.OfferID,
|
||||
"offerType": "StaticPrice",
|
||||
"devName": fmt.Sprintf("[BOOK] %s", o.Diplay.ShortDescription),
|
||||
"itemGrants": []string{},
|
||||
"requirements": aid.Ternary[[]aid.JSON](o.Meta.OnlyOnce, []aid.JSON{{
|
||||
"requirementType": "DenyOnFulfillment",
|
||||
"requiredId": o.GetOfferID(),
|
||||
"minQuantity": 1,
|
||||
}}, []aid.JSON{}),
|
||||
"fulfillmentIds": []string{o.OfferID},
|
||||
"categories": o.Categories,
|
||||
"metaInfo": []aid.JSON{
|
||||
{
|
||||
"Key": "TileSize",
|
||||
"Value": o.Meta.TileSize,
|
||||
},
|
||||
{
|
||||
"Key": "SectionId",
|
||||
"Value": o.Meta.SectionID,
|
||||
},
|
||||
{
|
||||
"Key": "NewDisplayAssetPath",
|
||||
"Value": o.Meta.NewDisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"Key": "DisplayAssetPath",
|
||||
"Value": o.Meta.DisplayAssetPath,
|
||||
},
|
||||
},
|
||||
"meta": aid.JSON{
|
||||
"TileSize": o.Meta.TileSize,
|
||||
"SectionId": o.Meta.SectionID,
|
||||
"DisplayAssetPath": o.Meta.DisplayAssetPath,
|
||||
"NewDisplayAssetPath": o.Meta.NewDisplayAssetPath,
|
||||
},
|
||||
"giftInfo": aid.JSON{
|
||||
"bIsEnabled": false,
|
||||
"forcedGiftBoxTemplateId": "",
|
||||
"purchaseRequirements": []string{},
|
||||
"giftRecordIds": []string{},
|
||||
},
|
||||
"prices": []aid.JSON{{
|
||||
"currencyType": "MtxCurrency",
|
||||
"currencySubType": "Currency",
|
||||
"regularPrice": o.Price.OriginalPrice,
|
||||
"dynamicRegularPrice": -1,
|
||||
"finalPrice": o.Price.FinalPrice,
|
||||
"basePrice": o.Price.OriginalPrice,
|
||||
"saleType": o.Price.SaleType,
|
||||
"saleExpiration": "9999-12-31T23:59:59.999Z",
|
||||
}},
|
||||
"displayAssetPath": o.Meta.DisplayAssetPath,
|
||||
"refundable": false,
|
||||
"title": o.Diplay.Title,
|
||||
"description": o.Diplay.Description,
|
||||
"shortDescription": o.Diplay.ShortDescription,
|
||||
"appStoreId": []string{},
|
||||
"dailyLimit": -1,
|
||||
"weeklyLimit": -1,
|
||||
"monthlyLimit": -1,
|
||||
"sortPriority": o.Meta.Priority,
|
||||
"catalogGroupPriority": 0,
|
||||
"filterWeight": 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeBattlePass) GenerateFortniteBulkOffersResponse() aid.JSON {
|
||||
return aid.JSON{}
|
||||
}
|
46
shop/catalog.go
Normal file
46
shop/catalog.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package shop
|
||||
|
||||
import "github.com/ectrc/snow/aid"
|
||||
|
||||
type StorefrontCatalog struct {
|
||||
Sections []*StorefrontCatalogSection
|
||||
}
|
||||
|
||||
func NewStorefrontCatalog() *StorefrontCatalog {
|
||||
return &StorefrontCatalog{
|
||||
Sections: make([]*StorefrontCatalogSection, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StorefrontCatalog) AddSection(section *StorefrontCatalogSection) {
|
||||
c.Sections = append(c.Sections, section)
|
||||
}
|
||||
func (c *StorefrontCatalog) AddSections(sections ...*StorefrontCatalogSection) {
|
||||
c.Sections = append(c.Sections, sections...)
|
||||
}
|
||||
|
||||
func (c *StorefrontCatalog) GetOfferByID(offerID string) (interface{}, StorefrontCatalogOfferEnum) {
|
||||
for _, section := range c.Sections {
|
||||
found, type_ := section.GetOfferByID(offerID)
|
||||
if found != nil {
|
||||
return found, type_
|
||||
}
|
||||
}
|
||||
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (c *StorefrontCatalog) GenerateFortniteCatalogResponse() aid.JSON {
|
||||
sectionsResponse := []aid.JSON{}
|
||||
|
||||
for _, section := range c.Sections {
|
||||
sectionsResponse = append(sectionsResponse, section.GenerateFortniteCatalogSectionResponse())
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"storefronts": sectionsResponse,
|
||||
"refreshIntervalHrs": 24,
|
||||
"dailyPurchaseHrs": 24,
|
||||
"expiration": "9999-12-31T23:59:59.999Z",
|
||||
}
|
||||
}
|
151
shop/item.go
Normal file
151
shop/item.go
Normal file
|
@ -0,0 +1,151 @@
|
|||
package shop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
)
|
||||
|
||||
type StorefrontCatalogOfferMetaTypeItem struct {
|
||||
TileSize string
|
||||
SectionID string
|
||||
DisplayAssetPath string
|
||||
NewDisplayAssetPath string
|
||||
BannerOverride string
|
||||
Giftable bool
|
||||
Refundable bool
|
||||
}
|
||||
|
||||
type StorefrontCatalogOfferTypeItem struct {
|
||||
OfferID string
|
||||
OfferType StorefrontCatalogOfferEnum
|
||||
Rewards []*StorefrontCatalogOfferGrant
|
||||
Price *StorefrontCatalogOfferPriceMtxCurrency
|
||||
Diplay *OfferDisplay
|
||||
Categories []string
|
||||
Meta *StorefrontCatalogOfferMetaTypeItem
|
||||
}
|
||||
|
||||
func NewItemCatalogOffer() *StorefrontCatalogOfferTypeItem {
|
||||
return &StorefrontCatalogOfferTypeItem{
|
||||
OfferID: aid.RandomString(32),
|
||||
OfferType: StorefrontCatalogOfferEnumItem,
|
||||
Rewards: make([]*StorefrontCatalogOfferGrant, 0),
|
||||
Price: &StorefrontCatalogOfferPriceMtxCurrency{},
|
||||
Diplay: &OfferDisplay{},
|
||||
Categories: make([]string, 0),
|
||||
Meta: &StorefrontCatalogOfferMetaTypeItem{},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeItem) GetOffer() *StorefrontCatalogOfferTypeItem {
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeItem) GetOfferID() string {
|
||||
return o.OfferID
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeItem) GetOfferType() StorefrontCatalogOfferEnum {
|
||||
return o.OfferType
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeItem) GetOfferPrice() *StorefrontCatalogOfferPriceMtxCurrency {
|
||||
return o.Price
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeItem) GetRewards() []*StorefrontCatalogOfferGrant {
|
||||
return o.Rewards
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeItem) GenerateFortniteCatalogOfferResponse() aid.JSON {
|
||||
itemGrantResponse := []aid.JSON{}
|
||||
purchaseRequirementsResponse := []aid.JSON{}
|
||||
developerNameResponse := "[ITEM]"
|
||||
|
||||
for _, reward := range o.Rewards {
|
||||
itemGrantResponse = append(itemGrantResponse, aid.JSON{
|
||||
"templateId": reward.TemplateID,
|
||||
"quantity": reward.Quantity,
|
||||
})
|
||||
|
||||
purchaseRequirementsResponse = append(purchaseRequirementsResponse, aid.JSON{
|
||||
"requirementType": "DenyOnItemOwnership",
|
||||
"requiredId": reward.TemplateID,
|
||||
"minQuantity": 1,
|
||||
})
|
||||
|
||||
developerNameResponse += fmt.Sprintf(" %dx %s", reward.Quantity, reward.TemplateID)
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"offerId": o.OfferID,
|
||||
"offerType": "StaticPrice",
|
||||
"devName": fmt.Sprintf("%s for %d MtxCurrency", developerNameResponse, o.Price.OriginalPrice),
|
||||
"itemGrants": itemGrantResponse,
|
||||
"requirements": purchaseRequirementsResponse,
|
||||
"categories": o.Categories,
|
||||
"metaInfo": []aid.JSON{
|
||||
{
|
||||
"Key": "TileSize",
|
||||
"Value": o.Meta.TileSize,
|
||||
},
|
||||
{
|
||||
"Key": "SectionId",
|
||||
"Value": o.Meta.SectionID,
|
||||
},
|
||||
{
|
||||
"Key": "NewDisplayAssetPath",
|
||||
"Value": o.Meta.NewDisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"Key": "DisplayAssetPath",
|
||||
"Value": o.Meta.DisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"Key": "BannerOverride",
|
||||
"Value": o.Meta.BannerOverride,
|
||||
},
|
||||
},
|
||||
"meta": aid.JSON{
|
||||
"TileSize": o.Meta.TileSize,
|
||||
"SectionId": o.Meta.SectionID,
|
||||
"DisplayAssetPath": o.Meta.DisplayAssetPath,
|
||||
"NewDisplayAssetPath": o.Meta.NewDisplayAssetPath,
|
||||
"BannerOverride": o.Meta.BannerOverride,
|
||||
},
|
||||
"giftInfo": aid.JSON{
|
||||
"bIsEnabled": o.Meta.Giftable,
|
||||
"forcedGiftBoxTemplateId": "",
|
||||
"purchaseRequirements": purchaseRequirementsResponse,
|
||||
"giftRecordIds": []string{},
|
||||
},
|
||||
"prices": []aid.JSON{{
|
||||
"currencyType": "MtxCurrency",
|
||||
"currencySubType": "Currency",
|
||||
"regularPrice": o.Price.OriginalPrice,
|
||||
"dynamicRegularPrice": -1,
|
||||
"finalPrice": o.Price.FinalPrice,
|
||||
"basePrice": o.Price.OriginalPrice,
|
||||
"saleExpiration": "9999-12-31T23:59:59.999Z",
|
||||
}},
|
||||
"bannerOverride": o.Meta.BannerOverride,
|
||||
"displayAssetPath": o.Meta.DisplayAssetPath,
|
||||
"refundable": o.Meta.Refundable,
|
||||
"title": o.Diplay.Title,
|
||||
"description": o.Diplay.Description,
|
||||
"shortDescription": o.Diplay.ShortDescription,
|
||||
"appStoreId": []string{},
|
||||
"fulfillmentIds": []string{},
|
||||
"dailyLimit": -1,
|
||||
"weeklyLimit": -1,
|
||||
"monthlyLimit": -1,
|
||||
"sortPriority": 0,
|
||||
"catalogGroupPriority": 0,
|
||||
"filterWeight": 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeItem) GenerateFortniteBulkOffersResponse() aid.JSON {
|
||||
return aid.JSON{}
|
||||
}
|
148
shop/kits.go
Normal file
148
shop/kits.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
package shop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
)
|
||||
|
||||
type StorefrontCatalogOfferMetaTypeStarterKit struct {
|
||||
TileSize string
|
||||
DisplayAssetPath string
|
||||
NewDisplayAssetPath string
|
||||
OriginalOffer int
|
||||
ExtraBonus int
|
||||
FeaturedImageURL string
|
||||
Priority int
|
||||
ReleaseSeason int
|
||||
}
|
||||
|
||||
type StorefrontCatalogOfferTypeStarterKit struct {
|
||||
OfferType StorefrontCatalogOfferEnum
|
||||
Rewards []*StorefrontCatalogOfferGrant
|
||||
Price *StorefrontCatalogOfferPriceRealMoney
|
||||
Diplay *OfferDisplay
|
||||
Categories []string
|
||||
Meta *StorefrontCatalogOfferMetaTypeStarterKit
|
||||
}
|
||||
|
||||
func NewStarterKitCatalogOffer() *StorefrontCatalogOfferTypeStarterKit {
|
||||
return &StorefrontCatalogOfferTypeStarterKit{
|
||||
OfferType: StorefrontCatalogOfferEnumStarterKit,
|
||||
Rewards: make([]*StorefrontCatalogOfferGrant, 0),
|
||||
Price: &StorefrontCatalogOfferPriceRealMoney{},
|
||||
Diplay: &OfferDisplay{},
|
||||
Categories: make([]string, 0),
|
||||
Meta: &StorefrontCatalogOfferMetaTypeStarterKit{},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeStarterKit) GetOffer() *StorefrontCatalogOfferTypeStarterKit {
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeStarterKit) GetOfferID() string {
|
||||
return fmt.Sprintf("kit://%s", strings.ReplaceAll(o.Diplay.Title, " ", ""))
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeStarterKit) GetOfferType() StorefrontCatalogOfferEnum {
|
||||
return o.OfferType
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeStarterKit) GetOfferPrice() *StorefrontCatalogOfferPriceRealMoney {
|
||||
return o.Price
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeStarterKit) GetRewards() []*StorefrontCatalogOfferGrant {
|
||||
return o.Rewards
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeStarterKit) GenerateFortniteCatalogOfferResponse() aid.JSON {
|
||||
return aid.JSON{
|
||||
"offerId": o.GetOfferID(),
|
||||
"offerType": "StaticPrice",
|
||||
"devName": fmt.Sprintf("[STARTER KIT] %s", o.Diplay.Title),
|
||||
"itemGrants": []aid.JSON{},
|
||||
"requirements": []aid.JSON{{
|
||||
"requirementType": "DenyOnFulfillment",
|
||||
"requiredId": o.GetOfferID(),
|
||||
"minQuantity": 1,
|
||||
}},
|
||||
"categories": o.Categories,
|
||||
"metaInfo": []aid.JSON{
|
||||
{
|
||||
"Key": "TileSize",
|
||||
"Value": o.Meta.TileSize,
|
||||
},
|
||||
{
|
||||
"Key": "NewDisplayAssetPath",
|
||||
"Value": o.Meta.NewDisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"Key": "DisplayAssetPath",
|
||||
"Value": o.Meta.DisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"key": "MtxQuantity",
|
||||
"value": o.Meta.OriginalOffer,
|
||||
},
|
||||
{
|
||||
"key": "MtxBonus",
|
||||
"value": o.Meta.ExtraBonus,
|
||||
},
|
||||
},
|
||||
"meta": aid.JSON{
|
||||
"TileSize": o.Meta.TileSize,
|
||||
"DisplayAssetPath": o.Meta.DisplayAssetPath,
|
||||
"NewDisplayAssetPath": o.Meta.NewDisplayAssetPath,
|
||||
"MtxQuantity": o.Meta.OriginalOffer,
|
||||
"MtxBonus": o.Meta.ExtraBonus,
|
||||
},
|
||||
"giftInfo": aid.JSON{
|
||||
"bIsEnabled": false,
|
||||
"forcedGiftBoxTemplateId": "",
|
||||
"purchaseRequirements": []aid.JSON{},
|
||||
"giftRecordIds": []string{},
|
||||
},
|
||||
"prices": []aid.JSON{{
|
||||
"currencyType": "RealMoney",
|
||||
"currencySubType": "",
|
||||
"regularPrice": -1,
|
||||
"dynamicRegularPrice": -1,
|
||||
"finalPrice": -1,
|
||||
"basePrice": -1,
|
||||
"saleExpiration": "9999-12-31T23:59:59.999Z",
|
||||
}},
|
||||
"displayAssetPath": o.Meta.DisplayAssetPath,
|
||||
"refundable": false,
|
||||
"title": o.Diplay.Title,
|
||||
"description": o.Diplay.Description,
|
||||
"shortDescription": o.Diplay.ShortDescription,
|
||||
"appStoreId": []string{
|
||||
"",
|
||||
o.GetOfferID(),
|
||||
},
|
||||
"dailyLimit": -1,
|
||||
"weeklyLimit": -1,
|
||||
"monthlyLimit": -1,
|
||||
"sortPriority": 0,
|
||||
"catalogGroupPriority": 0,
|
||||
"filterWeight": 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeStarterKit) GenerateFortniteBulkOffersResponse() aid.JSON {
|
||||
return aid.JSON{
|
||||
"id": o.GetOfferID(),
|
||||
"title": o.Diplay.Title,
|
||||
"shortDescription": o.Diplay.ShortDescription,
|
||||
"longDescription": o.Diplay.LongDescription,
|
||||
"creationDate": "0000-00-00T00:00:00.000Z",
|
||||
"price": o.Price.LocalPrice,
|
||||
"currentPrice": o.Price.LocalPrice,
|
||||
"currencyCode": "USD",
|
||||
"basePrice": o.Price.BasePrice,
|
||||
"basePriceCurrencyCode": "GBP",
|
||||
}
|
||||
}
|
157
shop/money.go
Normal file
157
shop/money.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package shop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
)
|
||||
|
||||
type StorefrontCatalogOfferMetaTypeCurrency struct {
|
||||
IconSize string
|
||||
DisplayAssetPath string
|
||||
NewDisplayAssetPath string
|
||||
BannerOverride string
|
||||
CurrencyAnalyticsName string
|
||||
OriginalOffer int
|
||||
ExtraBonus int
|
||||
FeaturedImageURL string
|
||||
Priority int
|
||||
}
|
||||
|
||||
type StorefrontCatalogOfferTypeCurrency struct {
|
||||
OfferType StorefrontCatalogOfferEnum
|
||||
Rewards []*StorefrontCatalogOfferGrant
|
||||
Price *StorefrontCatalogOfferPriceRealMoney
|
||||
Diplay *OfferDisplay
|
||||
Categories []string
|
||||
Meta *StorefrontCatalogOfferMetaTypeCurrency
|
||||
}
|
||||
|
||||
func NewCurrencyCatalogOffer() *StorefrontCatalogOfferTypeCurrency {
|
||||
return &StorefrontCatalogOfferTypeCurrency{
|
||||
OfferType: StorefrontCatalogOfferEnumItem,
|
||||
Rewards: make([]*StorefrontCatalogOfferGrant, 0),
|
||||
Price: &StorefrontCatalogOfferPriceRealMoney{},
|
||||
Diplay: &OfferDisplay{},
|
||||
Categories: make([]string, 0),
|
||||
Meta: &StorefrontCatalogOfferMetaTypeCurrency{},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeCurrency) GetOffer() *StorefrontCatalogOfferTypeCurrency {
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeCurrency) GetOfferID() string {
|
||||
return fmt.Sprintf("money://%s", strings.ReplaceAll(o.Diplay.Title, " ", ""))
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeCurrency) GetOfferType() StorefrontCatalogOfferEnum {
|
||||
return o.OfferType
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeCurrency) GetOfferPrice() *StorefrontCatalogOfferPriceRealMoney {
|
||||
return o.Price
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeCurrency) GetRewards() []*StorefrontCatalogOfferGrant {
|
||||
return o.Rewards
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeCurrency) GenerateFortniteCatalogOfferResponse() aid.JSON {
|
||||
return aid.JSON{
|
||||
"offerId": o.GetOfferID(),
|
||||
"offerType": "StaticPrice",
|
||||
"devName": fmt.Sprintf("[CURRENCY] %s", o.Diplay.Title),
|
||||
"itemGrants": []aid.JSON{},
|
||||
"requirements": []aid.JSON{},
|
||||
"fulfillmentIds": []string{o.GetOfferID()},
|
||||
"categories": o.Categories,
|
||||
"metaInfo": []aid.JSON{
|
||||
{
|
||||
"key": "IconSize",
|
||||
"value": o.Meta.IconSize,
|
||||
},
|
||||
{
|
||||
"Key": "NewDisplayAssetPath",
|
||||
"Value": o.Meta.NewDisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"Key": "DisplayAssetPath",
|
||||
"Value": o.Meta.DisplayAssetPath,
|
||||
},
|
||||
{
|
||||
"Key": "BannerOverride",
|
||||
"Value": o.Meta.BannerOverride,
|
||||
},
|
||||
{
|
||||
"Key": "CurrencyAnalyticsName",
|
||||
"Value": o.Meta.CurrencyAnalyticsName,
|
||||
},
|
||||
{
|
||||
"key": "MtxQuantity",
|
||||
"value": o.Meta.OriginalOffer,
|
||||
},
|
||||
{
|
||||
"key": "MtxBonus",
|
||||
"value": o.Meta.ExtraBonus,
|
||||
},
|
||||
},
|
||||
"meta": aid.JSON{
|
||||
"IconSize": o.Meta.IconSize,
|
||||
"DisplayAssetPath": o.Meta.DisplayAssetPath,
|
||||
"NewDisplayAssetPath": o.Meta.NewDisplayAssetPath,
|
||||
"BannerOverride": o.Meta.BannerOverride,
|
||||
"CurrencyAnalyticsName": o.Meta.CurrencyAnalyticsName,
|
||||
"MtxQuantity": o.Meta.OriginalOffer,
|
||||
"MtxBonus": o.Meta.ExtraBonus,
|
||||
},
|
||||
"giftInfo": aid.JSON{
|
||||
"bIsEnabled": false,
|
||||
"forcedGiftBoxTemplateId": "",
|
||||
"purchaseRequirements": []aid.JSON{},
|
||||
"giftRecordIds": []string{},
|
||||
},
|
||||
"prices": []aid.JSON{{
|
||||
"currencyType": "RealMoney",
|
||||
"currencySubType": "",
|
||||
"regularPrice": -1,
|
||||
"dynamicRegularPrice": -1,
|
||||
"finalPrice": -1,
|
||||
"basePrice": -1,
|
||||
"saleExpiration": "9999-12-31T23:59:59.999Z",
|
||||
}},
|
||||
"bannerOverride": o.Meta.BannerOverride,
|
||||
"displayAssetPath": o.Meta.DisplayAssetPath,
|
||||
"refundable": false,
|
||||
"title": o.Diplay.Title,
|
||||
"description": o.Diplay.Description,
|
||||
"shortDescription": o.Diplay.ShortDescription,
|
||||
"appStoreId": []string{
|
||||
"",
|
||||
o.GetOfferID(),
|
||||
},
|
||||
"dailyLimit": -1,
|
||||
"weeklyLimit": -1,
|
||||
"monthlyLimit": -1,
|
||||
"sortPriority": o.Meta.Priority,
|
||||
"catalogGroupPriority": 0,
|
||||
"filterWeight": 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StorefrontCatalogOfferTypeCurrency) GenerateFortniteBulkOffersResponse() aid.JSON {
|
||||
return aid.JSON{
|
||||
"id": o.GetOfferID(),
|
||||
"title": o.Diplay.Title,
|
||||
"shortDescription": o.Diplay.ShortDescription,
|
||||
"longDescription": o.Diplay.LongDescription,
|
||||
"creationDate": "0000-00-00T00:00:00.000Z",
|
||||
"price": o.Price.LocalPrice,
|
||||
"currentPrice": o.Price.LocalPrice,
|
||||
"currencyCode": "USD",
|
||||
"basePrice": o.Price.BasePrice,
|
||||
"basePriceCurrencyCode": "GBP",
|
||||
}
|
||||
}
|
92
shop/section.go
Normal file
92
shop/section.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package shop
|
||||
|
||||
import "github.com/ectrc/snow/aid"
|
||||
|
||||
func NewStorefrontCatalogSection(name string, type_ StorefrontCatalogOfferEnum) *StorefrontCatalogSection {
|
||||
return &StorefrontCatalogSection{
|
||||
Name: name,
|
||||
SectionType: type_,
|
||||
Offers: make([]interface{}, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StorefrontCatalogSection) GenerateFortniteCatalogSectionResponse() aid.JSON {
|
||||
catalogEntiresResponse := []aid.JSON{}
|
||||
|
||||
for _, entry := range s.Offers {
|
||||
switch s.SectionType {
|
||||
case StorefrontCatalogOfferEnumItem:
|
||||
s := entry.(*StorefrontCatalogOfferTypeItem)
|
||||
catalogEntiresResponse = append(catalogEntiresResponse, s.GenerateFortniteCatalogOfferResponse())
|
||||
case StorefrontCatalogOfferEnumCurrency:
|
||||
s := entry.(*StorefrontCatalogOfferTypeCurrency)
|
||||
catalogEntiresResponse = append(catalogEntiresResponse, s.GenerateFortniteCatalogOfferResponse())
|
||||
case StorefrontCatalogOfferEnumStarterKit:
|
||||
s := entry.(*StorefrontCatalogOfferTypeStarterKit)
|
||||
catalogEntiresResponse = append(catalogEntiresResponse, s.GenerateFortniteCatalogOfferResponse())
|
||||
case StorefrontCatalogOfferEnumBattlePass:
|
||||
s := entry.(*StorefrontCatalogOfferTypeBattlePass)
|
||||
catalogEntiresResponse = append(catalogEntiresResponse, s.GenerateFortniteCatalogOfferResponse())
|
||||
}
|
||||
}
|
||||
|
||||
return aid.JSON{
|
||||
"name": s.Name,
|
||||
"catalogEntries": catalogEntiresResponse,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StorefrontCatalogSection) GetGroupedOffersLength() int {
|
||||
if s.SectionType != StorefrontCatalogOfferEnumItem {
|
||||
return len(s.Offers)
|
||||
}
|
||||
|
||||
newOffers := []*StorefrontCatalogOfferTypeItem{}
|
||||
for _, offer := range s.Offers {
|
||||
newOffers = append(newOffers, offer.(*StorefrontCatalogOfferTypeItem))
|
||||
}
|
||||
|
||||
groupedOffers := map[string][]*StorefrontCatalogOfferTypeItem{}
|
||||
for _, offer := range newOffers {
|
||||
if _, ok := groupedOffers[offer.Categories[0]]; !ok {
|
||||
groupedOffers[offer.Categories[0]] = []*StorefrontCatalogOfferTypeItem{}
|
||||
}
|
||||
|
||||
groupedOffers[offer.Categories[0]] = append(groupedOffers[offer.Categories[0]], offer)
|
||||
}
|
||||
|
||||
return len(groupedOffers)
|
||||
}
|
||||
|
||||
func (s *StorefrontCatalogSection) AddOffer(offer interface{}) {
|
||||
s.Offers = append(s.Offers, offer)
|
||||
}
|
||||
|
||||
func (s *StorefrontCatalogSection) GetOfferByID(offerID string) (interface{}, StorefrontCatalogOfferEnum) {
|
||||
for _, offer := range s.Offers {
|
||||
switch s.SectionType {
|
||||
case StorefrontCatalogOfferEnumItem:
|
||||
o := offer.(*StorefrontCatalogOfferTypeItem)
|
||||
if o.GetOfferID() == offerID {
|
||||
return o, StorefrontCatalogOfferEnumItem
|
||||
}
|
||||
case StorefrontCatalogOfferEnumCurrency:
|
||||
o := offer.(*StorefrontCatalogOfferTypeCurrency)
|
||||
if o.GetOfferID() == offerID {
|
||||
return o, StorefrontCatalogOfferEnumCurrency
|
||||
}
|
||||
case StorefrontCatalogOfferEnumStarterKit:
|
||||
o := offer.(*StorefrontCatalogOfferTypeStarterKit)
|
||||
if o.GetOfferID() == offerID {
|
||||
return o, StorefrontCatalogOfferEnumStarterKit
|
||||
}
|
||||
case StorefrontCatalogOfferEnumBattlePass:
|
||||
o := offer.(*StorefrontCatalogOfferTypeBattlePass)
|
||||
if o.GetOfferID() == offerID {
|
||||
return o, StorefrontCatalogOfferEnumBattlePass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, -1
|
||||
}
|
206
shop/shop.go
Normal file
206
shop/shop.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
package shop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/fortnite"
|
||||
)
|
||||
|
||||
func GetShop() *StorefrontCatalog {
|
||||
aid.SetRandom(rand.New(rand.NewSource(int64(aid.Config.Fortnite.ShopSeed) + aid.CurrentDayUnix())))
|
||||
shop := NewStorefrontCatalog()
|
||||
|
||||
dailySection := NewStorefrontCatalogSection("BRDailyStorefront", StorefrontCatalogOfferEnumItem)
|
||||
weeklySection := NewStorefrontCatalogSection("BRWeeklyStorefront", StorefrontCatalogOfferEnumItem)
|
||||
moneySection := NewStorefrontCatalogSection("CurrencyStorefront", StorefrontCatalogOfferEnumCurrency)
|
||||
kitSection := NewStorefrontCatalogSection("BRStarterKits", StorefrontCatalogOfferEnumStarterKit)
|
||||
bookSection := NewStorefrontCatalogSection(fmt.Sprintf("BRSeason%d", aid.Config.Fortnite.Season), StorefrontCatalogOfferEnumBattlePass)
|
||||
shop.AddSections(bookSection, dailySection, weeklySection, moneySection, kitSection)
|
||||
|
||||
bookDefaultOffer := newBookOffer(aid.Ternary[string](fortnite.DataClient.SnowSeason.DefaultOfferID != "", fortnite.DataClient.SnowSeason.DefaultOfferID, "book://"+ aid.Hash([]byte(aid.RandomString(32)))), 950, 0, &StorefrontCatalogOfferGrant{TemplateID: "Snow:BattlePass", Quantity: 1, ProfileType: "athena"})
|
||||
bookDefaultOffer.Diplay.Title = "Battle Pass"
|
||||
bookDefaultOffer.Diplay.ShortDescription = "Claim your Season 8 Battle Pass!"
|
||||
bookDefaultOffer.Diplay.Description = "Fortnite Season 8\n\nInstantly get these items <Bold>valued at over 3,500 V-Bucks</>.\n • <ItemName>Blackheart</> Progressive Outfit\n • <ItemName>Hybrid</> Progressive Outfit\n • <Bold>50% Bonus</> Season Match XP\n • <Bold>10% Bonus</> Season Friend Match XP\n • <Bold>Extra Weekly Challenges</>\n\nPlay to level up your Battle Pass, unlocking <Bold>over 100 rewards</> (typically takes 75 to 150 hours of play).\n • <ItemName>Sidewinder</> and <Bold>4 more Outfits</>\n • <Bold>1,300 V-Bucks</>\n • 7 Emotes\n • 6 Wraps\n • 2 Pets\n • 5 Harvesting Tools\n • 4 Gliders\n • 4 Back Blings\n • 5 Contrails\n • 14 Sprays\n • 3 Music Tracks\n • 1 Toy\n • 20 Loading Screens\n • and so much more!\nWant it all faster? You can use V-Bucks to buy tiers any time!"
|
||||
bookDefaultOffer.Meta.DisplayAssetPath = fmt.Sprintf("/Game/Catalog/DisplayAssets/DA_BR_Season%d_BattlePass.DA_BR_Season%d_BattlePass", aid.Config.Fortnite.Season, aid.Config.Fortnite.Season)
|
||||
bookDefaultOffer.Meta.Priority = 1
|
||||
bookSection.AddOffer(bookDefaultOffer)
|
||||
|
||||
bookBundleOffer := newBookOffer(aid.Ternary[string](fortnite.DataClient.SnowSeason.BundleOfferID != "", fortnite.DataClient.SnowSeason.BundleOfferID, "book://"+ aid.Hash([]byte(aid.RandomString(32)))), 4700, 1850, []*StorefrontCatalogOfferGrant{
|
||||
{TemplateID: "Snow:BattlePass", Quantity: 1, ProfileType: "athena"},
|
||||
{TemplateID: "AccountResource:AthenaBattleStar", Quantity: 250, ProfileType: "athena"},
|
||||
}...)
|
||||
bookBundleOffer.Diplay.Title = "Battle Bundle"
|
||||
bookBundleOffer.Diplay.ShortDescription = "Claim your Season 8 Battle Pass + 25 Tiers!"
|
||||
bookBundleOffer.Diplay.Description = "Fortnite Season 8\n\nInstantly get these items <Bold>valued at over 10,000 V-Bucks</>.\n • <ItemName>Blackheart</> Progressive Outfit\n • <ItemName>Hybrid</> Progressive Outfit\n • <ItemName>Sidewinder</> Outfit\n • <ItemName>Tropical Camo</> Wrap\n • <ItemName>Woodsy</> Pet\n • <ItemName>Sky Serpents</> Glider\n • <ItemName>Cobra</> Back Bling\n • <ItemName>Flying Standard</> Contrail\n • 300 V-Bucks\n • 1 Music Track\n • <Bold>70% Bonus</> Season Match XP\n • <Bold>20% Bonus</> Season Friend Match XP\n • <Bold>Extra Weekly Challenges</>\n • and more!\n\nPlay to level up your Battle Pass, unlocking <Bold>over 75 rewards</> (typically takes 75 to 150 hours of play).\n • <Bold>4 more Outfits</>\n • <Bold>1,000 V-Bucks</>\n • 6 Emotes\n • 5 Wraps\n • 3 Gliders\n • 3 Back Blings\n • 4 Harvesting Tools\n • 4 Contrails\n • 1 Pet\n • 12 Sprays\n • 2 Music Tracks\n • and so much more!\nWant it all faster? You can use V-Bucks to buy tiers any time!"
|
||||
bookBundleOffer.Meta.DisplayAssetPath = fmt.Sprintf("/Game/Catalog/DisplayAssets/DA_BR_Season%d_BattlePassWithLevels.DA_BR_Season%d_BattlePassWithLevels", aid.Config.Fortnite.Season, aid.Config.Fortnite.Season)
|
||||
bookBundleOffer.Meta.Priority = 0
|
||||
bookSection.AddOffer(bookBundleOffer)
|
||||
|
||||
bookLevelOffer := newBookOffer(aid.Ternary[string](fortnite.DataClient.SnowSeason.TierOfferID != "", fortnite.DataClient.SnowSeason.TierOfferID, "book://"+ aid.Hash([]byte(aid.RandomString(32)))), 150, 150, &StorefrontCatalogOfferGrant{TemplateID: "AccountResource:AthenaBattleStar", Quantity: 10, ProfileType: "athena"})
|
||||
bookSection.AddOffer(bookLevelOffer)
|
||||
|
||||
for len(dailySection.Offers) <= fortnite.DataClient.GetStorefrontDailyItemCount(aid.Config.Fortnite.Season) {
|
||||
offer := newItemOffer(fortnite.GetRandomItemWithDisplayAssetOfNotType("AthenaCharacter"), true, true)
|
||||
offer.Meta.SectionID = "Daily"
|
||||
dailySection.AddOffer(offer)
|
||||
}
|
||||
|
||||
for weeklySection.GetGroupedOffersLength() < fortnite.DataClient.GetStorefrontWeeklySetCount(aid.Config.Fortnite.Season) {
|
||||
set := fortnite.GetRandomSet()
|
||||
for _, item := range set.Items {
|
||||
offer := newItemOffer(item, true, true)
|
||||
offer.Meta.SectionID = "Featured"
|
||||
offer.Categories = append(offer.Categories, set.BackendName)
|
||||
weeklySection.AddOffer(offer)
|
||||
}
|
||||
}
|
||||
|
||||
xp := NewItemCatalogOffer()
|
||||
xp.OfferID = "item://AthenaSeasonalXP"
|
||||
xp.Meta.TileSize = "Small"
|
||||
xp.Meta.Giftable = false
|
||||
xp.Meta.Refundable = false
|
||||
xp.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_FoundersPack_4_5.DA_FoundersPack_4_5"
|
||||
xp.Rewards = append(xp.Rewards, &StorefrontCatalogOfferGrant{
|
||||
TemplateID: "AccountResource:AthenaSeasonalXP",
|
||||
Quantity: 100000,
|
||||
ProfileType: "athena",
|
||||
})
|
||||
xp.Price.PriceType = StorefrontCatalogOfferPriceTypeMtxCurrency
|
||||
xp.Price.SaleType = StorefrontCatalogOfferPriceSaleTypeNone
|
||||
xp.Price.OriginalPrice = 1000
|
||||
xp.Price.FinalPrice = 1000
|
||||
weeklySection.AddOffer(xp)
|
||||
|
||||
moneySection.AddOffer(newMoneyOffer(1000, 0, "https://cdn1.epicgames.com/offer/fn/EGS_VBucks_1000_1200x1600-c8a13f66ba88744d5216f884855e2a4d", 3))
|
||||
moneySection.AddOffer(newMoneyOffer(2800, 300, "https://cdn1.epicgames.com/offer/fn/EGS_VBucks_2800_1200x1600-055112a56c0fb d65989470ece7c653f", 2))
|
||||
moneySection.AddOffer(newMoneyOffer(7500, 1500, "https://cdn1.epicgames.com/offer/fn/EGS_VBucks_5000_1200x1600-8ea53bb4ea3d75821153075df8e3ca95", 1))
|
||||
moneySection.AddOffer(newMoneyOffer(13500, 3500, "https://cdn1.epicgames.com/offer/fn/EGS_VBucks_13500_1200x1600-39489a289769bc6c1d14f4a8b53b48f4", 0))
|
||||
|
||||
lagunaKit := newKitOffer("The Laguna Pack", 499, 8, []*StorefrontCatalogOfferGrant{
|
||||
{TemplateID: "AthenaCharacter:CID_367_Athena_Commando_F_Tropical", Quantity: 1, ProfileType: "athena"},
|
||||
{TemplateID: "AthenaBackpack:BID_231_TropicalFemale", Quantity: 1, ProfileType: "athena"},
|
||||
{TemplateID: "AthenaItemWrap:Wrap_033_TropicalGirl", Quantity: 1, ProfileType: "athena"},
|
||||
{TemplateID: "Currency:MtxPurchased", Quantity: 600, ProfileType: "common_core"},
|
||||
}...)
|
||||
lagunaKit.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_367_Athena_Commando_F_Tropical.DA_Featured_CID_367_Athena_Commando_F_Tropical"
|
||||
lagunaKit.Meta.FeaturedImageURL = "https://fortnite-api.com/images/cosmetics/br/CID_367_Athena_Commando_F_Tropical/icon.png"
|
||||
kitSection.AddOffer(lagunaKit)
|
||||
|
||||
ikonikKit := newKitOffer("Ikonik Pack", 3999, 8, []*StorefrontCatalogOfferGrant{
|
||||
{TemplateID: "AthenaCharacter:CID_313_Athena_Commando_M_KpopFashion", Quantity: 1, ProfileType: "athena"},
|
||||
{TemplateID: "AthenaDance:EID_KPopDance03", Quantity: 1, ProfileType: "athena"},
|
||||
{TemplateID: "Currency:MtxPurchased", Quantity: 600, ProfileType: "common_core"},
|
||||
}...)
|
||||
ikonikKit.Meta.DisplayAssetPath = "/Game/Catalog/DisplayAssets/DA_Featured_CID_313_Athena_Commando_M_KpopFashion.DA_Featured_CID_313_Athena_Commando_M_KpopFashion"
|
||||
ikonikKit.Meta.FeaturedImageURL = "https://fortnite-api.com/images/cosmetics/br/CID_313_Athena_Commando_M_KpopFashion/icon.png"
|
||||
kitSection.AddOffer(ikonikKit)
|
||||
|
||||
return shop
|
||||
}
|
||||
|
||||
func newItemOffer(item *fortnite.APICosmeticDefinition, addAssets, giftable bool) *StorefrontCatalogOfferTypeItem {
|
||||
displayAsset := regexp.MustCompile(`[^/]+$`).FindString(item.DisplayAssetPath)
|
||||
|
||||
offer := NewItemCatalogOffer()
|
||||
offer.Meta.TileSize = aid.Ternary[string](item.Type.BackendValue == "AthenaCharacter", "Small", "Normal")
|
||||
offer.Meta.Giftable = giftable
|
||||
offer.Meta.Refundable = true
|
||||
if addAssets {
|
||||
offer.Meta.DisplayAssetPath = aid.Ternary[string](displayAsset != "", "/Game/Catalog/DisplayAssets/" + displayAsset + "." + displayAsset, "")
|
||||
offer.Meta.NewDisplayAssetPath = aid.Ternary[string](item.NewDisplayAssetPath != "", "/Game/Catalog/NewDisplayAssets/" + item.NewDisplayAssetPath + "." + item.NewDisplayAssetPath, "")
|
||||
}
|
||||
|
||||
offer.Rewards = append(offer.Rewards, &StorefrontCatalogOfferGrant{
|
||||
TemplateID: item.Type.BackendValue + ":" + item.ID,
|
||||
Quantity: 1,
|
||||
ProfileType: "athena",
|
||||
})
|
||||
|
||||
offer.Price.PriceType = StorefrontCatalogOfferPriceTypeMtxCurrency
|
||||
offer.Price.SaleType = StorefrontCatalogOfferPriceSaleTypeNone
|
||||
offer.Price.OriginalPrice = fortnite.DataClient.GetStorefrontCosmeticOfferPrice(item.Rarity.BackendValue, item.Type.BackendValue)
|
||||
offer.Price.FinalPrice = offer.Price.OriginalPrice
|
||||
|
||||
offer.OfferID = fmt.Sprintf("item://%s", aid.Hash([]byte(offer.OfferID)))
|
||||
|
||||
return offer
|
||||
}
|
||||
|
||||
func newMoneyOffer(real, bonus int, imgUrl string, position int) *StorefrontCatalogOfferTypeCurrency {
|
||||
format := aid.FormatNumber(real)
|
||||
offer := NewCurrencyCatalogOffer()
|
||||
|
||||
offer.Meta.IconSize = "Small"
|
||||
offer.Meta.CurrencyAnalyticsName = fmt.Sprintf("MtxPack%d", real)
|
||||
offer.Meta.OriginalOffer = real
|
||||
offer.Meta.ExtraBonus = bonus
|
||||
offer.Meta.DisplayAssetPath = fmt.Sprintf("/Game/Catalog/DisplayAssets/DA_MtxPack%d.DA_MtxPack%d", real, real)
|
||||
offer.Meta.FeaturedImageURL = imgUrl
|
||||
offer.Meta.Priority = position
|
||||
|
||||
offer.Diplay.Title = fmt.Sprintf("%s V-Bucks", format)
|
||||
offer.Diplay.Description = fmt.Sprintf("Buy %s Fortnite V-Bucks, the in-game currency that can be spent in Fortnite Battle Royale and Creative modes. You can purchase new customization items like Outfits, Gliders, Pickaxes, Emotes, Wraps and the latest season's Battle Pass! Gliders and Contrails may not be used in Save the World mode.", format)
|
||||
offer.Diplay.LongDescription = fmt.Sprintf("Buy %s Fortnite V-Bucks, the in-game currency that can be spent in Fortnite Battle Royale and Creative modes. You can purchase new customization items like Outfits, Gliders, Pickaxes, Emotes, Wraps and the latest season's Battle Pass! Gliders and Contrails may not be used in Save the World mode.\n\nAll V-Bucks purchased on the Epic Games Store are not redeemable or usable on Nintendo Switch™.", format)
|
||||
|
||||
offer.Price.PriceType = StorefrontCatalogOfferPriceTypeRealMoney
|
||||
offer.Price.BasePrice = float64(fortnite.DataClient.GetStorefrontCurrencyOfferPrice("GBP", real))
|
||||
offer.Price.LocalPrice = float64(fortnite.DataClient.GetStorefrontCurrencyOfferPrice("USD", real))
|
||||
|
||||
offer.Rewards = append(offer.Rewards, &StorefrontCatalogOfferGrant{
|
||||
TemplateID: "Currency:MtxPurchased",
|
||||
Quantity: real,
|
||||
ProfileType: "common_core",
|
||||
})
|
||||
|
||||
return offer
|
||||
}
|
||||
|
||||
func newKitOffer(title string, basePrice, season int, rewards ...*StorefrontCatalogOfferGrant) *StorefrontCatalogOfferTypeStarterKit {
|
||||
description := fmt.Sprintf("Jump into Fortnite Battle Royale with the %s. Includes:\n\n- 600 V-Bucks", strings.ReplaceAll(title, "The ", ""))
|
||||
for _, reward := range rewards {
|
||||
item := fortnite.DataClient.FortniteItems[strings.Split(reward.TemplateID, ":")[1]]
|
||||
if item != nil {
|
||||
description += fmt.Sprintf("\n- %s %s", item.Name, item.Type.DisplayValue)
|
||||
}
|
||||
}
|
||||
|
||||
offer := NewStarterKitCatalogOffer()
|
||||
|
||||
offer.Meta.ReleaseSeason = season
|
||||
offer.Meta.OriginalOffer = 600
|
||||
offer.Meta.ExtraBonus = 100
|
||||
|
||||
offer.Diplay.Title = title
|
||||
offer.Diplay.Description = description
|
||||
offer.Diplay.LongDescription = fmt.Sprintf("%s\n\nV-Bucks are an in-game currency that can be spent in both the Battle Royale PvP mode and the Save the World PvE campaign. In Battle Royale, you can use V-bucks to purchase new customization items like outfits, emotes, pickaxes, gliders, and more! In Save the World you can purchase Llama Pinata card packs that contain weapon, trap and gadget schematics as well as new Heroes and more! \n\nNote: Items do not transfer between the Battle Royale mode and the Save the World campaign.", description)
|
||||
|
||||
offer.Price.PriceType = StorefrontCatalogOfferPriceTypeRealMoney
|
||||
offer.Price.BasePrice = float64(fortnite.DataClient.GetStorefrontLocalizedOfferPrice("GBP", basePrice))
|
||||
offer.Price.LocalPrice = float64(fortnite.DataClient.GetStorefrontLocalizedOfferPrice("USD", basePrice))
|
||||
|
||||
offer.Rewards = rewards
|
||||
|
||||
return offer
|
||||
}
|
||||
|
||||
func newBookOffer(customId string, ogPrice, finalprice int, rewards ...*StorefrontCatalogOfferGrant) *StorefrontCatalogOfferTypeBattlePass {
|
||||
offer := NewBattlePassCatalogOffer()
|
||||
offer.OfferID = customId
|
||||
|
||||
offer.Meta.TileSize = "Normal"
|
||||
offer.Meta.SectionID = "BattlePass"
|
||||
|
||||
offer.Price.PriceType = StorefrontCatalogOfferPriceTypeMtxCurrency
|
||||
offer.Price.SaleType = StorefrontCatalogOfferPriceSaleTypeStrikethrough
|
||||
offer.Price.OriginalPrice = ogPrice
|
||||
offer.Price.FinalPrice = finalprice
|
||||
|
||||
offer.Rewards = rewards
|
||||
|
||||
return offer
|
||||
}
|
73
shop/types.go
Normal file
73
shop/types.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package shop
|
||||
|
||||
import "github.com/ectrc/snow/aid"
|
||||
|
||||
type ShopGrantProfileType string
|
||||
const ShopGrantProfileTypeAthena ShopGrantProfileType = "athena"
|
||||
const ShopGrantProfileTypeCommonCore ShopGrantProfileType = "common_core"
|
||||
|
||||
type StorefrontCatalogOfferGrant struct {
|
||||
TemplateID string
|
||||
Quantity int
|
||||
ProfileType ShopGrantProfileType
|
||||
}
|
||||
|
||||
type StorefrontCatalogOfferPriceType string
|
||||
const StorefrontCatalogOfferPriceTypeMtxCurrency StorefrontCatalogOfferPriceType = "MtxCurrency"
|
||||
const StorefrontCatalogOfferPriceTypeRealMoney StorefrontCatalogOfferPriceType = "RealMoney"
|
||||
|
||||
type StorefrontCatalogOfferPriceSaleType string
|
||||
const StorefrontCatalogOfferPriceSaleTypeNone StorefrontCatalogOfferPriceSaleType = ""
|
||||
const StorefrontCatalogOfferPriceSaleTypeAmountOff StorefrontCatalogOfferPriceSaleType = "AmountOff"
|
||||
const StorefrontCatalogOfferPriceSaleTypeStrikethrough StorefrontCatalogOfferPriceSaleType = "Strikethrough"
|
||||
|
||||
type StorefrontCatalogOfferPriceMtxCurrency struct {
|
||||
PriceType StorefrontCatalogOfferPriceType
|
||||
SaleType StorefrontCatalogOfferPriceSaleType
|
||||
OriginalPrice int
|
||||
FinalPrice int
|
||||
}
|
||||
|
||||
type StorefrontCatalogOfferPriceRealMoney struct {
|
||||
PriceType StorefrontCatalogOfferPriceType
|
||||
SaleType StorefrontCatalogOfferPriceSaleType
|
||||
BasePrice float64
|
||||
LocalPrice float64
|
||||
}
|
||||
|
||||
type OfferDisplay struct {
|
||||
Title string
|
||||
Description string
|
||||
ShortDescription string
|
||||
LongDescription string
|
||||
}
|
||||
|
||||
type StorefrontCatalogOfferEnum int
|
||||
const StorefrontCatalogOfferEnumItem StorefrontCatalogOfferEnum = 0
|
||||
const StorefrontCatalogOfferEnumCurrency StorefrontCatalogOfferEnum = 1
|
||||
const StorefrontCatalogOfferEnumStarterKit StorefrontCatalogOfferEnum = 2
|
||||
const StorefrontCatalogOfferEnumBattlePass StorefrontCatalogOfferEnum = 3
|
||||
|
||||
type StorefrontCatalogOfferGeneric interface {
|
||||
StorefrontCatalogOfferTypeItem | StorefrontCatalogOfferTypeCurrency | StorefrontCatalogOfferTypeStarterKit | StorefrontCatalogOfferTypeBattlePass
|
||||
}
|
||||
|
||||
type StorefrontCatalogOffer[T StorefrontCatalogOfferGeneric] interface {
|
||||
GetOffer() *T
|
||||
GetOfferID() string
|
||||
GetOfferType() StorefrontCatalogOfferEnum
|
||||
GetRewards() []*StorefrontCatalogOfferGrant
|
||||
GenerateFortniteCatalogOfferResponse() aid.JSON
|
||||
GenerateFortniteBulkOffersResponse() aid.JSON
|
||||
}
|
||||
|
||||
var storefrontCatalogOfferPriceMultiplier = map[string]float64{
|
||||
"USD": 1.2503128911,
|
||||
"GBP": 1.0,
|
||||
}
|
||||
|
||||
type StorefrontCatalogSection struct {
|
||||
SectionType StorefrontCatalogOfferEnum
|
||||
Name string
|
||||
Offers []interface{} // *StorefrontCatalogOfferTypeItem | *StorefrontCatalogOfferTypeCurrency | *StorefrontCatalogOfferTypeStarterKit | *StorefrontCatalogOfferTypeBattlePass
|
||||
}
|
|
@ -14,7 +14,11 @@ func EmitGiftReceived(person *person.Person) {
|
|||
}
|
||||
|
||||
s.JabberSendMessageToPerson(aid.JSON{
|
||||
"payload": aid.JSON{},
|
||||
"payload": aid.JSON{
|
||||
"gifts": []aid.JSON{{
|
||||
"Wahgsdhjgasjkd": "Wahgsdhjgasjkd",
|
||||
}},
|
||||
},
|
||||
"type": "com.epicgames.gift.received",
|
||||
"timestamp": time.Now().Format("2006-01-02T15:04:05.999Z"),
|
||||
})
|
||||
|
|
|
@ -3,6 +3,7 @@ package socket
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/ectrc/snow/aid"
|
||||
|
@ -29,7 +30,7 @@ func HandleNewJabberSocket(identifier string) {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
defer JabberSockets.Delete(socket.ID)
|
||||
defer socket.Remove()
|
||||
|
||||
for {
|
||||
_, message, failed := socket.Connection.ReadMessage()
|
||||
|
@ -57,9 +58,6 @@ func JabberSocketOnMessage(socket *Socket[JabberData], message []byte) {
|
|||
}
|
||||
|
||||
func jabberStreamHandler(socket *Socket[JabberData], parsed *etree.Document) error {
|
||||
socket.Write([]byte(`<stream:stream id="`+socket.ID+`" from="prod.ol.epicgames.com" xml:lang="*" version="1.0" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client"/>`))
|
||||
// socket.Write([]byte(`<open xmlns="urn:ietf:params:xml:ns:xmpp-framing" from="prod.ol.epicgames.com" version="1.0" id="`+ socket.ID +`" />`))
|
||||
// socket.Write([]byte(`<stream:features xmlns:stream="http://etherx.jabber.org/streams" id="`+ socket.ID +`" />`))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -106,7 +104,9 @@ func jabberIqSetHandler(socket *Socket[JabberData], parsed *etree.Document) erro
|
|||
}
|
||||
|
||||
func jabberIqGetHandler(socket *Socket[JabberData], parsed *etree.Document) error {
|
||||
socket.Write([]byte(`<iq xmlns="jabber:client" type="result" id="`+ parsed.Root().SelectAttr("id").Value +`" from="prod.ol.epicgames.com" to="`+ socket.Data.JabberID +`" />`))
|
||||
socket.Write([]byte(`<iq xmlns="jabber:client" type="result" id="`+ parsed.Root().SelectAttr("id").Value +`" from="prod.ol.epicgames.com" to="`+ parsed.Root().SelectAttr("from").Value +`" >
|
||||
<ping xmlns="urn:xmpp:ping"/>
|
||||
</iq`))
|
||||
socket.JabberNotifyFriends()
|
||||
return nil
|
||||
}
|
||||
|
@ -207,8 +207,6 @@ func jabberMessageHandler(socket *Socket[JabberData], parsed *etree.Document) er
|
|||
return nil
|
||||
}
|
||||
|
||||
// TODO: IMPLEMENT WHISPERING
|
||||
|
||||
func jabberMessageChatHandler(socket *Socket[JabberData], parsed *etree.Document) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -267,4 +265,21 @@ func (s *Socket[T]) JabberNotifyFriends() {
|
|||
jabberSocket.Write([]byte(`<presence xmlns="jabber:client" type="available" from="`+ jabberSocket.Data.JabberID +`" to="`+ jabberSocket.Data.JabberID +`">
|
||||
<status>`+ jabberSocket.Data.LastPresence +`</status>
|
||||
</presence>`))
|
||||
}
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
timer := time.NewTicker(5 * time.Second)
|
||||
|
||||
for {
|
||||
<-timer.C
|
||||
|
||||
JabberSockets.Range(func(key string, value *Socket[JabberData]) bool {
|
||||
value.Write([]byte(`<iq id="_xmpp_auth1" type="get" from="prod.ol.epicgames.com" xmlns="jabber:client">
|
||||
<ping xmlns="urn:xmpp:ping"/>
|
||||
</iq>`))
|
||||
return true
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package socket
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
|
@ -31,7 +32,20 @@ func (s *Socket[T]) Write(payload []byte) {
|
|||
s.M.Lock()
|
||||
defer s.M.Unlock()
|
||||
|
||||
s.Connection.WriteMessage(websocket.TextMessage, payload)
|
||||
err := s.Connection.WriteMessage(websocket.TextMessage, payload)
|
||||
if err != nil {
|
||||
s.Remove()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Socket[T]) Remove() {
|
||||
reflectType := reflect.TypeOf(s.Data).String()
|
||||
switch reflectType {
|
||||
case "*socket.JabberData":
|
||||
JabberSockets.Delete(s.ID)
|
||||
case "*socket.MatchmakerData":
|
||||
MatchmakerSockets.Delete(s.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func newSocket[T JabberData | MatchmakerData](conn WebSocket, data ...T) *Socket[T] {
|
||||
|
|
|
@ -62,7 +62,7 @@ func (a *AmazonClient) GetAllUserFiles() ([]string, error) {
|
|||
func (a *AmazonClient) CreateUserFile(fileName string, data []byte) error {
|
||||
_, err := a.client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(a.ClientSettingsBucket),
|
||||
Key: aws.String(fileName),
|
||||
Key: aws.String("client/"+fileName),
|
||||
Body: bytes.NewReader(data),
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -75,7 +75,7 @@ func (a *AmazonClient) CreateUserFile(fileName string, data []byte) error {
|
|||
func (a *AmazonClient) GetUserFile(fileName string) ([]byte, error) {
|
||||
getObjectOutput, err := a.client.GetObject(context.TODO(), &s3.GetObjectInput{
|
||||
Bucket: aws.String(a.ClientSettingsBucket),
|
||||
Key: aws.String(fileName),
|
||||
Key: aws.String("client/"+fileName),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -13,6 +13,10 @@ var (
|
|||
Assets embed.FS
|
||||
)
|
||||
|
||||
type snowFS struct {
|
||||
embed.FS
|
||||
}
|
||||
|
||||
func Asset(file string) (*[]byte) {
|
||||
data, err := Assets.ReadFile("mem/" + strings.ToLower(file))
|
||||
if err != nil {
|
||||
|
|
|
@ -16,10 +16,6 @@ func GetDefaultEngine() []byte {
|
|||
realPort := fmt.Sprintf("%d", portNumber)
|
||||
|
||||
str := `
|
||||
[OnlineSubsystemMcp.OnlinePaymentServiceMcp Fortnite]
|
||||
Domain="launcher-website-prod.ak.epicgames.com"
|
||||
BasePath="/logout?redirectUrl=https%3A%2F%2Fwww.unrealengine.com%2Fid%2Flogout%3FclientId%3Dxyza7891KKDWlczTxsyy7H3ExYgsNT4Y%26responseType%3Dcode%26redirectUrl%3Dhttps%253A%252F%252Ftesting-site.neonitedev.live%252Fid%252Flogin%253FredirectUrl%253Dhttps%253A%252F%252Ftesting-site.neonitedev.live%252Fpurchase%252Facquire&path="
|
||||
|
||||
[XMPP]
|
||||
bEnableWebsockets=true
|
||||
|
||||
|
@ -36,42 +32,59 @@ FortMatchmakingV2.EnableContentBeacon=0
|
|||
NumTestsPerRegion=5
|
||||
PingTimeout=3.0
|
||||
|
||||
[/Script/Qos.QosRegionManager]
|
||||
NumTestsPerRegion=5
|
||||
PingTimeout=3.0
|
||||
!RegionDefinitions=ClearArray
|
||||
+RegionDefinitions=(DisplayName=NSLOCTEXT("MMRegion", "Europe", "Europe"), RegionId="EU", bEnabled=true, bVisible=true, bAutoAssignable=true)
|
||||
+RegionDefinitions=(DisplayName=NSLOCTEXT("MMRegion", "North America", "North America"), RegionId="NA", bEnabled=true, bVisible=true, bAutoAssignable=true)
|
||||
+RegionDefinitions=(DisplayName=NSLOCTEXT("MMRegion", "Oceania", "Oceania"), RegionId="OCE", bEnabled=true, bVisible=true, bAutoAssignable=true)
|
||||
!DatacenterDefinitions=ClearArray
|
||||
+DatacenterDefinitions=(Id="DE", RegionId="EU", bEnabled=true, Servers[0]=(Address="142.132.145.234", Port=22222))
|
||||
+DatacenterDefinitions=(Id="VA", RegionId="NA", bEnabled=true, Servers[0]=(Address="69.10.34.38", Port=22222))
|
||||
+DatacenterDefinitions=(Id="SYD", RegionId="OCE", bEnabled=true, Servers[0]=(Address="139.99.209.91", Port=22222))
|
||||
!Datacenters=ClearArray
|
||||
+Datacenters=(DisplayName=NSLOCTEXT("MMRegion", "Europe", "Europe"), RegionId="EU", bEnabled=true, bVisible=true, bBeta=false, Servers[0]=(Address="142.132.145.234", Port=22222))
|
||||
+Datacenters=(DisplayName=NSLOCTEXT("MMRegion", "North America", "North America"), RegionId="NA", bEnabled=true, bVisible=true, bBeta=false, Servers[0]=(Address="69.10.34.38", Port=22222))
|
||||
+Datacenters=(DisplayName=NSLOCTEXT("MMRegion", "Oceania", "Oceania"), RegionId="OCE", bEnabled=true, bVisible=true, bBeta=false, Servers[0]=(Address="139.99.209.91", Port=22222))
|
||||
|
||||
[LwsWebSocket]
|
||||
bDisableCertValidation=true
|
||||
bDisableDomainWhitelist=true
|
||||
|
||||
[/Script/Engine.NetworkSettings]
|
||||
n.VerifyPeer=false
|
||||
|
||||
[WinHttpWebSocket]
|
||||
bDisableCertValidation=true
|
||||
bDisableDomainWhitelist=true`
|
||||
|
||||
if aid.Config.Fortnite.Season <= 2 {
|
||||
str += `
|
||||
|
||||
[OnlineSubsystemMcp.Xmpp]
|
||||
bUsePlainTextAuth=true
|
||||
bUseSSL=false
|
||||
Protocol=tcp
|
||||
ServerAddr="`+ aid.Config.API.Host + `"
|
||||
ServerAddr="`+ aid.Config.API.XMPP.Host + aid.Config.API.XMPP.Port + `"
|
||||
ServerPort=`+ realPort + `
|
||||
|
||||
[OnlineSubsystemMcp.Xmpp Prod]
|
||||
bUsePlainTextAuth=true
|
||||
bUseSSL=false
|
||||
Protocol=tcp
|
||||
ServerAddr="`+ aid.Config.API.Host + `"
|
||||
ServerAddr="`+ aid.Config.API.XMPP.Host + aid.Config.API.XMPP.Port + `"
|
||||
ServerPort=`+ realPort
|
||||
} else {
|
||||
str += `
|
||||
[OnlineSubsystemMcp.Xmpp]
|
||||
bUsePlainTextAuth=true
|
||||
bUseSSL=false
|
||||
Protocol=ws
|
||||
ServerAddr="ws://`+ aid.Config.API.Host + aid.Config.API.Port +`/?SNOW_SOCKET_CONNECTION"
|
||||
ServerAddr="ws://`+ aid.Config.API.XMPP.Host + aid.Config.API.XMPP.Port +`/?SNOW_SOCKET_CONNECTION"
|
||||
|
||||
[OnlineSubsystemMcp.Xmpp Prod]
|
||||
bUsePlainTextAuth=true
|
||||
bUseSSL=false
|
||||
Protocol=ws
|
||||
ServerAddr="ws://`+ aid.Config.API.Host + aid.Config.API.Port +`/?SNOW_SOCKET_CONNECTION"`
|
||||
ServerAddr="ws://`+ aid.Config.API.XMPP.Host + aid.Config.API.XMPP.Port +`/?SNOW_SOCKET_CONNECTION"`
|
||||
}
|
||||
|
||||
return []byte(str)
|
||||
|
@ -93,12 +106,23 @@ bShouldCheckIfPlatformAllowed=false
|
|||
|
||||
[EpicPurchaseFlow]
|
||||
bUsePaymentWeb=false
|
||||
CI="http://localhost:5173/purchase"
|
||||
GameDev="http://localhost:5173/purchase"
|
||||
Stage="http://127.0.0.1:5173/purchase"
|
||||
Prod="http://127.0.0.1:5173/purchase"
|
||||
CI="http://127.0.0.1:3000/purchase"
|
||||
GameDev="http://127.0.0.1:3000/purchase"
|
||||
Stage="http://127.0.0.1:3000/purchase"
|
||||
Prod="http://127.0.0.1:3000/purchase"
|
||||
UEPlatform="FNGame"
|
||||
|
||||
[/Script/FortniteGame.FortTextHotfixConfig]
|
||||
+TextReplacements=(Category=Game, bIsMinimalPatch=True, Namespace="", Key="68ADE44C49B20BFF78677799BE68B0EE", NativeString="FORTNITEMARES", LocalizedStrings=(("en","BOOST PERKS")))
|
||||
+TextReplacements=(Category=Game, bIsMinimalPatch=True, Namespace="", Key="BE6B17BD456F3F13EEB2998AF91DC717", NativeString="THANKS FOR PLAYING!", LocalizedStrings=(("en","THANKS FOR SUPPORTING SNOW!")))
|
||||
|
||||
[/Script/FortniteGame.FortGameInstance]
|
||||
!FrontEndPlaylistData=ClearArray
|
||||
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultSolo, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=True, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=0, bDisplayAsLimitedTime=False, DisplayPriority=0))
|
||||
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultDuo, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=True, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=0, bDisplayAsLimitedTime=False, DisplayPriority=1))
|
||||
+FrontEndPlaylistData=(PlaylistName=Playlist_DefaultSquad, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=True, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=0, bDisplayAsLimitedTime=False, DisplayPriority=2))
|
||||
+FrontEndPlaylistData=(PlaylistName=Playlist_ShowdownAlt_Solo, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=False, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=1, bDisplayAsLimitedTime=False, DisplayPriority=0))
|
||||
+FrontEndPlaylistData=(PlaylistName=Playlist_ShowdownAlt_Duos, PlaylistAccess=(bEnabled=True, bIsDefaultPlaylist=False, bVisibleWhenDisabled=True, bDisplayAsNew=False, CategoryIndex=1, bDisplayAsLimitedTime=False, DisplayPriority=1))
|
||||
`)}
|
||||
|
||||
func GetDefaultRuntime() []byte {return []byte(`
|
||||
|
@ -108,6 +132,11 @@ func GetDefaultRuntime() []byte {return []byte(`
|
|||
;+DisabledFrontendNavigationTabs=(TabName="Showdown",TabState=EFortRuntimeOptionTabState::Hidden)
|
||||
;+DisabledFrontendNavigationTabs=(TabName="AthenaStore",TabState=EFortRuntimeOptionTabState::Hidden)
|
||||
|
||||
[/Script/FortniteGame.FortRuntimeOptions]
|
||||
bForceBRMode=True
|
||||
bSkipSubgameSelect=True
|
||||
bEnableInGameMatchmaking=True
|
||||
|
||||
bEnableGlobalChat=true
|
||||
bDisableGifting=false
|
||||
bDisableGiftingPC=false
|
||||
|
|
98
storage/mem/controller.js
Normal file
98
storage/mem/controller.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
*
|
||||
* @typedef {Object} PurchaseFlow
|
||||
* @property {(reason: string) => Promise<void>} requestclose
|
||||
* @property {(receipt: {}) => Promise<void>} receipt
|
||||
* @property {(browserId: string, url: string) => Promise<boolean>} launchvalidatedexternalbrowserurl
|
||||
* @property {(url: string) => Promise<boolean>} launchexternalbrowserurl
|
||||
* @property {(browserId: string) => Promise<string>} getexternalbrowserpath
|
||||
* @property {(browserId: string) => Promise<string>} getexternalbrowsername
|
||||
* @property {(url: string) => Promise<string>} getdefaultexternalbrowserid
|
||||
*
|
||||
* @typedef {Object} Engine
|
||||
* @property {PurchaseFlow} purchaseflow
|
||||
*
|
||||
* @typedef {Object} Offer
|
||||
* @property {{
|
||||
* displayName: string
|
||||
* }} user
|
||||
* @property {{
|
||||
* id: string
|
||||
* name: string
|
||||
* price: number
|
||||
* imageUrl: string
|
||||
* type: string
|
||||
* }} offer
|
||||
*/
|
||||
|
||||
function getCookie(name) {
|
||||
var value = "; " + document.cookie;
|
||||
var parts = value.split("; " + name + "=");
|
||||
if (parts.length === 2) return parts.pop().split(";").shift();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Engine} engine
|
||||
* @returns
|
||||
*/
|
||||
const main = async (engine) => {
|
||||
const close = document.getElementById("close");
|
||||
const purchase = document.getElementById("purchaseOfferButton");
|
||||
const pf = engine.purchaseflow ? engine.purchaseflow : null;
|
||||
if (!pf) return;
|
||||
|
||||
const offerId = new URLSearchParams(window.location.search).get("offers");
|
||||
const offerResponse = await axios.get(
|
||||
`http://127.0.0.1:3000/purchase/offer?offerId=${offerId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: getCookie("EPIC_BEARER_TOKEN"),
|
||||
},
|
||||
}
|
||||
);
|
||||
if (offerResponse.status !== 200) return pf.requestclose("LoadFailure");
|
||||
const offer = offerResponse.data;
|
||||
|
||||
const image = document.getElementById("orderImage");
|
||||
image && (image.style.backgroundImage = `url(${offer.offer.imageUrl})`);
|
||||
|
||||
const title = document.getElementById("orderName");
|
||||
title && (title.innerText = offer.offer.name);
|
||||
|
||||
const price = document.getElementById("orderPrice");
|
||||
price && (price.innerText = "$" + offer.offer.price);
|
||||
|
||||
const totalPrice = document.getElementById("orderTotalPrice");
|
||||
totalPrice && (totalPrice.innerText = "$" + offer.offer.price);
|
||||
|
||||
const SubtotalPrice = document.getElementById("orderSubtotalPrice");
|
||||
SubtotalPrice && (SubtotalPrice.innerText = "$" + offer.offer.price);
|
||||
|
||||
const displayName = document.getElementById("displayName");
|
||||
displayName && (displayName.innerText = offer.user.displayName);
|
||||
|
||||
close && close.addEventListener("click", () => pf.requestclose("Escape"));
|
||||
purchase && purchase.addEventListener("click", () => buy(pf, offer));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {PurchaseFlow} pf
|
||||
* @param {Offer} offer
|
||||
*/
|
||||
const buy = async (pf, offer) => {
|
||||
const purchase = await axios.post(
|
||||
`http://127.0.0.1:3000/purchase/offer`,
|
||||
{
|
||||
offerId: offer.offer.id,
|
||||
type: offer.offer.type,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: getCookie("EPIC_BEARER_TOKEN"),
|
||||
},
|
||||
}
|
||||
);
|
||||
if (purchase.status !== 200) return pf.requestclose("PurchaseFailure");
|
||||
await pf.receipt(purchase.data.receipt);
|
||||
await pf.requestclose("WasSuccessful");
|
||||
};
|
510
storage/mem/purchase.html
Normal file
510
storage/mem/purchase.html
Normal file
|
@ -0,0 +1,510 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||
<script src="/purchase/assets/?asset=controller.js"></script>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Purchase Flow</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
font-family: "Open Sans", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
:root {
|
||||
--epic-background: #1e1e1e;
|
||||
--epic-summary: #262626;
|
||||
--epic-summary-hover: #2e2e2e;
|
||||
--epic-card: #242524;
|
||||
--epic-highlight: #28a7da;
|
||||
|
||||
--epic-color-active: #f5f5f5;
|
||||
--epic-color-semiactive: #a1a1a1;
|
||||
--epic-color-inactive: #636363;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.appContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
color: var(--epic-color-active);
|
||||
background-color: var(--epic-background);
|
||||
}
|
||||
|
||||
.order {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.orderSummary {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
width: 25rem;
|
||||
min-width: 25rem;
|
||||
height: 100%;
|
||||
background-color: var(--epic-summary);
|
||||
}
|
||||
|
||||
.orderSummaryHeader {
|
||||
font-weight: 600;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.orderClose {
|
||||
border: none;
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 0.25rem;
|
||||
cursor: pointer;
|
||||
border-radius: 0.65rem;
|
||||
background-color: var(--epic-summary);
|
||||
transition: background-color 50ms;
|
||||
}
|
||||
|
||||
.orderClose svg {
|
||||
fill: var(--epic-color-inactive);
|
||||
transition: fill 50ms;
|
||||
}
|
||||
|
||||
.orderClose:hover {
|
||||
background-color: var(--epic-card);
|
||||
}
|
||||
|
||||
.orderClose:hover svg {
|
||||
fill: var(--epic-color-active);
|
||||
}
|
||||
|
||||
.orderImage {
|
||||
/* aspect-ratio: 9/12; */
|
||||
width: 8rem;
|
||||
min-width: max-content;
|
||||
height: 10rem;
|
||||
min-height: max-content;
|
||||
border-radius: 0.5rem;
|
||||
image-rendering: optimizeSpeed;
|
||||
/* background-image: url("https://fortnite-api.com/images/cosmetics/br/CID_384_Athena_Commando_M_StreetAssassin/icon.png"); */
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-color: var(--epic-background);
|
||||
}
|
||||
|
||||
.orderTitle {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* gap: 1rem; */
|
||||
}
|
||||
|
||||
.orderTitleInformation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.orderTitleName {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.orderTitlePrice {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--epic-color-semiactive);
|
||||
}
|
||||
|
||||
.bigFatButButton {
|
||||
margin-top: auto;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 1.2rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--epic-color-active);
|
||||
border-radius: 0.25rem;
|
||||
background-color: #0078f2;
|
||||
transition: filter 50ms;
|
||||
}
|
||||
|
||||
.bigFatButButton:hover {
|
||||
filter: brightness(0.9) contrast(1.5);
|
||||
}
|
||||
|
||||
.priceBreakdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 0.15rem; */
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.priceBreakdown section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.priceBreakdown section p {
|
||||
font-weight: 400;
|
||||
font-size: 0.95rem;
|
||||
color: var(--epic-color-semiactive);
|
||||
}
|
||||
.priceBreakdown .divider {
|
||||
margin-top: 0.35rem;
|
||||
margin-bottom: 0.35rem;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: #636363;
|
||||
}
|
||||
|
||||
.priceBreakdown section.bold p {
|
||||
font-weight: 600;
|
||||
color: var(--epic-color-active);
|
||||
}
|
||||
|
||||
.specialBannerIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 3rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.specialBannerIcon svg {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
fill: #242424;
|
||||
}
|
||||
|
||||
.specialBanner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.35rem;
|
||||
width: 100%;
|
||||
border-radius: 0.25rem;
|
||||
background-image: linear-gradient(to right, #ccec86, #ffdd76);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.specialBanner p {
|
||||
font-size: 0.785rem;
|
||||
line-height: 1rem;
|
||||
font-weight: 500;
|
||||
color: #2c2d28;
|
||||
}
|
||||
|
||||
.specialBanner p b {
|
||||
font-weight: 700;
|
||||
color: #242424;
|
||||
}
|
||||
|
||||
.orderPaymentMethods {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 55rem;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.orderStatusContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.orderStatus {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 20rem;
|
||||
height: 3.5rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
border-bottom: 0.15rem solid var(--epic-summary);
|
||||
}
|
||||
|
||||
.orderStatus p {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--epic-color-inactive);
|
||||
}
|
||||
|
||||
.orderStatus:hover {
|
||||
border-bottom: 0.15rem solid var(--epic-summary-hover);
|
||||
}
|
||||
|
||||
.orderStatus.active {
|
||||
border-bottom: 0.15rem solid var(--epic-highlight);
|
||||
}
|
||||
|
||||
.orderStatus.active p {
|
||||
color: var(--epic-color-active);
|
||||
}
|
||||
|
||||
.orderStatus.fill {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: unset;
|
||||
cursor: default;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.orderStatus.fill:hover {
|
||||
border-bottom: 0.15rem solid var(--epic-summary);
|
||||
}
|
||||
|
||||
.orderStatusUserIcon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
fill: var(--epic-color-inactive);
|
||||
}
|
||||
|
||||
.paymentMethodsContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.paymentMethodsHeader {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.paymentMethodsContainer .divider {
|
||||
margin-top: 1.25rem;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.paymentMethod {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
padding: 0.8rem;
|
||||
min-height: 3rem;
|
||||
cursor: pointer;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--epic-card);
|
||||
transition: background-color 50ms;
|
||||
}
|
||||
|
||||
.paymentMethod:hover {
|
||||
background-color: var(--epic-summary-hover);
|
||||
}
|
||||
|
||||
.paymentMethod.active {
|
||||
cursor: default;
|
||||
background-color: var(--epic-card);
|
||||
}
|
||||
|
||||
.paymentMethodCard {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: end;
|
||||
justify-content: flex-end;
|
||||
padding: 0.5rem;
|
||||
width: 12rem;
|
||||
height: 7rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.paymentCardProvider {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.paymentInformation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
p.paymentCardNumber {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: var(--epic-color-semiactive);
|
||||
}
|
||||
|
||||
p.p {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 400;
|
||||
color: var(--epic-color-semiactive);
|
||||
}
|
||||
|
||||
p.notice {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
color: var(--epic-color-semiactive);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="appContainer">
|
||||
<div class="order">
|
||||
<!-- <OrderPaymentMethods /> -->
|
||||
<div class="orderPaymentMethods">
|
||||
<div class="orderStatusContainer">
|
||||
<div class="orderStatus active">
|
||||
<p>CHECKOUT</p>
|
||||
</div>
|
||||
<div class="orderStatus fill">
|
||||
<p class="orderStatusUsername" id="displayName"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="paymentMethodsContainer">
|
||||
<h2 class="paymentMethodsHeader">REVIEW AND PLACE ORDER</h2>
|
||||
<p class="p">YOUR PAYMENT METHODS</p>
|
||||
<!-- <div class="paymentMethod active">
|
||||
<div class="paymentMethodCard">
|
||||
<img
|
||||
src="https://logos-world.net/wp-content/uploads/2020/04/Visa-Logo.png"
|
||||
alt=""
|
||||
class="paymentCardProvider"
|
||||
/>
|
||||
</div>
|
||||
<div class="paymentInformation">
|
||||
<p class="paymentCardNumber">**** **** **** 0000</p>
|
||||
<p class="paymentCard">Free Purchase!</p>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="divider"></div>
|
||||
<p class="notice">
|
||||
Snow does not store your payment information. Your payment
|
||||
information is stored securely by Sellix.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- -->
|
||||
|
||||
<!-- <OrderSummary /> -->
|
||||
<div class="orderSummary">
|
||||
<header class="orderSummaryHeader">
|
||||
<p>ORDER SUMMARY</p>
|
||||
<button class="orderClose" id="close">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="icon"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="orderTitle">
|
||||
<div class="orderImage" id="orderImage"></div>
|
||||
<div class="orderTitleInformation">
|
||||
<h4 class="orderTitleName" id="orderName"></h4>
|
||||
<p class="orderTitlePrice" id="orderPrice"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="priceBreakdown">
|
||||
<section>
|
||||
<p>Price</p>
|
||||
<p id="orderSubtotalPrice"></p>
|
||||
</section>
|
||||
<section>
|
||||
<p>VAT included where applicable</p>
|
||||
</section>
|
||||
<div class="divider"></div>
|
||||
<section class="bold">
|
||||
<p>Total</p>
|
||||
<p id="orderTotalPrice"></p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="specialBanner">
|
||||
<div class="specialBannerIcon">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 01-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zM12 2a1 1 0 01.967.744L14.146 7.2 17.5 9.134a1 1 0 010 1.732l-3.354 1.935-1.18 4.455a1 1 0 01-1.933 0L9.854 12.8 6.5 10.866a1 1 0 010-1.732l3.354-1.935 1.18-4.455A1 1 0 0112 2z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p>
|
||||
Earn <b>50 V-Bucks</b> with this purchase. Rewards are available
|
||||
for use 14 days after purchase
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button class="bigFatButButton" id="purchaseOfferButton">
|
||||
PLACE ORDER
|
||||
</button>
|
||||
</div>
|
||||
<!-- -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const snow = {
|
||||
log: async (json) =>
|
||||
await axios.post("http://127.0.0.1:3000/snow/log", {
|
||||
json: json,
|
||||
url: window.location.href,
|
||||
}),
|
||||
};
|
||||
|
||||
const unrealEngineInjected = window.ue ? window.ue : null;
|
||||
unrealEngineInjected && main(unrealEngineInjected); // asyncronous!!!
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -49,6 +49,10 @@ func (s *PostgresStorage) MigrateAll() {
|
|||
s.Migrate(&DB_DiscordPerson{}, "Discords")
|
||||
s.Migrate(&DB_BanStatus{}, "Bans")
|
||||
s.Migrate(&DB_SeasonStat{}, "Stats")
|
||||
s.Migrate(&DB_Receipt{}, "Receipts")
|
||||
s.Migrate(&DB_ReceiptLoot{}, "ReceiptLoot")
|
||||
s.Migrate(&DB_VariantToken{}, "VariantTokens")
|
||||
s.Migrate(&DB_VariantTokenGrant{}, "VariantTokenGrants")
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DropTables() {
|
||||
|
@ -66,8 +70,12 @@ func (s *PostgresStorage) PreloadPerson() (tx *gorm.DB) {
|
|||
Preload("Profiles.Gifts").
|
||||
Preload("Profiles.Gifts.Loot").
|
||||
Preload("Profiles.Quests").
|
||||
Preload("Profiles.VariantTokens").
|
||||
Preload("Profiles.VariantTokens.VariantGrants").
|
||||
Preload("Profiles.Purchases").
|
||||
Preload("Profiles.Purchases.Loot").
|
||||
Preload("Receipts").
|
||||
Preload("Receipts.Loot").
|
||||
Preload("Discord").
|
||||
Preload("BanHistory").
|
||||
Preload("Stats")
|
||||
|
@ -220,6 +228,22 @@ func (s *PostgresStorage) DeleteGift(giftId string) {
|
|||
s.Postgres.Delete(&DB_Gift{}, "id = ?", giftId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveVariantToken(variantToken *DB_VariantToken) {
|
||||
s.Postgres.Save(variantToken)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DeleteVariantToken(variantTokenId string) {
|
||||
s.Postgres.Delete(&DB_VariantToken{}, "id = ?", variantTokenId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveVariantTokenGrant(variantTokenGrant *DB_VariantTokenGrant) {
|
||||
s.Postgres.Save(variantTokenGrant)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DeleteVariantTokenGrant(variantTokenGrantId string) {
|
||||
s.Postgres.Delete(&DB_VariantTokenGrant{}, "id = ?", variantTokenGrantId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveAttribute(attribute *DB_Attribute) {
|
||||
s.Postgres.Save(attribute)
|
||||
}
|
||||
|
@ -258,4 +282,28 @@ func (s *PostgresStorage) SaveBanStatus(banStatus *DB_BanStatus) {
|
|||
|
||||
func (s *PostgresStorage) DeleteBanStatus(banStatusId string) {
|
||||
s.Postgres.Delete(&DB_BanStatus{}, "id = ?", banStatusId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveReceipt(receipt *DB_Receipt) {
|
||||
s.Postgres.Save(receipt)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DeleteReceipt(receiptId string) {
|
||||
s.Postgres.Delete(&DB_Receipt{}, "id = ?", receiptId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveReceiptLoot(receiptLoot *DB_ReceiptLoot) {
|
||||
s.Postgres.Save(receiptLoot)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DeleteReceiptLoot(receiptLootId string) {
|
||||
s.Postgres.Delete(&DB_ReceiptLoot{}, "id = ?", receiptLootId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveSeasonStats(seasonStats *DB_SeasonStat) {
|
||||
s.Postgres.Save(seasonStats)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DeleteSeasonStats(seasonId string) {
|
||||
s.Postgres.Delete(&DB_SeasonStat{}, "id = ?", seasonId)
|
||||
}
|
|
@ -43,6 +43,11 @@ type Storage interface {
|
|||
SaveGift(gift *DB_Gift)
|
||||
DeleteGift(giftId string)
|
||||
|
||||
SaveVariantToken(variantToken *DB_VariantToken)
|
||||
SaveVariantTokenGrant(variantTokenGrant *DB_VariantTokenGrant)
|
||||
DeleteVariantToken(variantTokenId string)
|
||||
DeleteVariantTokenGrant(variantTokenGrantId string)
|
||||
|
||||
SaveAttribute(attribute *DB_Attribute)
|
||||
DeleteAttribute(attributeId string)
|
||||
|
||||
|
@ -57,6 +62,14 @@ type Storage interface {
|
|||
|
||||
SaveBanStatus(ban *DB_BanStatus)
|
||||
DeleteBanStatus(banId string)
|
||||
|
||||
SaveReceipt(receipt *DB_Receipt)
|
||||
SaveReceiptLoot(receiptLoot *DB_ReceiptLoot)
|
||||
DeleteReceipt(receiptId string)
|
||||
DeleteReceiptLoot(receiptLootId string)
|
||||
|
||||
SaveSeasonStats(season *DB_SeasonStat)
|
||||
DeleteSeasonStats(seasonId string)
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
|
@ -198,6 +211,22 @@ func (r *Repository) DeleteGift(giftId string) {
|
|||
r.Storage.DeleteGift(giftId)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveVariantToken(variantToken *DB_VariantToken) {
|
||||
r.Storage.SaveVariantToken(variantToken)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveVariantTokenGrant(variantTokenGrant *DB_VariantTokenGrant) {
|
||||
r.Storage.SaveVariantTokenGrant(variantTokenGrant)
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteVariantToken(variantTokenId string) {
|
||||
r.Storage.DeleteVariantToken(variantTokenId)
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteVariantTokenGrant(variantTokenGrantId string) {
|
||||
r.Storage.DeleteVariantTokenGrant(variantTokenGrantId)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveAttribute(attribute *DB_Attribute) {
|
||||
r.Storage.SaveAttribute(attribute)
|
||||
}
|
||||
|
@ -236,4 +265,28 @@ func (r *Repository) SaveBanStatus(ban *DB_BanStatus) {
|
|||
|
||||
func (r *Repository) DeleteBanStatus(banId string) {
|
||||
r.Storage.DeleteBanStatus(banId)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveReceipt(receipt *DB_Receipt) {
|
||||
r.Storage.SaveReceipt(receipt)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveReceiptLoot(receiptLoot *DB_ReceiptLoot) {
|
||||
r.Storage.SaveReceiptLoot(receiptLoot)
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteReceipt(receiptId string) {
|
||||
r.Storage.DeleteReceipt(receiptId)
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteReceiptLoot(receiptLootId string) {
|
||||
r.Storage.DeleteReceiptLoot(receiptLootId)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveSeasonStats(season *DB_SeasonStat) {
|
||||
r.Storage.SaveSeasonStats(season)
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteSeasonStats(seasonId string) {
|
||||
r.Storage.DeleteSeasonStats(seasonId)
|
||||
}
|
|
@ -15,6 +15,7 @@ type DB_Person struct {
|
|||
DisplayName string
|
||||
RefundTickets int
|
||||
Permissions int64
|
||||
Receipts []DB_Receipt `gorm:"foreignkey:PersonID"`
|
||||
Profiles []DB_Profile `gorm:"foreignkey:PersonID"`
|
||||
Stats []DB_SeasonStat `gorm:"foreignkey:PersonID"`
|
||||
Discord DB_DiscordPerson `gorm:"foreignkey:PersonID"`
|
||||
|
@ -35,6 +36,32 @@ func (DB_Relationship) TableName() string {
|
|||
return "Relationships"
|
||||
}
|
||||
|
||||
type DB_Receipt struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
PersonID string `gorm:"index"`
|
||||
OfferID string
|
||||
PurchaseDate int64
|
||||
TotalPaid int
|
||||
State string
|
||||
Loot []DB_ReceiptLoot `gorm:"foreignkey:ReceiptID"`
|
||||
}
|
||||
|
||||
func (DB_Receipt) TableName() string {
|
||||
return "Receipts"
|
||||
}
|
||||
|
||||
type DB_ReceiptLoot struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
ReceiptID string `gorm:"index"`
|
||||
TemplateID string
|
||||
Quantity int
|
||||
ProfileType string
|
||||
}
|
||||
|
||||
func (DB_ReceiptLoot) TableName() string {
|
||||
return "ReceiptLoot"
|
||||
}
|
||||
|
||||
type DB_Profile struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
PersonID string `gorm:"index"`
|
||||
|
@ -44,6 +71,7 @@ type DB_Profile struct {
|
|||
Attributes []DB_Attribute `gorm:"foreignkey:ProfileID"`
|
||||
Loadouts []DB_Loadout `gorm:"foreignkey:ProfileID"`
|
||||
Purchases []DB_Purchase `gorm:"foreignkey:ProfileID"`
|
||||
VariantTokens []DB_VariantToken `gorm:"foreignkey:ProfileID"`
|
||||
Type string
|
||||
Revision int
|
||||
}
|
||||
|
@ -182,6 +210,32 @@ func (DB_GiftLoot) TableName() string {
|
|||
return "GiftLoot"
|
||||
}
|
||||
|
||||
type DB_VariantToken struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
ProfileID string `gorm:"index"`
|
||||
TemplateID string
|
||||
Name string
|
||||
AutoEquipOnGrant bool
|
||||
CreateGiftboxOnGrant bool
|
||||
MarkItemUnseenOnGrant bool
|
||||
VariantGrants []DB_VariantTokenGrant `gorm:"foreignkey:VariantTokenID"`
|
||||
}
|
||||
|
||||
func (DB_VariantToken) TableName() string {
|
||||
return "VariantTokens"
|
||||
}
|
||||
|
||||
type DB_VariantTokenGrant struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
VariantTokenID string `gorm:"index"`
|
||||
Channel string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (DB_VariantTokenGrant) TableName() string {
|
||||
return "VariantTokenGrants"
|
||||
}
|
||||
|
||||
type DB_DiscordPerson struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
PersonID string
|
||||
|
@ -199,11 +253,11 @@ func (DB_DiscordPerson) TableName() string {
|
|||
type DB_SeasonStat struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
PersonID string
|
||||
Build string
|
||||
Season int
|
||||
SeasonXP int
|
||||
SeasonalLevel int
|
||||
SeasonalTier int
|
||||
BattleStars int
|
||||
BookXP int
|
||||
BookPurchased bool
|
||||
Hype int
|
||||
}
|
||||
|
||||
func (DB_SeasonStat) TableName() string {
|
||||
|
|
Loading…
Reference in New Issue
Block a user