Gifting support
This commit is contained in:
parent
85383e4865
commit
3f5adcba6c
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)`)
|
||||||
`)
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user