Gifting support

This commit is contained in:
Eccentric 2024-01-31 21:40:47 +00:00
parent 85383e4865
commit 3f5adcba6c
6 changed files with 137 additions and 43 deletions

View File

@ -296,12 +296,13 @@ func (e *Entry) GenerateResponse(p *person.Person) aid.JSON {
"itemGrants": []aid.JSON{}, "itemGrants": []aid.JSON{},
"metaInfo": e.Meta, "metaInfo": e.Meta,
"meta": aid.JSON{}, "meta": aid.JSON{},
"displayAssetPath": e.DisplayAssetPath,
"title": e.Title, "title": e.Title,
"displayAssetPath": e.DisplayAssetPath,
"shortDescription": e.ShortDescription, "shortDescription": e.ShortDescription,
} }
grants := []aid.JSON{} grants := []aid.JSON{}
requirements := []aid.JSON{} requirements := []aid.JSON{}
purchaseRequirements := []aid.JSON{}
meta := []aid.JSON{} meta := []aid.JSON{}
for _, templateId := range e.Grants { for _, templateId := range e.Grants {
@ -316,6 +317,12 @@ func (e *Entry) GenerateResponse(p *person.Person) aid.JSON {
"requiredId": item.ID, "requiredId": item.ID,
"minQuantity": 1, "minQuantity": 1,
}) })
purchaseRequirements = append(purchaseRequirements, aid.JSON{
"requirementType": "DenyOnItemOwnership",
"requiredId": item.ID,
"minQuantity": 1,
})
} }
} }
@ -331,6 +338,13 @@ func (e *Entry) GenerateResponse(p *person.Person) aid.JSON {
json["itemGrants"] = grants json["itemGrants"] = grants
json["requirements"] = requirements json["requirements"] = requirements
json["metaInfo"] = meta json["metaInfo"] = meta
json["giftInfo"] = aid.JSON{
"bIsEnabled": true,
"forcedGiftBoxTemplateId": "",
"purchaseRequirements": purchaseRequirements,
"giftRecordIds": []any{},
}
return json return json
} }

View File

