Update profiles to add MultiUpdates and Notifications;

Add purchasing form storefront!
This commit is contained in:
eccentric 2023-12-03 20:16:40 +00:00
parent 4336da2e4d
commit f4fa7866fd
6 changed files with 239 additions and 40 deletions

View File

@ -28,7 +28,9 @@ func NewFortnitePerson(displayName string, key string) *p.Person {
person.AccessKey = key
for _, item := range defaultAthenaItems {
person.AthenaProfile.Items.AddItem(p.NewItem(item, 1))
item := p.NewItem(item, 1)
item.HasSeen = true
person.AthenaProfile.Items.AddItem(item)
}
for _, item := range defaultCommonCoreItems {
@ -51,10 +53,8 @@ func NewFortnitePerson(displayName string, key string) *p.Person {
}
if item == "Currency:MtxPurchased" {
item := p.NewItem(item, 0)
item.HasSeen = true
person.CommonCoreProfile.Items.AddItem(item).Save()
person.Profile0Profile.Items.AddItem(item).Save()
person.CommonCoreProfile.Items.AddItem(p.NewItem(item, 9999)).Save()
person.Profile0Profile.Items.AddItem(p.NewItem(item, 9999)).Save()
continue
}

View File

@ -58,7 +58,7 @@ func (c *Catalog) GenerateFortniteCatalog(p *person.Person) aid.JSON {
return json
}
func (c *Catalog) IsDuplicate(entry Entry) bool {
func (c *Catalog) CheckIfOfferIsDuplicate(entry Entry) bool {
for _, storefront := range c.Storefronts {
for _, catalogEntry := range storefront.CatalogEntries {
if catalogEntry.Grants[0] == entry.Grants[0] {
@ -70,6 +70,18 @@ func (c *Catalog) IsDuplicate(entry Entry) bool {
return false
}
func (c *Catalog) GetOfferById(id string) *Entry {
for _, storefront := range c.Storefronts {
for _, catalogEntry := range storefront.CatalogEntries {
if catalogEntry.ID == id {
return &catalogEntry
}
}
}
return nil
}
type Storefront struct {
Name string `json:"name"`
CatalogEntries []Entry `json:"catalogEntries"`
@ -111,12 +123,14 @@ type Entry struct {
NewDisplayAssetPath string
Title string
ShortDescription string
ProfileType string
}
func NewCatalogEntry(meta ...aid.JSON) *Entry {
func NewCatalogEntry(profile string, meta ...aid.JSON) *Entry {
return &Entry{
ID: uuid.New().String(),
Meta: meta,
ProfileType: profile,
}
}
@ -305,7 +319,7 @@ func GenerateStorefront() {
}
item := Cosmetics.GetRandomItemByType("AthenaCharacter")
entry := NewCatalogEntry()
entry := NewCatalogEntry("athena")
entry.SetSection("Daily")
if !entry.IsNewDisplayAssetValid(item.ID) {
@ -322,7 +336,11 @@ func GenerateStorefront() {
entry.SetTileSize("Normal")
entry.Priority = 1
if storefront.IsDuplicate(*entry) {
if item.Backpack != "" {
entry.AddGrant("AthenaBackpack:" + item.Backpack)
}
if storefront.CheckIfOfferIsDuplicate(*entry) {
continue
}
@ -331,7 +349,7 @@ func GenerateStorefront() {
for i := 0; i < 6; i++ {
item := Cosmetics.GetRandomItemByNotType("AthenaCharacter")
entry := NewCatalogEntry()
entry := NewCatalogEntry("athena")
entry.SetSection("Daily")
if !entry.IsNewDisplayAssetValid(item.ID) {
@ -347,7 +365,7 @@ func GenerateStorefront() {
entry.AddGrant(item.Type.BackendValue + ":" + item.ID)
entry.SetTileSize("Small")
if storefront.IsDuplicate(*entry) {
if storefront.CheckIfOfferIsDuplicate(*entry) {
continue
}
@ -366,7 +384,7 @@ func GenerateStorefront() {
itemsAdded := 0
itemsToAdd := []*Entry{}
for _, item := range set.Items {
entry := NewCatalogEntry()
entry := NewCatalogEntry("athena")
entry.SetSection("Featured")
entry.SetPanel(set.BackendName)
@ -402,7 +420,7 @@ func GenerateStorefront() {
}
for _, entry := range itemsToAdd {
if storefront.IsDuplicate(*entry) {
if storefront.CheckIfOfferIsDuplicate(*entry) {
continue
}
@ -416,4 +434,5 @@ func GenerateStorefront() {
storefront.Add(weekly)
StaticCatalog = storefront
aid.Print("Generated new random storefront")
}

View File

@ -7,13 +7,14 @@ import (
"time"
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/fortnite"
p "github.com/ectrc/snow/person"
"github.com/gofiber/fiber/v2"
)
var (
profileActions = map[string]func(c *fiber.Ctx, person *p.Person, profile *p.Profile) error {
profileActions = map[string]func(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
"QueryProfile": PostQueryProfileAction,
"ClientQuestLogin": PostQueryProfileAction,
"MarkItemSeen": PostMarkItemSeenAction,
@ -22,6 +23,7 @@ var (
"SetBattleRoyaleBanner": PostSetBattleRoyaleBannerAction,
"SetCosmeticLockerSlot": PostSetCosmeticLockerSlotAction,
"SetCosmeticLockerBanner": PostSetCosmeticLockerBannerAction,
"PurchaseCatalogEntry": PostPurchaseCatalogEntryAction,
}
)
@ -32,27 +34,74 @@ func PostProfileAction(c *fiber.Ctx) error {
}
profile := person.GetProfileFromType(c.Query("profileId"))
if action, ok := profileActions[c.Params("action")]; ok && profile != nil {
defer profile.ClearProfileChanges()
before := profile.Snapshot()
if err := action(c, person, profile); err != nil {
if profile == nil {
return c.Status(404).JSON(aid.ErrorBadRequest("No Profile Found"))
}
defer profile.ClearProfileChanges()
profileSnapshots := map[string]*p.ProfileSnapshot{
"athena": nil,
"common_core": nil,
"common_public": nil,
}
for key := range profileSnapshots {
profileSnapshots[key] = person.GetProfileFromType(key).Snapshot()
}
notifications := []aid.JSON{}
action, ok := profileActions[c.Params("action")];
if ok && profile != nil {
if err := action(c, person, profile, &notifications); err != nil {
return c.Status(400).JSON(aid.ErrorBadRequest(err.Error()))
}
profile.Diff(before)
}
for key, profileSnapshot := range profileSnapshots {
profile := person.GetProfileFromType(key)
if profile == nil {
continue
}
if profileSnapshot == nil {
continue
}
profile.Diff(profileSnapshot)
}
revision, _ := strconv.Atoi(c.Query("rvn"))
if revision == -1 && profile == nil {
revision = 1
}
if revision == -1 && profile != nil {
if revision == -1 {
revision = profile.Revision
}
revision++
profile.Revision = revision
profile.Save()
delete(profileSnapshots, profile.Type)
changes := []interface{}{}
if profile != nil {
changes = profile.Changes
multiUpdate := []aid.JSON{}
for key := range profileSnapshots {
profile := person.GetProfileFromType(key)
if profile == nil {
continue
}
profile.Revision++
if len(profile.Changes) == 0 {
continue
}
multiUpdate = append(multiUpdate, aid.JSON{
"profileId": profile.Type,
"profileRevision": profile.Revision,
"profileCommandRevision": profile.Revision,
"profileChangesBaseRevision": profile.Revision - 1,
"profileChanges": profile.Changes,
})
profile.ClearProfileChanges()
profile.Save()
}
return c.Status(200).JSON(aid.JSON{
@ -60,20 +109,20 @@ func PostProfileAction(c *fiber.Ctx) error {
"profileRevision": revision,
"profileCommandRevision": revision,
"profileChangesBaseRevision": revision - 1,
"profileChanges": changes,
"multiUpdate": []aid.JSON{},
"notifications": []aid.JSON{},
"profileChanges": profile.Changes,
"multiUpdate": multiUpdate,
"notifications": notifications,
"responseVersion": 1,
"serverTime": time.Now().Format("2006-01-02T15:04:05.999Z"),
})
}
func PostQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error {
func PostQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
profile.CreateFullProfileUpdateChange()
return nil
}
func PostMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error {
func PostMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
ItemIds []string `json:"itemIds"`
}
@ -96,7 +145,7 @@ func PostMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile)
return nil
}
func PostEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error {
func PostEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
SlotName string `json:"slotName" binding:"required"`
ItemToSlot string `json:"itemToSlot"`
@ -141,7 +190,7 @@ func PostEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person, pr
return nil
}
func PostSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error {
func PostSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
HomebaseBannerColorID string `json:"homebaseBannerColorId" binding:"required"`
HomebaseBannerIconID string `json:"homebaseBannerIconId" binding:"required"`
@ -182,7 +231,7 @@ func PostSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *p.
return nil
}
func PostSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error {
func PostSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
ItemIds []string `json:"itemIds" binding:"required"`
Favorite []bool `json:"itemFavStatus" binding:"required"`
@ -206,7 +255,7 @@ func PostSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, profil
return nil
}
func PostSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error {
func PostSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
Category string `json:"category" binding:"required"` // item type e.g. Character
ItemToSlot string `json:"itemToSlot" binding:"required"` // template id
@ -275,7 +324,7 @@ func PostSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *p.
return nil
}
func PostSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error {
func PostSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
LockerItem string `json:"lockerItem" binding:"required"` // locker id
BannerColorTemplateName string `json:"bannerColorTemplateName" binding:"required"` // template id
@ -294,7 +343,6 @@ func PostSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *
icon := profile.Items.GetItemByTemplateID("HomebaseBannerIcon:" + body.BannerIconTemplateName)
if icon == nil {
// return fmt.Errorf("icon item not found")
icon = &p.Item{
ID: body.BannerIconTemplateName,
}
@ -309,5 +357,82 @@ func PostSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *
currentLocker.BannerID = icon.ID
go currentLocker.Save()
return nil
}
func PostPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
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 {
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")
}
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 < body.ExpectedTotalPrice {
return fmt.Errorf("not enough vbucks")
}
vbucks.Quantity -= body.ExpectedTotalPrice
go func() {
profile0Vbucks.Quantity = vbucks.Quantity // for season 2 and lower
vbucks.Save()
profile0Vbucks.Save()
}()
if offer.ProfileType != "athena" {
return fmt.Errorf("save the world not implemeted yet!")
}
loot := []aid.JSON{}
for i := 0; i < body.PurchaseQuantity; i++ {
for _, grant := range offer.Grants {
if profile.Items.GetItemByTemplateID(grant) != nil {
continue
}
item := p.NewItem(grant, 1)
person.AthenaProfile.Items.AddItem(item)
loot = append(loot, aid.JSON{
"itemType": item.TemplateID,
"itemGuid": item.ID,
"quantity": item.Quantity,
"itemProfile": offer.ProfileType,
})
}
}
*notifications = append(*notifications, aid.JSON{
"type": "CatalogPurchase",
"lootResult": aid.JSON{
"items": loot,
},
"primary": true,
})
return nil
}

View File

@ -13,6 +13,7 @@ type Person struct {
CommonCoreProfile *Profile
CommonPublicProfile *Profile
Profile0Profile *Profile
CollectionsProfile *Profile
}
type Option struct {
@ -29,6 +30,7 @@ func NewPerson() *Person {
CommonCoreProfile: NewProfile("common_core"),
CommonPublicProfile: NewProfile("common_public"),
Profile0Profile: NewProfile("profile0"),
CollectionsProfile: NewProfile("collections"),
}
}
@ -73,6 +75,7 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
commonCoreProfile := NewProfile("common_core")
commonPublicProfile := NewProfile("common_public")
profile0 := NewProfile("profile0")
collectionsProfile := NewProfile("collections")
for _, profile := range databasePerson.Profiles {
if profile.Type == "athena" {
@ -94,6 +97,11 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
profile0.ID = profile.ID
profile0 = FromDatabaseProfile(&profile)
}
if profile.Type == "collections" {
collectionsProfile.ID = profile.ID
collectionsProfile = FromDatabaseProfile(&profile)
}
}
person := &Person{
@ -104,6 +112,7 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
CommonCoreProfile: commonCoreProfile,
CommonPublicProfile: commonPublicProfile,
Profile0Profile: profile0,
CollectionsProfile: collectionsProfile,
}
cache.SavePerson(person)
@ -144,6 +153,8 @@ func (p *Person) GetProfileFromType(profileType string) *Profile {
return p.CommonPublicProfile
case "profile0":
return p.Profile0Profile
case "collections":
return p.CollectionsProfile
}
return nil
@ -167,6 +178,7 @@ func (p *Person) ToDatabase() *storage.DB_Person {
"athena": p.AthenaProfile,
"common_public": p.CommonPublicProfile,
"profile0": p.Profile0Profile,
"collections": p.CollectionsProfile,
}
for profileType, profile := range profilesToConvert {
@ -234,5 +246,6 @@ func (p *Person) Snapshot() *PersonSnapshot {
CommonCoreProfile: *p.CommonCoreProfile.Snapshot(),
CommonPublicProfile: *p.CommonPublicProfile.Snapshot(),
Profile0Profile: *p.Profile0Profile.Snapshot(),
CollectionsProfile: *p.CollectionsProfile.Snapshot(),
}
}

View File

@ -32,9 +32,9 @@ func NewProfile(profile string) *Profile {
Quests: NewQuestMutex(&storage.DB_Profile{ID: id, Type: profile}),
Attributes: NewAttributeMutex(&storage.DB_Profile{ID: id, Type: profile}),
Loadouts: NewLoadoutMutex(&storage.DB_Profile{ID: id, Type: profile}),
Type: profile,
Type: profile,
Revision: 0,
Changes: []interface{}{},
Changes: []interface{}{},
}
}
@ -129,7 +129,7 @@ func (p *Profile) GenerateFortniteProfileEntry() aid.JSON {
}
func (p *Profile) Save() {
// storage.Repo.SaveProfile(p.ToDatabase())
storage.Repo.SaveProfile(p.ToDatabase())
}
func (p *Profile) Snapshot() *ProfileSnapshot {
@ -398,4 +398,45 @@ func (p *Profile) CreateFullProfileUpdateChange() {
func (p *Profile) ClearProfileChanges() {
p.Changes = []interface{}{}
}
func (p *Profile) ToDatabase() *storage.DB_Profile {
dbProfile := storage.DB_Profile{
ID: p.ID,
PersonID: p.PersonID,
Type: p.Type,
Items: []storage.DB_Item{},
Gifts: []storage.DB_Gift{},
Quests: []storage.DB_Quest{},
Loadouts: []storage.DB_Loadout{},
Attributes: []storage.DB_PAttribute{},
Revision: p.Revision,
}
// p.Items.RangeItems(func(id string, item *Item) bool {
// dbProfile.Items = append(dbProfile.Items, *item.ToDatabase(dbProfile.PersonID))
// return true
// })
p.Gifts.RangeGifts(func(id string, gift *Gift) bool {
dbProfile.Gifts = append(dbProfile.Gifts, *gift.ToDatabase(dbProfile.PersonID))
return true
})
p.Quests.RangeQuests(func(id string, quest *Quest) bool {
dbProfile.Quests = append(dbProfile.Quests, *quest.ToDatabase(dbProfile.PersonID))
return true
})
p.Attributes.RangeAttributes(func(key string, value *Attribute) bool {
dbProfile.Attributes = append(dbProfile.Attributes, *value.ToDatabase(dbProfile.PersonID))
return true
})
p.Loadouts.RangeLoadouts(func(id string, loadout *Loadout) bool {
dbProfile.Loadouts = append(dbProfile.Loadouts, *loadout.ToDatabase(dbProfile.PersonID))
return true
})
return &dbProfile
}

View File

@ -7,6 +7,7 @@ type PersonSnapshot struct {
CommonCoreProfile ProfileSnapshot
CommonPublicProfile ProfileSnapshot
Profile0Profile ProfileSnapshot
CollectionsProfile ProfileSnapshot
}
type ProfileSnapshot struct {