Add vbucks in store! (not real)

This commit is contained in:
Eccentric 2024-02-19 04:07:40 +00:00
parent 590917adbb
commit 749643be88
10 changed files with 420 additions and 140 deletions

View File

@ -49,6 +49,7 @@ type CS struct {
Password bool
DisableClientCredentials bool
ShopSeed int
EnableVBucks bool
}
}
@ -175,4 +176,5 @@ func LoadConfig(file []byte) {
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)
Config.Fortnite.EnableVBucks = cfg.Section("fortnite").Key("enable_vbucks").MustBool(false)
}

View File

@ -31,3 +31,15 @@ func FiberCors() fiber.Handler {
AllowHeaders: "Origin, Content-Type, Accept, Authorization, X-Requested-With",
})
}
// https://github.com/gofiber/fiber/issues/510
func FiberGetQueries(c *fiber.Ctx, queryKeys ...string) map[string][]string {
argsMaps := make(map[string][]string)
for _, keys := range queryKeys {
param := c.Request().URI().QueryArgs().PeekMulti(keys)
for _, value := range param {
argsMaps[keys] = append(argsMaps[keys], string(value))
}
}
return argsMaps
}

View File

@ -80,3 +80,7 @@ disable_client_credentials=false
; 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
; this will enable vbucks in the store tab
; at this time it is not possible to buy vbucks
; this is only for testing purposes
enable_vbucks=true

View File