@ -24,6 +24,8 @@ var (
"SetCosmeticLockerSlot": clientSetCosmeticLockerSlotAction, "SetCosmeticLockerSlot": clientSetCosmeticLockerSlotAction,
"SetCosmeticLockerBanner": clientSetCosmeticLockerBannerAction, "SetCosmeticLockerBanner": clientSetCosmeticLockerBannerAction,
"PurchaseCatalogEntry": clientPurchaseCatalogEntryAction, "PurchaseCatalogEntry": clientPurchaseCatalogEntryAction,
"GiftCatalogEntry": clientGiftCatalogEntryAction,
"RemoveGiftBox": clientRemoveGiftBoxAction,
} }
) )
@ -70,8 +72,7 @@ func PostClientProfileAction(c *fiber.Ctx) error {
profile.Diff(profileSnapshot) profile.Diff(profileSnapshot)
} }
known, _ := strconv.Atoi(c.Query("rvn")) revision, _ := strconv.Atoi(c.Query("rvn"))
revision := known
if revision == -1 { if revision == -1 {
revision = profile.Revision revision = profile.Revision
} }
@ -81,8 +82,6 @@ func PostClientProfileAction(c *fiber.Ctx) error {
delete(profileSnapshots, profile.Type) delete(profileSnapshots, profile.Type)
multiUpdate := []aid.JSON{} multiUpdate := []aid.JSON{}
if known != -1 {
for key := range profileSnapshots { for key := range profileSnapshots {
profile := person.GetProfileFromType(key) profile := person.GetProfileFromType(key)
if profile == nil { if profile == nil {
@ -105,7 +104,6 @@ func PostClientProfileAction(c *fiber.Ctx) error {
profile.ClearProfileChanges() profile.ClearProfileChanges()
go profile.Save() go profile.Save()
} }
}
return c.Status(200).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"profileId": c.Query("profileId"), "profileId": c.Query("profileId"),
@ -121,11 +119,7 @@ func PostClientProfileAction(c *fiber.Ctx) error {
} }
func clientQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error { func clientQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
known, _ := strconv.Atoi(c.Query("rvn")) profile.CreateFullProfileUpdateChange()
if known == -1 {
profile.CreateFullProfileUpdateChange()
}
return nil return nil
} }
@ -134,8 +128,7 @@ func clientMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile
ItemIds []string `json:"itemIds"` ItemIds []string `json:"itemIds"`
} }
err := c.BodyParser(&body) if err := c.BodyParser(&body); err != nil {
if err != nil {
return fmt.Errorf("invalid Body") return fmt.Errorf("invalid Body")
} }
@ -159,8 +152,7 @@ func clientEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person,
IndexWithinSlot int `json:"indexWithinSlot"` IndexWithinSlot int `json:"indexWithinSlot"`
} }
err := c.BodyParser(&body) if err := c.BodyParser(&body); err != nil {
if err != nil {
return fmt.Errorf("invalid Body") return fmt.Errorf("invalid Body")
} }
@ -207,8 +199,7 @@ func clientSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *
HomebaseBannerIconID string `json:"homebaseBannerIconId" binding:"required"` HomebaseBannerIconID string `json:"homebaseBannerIconId" binding:"required"`
} }
err := c.BodyParser(&body) if err := c.BodyParser(&body); err != nil {
if err != nil {
return fmt.Errorf("invalid Body") return fmt.Errorf("invalid Body")
} }
@ -248,8 +239,7 @@ func clientSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, prof
Favorite []bool `json:"itemFavStatus" binding:"required"` Favorite []bool `json:"itemFavStatus" binding:"required"`
} }
err := c.BodyParser(&body) if err := c.BodyParser(&body); err != nil {
if err != nil {
return fmt.Errorf("invalid Body") return fmt.Errorf("invalid Body")
} }
@ -275,8 +265,7 @@ func clientSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *
VariantUpdates []aid.JSON `json:"variantUpdates" binding:"required"` // variant updates VariantUpdates []aid.JSON `json:"variantUpdates" binding:"required"` // variant updates
} }
err := c.BodyParser(&body) if err := c.BodyParser(&body); err != nil {
if err != nil {
return fmt.Errorf("invalid Body") return fmt.Errorf("invalid Body")
} }
@ -342,8 +331,7 @@ func clientSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile
BannerIconTemplateName string `json:"bannerIconTemplateName" binding:"required"` // template id BannerIconTemplateName string `json:"bannerIconTemplateName" binding:"required"` // template id
} }
err := c.BodyParser(&body) if err := c.BodyParser(&body); err != nil {
if err != nil {
return fmt.Errorf("invalid Body") return fmt.Errorf("invalid Body")
} }
@ -372,14 +360,13 @@ func clientSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile
} }
func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error { func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct{ var body struct {
OfferID string `json:"offerId" binding:"required"` OfferID string `json:"offerId" binding:"required"`
PurchaseQuantity int `json:"purchaseQuantity" binding:"required"` PurchaseQuantity int `json:"purchaseQuantity" binding:"required"`
ExpectedTotalPrice int `json:"expectedTotalPrice" binding:"required"` ExpectedTotalPrice int `json:"expectedTotalPrice" binding:"required"`
} }
err := c.BodyParser(&body) if err := c.BodyParser(&body); err != nil {
if err != nil {
return fmt.Errorf("invalid Body") return fmt.Errorf("invalid Body")
} }
@ -409,7 +396,7 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
vbucks.Quantity -= body.ExpectedTotalPrice vbucks.Quantity -= body.ExpectedTotalPrice
go func() { go func() {
profile0Vbucks.Quantity = vbucks.Quantity // for season 2 and lower profile0Vbucks.Quantity = vbucks.Quantity
vbucks.Save() vbucks.Save()
profile0Vbucks.Save() profile0Vbucks.Save()
}() }()
@ -451,3 +438,95 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
return nil return nil
} }
func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
Currency string `json:"currency" binding:"required"`
CurrencySubType string `json:"currencySubType" binding:"required"`
ExpectedTotalPrice int `json:"expectedTotalPrice" binding:"required"`
GameContext string `json:"gameContext" binding:"required"`
GiftWrapTemplateId string `json:"giftWrapTemplateId" binding:"required"`
PersonalMessage string `json:"personalMessage" binding:"required"`
ReceiverAccountIds []string `json:"receiverAccountIds" binding:"required"`
OfferId string `json:"offerId" binding:"required"`
}
if err := c.BodyParser(&body); err != nil {
return fmt.Errorf("invalid Body")
}
offer := fortnite.StaticCatalog.GetOfferById(body.OfferId)
if offer == nil {
return fmt.Errorf("offer not found")
}
if offer.Price != body.ExpectedTotalPrice {
return fmt.Errorf("invalid price")
}
price := offer.Price * len(body.ReceiverAccountIds)
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
if vbucks == nil {
return fmt.Errorf("vbucks not found")
}
profile0Vbucks := person.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
if profile0Vbucks == nil {
return fmt.Errorf("profile0vbucks not found")
}
if vbucks.Quantity < price {
return fmt.Errorf("not enough vbucks")
}
vbucks.Quantity -= price
go func() {
profile0Vbucks.Quantity = price
vbucks.Save()
profile0Vbucks.Save()
}()
for _, receiverAccountId := range body.ReceiverAccountIds {
receiverPerson := p.Find(receiverAccountId)
if receiverPerson == nil {
continue
}
gift := p.NewGift(body.GiftWrapTemplateId, 1, person.ID, body.PersonalMessage)
for _, grant := range offer.Grants {
item := p.NewItem(grant, 1)
item.ProfileType = offer.ProfileType
gift.AddLoot(item)
}
receiverPerson.CommonCoreProfile.Gifts.AddGift(gift)
receiverPerson.CommonCoreProfile.Save()
}
return nil
}
func clientRemoveGiftBoxAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
GiftBoxItemId string `json:"giftBoxItemId" binding:"required"`
}
if err := c.BodyParser(&body); err != nil {
return fmt.Errorf("invalid Body")
}
gift := profile.Gifts.GetGift(body.GiftBoxItemId)
if gift == nil {
return fmt.Errorf("gift not found")
}
for _, item := range gift.Loot {
person.GetProfileFromType(item.ProfileType).Items.AddItem(item).Save()
}
profile.Gifts.DeleteGift(gift.ID)
return nil
}

