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{},
|
||||
"metaInfo": e.Meta,
|
||||
"meta": aid.JSON{},
|
||||
"displayAssetPath": e.DisplayAssetPath,
|
||||
"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 {
|
||||
|
@ -316,6 +317,12 @@ func (e *Entry) GenerateResponse(p *person.Person) aid.JSON {
|
|||
"requiredId": item.ID,
|
||||
"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["requirements"] = requirements
|
||||
json["metaInfo"] = meta
|
||||
json["giftInfo"] = aid.JSON{
|
||||
"bIsEnabled": true,
|
||||
"forcedGiftBoxTemplateId": "",
|
||||
"purchaseRequirements": purchaseRequirements,
|
||||
"giftRecordIds": []any{},
|
||||
}
|
||||
|
||||
|
||||
return json
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ var (
|
|||
"SetCosmeticLockerSlot": clientSetCosmeticLockerSlotAction,
|
||||
"SetCosmeticLockerBanner": clientSetCosmeticLockerBannerAction,
|
||||
"PurchaseCatalogEntry": clientPurchaseCatalogEntryAction,
|
||||
"GiftCatalogEntry": clientGiftCatalogEntryAction,
|
||||
"RemoveGiftBox": clientRemoveGiftBoxAction,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -70,8 +72,7 @@ func PostClientProfileAction(c *fiber.Ctx) error {
|
|||
profile.Diff(profileSnapshot)
|
||||
}
|
||||
|
||||
known, _ := strconv.Atoi(c.Query("rvn"))
|
||||
revision := known
|
||||
revision, _ := strconv.Atoi(c.Query("rvn"))
|
||||
if revision == -1 {
|
||||
revision = profile.Revision
|
||||
}
|
||||
|
@ -81,8 +82,6 @@ func PostClientProfileAction(c *fiber.Ctx) error {
|
|||
delete(profileSnapshots, profile.Type)
|
||||
|
||||
multiUpdate := []aid.JSON{}
|
||||
if known != -1 {
|
||||
|
||||
for key := range profileSnapshots {
|
||||
profile := person.GetProfileFromType(key)
|
||||
if profile == nil {
|
||||
|
@ -105,7 +104,6 @@ func PostClientProfileAction(c *fiber.Ctx) error {
|
|||
profile.ClearProfileChanges()
|
||||
go profile.Save()
|
||||
}
|
||||
}
|
||||
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"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 {
|
||||
known, _ := strconv.Atoi(c.Query("rvn"))
|
||||
|
||||
if known == -1 {
|
||||
profile.CreateFullProfileUpdateChange()
|
||||
}
|
||||
profile.CreateFullProfileUpdateChange()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -134,8 +128,7 @@ func clientMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile
|
|||
ItemIds []string `json:"itemIds"`
|
||||
}
|
||||
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return fmt.Errorf("invalid Body")
|
||||
}
|
||||
|
||||
|
@ -159,8 +152,7 @@ func clientEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person,
|
|||
IndexWithinSlot int `json:"indexWithinSlot"`
|
||||
}
|
||||
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return fmt.Errorf("invalid Body")
|
||||
}
|
||||
|
||||
|
@ -207,8 +199,7 @@ func clientSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *
|
|||
HomebaseBannerIconID string `json:"homebaseBannerIconId" binding:"required"`
|
||||
}
|
||||
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return fmt.Errorf("invalid Body")
|
||||
}
|
||||
|
||||
|
@ -248,8 +239,7 @@ func clientSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, prof
|
|||
Favorite []bool `json:"itemFavStatus" binding:"required"`
|
||||
}
|
||||
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
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 {
|
||||
var body struct{
|
||||
var body struct {
|
||||
OfferID string `json:"offerId" binding:"required"`
|
||||
PurchaseQuantity int `json:"purchaseQuantity" binding:"required"`
|
||||
ExpectedTotalPrice int `json:"expectedTotalPrice" binding:"required"`
|
||||
}
|
||||
|
||||
err := c.BodyParser(&body)
|
||||
if err != nil {
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return fmt.Errorf("invalid Body")
|
||||
}
|
||||
|
||||
|
@ -409,7 +396,7 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
|
|||
vbucks.Quantity -= body.ExpectedTotalPrice
|
||||
|
||||
go func() {
|
||||
profile0Vbucks.Quantity = vbucks.Quantity // for season 2 and lower
|
||||
profile0Vbucks.Quantity = vbucks.Quantity
|
||||
vbucks.Save()
|
||||
profile0Vbucks.Save()
|
||||
}()
|
||||
|
@ -449,5 +436,97 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
|
|||
"primary": true,
|
||||
})
|
||||
|
||||
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 {
|
||||
return c.Status(fiber.StatusOK).JSON(aid.JSON{
|
||||
return c.Status(200).JSON(aid.JSON{
|
||||
"enumerateFilesPath": "/api/cloudstorage/system",
|
||||
"enableMigration": true,
|
||||
"enableWrites": true,
|
||||
|
@ -55,15 +55,17 @@ func GetCloudStorageConfig(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func GetCloudStorageFile(c *fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/octet-stream")
|
||||
switch c.Params("fileName") {
|
||||
case "DefaultEngine.ini":
|
||||
c.Set("Content-Type", "application/octet-stream")
|
||||
c.Status(fiber.StatusOK)
|
||||
c.Send(storage.GetDefaultEngine())
|
||||
return nil
|
||||
return c.Status(200).Send(storage.GetDefaultEngine())
|
||||
case "DefaultGame.ini":
|
||||
return c.Status(200).Send(storage.GetDefaultGame())
|
||||
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 {
|
||||
|
|
|
@ -2,6 +2,7 @@ package person
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
"github.com/ectrc/snow/storage"
|
||||
|
@ -33,7 +34,7 @@ func (r *Relationship) ToDatabase() *storage.DB_Relationship {
|
|||
func (r *Relationship) GenerateFortniteFriendEntry(t RelationshipGenerateType) aid.JSON {
|
||||
result := aid.JSON{
|
||||
"status": r.Status,
|
||||
"created": "0000-00-00T00:00:00.000Z",
|
||||
"created": time.Now().Add(-time.Hour * 24 * 3).Format(time.RFC3339),
|
||||
"favorite": false,
|
||||
}
|
||||
|
||||
|
@ -46,8 +47,6 @@ func (r *Relationship) GenerateFortniteFriendEntry(t RelationshipGenerateType) a
|
|||
result["accountId"] = r.From.ID
|
||||
}
|
||||
|
||||
aid.PrintJSON(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
@ -13,9 +13,10 @@ Performance first, universal Fortnite private server backend written in Go.
|
|||
|
||||
## What's next?
|
||||
|
||||
- Gifting, Matchmaker and Battle Pass support.
|
||||
- Interact with external Services like Amazon S3 Buckets to save player data externally.
|
||||
- Refactor the XMPP solution to use [melium/xmpp](https://github.com/mellium/xmpp)
|
||||
- More profile actions like `RefundMtxPurchase` and `CopyCosmeticLoadout`.
|
||||
- Integrating matchmaking with a hoster to smartly put players into games and know when servers become available.
|
||||
- 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
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ bShouldCheckIfPlatformAllowed=false`)
|
|||
}
|
||||
|
||||
func GetDefaultRuntime() []byte {
|
||||
return []byte(`
|
||||
return []byte(`
|
||||
[/Script/FortniteGame.FortRuntimeOptions]
|
||||
bEnableGlobalChat=true
|
||||
bDisableGifting=false
|
||||
|
@ -61,6 +61,5 @@ bDisableGiftingPC=false
|
|||
bDisableGiftingPS4=false
|
||||
bDisableGiftingXB=false
|
||||
!ExperimentalCohortPercent=ClearArray
|
||||
+ExperimentalCohortPercent=(CohortPercent=100,ExperimentNum=20)
|
||||
`)
|
||||
+ExperimentalCohortPercent=(CohortPercent=100,ExperimentNum=20)`)
|
||||
}
|
Loading…
Reference in New Issue
Block a user