Rework automatic storefront!

This commit is contained in:
Eccentric 2024-02-18 00:09:02 +00:00
parent dea3770027
commit 3fcda14f1a
15 changed files with 404 additions and 517 deletions

View File

@ -1,7 +1,6 @@
package aid
import (
m "math/rand"
"os"
"os/signal"
"regexp"
@ -15,20 +14,6 @@ func WaitForExit() {
<-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 {
str := ""
for i, char := range ReverseString(strconv.Itoa(number)) {
@ -60,7 +45,6 @@ func ToHex(number int) string {
}
func Regex(str, regex string) *string {
// reg := regexp.MustCompile(`(?:CID_)(\d+|A_\d+)(?:_.+)`).FindStringSubmatch(strings.Join(split[:], "_"))
reg := regexp.MustCompile(regex).FindStringSubmatch(str)
if len(reg) > 1 {
return &reg[1]

View File

@ -48,6 +48,7 @@ type CS struct {
Everything bool
Password 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.Password = !(cfg.Section("fortnite").Key("disable_password").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
View 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
}

View File

@ -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")
}
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
var unitMap = map[string]uint64{

View File

@ -75,4 +75,8 @@ disable_password=false
; this will disable the client credentials grant type
; 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
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

View File

@ -157,7 +157,7 @@ func (c *ExternalDataClient) AddDisplayAssetToItem(displayAsset string) {
if found == nil && split[0] == "CID" {
r := aid.Regex(strings.Join(split[:], "_"), `(?:CID_)(\d+|A_\d+)(?:_.+)`)
if r != nil {
found = ItemByShallowID(*r)
found = GetItemByShallowID(*r)
}
}
@ -193,7 +193,7 @@ func PreloadCosmetics() error {
return nil
}
func ItemByShallowID(shallowID string) *FortniteItem {
func GetItemByShallowID(shallowID string) *FortniteItem {
for _, item := range External.TypedFortniteItems["AthenaCharacter"] {
if strings.Contains(item.ID, shallowID) {
return item
@ -203,43 +203,54 @@ func ItemByShallowID(shallowID string) *FortniteItem {
return nil
}
func RandomItemByType(itemType string) *FortniteItem {
items := External.TypedFortniteItemsWithDisplayAssets[itemType]
func GetRandomItemWithDisplayAsset() *FortniteItem {
items := External.FortniteItemsWithDisplayAssets
if len(items) == 0 {
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 {
allItems := []*FortniteItem{}
for key, items := range External.TypedFortniteItemsWithDisplayAssets {
if key == notItemType {
func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem {
flat := []FortniteItem{}
for t, items := range External.TypedFortniteItemsWithDisplayAssets {
if t == notType {
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 RandomSet() *FortniteSet {
sets := []*FortniteSet{}
func GetRandomSet() *FortniteSet {
sets := []FortniteSet{}
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))]
}

View File

@ -1,476 +1,350 @@
package fortnite
import (
"strings"
"math/rand"
"regexp"
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/person"
"github.com/google/uuid"
)
var (
Rarities = map[string]map[string]int{
priceLookup = map[string]map[string]int{
"EFortRarity::Legendary": {
"AthenaCharacter": 2000,
"AthenaBackpack": 1500,
"AthenaPickaxe": 1500,
"AthenaGlider": 1800,
"AthenaDance": 500,
"AthenaItemWrap": 800,
"AthenaBackpack": 1500,
"AthenaPickaxe": 1500,
"AthenaGlider": 1800,
"AthenaDance": 500,
"AthenaItemWrap": 800,
},
"EFortRarity::Epic": {
"AthenaCharacter": 1500,
"AthenaBackpack": 1200,
"AthenaPickaxe": 1200,
"AthenaGlider": 1500,
"AthenaDance": 800,
"AthenaItemWrap": 800,
"AthenaBackpack": 1200,
"AthenaPickaxe": 1200,
"AthenaGlider": 1500,
"AthenaDance": 800,
"AthenaItemWrap": 800,
},
"EFortRarity::Rare": {
"AthenaCharacter": 1200,
"AthenaBackpack": 800,
"AthenaPickaxe": 800,
"AthenaGlider": 800,
"AthenaDance": 500,
"AthenaItemWrap": 600,
"AthenaBackpack": 800,
"AthenaPickaxe": 800,
"AthenaGlider": 800,
"AthenaDance": 500,
"AthenaItemWrap": 600,
},
"EFortRarity::Uncommon": {
"AthenaCharacter": 800,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
"EFortRarity::Common": {
"AthenaCharacter": 500,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"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 {
return Rarities[rarity][backendType]
func price(rarity, type_ string) int {
return priceLookup[rarity][type_]
}
type Catalog struct {
RefreshIntervalHrs int `json:"refreshIntervalHrs"`
DailyPurchaseHrs int `json:"dailyPurchaseHrs"`
Expiration string `json:"expiration"`
Storefronts []Storefront `json:"storefronts"`
}
func dailyItems(season int) int {
var items int
func NewCatalog() *Catalog {
return &Catalog{
RefreshIntervalHrs: 24,
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
}
for s, i := range dailyItemLookup {
if season >= s {
items = i
}
}
return false
return items
}
func (c *Catalog) GetOfferById(id string) *Entry {
for _, storefront := range c.Storefronts {
for _, catalogEntry := range storefront.CatalogEntries {
if catalogEntry.ID == id {
return &catalogEntry
func weeklySets(season int) int {
var sets int
for s, i := range weeklySetLookup {
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
}
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")
}

View File

@ -601,12 +601,12 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
return fmt.Errorf("invalid Body")
}
offer := fortnite.StaticCatalog.GetOfferById(body.OfferID)
offer := fortnite.GetOfferByOfferId(body.OfferID)
if offer == nil {
return fmt.Errorf("offer not found")
}
if offer.Price != body.ExpectedTotalPrice {
if offer.TotalPrice != body.ExpectedTotalPrice {
return fmt.Errorf("invalid price")
}
@ -629,7 +629,7 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
vbucks.Save()
profile0Vbucks.Save()
if offer.ProfileType != "athena" {
if offer.Meta.ProfileId != "athena" {
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)
for i := 0; i < body.PurchaseQuantity; i++ {
for _, grant := range offer.Grants {
if profile.Items.GetItemByTemplateID(grant) != nil {
item := profile.Items.GetItemByTemplateID(grant)
templateId := grant.Type.BackendValue + ":" + grant.ID
if profile.Items.GetItemByTemplateID(templateId) != nil {
item := profile.Items.GetItemByTemplateID(templateId)
item.Quantity++
go item.Save()
continue
}
item := p.NewItem(grant, 1)
item := p.NewItem(templateId, 1)
person.AthenaProfile.Items.AddItem(item)
purchase.AddLoot(item)
@ -653,7 +654,7 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
"itemType": item.TemplateID,
"itemGuid": item.ID,
"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")
}
offer := fortnite.StaticCatalog.GetOfferById(body.OfferId)
offer := fortnite.GetOfferByOfferId(body.OfferId)
if offer == nil {
return fmt.Errorf("offer not found")
}
if offer.Price != body.ExpectedTotalPrice {
if offer.TotalPrice != body.ExpectedTotalPrice {
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 {
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")
}
}
}
price := offer.Price * len(body.ReceiverAccountIds)
price := offer.TotalPrice * len(body.ReceiverAccountIds)
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
if vbucks == nil {
@ -793,8 +794,8 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
receiverPerson := p.Find(receiverAccountId)
gift := p.NewGift(body.GiftWrapTemplateId, 1, person.ID, body.PersonalMessage)
for _, grant := range offer.Grants {
item := p.NewItem(grant, 1)
item.ProfileType = offer.ProfileType
item := p.NewItem(grant.Type.BackendValue + ":" + grant.ID, 1)
item.ProfileType = offer.Meta.ProfileId
gift.AddLoot(item)
}

View File

@ -72,5 +72,6 @@ func GetSnowParties(c *fiber.Ctx) error {
}
func GetSnowShop(c *fiber.Ctx) error {
return c.JSON(fortnite.StaticCatalog)
shop := fortnite.NewRandomFortniteCatalog()
return c.JSON(shop.GenerateFortniteCatalog())
}

View File

@ -21,7 +21,7 @@ func MiddlewareWebsocket(c *fiber.Ctx) error {
protocol = "matchmaking"
}
c.Locals("identifier", "ws-"+aid.RandomString(8))
c.Locals("identifier", "ws-"+aid.RandomString(18))
c.Locals("protocol", protocol)
return c.Next()

View File

@ -5,15 +5,13 @@ import (
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/fortnite"
"github.com/ectrc/snow/person"
"github.com/ectrc/snow/storage"
"github.com/gofiber/fiber/v2"
)
func GetStorefrontCatalog(c *fiber.Ctx) error {
person := c.Locals("person").(*person.Person)
return c.Status(fiber.StatusOK).JSON(fortnite.StaticCatalog.GenerateFortniteCatalog(person))
shop := fortnite.NewRandomFortniteCatalog()
return c.Status(200).JSON(shop.GenerateFortniteCatalog())
}
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.StatusOK).JSON(keychain)
return c.Status(200).JSON(keychain)
}

View File

@ -105,7 +105,7 @@ func (t *tcpServer) handle(conn net.Conn) {
c: &conn,
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)
tcpClient.loop()

View File

@ -21,7 +21,6 @@ var configFile []byte
func init() {
aid.LoadConfig(configFile)
var device storage.Storage
switch aid.Config.Database.Type {
case "postgres":
@ -46,7 +45,7 @@ func init() {
func init() {
discord.IntialiseClient()
fortnite.PreloadCosmetics()
fortnite.GenerateRandomStorefront()
fortnite.NewRandomFortniteCatalog()
for _, username := range aid.Config.Accounts.Gods {
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) })
if aid.Config.Fortnite.Season <= 2 {
// t := handlers.NewServer()
// go t.Listen()
t := handlers.NewServer()
go t.Listen()
}
err := r.Listen("0.0.0.0" + aid.Config.API.Port)

View File

@ -13,7 +13,6 @@
## 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.
- 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.
@ -27,10 +26,10 @@ And once battle royale is completed ...
- **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.
- **Universal Item Shop** Works on all builds and will be updated every 24 hours.
- **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.
- **Discord Bot** Very useful to control players, their inventory and their settings

View File

@ -3,7 +3,6 @@ package socket
import (
"fmt"
"reflect"
"strings"
"github.com/beevik/etree"
"github.com/ectrc/snow/aid"
@ -26,11 +25,8 @@ var jabberHandlers = map[string]func(*Socket[JabberData], *etree.Document) error
}
func HandleNewJabberSocket(identifier string) {
aid.Print("new jabber handle: " + identifier)
socket, ok := JabberSockets.Get(identifier)
if !ok {
aid.Print("socket not found", identifier)
return
}
defer JabberSockets.Delete(socket.ID)
@ -38,7 +34,6 @@ func HandleNewJabberSocket(identifier string) {
for {
_, message, failed := socket.Connection.ReadMessage()
if failed != nil {
aid.Print("jabber message failed", failed)
break
}
@ -47,14 +42,8 @@ func HandleNewJabberSocket(identifier string) {
}
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()
if err := parsed.ReadFromString(string(message)); err != nil {
aid.Print("jabber message failed to parse", err)
return
}
@ -63,11 +52,8 @@ func JabberSocketOnMessage(socket *Socket[JabberData], message []byte) {
socket.Connection.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, err.Error()))
return
}
return
}
aid.Print("jabber message handled")
}
func jabberStreamHandler(socket *Socket[JabberData], parsed *etree.Document) error {