View File

@ -38,11 +38,11 @@ func GetCloudStorageFiles(c *fiber.Ctx) error {
}) })
} }
return c.Status(fiber.StatusOK).JSON(result) return c.Status(200).JSON(result)
} }
func GetCloudStorageConfig(c *fiber.Ctx) error { func GetCloudStorageConfig(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"enumerateFilesPath": "/api/cloudstorage/system", "enumerateFilesPath": "/api/cloudstorage/system",
"enableMigration": true, "enableMigration": true,
"enableWrites": true, "enableWrites": true,
@ -55,15 +55,17 @@ func GetCloudStorageConfig(c *fiber.Ctx) error {
} }
func GetCloudStorageFile(c *fiber.Ctx) error { func GetCloudStorageFile(c *fiber.Ctx) error {
c.Set("Content-Type", "application/octet-stream")
switch c.Params("fileName") { switch c.Params("fileName") {
case "DefaultEngine.ini": case "DefaultEngine.ini":
c.Set("Content-Type", "application/octet-stream") return c.Status(200).Send(storage.GetDefaultEngine())
c.Status(fiber.StatusOK) case "DefaultGame.ini":
c.Send(storage.GetDefaultEngine()) return c.Status(200).Send(storage.GetDefaultGame())
return nil case "DefaultRuntimeOptions.ini":
return c.Status(200).Send(storage.GetDefaultRuntime())
} }
return c.Status(400).JSON(aid.ErrorBadRequest) return c.Status(404).JSON(aid.ErrorBadRequest("File not found"))
} }
func GetUserStorageFiles(c *fiber.Ctx) error { func GetUserStorageFiles(c *fiber.Ctx) error {

View File

@ -2,6 +2,7 @@ package person
import ( import (
"fmt" "fmt"
"time"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage" "github.com/ectrc/snow/storage"
@ -33,7 +34,7 @@ func (r *Relationship) ToDatabase() *storage.DB_Relationship {
func (r *Relationship) GenerateFortniteFriendEntry(t RelationshipGenerateType) aid.JSON { func (r *Relationship) GenerateFortniteFriendEntry(t RelationshipGenerateType) aid.JSON {
result := aid.JSON{ result := aid.JSON{
"status": r.Status, "status": r.Status,
"created": "0000-00-00T00:00:00.000Z", "created": time.Now().Add(-time.Hour * 24 * 3).Format(time.RFC3339),
"favorite": false, "favorite": false,
} }
@ -46,8 +47,6 @@ func (r *Relationship) GenerateFortniteFriendEntry(t RelationshipGenerateType) a
result["accountId"] = r.From.ID result["accountId"] = r.From.ID
} }
aid.PrintJSON(result)
return result return result
} }

View File

@ -13,9 +13,10 @@ Performance first, universal Fortnite private server backend written in Go.
## What's next? ## What's next?
- Gifting, Matchmaker and Battle Pass support. - More profile actions like `RefundMtxPurchase` and `CopyCosmeticLoadout`.
- Interact with external Services like Amazon S3 Buckets to save player data externally. - Integrating matchmaking with a hoster to smartly put players into games and know when servers become available.
- Refactor the XMPP solution to use [melium/xmpp](https://github.com/mellium/xmpp) - Interact with external services like Amazon S3 or Cloudflare R2 to save player data externally.
- Refactor the XMPP solution to use [melium/xmpp](https://github.com/mellium/xmpp).
## Version Support ## Version Support

View File

@ -53,7 +53,7 @@ bShouldCheckIfPlatformAllowed=false`)
} }
func GetDefaultRuntime() []byte { func GetDefaultRuntime() []byte {
return []byte(` return []byte(`
[/Script/FortniteGame.FortRuntimeOptions] [/Script/FortniteGame.FortRuntimeOptions]
bEnableGlobalChat=true bEnableGlobalChat=true
bDisableGifting=false bDisableGifting=false
@ -61,6 +61,5 @@ bDisableGiftingPC=false
bDisableGiftingPS4=false bDisableGiftingPS4=false
bDisableGiftingXB=false bDisableGiftingXB=false
!ExperimentalCohortPercent=ClearArray !ExperimentalCohortPercent=ClearArray
+ExperimentalCohortPercent=(CohortPercent=100,ExperimentNum=20) +ExperimentalCohortPercent=(CohortPercent=100,ExperimentNum=20)`)
`)
} }