Rework automatic storefront!
This commit is contained in:
parent
dea3770027
commit
3fcda14f1a
16
aid/aid.go
16
aid/aid.go
|
@ -1,7 +1,6 @@
|
||||||
package aid
|
package aid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
m "math/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -15,20 +14,6 @@ func WaitForExit() {
|
||||||
<-sc
|
<-sc
|
||||||
}
|
}
|
||||||
|
|
||||||
func RandomString(n int) string {
|
|
||||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
|
||||||
|
|
||||||
s := make([]rune, n)
|
|
||||||
for i := range s {
|
|
||||||
s[i] = letters[m.Intn(len(letters))]
|
|
||||||
}
|
|
||||||
return string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RandomInt(min, max int) int {
|
|
||||||
return m.Intn(max - min) + min
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatNumber(number int) string {
|
func FormatNumber(number int) string {
|
||||||
str := ""
|
str := ""
|
||||||
for i, char := range ReverseString(strconv.Itoa(number)) {
|
for i, char := range ReverseString(strconv.Itoa(number)) {
|
||||||
|
@ -60,7 +45,6 @@ func ToHex(number int) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Regex(str, regex string) *string {
|
func Regex(str, regex string) *string {
|
||||||
// reg := regexp.MustCompile(`(?:CID_)(\d+|A_\d+)(?:_.+)`).FindStringSubmatch(strings.Join(split[:], "_"))
|
|
||||||
reg := regexp.MustCompile(regex).FindStringSubmatch(str)
|
reg := regexp.MustCompile(regex).FindStringSubmatch(str)
|
||||||
if len(reg) > 1 {
|
if len(reg) > 1 {
|
||||||
return ®[1]
|
return ®[1]
|
||||||
|
|
|
@ -48,6 +48,7 @@ type CS struct {
|
||||||
Everything bool
|
Everything bool
|
||||||
Password bool
|
Password bool
|
||||||
DisableClientCredentials bool
|
DisableClientCredentials bool
|
||||||
|
ShopSeed int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,4 +174,5 @@ func LoadConfig(file []byte) {
|
||||||
Config.Fortnite.Everything = cfg.Section("fortnite").Key("everything").MustBool(false)
|
Config.Fortnite.Everything = cfg.Section("fortnite").Key("everything").MustBool(false)
|
||||||
Config.Fortnite.Password = !(cfg.Section("fortnite").Key("disable_password").MustBool(false))
|
Config.Fortnite.Password = !(cfg.Section("fortnite").Key("disable_password").MustBool(false))
|
||||||
Config.Fortnite.DisableClientCredentials = cfg.Section("fortnite").Key("disable_client_credentials").MustBool(false)
|
Config.Fortnite.DisableClientCredentials = cfg.Section("fortnite").Key("disable_client_credentials").MustBool(false)
|
||||||
|
Config.Fortnite.ShopSeed = cfg.Section("fortnite").Key("shop_seed").MustInt(0)
|
||||||
}
|
}
|
24
aid/random.go
Normal file
24
aid/random.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package aid
|
||||||
|
|
||||||
|
import "math/rand"
|
||||||
|
|
||||||
|
var Random *rand.Rand
|
||||||
|
|
||||||
|
func SetRandom(r *rand.Rand) {
|
||||||
|
Random = r
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandomString(n int) string {
|
||||||
|
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
|
||||||
|
s := make([]rune, n)
|
||||||
|
for i := range s {
|
||||||
|
s[i] = letters[Random.Intn(len(letters))]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandomInt(min, max int) int {
|
||||||
|
return Random.Intn(max-min) + min
|
||||||
|
}
|
|
@ -17,6 +17,10 @@ func TimeEndOfWeekString() string {
|
||||||
return time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 23, 59, 59, 999999999, time.Now().Location()).AddDate(0, 0, 7).Format("2006-01-02T15:04:05.999Z")
|
return time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 23, 59, 59, 999999999, time.Now().Location()).AddDate(0, 0, 7).Format("2006-01-02T15:04:05.999Z")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CurrentDayUnix() int64 {
|
||||||
|
return time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Now().Location()).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
// everything below is taken from the golang standard library I just added the extra units to the map
|
// everything below is taken from the golang standard library I just added the extra units to the map
|
||||||
|
|
||||||
var unitMap = map[string]uint64{
|
var unitMap = map[string]uint64{
|
||||||
|
|
|
@ -75,4 +75,8 @@ disable_password=false
|
||||||
; this will disable the client credentials grant type
|
; this will disable the client credentials grant type
|
||||||
; however this will also disable a user to get the hotfixes before login
|
; however this will also disable a user to get the hotfixes before login
|
||||||
; so xmpp and other hotfix related things will be delayed by ~1 minute
|
; so xmpp and other hotfix related things will be delayed by ~1 minute
|
||||||
disable_client_credentials=false
|
disable_client_credentials=false
|
||||||
|
; this is used to generate a random shop
|
||||||
|
; each number will generate a different shop for the day
|
||||||
|
; the shop will stay the same for the entire day even after server restarts
|
||||||
|
shop_seed=0
|
|
@ -157,7 +157,7 @@ func (c *ExternalDataClient) AddDisplayAssetToItem(displayAsset string) {
|
||||||
if found == nil && split[0] == "CID" {
|
if found == nil && split[0] == "CID" {
|
||||||
r := aid.Regex(strings.Join(split[:], "_"), `(?:CID_)(\d+|A_\d+)(?:_.+)`)
|
r := aid.Regex(strings.Join(split[:], "_"), `(?:CID_)(\d+|A_\d+)(?:_.+)`)
|
||||||
if r != nil {
|
if r != nil {
|
||||||
found = ItemByShallowID(*r)
|
found = GetItemByShallowID(*r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ func PreloadCosmetics() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ItemByShallowID(shallowID string) *FortniteItem {
|
func GetItemByShallowID(shallowID string) *FortniteItem {
|
||||||
for _, item := range External.TypedFortniteItems["AthenaCharacter"] {
|
for _, item := range External.TypedFortniteItems["AthenaCharacter"] {
|
||||||
if strings.Contains(item.ID, shallowID) {
|
if strings.Contains(item.ID, shallowID) {
|
||||||
return item
|
return item
|
||||||
|
@ -203,43 +203,54 @@ func ItemByShallowID(shallowID string) *FortniteItem {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RandomItemByType(itemType string) *FortniteItem {
|
func GetRandomItemWithDisplayAsset() *FortniteItem {
|
||||||
items := External.TypedFortniteItemsWithDisplayAssets[itemType]
|
items := External.FortniteItemsWithDisplayAssets
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return items[aid.RandomInt(0, len(items))]
|
flat := []FortniteItem{}
|
||||||
|
for _, item := range items {
|
||||||
|
flat = append(flat, *item)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc[[]FortniteItem](flat, func(a, b FortniteItem) int {
|
||||||
|
return strings.Compare(a.ID, b.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
return &flat[aid.RandomInt(0, len(flat))]
|
||||||
}
|
}
|
||||||
|
|
||||||
func RandomItemByNotType(notItemType string) *FortniteItem {
|
func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem {
|
||||||
allItems := []*FortniteItem{}
|
flat := []FortniteItem{}
|
||||||
|
|
||||||
for key, items := range External.TypedFortniteItemsWithDisplayAssets {
|
for t, items := range External.TypedFortniteItemsWithDisplayAssets {
|
||||||
if key == notItemType {
|
if t == notType {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
allItems = append(allItems, items...)
|
for _, item := range items {
|
||||||
|
flat = append(flat, *item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allItems[aid.RandomInt(0, len(allItems))]
|
slices.SortFunc[[]FortniteItem](flat, func(a, b FortniteItem) int {
|
||||||
|
return strings.Compare(a.ID, b.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
return &flat[aid.RandomInt(0, len(flat))]
|
||||||
}
|
}
|
||||||
|
|
||||||
func RandomItemWithFeaturedImage() *FortniteItem {
|
|
||||||
items := External.FortniteItemsWithFeaturedImage
|
|
||||||
if len(items) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return items[aid.RandomInt(0, len(items))]
|
func GetRandomSet() *FortniteSet {
|
||||||
}
|
sets := []FortniteSet{}
|
||||||
|
|
||||||
func RandomSet() *FortniteSet {
|
|
||||||
sets := []*FortniteSet{}
|
|
||||||
for _, set := range External.FortniteSets {
|
for _, set := range External.FortniteSets {
|
||||||
sets = append(sets, set)
|
sets = append(sets, *set)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sets[aid.RandomInt(0, len(sets))]
|
slices.SortFunc[[]FortniteSet](sets, func(a, b FortniteSet) int {
|
||||||
|
return strings.Compare(a.BackendName, b.BackendName)
|
||||||
|
})
|
||||||
|
|
||||||
|
return &sets[aid.RandomInt(0, len(sets))]
|
||||||
}
|
}
|
744
fortnite/shop.go
744
fortnite/shop.go
|
@ -1,476 +1,350 @@
|
||||||
package fortnite
|
package fortnite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"math/rand"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/ectrc/snow/aid"
|
"github.com/ectrc/snow/aid"
|
||||||
"github.com/ectrc/snow/person"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Rarities = map[string]map[string]int{
|
priceLookup = map[string]map[string]int{
|
||||||
"EFortRarity::Legendary": {
|
"EFortRarity::Legendary": {
|
||||||
"AthenaCharacter": 2000,
|
"AthenaCharacter": 2000,
|
||||||
"AthenaBackpack": 1500,
|
"AthenaBackpack": 1500,
|
||||||
"AthenaPickaxe": 1500,
|
"AthenaPickaxe": 1500,
|
||||||
"AthenaGlider": 1800,
|
"AthenaGlider": 1800,
|
||||||
"AthenaDance": 500,
|
"AthenaDance": 500,
|
||||||
"AthenaItemWrap": 800,
|
"AthenaItemWrap": 800,
|
||||||
},
|
},
|
||||||
"EFortRarity::Epic": {
|
"EFortRarity::Epic": {
|
||||||
"AthenaCharacter": 1500,
|
"AthenaCharacter": 1500,
|
||||||
"AthenaBackpack": 1200,
|
"AthenaBackpack": 1200,
|
||||||
"AthenaPickaxe": 1200,
|
"AthenaPickaxe": 1200,
|
||||||
"AthenaGlider": 1500,
|
"AthenaGlider": 1500,
|
||||||
"AthenaDance": 800,
|
"AthenaDance": 800,
|
||||||
"AthenaItemWrap": 800,
|
"AthenaItemWrap": 800,
|
||||||
},
|
},
|
||||||
"EFortRarity::Rare": {
|
"EFortRarity::Rare": {
|
||||||
"AthenaCharacter": 1200,
|
"AthenaCharacter": 1200,
|
||||||
"AthenaBackpack": 800,
|
"AthenaBackpack": 800,
|
||||||
"AthenaPickaxe": 800,
|
"AthenaPickaxe": 800,
|
||||||
"AthenaGlider": 800,
|
"AthenaGlider": 800,
|
||||||
"AthenaDance": 500,
|
"AthenaDance": 500,
|
||||||
"AthenaItemWrap": 600,
|
"AthenaItemWrap": 600,
|
||||||
},
|
},
|
||||||
"EFortRarity::Uncommon": {
|
"EFortRarity::Uncommon": {
|
||||||
"AthenaCharacter": 800,
|
"AthenaCharacter": 800,
|
||||||
"AthenaBackpack": 200,
|
"AthenaBackpack": 200,
|
||||||
"AthenaPickaxe": 500,
|
"AthenaPickaxe": 500,
|
||||||
"AthenaGlider": 500,
|
"AthenaGlider": 500,
|
||||||
"AthenaDance": 200,
|
"AthenaDance": 200,
|
||||||
"AthenaItemWrap": 300,
|
"AthenaItemWrap": 300,
|
||||||
},
|
},
|
||||||
"EFortRarity::Common": {
|
"EFortRarity::Common": {
|
||||||
"AthenaCharacter": 500,
|
"AthenaCharacter": 500,
|
||||||
"AthenaBackpack": 200,
|
"AthenaBackpack": 200,
|
||||||
"AthenaPickaxe": 500,
|
"AthenaPickaxe": 500,
|
||||||
"AthenaGlider": 500,
|
"AthenaGlider": 500,
|
||||||
"AthenaDance": 200,
|
"AthenaDance": 200,
|
||||||
"AthenaItemWrap": 300,
|
"AthenaItemWrap": 300,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
StaticCatalog = NewCatalog()
|
|
||||||
|
dailyItemLookup = map[int]int{
|
||||||
|
2: 4,
|
||||||
|
4: 6,
|
||||||
|
13: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
weeklySetLookup = map[int]int{
|
||||||
|
2: 2,
|
||||||
|
4: 3,
|
||||||
|
11: 4,
|
||||||
|
13: 3,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetPriceForRarity(rarity string, backendType string) int {
|
func price(rarity, type_ string) int {
|
||||||
return Rarities[rarity][backendType]
|
return priceLookup[rarity][type_]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Catalog struct {
|
func dailyItems(season int) int {
|
||||||
RefreshIntervalHrs int `json:"refreshIntervalHrs"`
|
var items int
|
||||||
DailyPurchaseHrs int `json:"dailyPurchaseHrs"`
|
|
||||||
Expiration string `json:"expiration"`
|
|
||||||
Storefronts []Storefront `json:"storefronts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCatalog() *Catalog {
|
for s, i := range dailyItemLookup {
|
||||||
return &Catalog{
|
if season >= s {
|
||||||
RefreshIntervalHrs: 24,
|
items = i
|
||||||
DailyPurchaseHrs: 24,
|
|
||||||
Expiration: aid.TimeEndOfDay(),
|
|
||||||
Storefronts: []Storefront{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) Add(storefront *Storefront) {
|
|
||||||
c.Storefronts = append(c.Storefronts, *storefront)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) GenerateFortniteCatalog(p *person.Person) aid.JSON {
|
|
||||||
json := aid.JSON{
|
|
||||||
"refreshIntervalHrs": c.RefreshIntervalHrs,
|
|
||||||
"dailyPurchaseHrs": c.DailyPurchaseHrs,
|
|
||||||
"expiration": c.Expiration,
|
|
||||||
"storefronts": []aid.JSON{},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, storefront := range c.Storefronts {
|
|
||||||
json["storefronts"] = append(json["storefronts"].([]aid.JSON), storefront.GenerateResponse(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) CheckIfOfferIsDuplicate(entry Entry) bool {
|
|
||||||
for _, storefront := range c.Storefronts {
|
|
||||||
for _, catalogEntry := range storefront.CatalogEntries {
|
|
||||||
if catalogEntry.Grants[0] == entry.Grants[0] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Catalog) GetOfferById(id string) *Entry {
|
func weeklySets(season int) int {
|
||||||
for _, storefront := range c.Storefronts {
|
var sets int
|
||||||
for _, catalogEntry := range storefront.CatalogEntries {
|
|
||||||
if catalogEntry.ID == id {
|
for s, i := range weeklySetLookup {
|
||||||
return &catalogEntry
|
if season >= s {
|
||||||
|
sets = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sets
|
||||||
|
}
|
||||||
|
|
||||||
|
type FortniteCatalogSectionOffer 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() *FortniteCatalogSectionOffer {
|
||||||
|
return &FortniteCatalogSectionOffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FortniteCatalogSectionOffer) GenerateID() {
|
||||||
|
for _, item := range f.Grants {
|
||||||
|
f.ID += item.Type.BackendValue + ":" + item.ID + ","
|
||||||
|
}
|
||||||
|
|
||||||
|
f.ID = "v2:/" + aid.Hash([]byte(f.ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FortniteCatalogSectionOffer) GenerateTotalPrice() {
|
||||||
|
if !f.BundleInfo.IsBundle {
|
||||||
|
f.TotalPrice = price(f.Grants[0].Rarity.BackendValue, f.Grants[0].Type.BackendValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range f.Grants {
|
||||||
|
f.TotalPrice += price(item.Rarity.BackendValue, item.Rarity.BackendValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FortniteCatalogSectionOffer) GenerateFortniteCatalogSectionOffer() 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 []*FortniteCatalogSectionOffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFortniteCatalogSection(name string) *FortniteCatalogSection {
|
||||||
|
return &FortniteCatalogSection{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FortniteCatalogSection) GenerateFortniteCatalogSection() aid.JSON {
|
||||||
|
catalogEntiresResponse := []aid.JSON{}
|
||||||
|
for _, offer := range f.Offers {
|
||||||
|
catalogEntiresResponse = append(catalogEntiresResponse, offer.GenerateFortniteCatalogSectionOffer())
|
||||||
|
}
|
||||||
|
|
||||||
|
return aid.JSON{
|
||||||
|
"name": f.Name,
|
||||||
|
"catalogEntries": catalogEntiresResponse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FortniteCatalogSection) GetGroupedOffers() map[string][]*FortniteCatalogSectionOffer {
|
||||||
|
groupedOffers := map[string][]*FortniteCatalogSectionOffer{}
|
||||||
|
|
||||||
|
for _, offer := range f.Offers {
|
||||||
|
if groupedOffers[offer.Meta.Category] == nil {
|
||||||
|
groupedOffers[offer.Meta.Category] = []*FortniteCatalogSectionOffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupedOffers[offer.Meta.Category] = append(groupedOffers[offer.Meta.Category], offer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupedOffers
|
||||||
|
}
|
||||||
|
|
||||||
|
type FortniteCatalog struct {
|
||||||
|
Sections []*FortniteCatalogSection
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFortniteCatalog() *FortniteCatalog {
|
||||||
|
return &FortniteCatalog{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FortniteCatalog) GenerateFortniteCatalog() aid.JSON {
|
||||||
|
catalogSectionsResponse := []aid.JSON{}
|
||||||
|
for _, section := range f.Sections {
|
||||||
|
catalogSectionsResponse = append(catalogSectionsResponse, section.GenerateFortniteCatalogSection())
|
||||||
|
}
|
||||||
|
|
||||||
|
return aid.JSON{
|
||||||
|
"storefronts": catalogSectionsResponse,
|
||||||
|
"refreshIntervalHrs": 24,
|
||||||
|
"dailyPurchaseHrs": 24,
|
||||||
|
"expiration": "9999-12-31T23:59:59.999Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) < dailyItems(aid.Config.Fortnite.Season) {
|
||||||
|
entry := newEntryFromFortniteItem(GetRandomItemWithDisplayAssetOfNotType("AthenaCharacter"), false)
|
||||||
|
entry.Meta.SectionId = "Daily"
|
||||||
|
daily.Offers = append(daily.Offers, entry)
|
||||||
|
}
|
||||||
|
catalog.Sections = append(catalog.Sections, daily)
|
||||||
|
|
||||||
|
weekly := NewFortniteCatalogSection("BRWeeklyStorefront")
|
||||||
|
for len(weekly.GetGroupedOffers()) < weeklySets(aid.Config.Fortnite.Season) {
|
||||||
|
set := GetRandomSet()
|
||||||
|
for _, item := range set.Items {
|
||||||
|
if item.DisplayAssetPath == "" || item.DisplayAssetPath2 == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := newEntryFromFortniteItem(item, true)
|
||||||
|
entry.Meta.Category = set.BackendName
|
||||||
|
entry.Meta.SectionId = "Featured"
|
||||||
|
weekly.Offers = append(weekly.Offers, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catalog.Sections = append(catalog.Sections, weekly)
|
||||||
|
|
||||||
|
return catalog
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEntryFromFortniteItem(fortniteItem *FortniteItem, addAssets bool) *FortniteCatalogSectionOffer {
|
||||||
|
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 GetOfferByOfferId(id string) *FortniteCatalogSectionOffer {
|
||||||
|
catalog := NewRandomFortniteCatalog()
|
||||||
|
|
||||||
|
for _, section := range catalog.Sections {
|
||||||
|
for _, offer := range section.Offers {
|
||||||
|
if offer.ID == id {
|
||||||
|
return offer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
type Storefront struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
CatalogEntries []Entry `json:"catalogEntries"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStorefront(name string) *Storefront {
|
|
||||||
return &Storefront{
|
|
||||||
Name: name,
|
|
||||||
CatalogEntries: []Entry{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storefront) Add(entry Entry) {
|
|
||||||
s.CatalogEntries = append(s.CatalogEntries, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storefront) GenerateResponse(p *person.Person) aid.JSON {
|
|
||||||
json := aid.JSON{
|
|
||||||
"name": s.Name,
|
|
||||||
"catalogEntries": []aid.JSON{},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range s.CatalogEntries {
|
|
||||||
json["catalogEntries"] = append(json["catalogEntries"].([]aid.JSON), entry.GenerateResponse(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
type Entry struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Price int
|
|
||||||
Meta []aid.JSON
|
|
||||||
Panel string
|
|
||||||
Priority int
|
|
||||||
Grants []string
|
|
||||||
DisplayAssetPath string
|
|
||||||
NewDisplayAssetPath string
|
|
||||||
Title string
|
|
||||||
ShortDescription string
|
|
||||||
ProfileType string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCatalogEntry(profile string, meta ...aid.JSON) *Entry {
|
|
||||||
return &Entry{
|
|
||||||
ID: uuid.New().String(),
|
|
||||||
Meta: meta,
|
|
||||||
ProfileType: profile,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) AddGrant(templateId string) *Entry {
|
|
||||||
e.Grants = append(e.Grants, templateId)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) AddMeta(key string, value interface{}) *Entry {
|
|
||||||
e.Meta = append(e.Meta, aid.JSON{
|
|
||||||
"Key": key,
|
|
||||||
"Value": value,
|
|
||||||
})
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetTileSize(size string) *Entry {
|
|
||||||
e.Meta = append(e.Meta, aid.JSON{
|
|
||||||
"Key": "TileSize",
|
|
||||||
"Value": size,
|
|
||||||
})
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetPanel(panel string) *Entry {
|
|
||||||
e.Panel = panel
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetSection(sectionId string) *Entry {
|
|
||||||
for _, m := range e.Meta {
|
|
||||||
if m["Key"] == "SectionId" {
|
|
||||||
m["Value"] = sectionId
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Meta = append(e.Meta, aid.JSON{
|
|
||||||
"Key": "SectionId",
|
|
||||||
"Value": sectionId,
|
|
||||||
})
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetDisplayAsset(asset string) *Entry {
|
|
||||||
displayAsset := "DAv2_Featured_" + asset
|
|
||||||
e.DisplayAssetPath = "/Game/Catalog/DisplayAssets/" + displayAsset + "." + displayAsset
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetNewDisplayAsset(asset string) *Entry {
|
|
||||||
e.NewDisplayAssetPath = "/Game/Catalog/NewDisplayAssets/" + asset + "." + asset
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetDisplayAssetPath(path string) *Entry {
|
|
||||||
paths := strings.Split(path, "/")
|
|
||||||
id := paths[len(paths)-1]
|
|
||||||
|
|
||||||
e.DisplayAssetPath = "/Game/Catalog/DisplayAssets/" + id + "." + id
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetNewDisplayAssetPath(path string) *Entry {
|
|
||||||
e.NewDisplayAssetPath = path
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetTitle(title string) *Entry {
|
|
||||||
e.Title = title
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetShortDescription(description string) *Entry {
|
|
||||||
e.ShortDescription = description
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) SetPrice(price int) *Entry {
|
|
||||||
e.Price = price
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Entry) GenerateResponse(p *person.Person) aid.JSON {
|
|
||||||
grantStrings := e.Grants
|
|
||||||
for _, grant := range grantStrings {
|
|
||||||
e.Name += grant + "-"
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.NewDisplayAssetPath == "" && len(e.Grants) != 0 {
|
|
||||||
safeTemplateId := strings.ReplaceAll(strings.Split(e.Grants[0], ":")[1], "Athena_Commando_", "")
|
|
||||||
newDisplayAsset := "DAv2_" + safeTemplateId
|
|
||||||
e.NewDisplayAssetPath = "/Game/Catalog/NewDisplayAssets/" + newDisplayAsset + "." + newDisplayAsset
|
|
||||||
}
|
|
||||||
e.AddMeta("NewDisplayAssetPath", e.NewDisplayAssetPath)
|
|
||||||
|
|
||||||
if e.DisplayAssetPath == "" && len(e.Grants) != 0 {
|
|
||||||
displayAsset := "DA_Featured_" + strings.Split(e.Grants[0], ":")[1]
|
|
||||||
e.DisplayAssetPath = "/Game/Catalog/DisplayAssets/" + displayAsset + "." + displayAsset
|
|
||||||
}
|
|
||||||
e.AddMeta("DisplayAssetPath", e.DisplayAssetPath)
|
|
||||||
|
|
||||||
json := aid.JSON{
|
|
||||||
"offerId": e.ID,
|
|
||||||
"devName": e.Name,
|
|
||||||
"offerType": "StaticPrice",
|
|
||||||
"prices": []aid.JSON{
|
|
||||||
{
|
|
||||||
"currencyType": "MtxCurrency",
|
|
||||||
"currencySubType": "Currency",
|
|
||||||
"regularPrice": e.Price,
|
|
||||||
"dynamicRegularPrice": e.Price,
|
|
||||||
"finalPrice": e.Price,
|
|
||||||
"basePrice": e.Price,
|
|
||||||
"saleExpiration": aid.TimeEndOfDay(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"categories": []string{},
|
|
||||||
"catalogGroupPriority": 0,
|
|
||||||
"sortPriority": e.Priority,
|
|
||||||
"dailyLimit": -1,
|
|
||||||
"weeklyLimit": -1,
|
|
||||||
"monthlyLimit": -1,
|
|
||||||
"fufillmentIds": []string{},
|
|
||||||
"filterWeight": 0.0,
|
|
||||||
"appStoreId": []string{},
|
|
||||||
"refundable": false,
|
|
||||||
"itemGrants": []aid.JSON{},
|
|
||||||
"metaInfo": e.Meta,
|
|
||||||
"meta": aid.JSON{},
|
|
||||||
"title": e.Title,
|
|
||||||
"displayAssetPath": e.DisplayAssetPath,
|
|
||||||
"shortDescription": e.ShortDescription,
|
|
||||||
}
|
|
||||||
grants := []aid.JSON{}
|
|
||||||
requirements := []aid.JSON{}
|
|
||||||
purchaseRequirements := []aid.JSON{}
|
|
||||||
meta := []aid.JSON{}
|
|
||||||
|
|
||||||
for _, templateId := range e.Grants {
|
|
||||||
grants = append(grants, aid.JSON{
|
|
||||||
"templateId": templateId,
|
|
||||||
"quantity": 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
if item := p.AthenaProfile.Items.GetItemByTemplateID(templateId); item != nil {
|
|
||||||
requirements = append(requirements, aid.JSON{
|
|
||||||
"requirementType": "DenyOnItemOwnership",
|
|
||||||
"requiredId": item.ID,
|
|
||||||
"minQuantity": 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
purchaseRequirements = append(purchaseRequirements, aid.JSON{
|
|
||||||
"requirementType": "DenyOnItemOwnership",
|
|
||||||
"requiredId": item.ID,
|
|
||||||
"minQuantity": 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range e.Meta {
|
|
||||||
meta = append(meta, m)
|
|
||||||
json["meta"].(aid.JSON)[m["Key"].(string)] = m["Value"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Panel != "" {
|
|
||||||
json["categories"] = []string{e.Panel}
|
|
||||||
}
|
|
||||||
|
|
||||||
json["itemGrants"] = grants
|
|
||||||
json["requirements"] = requirements
|
|
||||||
json["metaInfo"] = meta
|
|
||||||
json["giftInfo"] = aid.JSON{
|
|
||||||
"bIsEnabled": true,
|
|
||||||
"forcedGiftBoxTemplateId": "",
|
|
||||||
"purchaseRequirements": purchaseRequirements,
|
|
||||||
"giftRecordIds": []any{},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateRandomStorefront() {
|
|
||||||
storefront := NewCatalog()
|
|
||||||
|
|
||||||
daily := NewStorefront("BRDailyStorefront")
|
|
||||||
weekly := NewStorefront("BRWeeklyStorefront")
|
|
||||||
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
if aid.Config.Fortnite.Season < 14 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
item := RandomItemByType("AthenaCharacter")
|
|
||||||
entry := NewCatalogEntry("athena")
|
|
||||||
entry.SetSection("Daily")
|
|
||||||
entry.SetNewDisplayAsset(item.DisplayAssetPath2)
|
|
||||||
|
|
||||||
if item.DisplayAssetPath != "" {
|
|
||||||
entry.SetDisplayAssetPath(item.DisplayAssetPath)
|
|
||||||
}
|
|
||||||
entry.SetPrice(GetPriceForRarity(item.Rarity.BackendValue, item.Type.BackendValue))
|
|
||||||
entry.AddGrant(item.Type.BackendValue + ":" + item.ID)
|
|
||||||
entry.SetTileSize("Normal")
|
|
||||||
entry.Priority = 1
|
|
||||||
|
|
||||||
if item.Backpack != nil {
|
|
||||||
entry.AddGrant("AthenaBackpack:" + item.Backpack.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if storefront.CheckIfOfferIsDuplicate(*entry) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
daily.Add(*entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 6; i++ {
|
|
||||||
item := RandomItemByNotType("AthenaCharacter")
|
|
||||||
entry := NewCatalogEntry("athena")
|
|
||||||
entry.SetSection("Daily")
|
|
||||||
|
|
||||||
if item.DisplayAssetPath2 == "" {
|
|
||||||
i--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entry.SetNewDisplayAsset(item.DisplayAssetPath2)
|
|
||||||
|
|
||||||
if item.DisplayAssetPath != "" {
|
|
||||||
entry.SetDisplayAssetPath(item.DisplayAssetPath)
|
|
||||||
}
|
|
||||||
entry.SetPrice(GetPriceForRarity(item.Rarity.BackendValue, item.Type.BackendValue))
|
|
||||||
entry.AddGrant(item.Type.BackendValue + ":" + item.ID)
|
|
||||||
entry.SetTileSize("Small")
|
|
||||||
|
|
||||||
if storefront.CheckIfOfferIsDuplicate(*entry) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
daily.Add(*entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
minimumItems := 8
|
|
||||||
if aid.Config.Fortnite.Season < 11 {
|
|
||||||
minimumItems = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
minimumSets := 4
|
|
||||||
if aid.Config.Fortnite.Season <+ 12 {
|
|
||||||
minimumSets = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
setsAdded := 0
|
|
||||||
for len(weekly.CatalogEntries) < minimumItems || setsAdded < minimumSets {
|
|
||||||
set := RandomSet()
|
|
||||||
|
|
||||||
itemsAdded := 0
|
|
||||||
itemsToAdd := []*Entry{}
|
|
||||||
for _, item := range set.Items {
|
|
||||||
entry := NewCatalogEntry("athena")
|
|
||||||
entry.SetSection("Featured")
|
|
||||||
entry.SetPanel(set.BackendName)
|
|
||||||
entry.SetNewDisplayAsset(item.DisplayAssetPath2)
|
|
||||||
|
|
||||||
if item.Type.BackendValue == "AthenaCharacter" {
|
|
||||||
entry.SetTileSize("Normal")
|
|
||||||
if aid.Config.Fortnite.Season < 14 {
|
|
||||||
itemsAdded += 1
|
|
||||||
} else {
|
|
||||||
itemsAdded += 2
|
|
||||||
}
|
|
||||||
entry.Priority = 1
|
|
||||||
} else {
|
|
||||||
entry.SetTileSize("Small")
|
|
||||||
itemsAdded += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.DisplayAssetPath != "" {
|
|
||||||
entry.SetDisplayAssetPath(item.DisplayAssetPath)
|
|
||||||
}
|
|
||||||
entry.SetPrice(GetPriceForRarity(item.Rarity.BackendValue, item.Type.BackendValue))
|
|
||||||
entry.AddGrant(item.Type.BackendValue + ":" + item.ID)
|
|
||||||
|
|
||||||
itemsToAdd = append(itemsToAdd, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
if itemsAdded % 2 != 0 {
|
|
||||||
itemsToAdd = itemsToAdd[:len(itemsToAdd)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range itemsToAdd {
|
|
||||||
if storefront.CheckIfOfferIsDuplicate(*entry) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
weekly.Add(*entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
setsAdded++
|
|
||||||
}
|
|
||||||
|
|
||||||
storefront.Add(daily)
|
|
||||||
storefront.Add(weekly)
|
|
||||||
|
|
||||||
StaticCatalog = storefront
|
|
||||||
aid.Print("(snow) generated random storefront")
|
|
||||||
}
|
}
|
|
@ -601,12 +601,12 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
|
||||||
return fmt.Errorf("invalid Body")
|
return fmt.Errorf("invalid Body")
|
||||||
}
|
}
|
||||||
|
|
||||||
offer := fortnite.StaticCatalog.GetOfferById(body.OfferID)
|
offer := fortnite.GetOfferByOfferId(body.OfferID)
|
||||||
if offer == nil {
|
if offer == nil {
|
||||||
return fmt.Errorf("offer not found")
|
return fmt.Errorf("offer not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if offer.Price != body.ExpectedTotalPrice {
|
if offer.TotalPrice != body.ExpectedTotalPrice {
|
||||||
return fmt.Errorf("invalid price")
|
return fmt.Errorf("invalid price")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,7 +629,7 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
|
||||||
vbucks.Save()
|
vbucks.Save()
|
||||||
profile0Vbucks.Save()
|
profile0Vbucks.Save()
|
||||||
|
|
||||||
if offer.ProfileType != "athena" {
|
if offer.Meta.ProfileId != "athena" {
|
||||||
return fmt.Errorf("save the world not implemeted yet")
|
return fmt.Errorf("save the world not implemeted yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,15 +637,16 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
|
||||||
purchase := p.NewPurchase(body.OfferID, body.ExpectedTotalPrice)
|
purchase := p.NewPurchase(body.OfferID, body.ExpectedTotalPrice)
|
||||||
for i := 0; i < body.PurchaseQuantity; i++ {
|
for i := 0; i < body.PurchaseQuantity; i++ {
|
||||||
for _, grant := range offer.Grants {
|
for _, grant := range offer.Grants {
|
||||||
if profile.Items.GetItemByTemplateID(grant) != nil {
|
templateId := grant.Type.BackendValue + ":" + grant.ID
|
||||||
item := profile.Items.GetItemByTemplateID(grant)
|
if profile.Items.GetItemByTemplateID(templateId) != nil {
|
||||||
|
item := profile.Items.GetItemByTemplateID(templateId)
|
||||||
item.Quantity++
|
item.Quantity++
|
||||||
go item.Save()
|
go item.Save()
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
item := p.NewItem(grant, 1)
|
item := p.NewItem(templateId, 1)
|
||||||
person.AthenaProfile.Items.AddItem(item)
|
person.AthenaProfile.Items.AddItem(item)
|
||||||
purchase.AddLoot(item)
|
purchase.AddLoot(item)
|
||||||
|
|
||||||
|
@ -653,7 +654,7 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
|
||||||
"itemType": item.TemplateID,
|
"itemType": item.TemplateID,
|
||||||
"itemGuid": item.ID,
|
"itemGuid": item.ID,
|
||||||
"quantity": item.Quantity,
|
"quantity": item.Quantity,
|
||||||
"itemProfile": offer.ProfileType,
|
"itemProfile": offer.Meta.ProfileId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -746,12 +747,12 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
|
||||||
return fmt.Errorf("invalid Body")
|
return fmt.Errorf("invalid Body")
|
||||||
}
|
}
|
||||||
|
|
||||||
offer := fortnite.StaticCatalog.GetOfferById(body.OfferId)
|
offer := fortnite.GetOfferByOfferId(body.OfferId)
|
||||||
if offer == nil {
|
if offer == nil {
|
||||||
return fmt.Errorf("offer not found")
|
return fmt.Errorf("offer not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if offer.Price != body.ExpectedTotalPrice {
|
if offer.TotalPrice != body.ExpectedTotalPrice {
|
||||||
return fmt.Errorf("invalid price")
|
return fmt.Errorf("invalid price")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,13 +763,13 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, grant := range offer.Grants {
|
for _, grant := range offer.Grants {
|
||||||
if receiverPerson.AthenaProfile.Items.GetItemByTemplateID(grant) != nil {
|
if receiverPerson.AthenaProfile.Items.GetItemByTemplateID(grant.Type.BackendValue + ":" + grant.ID) != nil {
|
||||||
return fmt.Errorf("one or more receivers has one of the items")
|
return fmt.Errorf("one or more receivers has one of the items")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
price := offer.Price * len(body.ReceiverAccountIds)
|
price := offer.TotalPrice * len(body.ReceiverAccountIds)
|
||||||
|
|
||||||
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
|
||||||
if vbucks == nil {
|
if vbucks == nil {
|
||||||
|
@ -793,8 +794,8 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
|
||||||
receiverPerson := p.Find(receiverAccountId)
|
receiverPerson := p.Find(receiverAccountId)
|
||||||
gift := p.NewGift(body.GiftWrapTemplateId, 1, person.ID, body.PersonalMessage)
|
gift := p.NewGift(body.GiftWrapTemplateId, 1, person.ID, body.PersonalMessage)
|
||||||
for _, grant := range offer.Grants {
|
for _, grant := range offer.Grants {
|
||||||
item := p.NewItem(grant, 1)
|
item := p.NewItem(grant.Type.BackendValue + ":" + grant.ID, 1)
|
||||||
item.ProfileType = offer.ProfileType
|
item.ProfileType = offer.Meta.ProfileId
|
||||||
gift.AddLoot(item)
|
gift.AddLoot(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,5 +72,6 @@ func GetSnowParties(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSnowShop(c *fiber.Ctx) error {
|
func GetSnowShop(c *fiber.Ctx) error {
|
||||||
return c.JSON(fortnite.StaticCatalog)
|
shop := fortnite.NewRandomFortniteCatalog()
|
||||||
|
return c.JSON(shop.GenerateFortniteCatalog())
|
||||||
}
|
}
|
|
@ -21,7 +21,7 @@ func MiddlewareWebsocket(c *fiber.Ctx) error {
|
||||||
protocol = "matchmaking"
|
protocol = "matchmaking"
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Locals("identifier", "ws-"+aid.RandomString(8))
|
c.Locals("identifier", "ws-"+aid.RandomString(18))
|
||||||
c.Locals("protocol", protocol)
|
c.Locals("protocol", protocol)
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
|
|
|
@ -5,15 +5,13 @@ import (
|
||||||
|
|
||||||
"github.com/ectrc/snow/aid"
|
"github.com/ectrc/snow/aid"
|
||||||
"github.com/ectrc/snow/fortnite"
|
"github.com/ectrc/snow/fortnite"
|
||||||
"github.com/ectrc/snow/person"
|
|
||||||
"github.com/ectrc/snow/storage"
|
"github.com/ectrc/snow/storage"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetStorefrontCatalog(c *fiber.Ctx) error {
|
func GetStorefrontCatalog(c *fiber.Ctx) error {
|
||||||
person := c.Locals("person").(*person.Person)
|
shop := fortnite.NewRandomFortniteCatalog()
|
||||||
|
return c.Status(200).JSON(shop.GenerateFortniteCatalog())
|
||||||
return c.Status(fiber.StatusOK).JSON(fortnite.StaticCatalog.GenerateFortniteCatalog(person))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStorefrontKeychain(c *fiber.Ctx) error {
|
func GetStorefrontKeychain(c *fiber.Ctx) error {
|
||||||
|
@ -23,5 +21,5 @@ func GetStorefrontKeychain(c *fiber.Ctx) error {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(aid.JSON{"error":err.Error()})
|
return c.Status(fiber.StatusInternalServerError).JSON(aid.JSON{"error":err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(keychain)
|
return c.Status(200).JSON(keychain)
|
||||||
}
|
}
|
|
@ -105,7 +105,7 @@ func (t *tcpServer) handle(conn net.Conn) {
|
||||||
c: &conn,
|
c: &conn,
|
||||||
buffer: make([]byte, 1024),
|
buffer: make([]byte, 1024),
|
||||||
}
|
}
|
||||||
tcpClient.jabber = socket.NewJabberSocket(tcpClient, "tcp-"+aid.RandomString(8), socket.JabberData{})
|
tcpClient.jabber = socket.NewJabberSocket(tcpClient, "tcp-"+aid.RandomString(18), socket.JabberData{})
|
||||||
socket.JabberSockets.Set(tcpClient.jabber.ID, tcpClient.jabber)
|
socket.JabberSockets.Set(tcpClient.jabber.ID, tcpClient.jabber)
|
||||||
|
|
||||||
tcpClient.loop()
|
tcpClient.loop()
|
||||||
|
|
7
main.go
7
main.go
|
@ -21,7 +21,6 @@ var configFile []byte
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
aid.LoadConfig(configFile)
|
aid.LoadConfig(configFile)
|
||||||
|
|
||||||
var device storage.Storage
|
var device storage.Storage
|
||||||
switch aid.Config.Database.Type {
|
switch aid.Config.Database.Type {
|
||||||
case "postgres":
|
case "postgres":
|
||||||
|
@ -46,7 +45,7 @@ func init() {
|
||||||
func init() {
|
func init() {
|
||||||
discord.IntialiseClient()
|
discord.IntialiseClient()
|
||||||
fortnite.PreloadCosmetics()
|
fortnite.PreloadCosmetics()
|
||||||
fortnite.GenerateRandomStorefront()
|
fortnite.NewRandomFortniteCatalog()
|
||||||
|
|
||||||
for _, username := range aid.Config.Accounts.Gods {
|
for _, username := range aid.Config.Accounts.Gods {
|
||||||
found := person.FindByDisplay(username)
|
found := person.FindByDisplay(username)
|
||||||
|
@ -192,8 +191,8 @@ func main() {
|
||||||
r.All("*", func(c *fiber.Ctx) error { return c.Status(fiber.StatusNotFound).JSON(aid.ErrorNotFound) })
|
r.All("*", func(c *fiber.Ctx) error { return c.Status(fiber.StatusNotFound).JSON(aid.ErrorNotFound) })
|
||||||
|
|
||||||
if aid.Config.Fortnite.Season <= 2 {
|
if aid.Config.Fortnite.Season <= 2 {
|
||||||
// t := handlers.NewServer()
|
t := handlers.NewServer()
|
||||||
// go t.Listen()
|
go t.Listen()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := r.Listen("0.0.0.0" + aid.Config.API.Port)
|
err := r.Listen("0.0.0.0" + aid.Config.API.Port)
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
## What's up next?
|
## What's up next?
|
||||||
|
|
||||||
- Seeded randomization for the **Item Shop** instead of a random number generator. This will ensure that even if the backend is restarted, the same random items will be in the shop during that day.
|
|
||||||
- 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.
|
- 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.
|
- 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.
|
- 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.
|
||||||
|
@ -27,10 +26,10 @@ And once battle royale is completed ...
|
||||||
- **XMPP** For interacting with friends, parties and gifting.
|
- **XMPP** For interacting with friends, parties and gifting.
|
||||||
- **Friends** On every build, this will allow for adding, removing and blocking friends.
|
- **Friends** On every build, this will allow for adding, removing and blocking friends.
|
||||||
- **Party System V2** This replaces the legacy xmpp driven party system.
|
- **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.
|
- **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.
|
- **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.
|
- **Item Refunding** Of previous shop purchases, will use a refund ticket if refunded in time.
|
||||||
- **Universal Item Shop** Works on all builds and will be updated every 24 hours.
|
|
||||||
- **Client Settings Storage** Uses amazon buckets to store client settings.
|
- **Client Settings Storage** Uses amazon buckets to store client settings.
|
||||||
- **Support A Creator 5%** Use any display name and each purchase will give them 5% of the vbucks spent.
|
- **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
|
- **Discord Bot** Very useful to control players, their inventory and their settings
|
||||||
|
|
|
@ -3,7 +3,6 @@ package socket
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
"github.com/ectrc/snow/aid"
|
"github.com/ectrc/snow/aid"
|
||||||
|
@ -26,11 +25,8 @@ var jabberHandlers = map[string]func(*Socket[JabberData], *etree.Document) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleNewJabberSocket(identifier string) {
|
func HandleNewJabberSocket(identifier string) {
|
||||||
aid.Print("new jabber handle: " + identifier)
|
|
||||||
|
|
||||||
socket, ok := JabberSockets.Get(identifier)
|
socket, ok := JabberSockets.Get(identifier)
|
||||||
if !ok {
|
if !ok {
|
||||||
aid.Print("socket not found", identifier)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer JabberSockets.Delete(socket.ID)
|
defer JabberSockets.Delete(socket.ID)
|
||||||
|
@ -38,7 +34,6 @@ func HandleNewJabberSocket(identifier string) {
|
||||||
for {
|
for {
|
||||||
_, message, failed := socket.Connection.ReadMessage()
|
_, message, failed := socket.Connection.ReadMessage()
|
||||||
if failed != nil {
|
if failed != nil {
|
||||||
aid.Print("jabber message failed", failed)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,14 +42,8 @@ func HandleNewJabberSocket(identifier string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func JabberSocketOnMessage(socket *Socket[JabberData], message []byte) {
|
func JabberSocketOnMessage(socket *Socket[JabberData], message []byte) {
|
||||||
if strings.Contains(string(message), `">`) {
|
|
||||||
message = []byte(strings.ReplaceAll(string(message), `">`, `"/>`))
|
|
||||||
}
|
|
||||||
|
|
||||||
aid.Print("jabber message", string(message))
|
|
||||||
parsed := etree.NewDocument()
|
parsed := etree.NewDocument()
|
||||||
if err := parsed.ReadFromString(string(message)); err != nil {
|
if err := parsed.ReadFromString(string(message)); err != nil {
|
||||||
aid.Print("jabber message failed to parse", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,11 +52,8 @@ func JabberSocketOnMessage(socket *Socket[JabberData], message []byte) {
|
||||||
socket.Connection.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, err.Error()))
|
socket.Connection.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aid.Print("jabber message handled")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func jabberStreamHandler(socket *Socket[JabberData], parsed *etree.Document) error {
|
func jabberStreamHandler(socket *Socket[JabberData], parsed *etree.Document) error {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user