@ -13,10 +13,10 @@ import (
)
var (
External *ExternalDataClient
DataClient *dataClient
)
type ExternalDataClient struct {
type dataClient struct {
h *http.Client
FortniteSets map[string]*FortniteSet `json:"sets"`
FortniteItems map[string]*FortniteItem `json:"items"`
@ -25,10 +25,14 @@ type ExternalDataClient struct {
TypedFortniteItems map[string][]*FortniteItem `json:"-"`
TypedFortniteItemsWithDisplayAssets map[string][]*FortniteItem `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:"-"`
}
func NewExternalDataClient() *ExternalDataClient {
return &ExternalDataClient{
func NewDataClient() *dataClient {
return &dataClient{
h: &http.Client{},
FortniteItems: make(map[string]*FortniteItem),
FortniteSets: make(map[string]*FortniteSet),
@ -37,10 +41,76 @@ func NewExternalDataClient() *ExternalDataClient {
TypedFortniteItems: make(map[string][]*FortniteItem),
TypedFortniteItemsWithDisplayAssets: make(map[string][]*FortniteItem),
SnowVariantTokens: make(map[string]*FortniteVariantToken),
StorefrontDailyItemCountLookup: []struct{Season int;Items int}{
{2, 4},
{4, 6},
{13, 10},
},
StorefrontWeeklySetCountLookup: []struct{Season int;Sets int}{
{2, 2},
{4, 3},
{13, 5},
},
StorefrontCosmeticOfferPriceLookup: map[string]map[string]int{
"EFortRarity::Legendary": {
"AthenaCharacter": 2000,
"AthenaBackpack": 1500,
"AthenaPickaxe": 1500,
"AthenaGlider": 1800,
"AthenaDance": 500,
"AthenaItemWrap": 800,
},
"EFortRarity::Epic": {
"AthenaCharacter": 1500,
"AthenaBackpack": 1200,
"AthenaPickaxe": 1200,
"AthenaGlider": 1500,
"AthenaDance": 800,
"AthenaItemWrap": 800,
},
"EFortRarity::Rare": {
"AthenaCharacter": 1200,
"AthenaBackpack": 800,
"AthenaPickaxe": 800,
"AthenaGlider": 800,
"AthenaDance": 500,
"AthenaItemWrap": 600,
},
"EFortRarity::Uncommon": {
"AthenaCharacter": 800,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
"EFortRarity::Common": {
"AthenaCharacter": 500,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
},
StorefrontCurrencyOfferPriceLookup: map[string]map[int]int{
"USD": {
1000: 999,
2800: 2499,
5000: 3999,
13500: 9999,
},
"GBP": {
1000: 799,
2800: 1999,
5000: 3499,
13500: 7999,
},
},
}
}
func (c *ExternalDataClient) LoadExternalData() {
func (c *dataClient) LoadExternalData() {
req, err := http.NewRequest("GET", "https://fortnite-api.com/v2/cosmetics/br", nil)
if err != nil {
return
@ -113,7 +183,7 @@ func (c *ExternalDataClient) LoadExternalData() {
}
}
func (c *ExternalDataClient) LoadItem(item *FortniteItem) {
func (c *dataClient) LoadItem(item *FortniteItem) {
if item.Introduction.BackendValue > aid.Config.Fortnite.Season || item.Introduction.BackendValue == 0 {
return
}
@ -151,7 +221,7 @@ func (c *ExternalDataClient) LoadItem(item *FortniteItem) {
c.FortniteItemsWithFeaturedImage = append(c.FortniteItemsWithFeaturedImage, item)
}
func (c *ExternalDataClient) AddBackpackToItem(backpack *FortniteItem) {
func (c *dataClient) AddBackpackToItem(backpack *FortniteItem) {
if backpack.ItemPreviewHeroPath == "" {
return
}
@ -165,7 +235,7 @@ func (c *ExternalDataClient) AddBackpackToItem(backpack *FortniteItem) {
character.Backpack = backpack
}
func (c *ExternalDataClient) AddDisplayAssetToItem(displayAsset string) {
func (c *dataClient) AddDisplayAssetToItem(displayAsset string) {
split := strings.Split(displayAsset, "_")[1:]
found := c.FortniteItems[strings.Join(split[:], "_")]
@ -185,7 +255,7 @@ func (c *ExternalDataClient) AddDisplayAssetToItem(displayAsset string) {
c.TypedFortniteItemsWithDisplayAssets[found.Type.BackendValue] = append(c.TypedFortniteItemsWithDisplayAssets[found.Type.BackendValue], found)
}
func (c *ExternalDataClient) AddNumericStylesToItem(item *FortniteItem) {
func (c *dataClient) AddNumericStylesToItem(item *FortniteItem) {
ownedStyles := []FortniteVariantChannel{}
for i := 0; i < 100; i++ {
ownedStyles = append(ownedStyles, FortniteVariantChannel{
@ -200,16 +270,46 @@ func (c *ExternalDataClient) AddNumericStylesToItem(item *FortniteItem) {
})
}
func PreloadCosmetics() error {
External = NewExternalDataClient()
External.LoadExternalData()
func (c *dataClient) GetStorefrontDailyItemCount(season int) int {
currentValue := 4
for _, item := range c.StorefrontDailyItemCountLookup {
if item.Season > season {
continue
}
currentValue = item.Items
}
return currentValue
}
aid.Print("(snow) " + fmt.Sprint(len(External.FortniteItems)) + " cosmetics loaded from fortnite-api.com")
func (c *dataClient) GetStorefrontWeeklySetCount(season int) int {
currentValue := 2
for _, item := range c.StorefrontWeeklySetCountLookup {
if item.Season > season {
continue
}
currentValue = item.Sets
}
return currentValue
}
func (c *dataClient) GetStorefrontCosmeticOfferPrice(rarity string, type_ string) int {
return c.StorefrontCosmeticOfferPriceLookup[rarity][type_]
}
func (c *dataClient) GetStorefrontCurrencyOfferPrice(currency string, amount int) int {
return c.StorefrontCurrencyOfferPriceLookup[currency][amount]
}
func PreloadCosmetics() error {
DataClient = NewDataClient()
DataClient.LoadExternalData()
aid.Print("(snow) " + fmt.Sprint(len(DataClient.FortniteItems)) + " cosmetics loaded from fortnite-api.com")
return nil
}
func GetItemByShallowID(shallowID string) *FortniteItem {
for _, item := range External.TypedFortniteItems["AthenaCharacter"] {
for _, item := range DataClient.TypedFortniteItems["AthenaCharacter"] {
if strings.Contains(item.ID, shallowID) {
return item
}
@ -219,7 +319,7 @@ func GetItemByShallowID(shallowID string) *FortniteItem {
}
func GetRandomItemWithDisplayAsset() *FortniteItem {
items := External.FortniteItemsWithDisplayAssets
items := DataClient.FortniteItemsWithDisplayAssets
if len(items) == 0 {
return nil
}
@ -239,7 +339,7 @@ func GetRandomItemWithDisplayAsset() *FortniteItem {
func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem {
flat := []FortniteItem{}
for t, items := range External.TypedFortniteItemsWithDisplayAssets {
for t, items := range DataClient.TypedFortniteItemsWithDisplayAssets {
if t == notType {
continue
}
@ -258,7 +358,10 @@ func GetRandomItemWithDisplayAssetOfNotType(notType string) *FortniteItem {
func GetRandomSet() *FortniteSet {
sets := []FortniteSet{}
for _, set := range External.FortniteSets {
for _, set := range DataClient.FortniteSets {
if set.BackendName == "" {
continue
}
sets = append(sets, *set)
}

View File

@ -31,7 +31,7 @@ func NewFortnitePerson(displayName string, everything bool) *p.Person {
func GiveEverything(person *p.Person) {
items := make([]storage.DB_Item, 0)
for _, item := range External.FortniteItems {
for _, item := range DataClient.FortniteItems {
if strings.Contains(strings.ToLower(item.ID), "random") {
continue
}

View File

@ -1,110 +1,177 @@
package fortnite
import (
"fmt"
"math/rand"
"regexp"
"time"
"github.com/ectrc/snow/aid"
"github.com/google/uuid"
)
var (
priceLookup = map[string]map[string]int{
"EFortRarity::Legendary": {
"AthenaCharacter": 2000,
"AthenaBackpack": 1500,
"AthenaPickaxe": 1500,
"AthenaGlider": 1800,
"AthenaDance": 500,
"AthenaItemWrap": 800,
},
"EFortRarity::Epic": {
"AthenaCharacter": 1500,
"AthenaBackpack": 1200,
"AthenaPickaxe": 1200,
"AthenaGlider": 1500,
"AthenaDance": 800,
"AthenaItemWrap": 800,
},
"EFortRarity::Rare": {
"AthenaCharacter": 1200,
"AthenaBackpack": 800,
"AthenaPickaxe": 800,
"AthenaGlider": 800,
"AthenaDance": 500,
"AthenaItemWrap": 600,
},
"EFortRarity::Uncommon": {
"AthenaCharacter": 800,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
"EFortRarity::Common": {
"AthenaCharacter": 500,
"AthenaBackpack": 200,
"AthenaPickaxe": 500,
"AthenaGlider": 500,
"AthenaDance": 200,
"AthenaItemWrap": 300,
},
}
dailyItemLookup = []struct {
Season int
Items int
}{
{2, 4},
{4, 6},
{13, 10},
type FortniteCatalogCurrencyOffer struct {
ID string
DevName string
Price struct {
OriginalOffer int
ExtraBonus int
}
weeklySetLookup = []struct {
Season int
Sets int
}{
{2, 2},
{4, 3},
{11, 4},
{13, 3},
Meta struct {
IconSize string
CurrencyAnalyticsName string
BannerOverride string
}
)
func price(rarity, type_ string) int {
return priceLookup[rarity][type_]
Title string
Description string
LongDescription string
Priority int
}
func dailyItems(season int) int {
currentValue := 4
for _, item := range dailyItemLookup {
if item.Season > season {
continue
func NewFortniteCatalogCurrencyOffer(original, bonus int) *FortniteCatalogCurrencyOffer {
return &FortniteCatalogCurrencyOffer{
ID: "v2:/"+aid.RandomString(32),
Price: struct {
OriginalOffer int
ExtraBonus int
}{original, bonus},
}
currentValue = item.Items
}
return currentValue
}
func weeklySets(season int) int {
currentValue := 2
for _, set := range weeklySetLookup {
if set.Season > season {
continue
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{},
}
currentValue = set.Sets
}
return currentValue
}
type FortniteCatalogSectionOffer struct {
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
@ -128,11 +195,11 @@ type FortniteCatalogSectionOffer struct {
}
}
func NewFortniteCatalogSectionOffer() *FortniteCatalogSectionOffer {
return &FortniteCatalogSectionOffer{}
func NewFortniteCatalogSectionOffer() *FortniteCatalogCosmeticOffer {
return &FortniteCatalogCosmeticOffer{}
}
func (f *FortniteCatalogSectionOffer) GenerateID() {
func (f *FortniteCatalogCosmeticOffer) GenerateID() {
for _, item := range f.Grants {
f.ID += item.Type.BackendValue + ":" + item.ID + ","
}
@ -140,18 +207,18 @@ func (f *FortniteCatalogSectionOffer) GenerateID() {
f.ID = "v2:/" + aid.Hash([]byte(f.ID))
}
func (f *FortniteCatalogSectionOffer) GenerateTotalPrice() {
func (f *FortniteCatalogCosmeticOffer) GenerateTotalPrice() {
if !f.BundleInfo.IsBundle {
f.TotalPrice = price(f.Grants[0].Rarity.BackendValue, f.Grants[0].Type.BackendValue)
f.TotalPrice = DataClient.GetStorefrontCosmeticOfferPrice(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)
f.TotalPrice += DataClient.GetStorefrontCosmeticOfferPrice(item.Rarity.BackendValue, item.Rarity.BackendValue)
}
}
func (f *FortniteCatalogSectionOffer) GenerateFortniteCatalogSectionOffer() aid.JSON {
func (f *FortniteCatalogCosmeticOffer) GenerateFortniteCatalogCosmeticOfferResponse() aid.JSON {
f.GenerateTotalPrice()
itemGrantResponse := []aid.JSON{}
@ -234,7 +301,7 @@ func (f *FortniteCatalogSectionOffer) GenerateFortniteCatalogSectionOffer() aid.
type FortniteCatalogSection struct {
Name string
Offers []*FortniteCatalogSectionOffer
Offers []*FortniteCatalogCosmeticOffer
}
func NewFortniteCatalogSection(name string) *FortniteCatalogSection {
@ -243,10 +310,10 @@ func NewFortniteCatalogSection(name string) *FortniteCatalogSection {
}
}
func (f *FortniteCatalogSection) GenerateFortniteCatalogSection() aid.JSON {
func (f *FortniteCatalogSection) GenerateFortniteCatalogSectionResponse() aid.JSON {
catalogEntiresResponse := []aid.JSON{}
for _, offer := range f.Offers {
catalogEntiresResponse = append(catalogEntiresResponse, offer.GenerateFortniteCatalogSectionOffer())
catalogEntiresResponse = append(catalogEntiresResponse, offer.GenerateFortniteCatalogCosmeticOfferResponse())
}
return aid.JSON{
@ -255,12 +322,12 @@ func (f *FortniteCatalogSection) GenerateFortniteCatalogSection() aid.JSON {
}
}
func (f *FortniteCatalogSection) GetGroupedOffers() map[string][]*FortniteCatalogSectionOffer {
groupedOffers := map[string][]*FortniteCatalogSectionOffer{}
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] = []*FortniteCatalogSectionOffer{}
groupedOffers[offer.Meta.Category] = []*FortniteCatalogCosmeticOffer{}
}
groupedOffers[offer.Meta.Category] = append(groupedOffers[offer.Meta.Category], offer)
@ -271,18 +338,33 @@ func (f *FortniteCatalogSection) GetGroupedOffers() map[string][]*FortniteCatalo
type FortniteCatalog struct {
Sections []*FortniteCatalogSection
MoneyOffers []*FortniteCatalogCurrencyOffer
}
func NewFortniteCatalog() *FortniteCatalog {
return &FortniteCatalog{}
return &FortniteCatalog{
Sections: []*FortniteCatalogSection{},
MoneyOffers: []*FortniteCatalogCurrencyOffer{},
}
}
func (f *FortniteCatalog) GenerateFortniteCatalog() aid.JSON {
func (f *FortniteCatalog) GenerateFortniteCatalogResponse() aid.JSON {
catalogSectionsResponse := []aid.JSON{}
for _, section := range f.Sections {
catalogSectionsResponse = append(catalogSectionsResponse, section.GenerateFortniteCatalogSection())
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,
})
return aid.JSON{
"storefronts": catalogSectionsResponse,
"refreshIntervalHrs": 24,
@ -291,27 +373,49 @@ func (f *FortniteCatalog) GenerateFortniteCatalog() aid.JSON {
}
}
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 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)
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.Sections = append(catalog.Sections, daily)
weekly := NewFortniteCatalogSection("BRWeeklyStorefront")
for len(weekly.GetGroupedOffers()) < weeklySets(aid.Config.Fortnite.Season) {
for len(weekly.GetGroupedOffers()) < DataClient.GetStorefrontWeeklySetCount(aid.Config.Fortnite.Season) {
set := GetRandomSet()
for _, item := range set.Items {
if item.DisplayAssetPath == "" || item.DisplayAssetPath2 == "" {
continue
}
entry := newEntryFromFortniteItem(item, true)
entry := newCosmeticOfferFromFortniteitem(item, true)
entry.Meta.Category = set.BackendName
entry.Meta.SectionId = "Featured"
weekly.Offers = append(weekly.Offers, entry)
@ -319,10 +423,39 @@ func NewRandomFortniteCatalog() *FortniteCatalog {
}
catalog.Sections = append(catalog.Sections, weekly)
if aid.Config.Fortnite.EnableVBucks {
smallCurrencyOffer := newCurrencyOfferFromName("Small Currency Pack", 1000, 0)
smallCurrencyOffer.Meta.IconSize = "XSmall"
smallCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack1000"
smallCurrencyOffer.Priority = -len(catalog.MoneyOffers)
catalog.MoneyOffers = append(catalog.MoneyOffers, smallCurrencyOffer)
mediumCurrencyOffer := newCurrencyOfferFromName("Medium Currency Pack", 2000, 800)
mediumCurrencyOffer.Meta.IconSize = "Small"
mediumCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack2800"
mediumCurrencyOffer.Meta.BannerOverride = "12PercentExtra"
mediumCurrencyOffer.Priority = -len(catalog.MoneyOffers)
catalog.MoneyOffers = append(catalog.MoneyOffers, mediumCurrencyOffer)
intermediateCurrencyOffer := newCurrencyOfferFromName("Intermediate Currency Pack", 4000, 1000)
intermediateCurrencyOffer.Meta.IconSize = "Medium"
intermediateCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack5000"
intermediateCurrencyOffer.Meta.BannerOverride = "25PercentExtra"
intermediateCurrencyOffer.Priority = -len(catalog.MoneyOffers)
catalog.MoneyOffers = append(catalog.MoneyOffers, intermediateCurrencyOffer)
jumboCurrencyOffer := newCurrencyOfferFromName("Jumbo Currency Pack", 10000, 3500)
jumboCurrencyOffer.Meta.IconSize = "XLarge"
jumboCurrencyOffer.Meta.CurrencyAnalyticsName = "MtxPack13500"
jumboCurrencyOffer.Meta.BannerOverride = "35PercentExtra"
jumboCurrencyOffer.Priority = -len(catalog.MoneyOffers)
catalog.MoneyOffers = append(catalog.MoneyOffers, jumboCurrencyOffer)
}
return catalog
}
func newEntryFromFortniteItem(fortniteItem *FortniteItem, addAssets bool) *FortniteCatalogSectionOffer {
func newCosmeticOfferFromFortniteitem(fortniteItem *FortniteItem, addAssets bool) *FortniteCatalogCosmeticOffer {
displayAsset := regexp.MustCompile(`[^/]+$`).FindString(fortniteItem.DisplayAssetPath)
entry := NewFortniteCatalogSectionOffer()
@ -345,16 +478,15 @@ func newEntryFromFortniteItem(fortniteItem *FortniteItem, addAssets bool) *Fortn
return entry
}
func GetOfferByOfferId(id string) *FortniteCatalogSectionOffer {
catalog := NewRandomFortniteCatalog()
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™."
for _, section := range catalog.Sections {
for _, offer := range section.Offers {
if offer.ID == id {
return offer
}
}
}
return nil
}

View File

@ -16,7 +16,7 @@ func MiddlewareOnlyDebug(c *fiber.Ctx) error {
}
func GetSnowPreloadedCosmetics(c *fiber.Ctx) error {
return c.JSON(fortnite.External)
return c.JSON(fortnite.DataClient)
}
func GetSnowCachedPlayers(c *fiber.Ctx) error {
@ -43,7 +43,7 @@ func GetSnowParties(c *fiber.Ctx) error {
func GetSnowShop(c *fiber.Ctx) error {
shop := fortnite.NewRandomFortniteCatalog()
return c.JSON(shop.GenerateFortniteCatalog())
return c.JSON(shop.GenerateFortniteCatalogResponse())
}
//

View File

@ -1,6 +1,8 @@
package handlers
import (
"strings"
"github.com/goccy/go-json"
"github.com/ectrc/snow/aid"
@ -11,7 +13,7 @@ import (
func GetStorefrontCatalog(c *fiber.Ctx) error {
shop := fortnite.NewRandomFortniteCatalog()
return c.Status(200).JSON(shop.GenerateFortniteCatalog())
return c.Status(200).JSON(shop.GenerateFortniteCatalogResponse())
}
func GetStorefrontKeychain(c *fiber.Ctx) error {
@ -23,3 +25,25 @@ func GetStorefrontKeychain(c *fiber.Ctx) error {
return c.Status(200).JSON(keychain)
}
func GetStorefrontCatalogBulkOffers(c *fiber.Ctx) error {
shop := fortnite.NewRandomFortniteCatalog()
appStoreIdBytes := c.Request().URI().QueryArgs().PeekMulti("id")
appStoreIds := make([]string, len(appStoreIdBytes))
for i, id := range appStoreIdBytes {
appStoreIds[i] = string(id)
}
response := aid.JSON{}
for _, id := range appStoreIds {
offer := shop.FindCurrencyOfferById(strings.ReplaceAll(id, "app-", ""))
if offer == nil {
continue
}
response[id] = offer.GenerateFortniteCatalogBulkOfferResponse()
}
return c.Status(200).JSON(response)
}

View File

@ -112,6 +112,7 @@ func main() {
storefront.Use(aid.FiberLimiter(4))
storefront.Get("/catalog", handlers.GetStorefrontCatalog)
storefront.Get("/keychain", handlers.GetStorefrontKeychain)
r.Get("/catalog/api/shared/bulk/offers", handlers.GetStorefrontCatalogBulkOffers)
matchmaking := fortnite.Group("/matchmaking")
matchmaking.Get("/session/findPlayer/:accountId", handlers.GetMatchmakingSession)

View File

@ -30,7 +30,9 @@ And once battle royale is completed ...
- **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** By 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