diff --git a/aid/config.go b/aid/config.go index c909b4a..ce9f9f5 100644 --- a/aid/config.go +++ b/aid/config.go @@ -10,6 +10,7 @@ type CS struct { Database struct { URI string Type string + DropAllTables bool } Output struct { Level string @@ -33,6 +34,7 @@ func LoadConfig() { panic(err) } + Config.Database.DropAllTables = cfg.Section("database").Key("drop").MustBool(false) Config.Database.URI = cfg.Section("database").Key("uri").String() if Config.Database.URI == "" { panic("Database URI is empty") diff --git a/aid/print.go b/aid/print.go index a32ab25..c6fbcd3 100644 --- a/aid/print.go +++ b/aid/print.go @@ -6,6 +6,14 @@ import ( "time" ) +func Print(v ...interface{}) { + if Config.Output.Level == "prod" { + return + } + + fmt.Println(v...) +} + func PrintJSON(v interface{}) { if Config.Output.Level == "prod" || Config.Output.Level == "time" { return diff --git a/default.config.ini b/default.config.ini index 1e0f7d8..e0d4252 100644 --- a/default.config.ini +++ b/default.config.ini @@ -3,7 +3,12 @@ uri="host=localhost user=postgres password=pass dbname=snow port=5432 sslmode=disable" ; postgres type="postgres" +; drop all tables at start of program +drop=false [output] ; level of logging -level="dev" # dev, prod \ No newline at end of file +; info = everything +; time = only time taken +; prod = only errors +level="info" \ No newline at end of file diff --git a/main.go b/main.go index d3fb1b1..a9f6bf8 100644 --- a/main.go +++ b/main.go @@ -5,21 +5,16 @@ import ( "github.com/ectrc/snow/person" "github.com/ectrc/snow/storage" ) - -const ( - DROP_TABLES = true -) - func init() { aid.LoadConfig() var device storage.Storage - switch aid.Config.Database.Type { case "postgres": postgresStorage := storage.NewPostgresStorage() - if DROP_TABLES { + if aid.Config.Database.DropAllTables { + aid.Print("Dropping all tables") postgresStorage.DropTables() } @@ -41,63 +36,17 @@ func init() { } func init() { - if DROP_TABLES { - user := person.NewPerson() - { - user.CommonCoreProfile.Attributes.AddAttribute(person.NewAttribute("xp", 1030)) - user.CommonCoreProfile.Attributes.AddAttribute(person.NewAttribute("level", 100)) - user.CommonCoreProfile.Attributes.AddAttribute(person.NewAttribute("quest_manager", aid.JSON{})) - - user.CommonCoreProfile.Items.AddItem(person.NewItem("Currency:MtxPurchased", 100)) - user.CommonCoreProfile.Items.AddItem(person.NewItem("Token:CampaignAccess", 1)) - - quest := person.NewQuest("Quest:Quest_1", "ChallengeBundle:Daily_1", "ChallengeBundleSchedule:Paid_1") - { - quest.AddObjective("quest_objective_eliminateplayers", 0) - quest.AddObjective("quest_objective_top1", 0) - quest.AddObjective("quest_objective_place_top10", 0) - - quest.UpdateObjectiveCount("quest_objective_eliminateplayers", 10) - quest.UpdateObjectiveCount("quest_objective_place_top10", -3) - - quest.RemoveObjective("quest_objective_top1") - } - user.AthenaProfile.Quests.AddQuest(quest) - - giftBox := person.NewGift("GiftBox:GB_Default", 1, user.ID, "Hello, Bully!") - { - giftBox.AddLoot(person.NewItemWithType("AthenaCharacter:CID_002_Athena_Commando_F_Default", 1, "athena")) - } - user.CommonCoreProfile.Gifts.AddGift(giftBox) - } - user.Save() - - snapshot := user.CommonCoreProfile.Snapshot() - { - vbucks := user.CommonCoreProfile.Items.GetItemByTemplateID("Currency:MtxPurchased") - vbucks.Quantity = 200 - vbucks.Favorite = true - - user.CommonCoreProfile.Items.DeleteItem(user.CommonCoreProfile.Items.GetItemByTemplateID("Token:CampaignAccess").ID) - user.CommonCoreProfile.Items.AddItem(person.NewItem("Token:ReceiveMtxCurrency", 1)) - } - user.CommonCoreProfile.Diff(snapshot) - user.Save() - } - go storage.Cache.CacheKiller() } func main() { - var users []*person.Person - aid.PrintTime("Fetching Persons", func() { - users = person.AllFromDatabase() + users := person.AllFromDatabase() + aid.Print("Found", len(users), "users") + for _, user := range users { + aid.Print(user.ID) + } }) - for _, user := range users { - aid.PrintJSON(user.Snapshot()) - } - // aid.WaitForExit() } \ No newline at end of file diff --git a/person/changes.go b/person/changes.go index 6bd6273..bd9ae76 100644 --- a/person/changes.go +++ b/person/changes.go @@ -2,10 +2,6 @@ package person import "github.com/ectrc/snow/aid" -type ProfileChange struct { - ChangeType string `json:"changeType"` -} - type FullProfileUpdate struct { ChangeType string `json:"changeType"` Profile aid.JSON `json:"profile"` diff --git a/person/gift.go b/person/gift.go index 1e11996..4b8c846 100644 --- a/person/gift.go +++ b/person/gift.go @@ -3,6 +3,7 @@ package person import ( "time" + "github.com/ectrc/snow/aid" "github.com/ectrc/snow/storage" "github.com/google/uuid" ) @@ -47,6 +48,34 @@ func FromDatabaseGift(gift *storage.DB_Gift) *Gift { } } +func (g *Gift) GenerateFortniteGiftEntry() aid.JSON { + json := aid.JSON{ + "templateId": g.TemplateID, + "attributes": aid.JSON{ + "params": aid.JSON{}, + "lootList": []aid.JSON{}, + "fromAccountId": g.FromID, + "giftedOn": time.Unix(g.GiftedAt, 0).Format(time.RFC3339), + }, + "quantity": 1, + } + + for _, loot := range g.Loot { + json["attributes"].(aid.JSON)["lootList"] = append(json["attributes"].(aid.JSON)["lootList"].([]aid.JSON), aid.JSON{ + "itemGuid": loot.ID, + "itemType": loot.TemplateID, + "itemProfile": loot.ProfileType, + "quantity": loot.Quantity, + }) + } + + if g.Message != "" { + json["attributes"].(aid.JSON)["params"].(aid.JSON)["userMessage"] = g.Message + } + + return json +} + func (g *Gift) AddLoot(loot *Item) { g.Loot = append(g.Loot, loot) //storage.Repo.SaveGiftLoot(g.ID, loot) diff --git a/person/item.go b/person/item.go index b11af95..c367a67 100644 --- a/person/item.go +++ b/person/item.go @@ -70,10 +70,10 @@ func FromDatabaseLoot(item *storage.DB_Loot) *Item { } func (i *Item) GenerateFortniteItemEntry() aid.JSON { - varaints := []aid.JSON{} + variants := []aid.JSON{} for _, variant := range i.Variants { - varaints = append(varaints, aid.JSON{ + variants = append(variants, aid.JSON{ "channel": variant.Channel, "owned": variant.Owned, "active": variant.Active, @@ -83,7 +83,7 @@ func (i *Item) GenerateFortniteItemEntry() aid.JSON { return aid.JSON{ "templateId": i.TemplateID, "attributes": aid.JSON{ - "variants": varaints, + "variants": variants, "favorite": i.Favorite, "item_seen": i.HasSeen, }, diff --git a/person/profile.go b/person/profile.go index 2082b09..af01a8f 100644 --- a/person/profile.go +++ b/person/profile.go @@ -15,7 +15,7 @@ type Profile struct { Quests *QuestMutex Attributes *AttributeMutex Type string - Revision int + Revision int Changes []interface{} } @@ -28,6 +28,7 @@ func NewProfile(profile string) *Profile { Attributes: NewAttributeMutex(), Type: profile, Revision: 0, + Changes: []interface{}{}, } } @@ -118,8 +119,6 @@ func (p *Profile) Diff(snapshot *ProfileSnapshot) []diff.Change { return nil } - // aid.PrintJSON(changes) - for _, change := range changes { switch change.Path[0] { case "Items": @@ -138,12 +137,75 @@ func (p *Profile) Diff(snapshot *ProfileSnapshot) []diff.Change { if change.Type == "update" && change.Path[2] != "Quantity" { p.CreateItemAttributeChangedChange(p.Items.GetItem(change.Path[1]), change.Path[2]) } + case "Quests": + if change.Type == "create" && change.Path[2] == "ID" { + p.CreateQuestAddedChange(p.Quests.GetQuest(change.Path[1])) + } + + if change.Type == "delete" && change.Path[2] == "ID" { + p.CreateItemRemovedChange(change.Path[1]) + } + case "Gifts": + if change.Type == "create" && change.Path[2] == "ID" { + p.CreateGiftAddedChange(p.Gifts.GetGift(change.Path[1])) + } + + if change.Type == "delete" && change.Path[2] == "ID" { + p.CreateItemRemovedChange(change.Path[1]) + } + case "Attributes": + if change.Type == "create" && change.Path[2] == "ID" { + p.CreateStatModifiedChange(p.Attributes.GetAttribute(change.Path[1])) + } + + if change.Type == "update" && change.Path[2] == "Value" { + p.CreateStatModifiedChange(p.Attributes.GetAttribute(change.Path[1])) + } } } return changes } +func (p *Profile) CreateStatModifiedChange(attribute *Attribute) { + if attribute == nil { + fmt.Println("error getting attribute from profile", attribute.ID) + return + } + + p.Changes = append(p.Changes, StatModified{ + ChangeType: "statModified", + Name: attribute.Key, + Value: attribute.Value, + }) +} + +func (p *Profile) CreateGiftAddedChange(gift *Gift) { + if gift == nil { + fmt.Println("error getting gift from profile", gift.ID) + return + } + + p.Changes = append(p.Changes, ItemAdded{ + ChangeType: "itemAdded", + ItemId: gift.ID, + Item: gift.GenerateFortniteGiftEntry(), + }) +} + +func (p *Profile) CreateQuestAddedChange(quest *Quest) { + if quest == nil { + fmt.Println("error getting quest from profile", quest.ID) + return + } + + p.Changes = append(p.Changes, ItemAdded{ + ChangeType: "itemAdded", + ItemId: quest.ID, + Item: quest.GenerateFortniteQuestEntry(), + }) +} + func (p *Profile) CreateItemAddedChange(item *Item) { if item == nil { fmt.Println("error getting item from profile", item.ID) diff --git a/person/quest.go b/person/quest.go index ff71700..ff3f107 100644 --- a/person/quest.go +++ b/person/quest.go @@ -1,6 +1,7 @@ package person import ( + "github.com/ectrc/snow/aid" "github.com/ectrc/snow/storage" "github.com/google/uuid" ) @@ -8,6 +9,7 @@ import ( type Quest struct { ID string TemplateID string + State string Objectives []string ObjectiveCounts []int64 BundleID string @@ -18,6 +20,7 @@ func NewQuest(templateID string, bundleID string, scheduleID string) *Quest { return &Quest{ ID: uuid.New().String(), TemplateID: templateID, + State: "Active", Objectives: []string{}, ObjectiveCounts: []int64{}, BundleID: bundleID, @@ -25,10 +28,21 @@ func NewQuest(templateID string, bundleID string, scheduleID string) *Quest { } } +func NewDailyQuest(templateID string) *Quest { + return &Quest{ + ID: uuid.New().String(), + TemplateID: templateID, + State: "Active", + Objectives: []string{}, + ObjectiveCounts: []int64{}, + } +} + func FromDatabaseQuest(quest *storage.DB_Quest, profileType *string) *Quest { return &Quest{ ID: quest.ID, TemplateID: quest.TemplateID, + State: quest.State, Objectives: quest.Objectives, ObjectiveCounts: quest.ObjectiveCounts, BundleID: quest.BundleID, @@ -36,6 +50,23 @@ func FromDatabaseQuest(quest *storage.DB_Quest, profileType *string) *Quest { } } +func (q *Quest) GenerateFortniteQuestEntry() aid.JSON { + json := aid.JSON{ + "templateId": q.TemplateID, + "attributes": aid.JSON{ + "quest_state": q.State, + "challenge_bundle_id": q.BundleID, + }, + "quantity": 1, + } + + for i, objective := range q.Objectives { + json["attributes"].(aid.JSON)["completion_" + objective] = q.ObjectiveCounts[i] + } + + return json +} + func (q *Quest) Delete() { storage.Repo.DeleteQuest(q.ID) } @@ -98,6 +129,7 @@ func (q *Quest) ToDatabase(profileId string) *storage.DB_Quest { ProfileID: profileId, ID: q.ID, TemplateID: q.TemplateID, + State: q.State, Objectives: q.Objectives, ObjectiveCounts: q.ObjectiveCounts, BundleID: q.BundleID, diff --git a/person/sync.go b/person/sync.go index a4861d9..480d187 100644 --- a/person/sync.go +++ b/person/sync.go @@ -165,17 +165,17 @@ func NewAttributeMutex() *AttributeMutex { } func (m *AttributeMutex) AddAttribute(attribute *Attribute) { - m.Store(attribute.Key, attribute) + m.Store(attribute.ID, attribute) // storage.Repo.SaveAttribute(key, value) } -func (m *AttributeMutex) DeleteAttribute(key string) { - m.Delete(key) +func (m *AttributeMutex) DeleteAttribute(id string) { + m.Delete(id) // storage.Repo.DeleteAttribute(key) } -func (m *AttributeMutex) GetAttribute(key string) *Attribute { - value, ok := m.Load(key) +func (m *AttributeMutex) GetAttribute(id string) *Attribute { + value, ok := m.Load(id) if !ok { return nil } @@ -183,8 +183,32 @@ func (m *AttributeMutex) GetAttribute(key string) *Attribute { return value.(*Attribute) } -func (m *AttributeMutex) RangeAttributes(f func(key string, value *Attribute) bool) { +func (m *AttributeMutex) GetAttributeByKey(key string) *Attribute { + var found *Attribute + + m.RangeAttributes(func(id string, attribute *Attribute) bool { + if attribute.Key == key { + found = attribute + return false + } + + return true + }) + + return found +} + +func (m *AttributeMutex) RangeAttributes(f func(id string, attribute *Attribute) bool) { m.Range(func(key, value interface{}) bool { return f(key.(string), value.(*Attribute)) }) +} + +func (m *AttributeMutex) Count() int { + count := 0 + m.Range(func(key, value interface{}) bool { + count++ + return true + }) + return count } \ No newline at end of file diff --git a/readme.md b/readme.md index 268ddcb..5d05276 100644 --- a/readme.md +++ b/readme.md @@ -11,34 +11,22 @@ Performance first, universal Fortnite backend written in Go. ## Examples -### Person Structure +### Quests ```golang -user := person.NewPerson() -{ - user.CommonCoreProfile.Items.AddItem(person.NewItem("Currency:MtxPurchased", 100)) - user.CommonCoreProfile.Items.AddItem(person.NewItem("Token:CampaignAccess", 1)) +schedule := person.NewItem("ChallengeBundleSchedule:Paid_1", 1) +user.AthenaProfile.Items.AddItem(schedule) - quest := person.NewQuest("Quest:Quest_1", "ChallengeBundle:Daily_1", "ChallengeBundleSchedule:Paid_1") - { - quest.AddObjective("quest_objective_eliminateplayers", 0) - quest.AddObjective("quest_objective_top1", 0) - quest.AddObjective("quest_objective_place_top10", 0) +bundle := person.NewItem("ChallengeBundle:Daily_1", 1) +user.AthenaProfile.Items.AddItem(bundle) - quest.UpdateObjectiveCount("quest_objective_eliminateplayers", 10) - quest.UpdateObjectiveCount("quest_objective_place_top10", -3) +quest := person.NewQuest("Quest:Quest_2", bundle.ID, schedule.ID) +quest.AddObjective("quest_objective_eliminateplayers", 0) +user.AthenaProfile.Quests.AddQuest(quest) - quest.RemoveObjective("quest_objective_top1") - } - user.AthenaProfile.Quests.AddQuest(quest) - - giftBox := person.NewGift("GiftBox:GB_Default", 1, user.ID, "Hello, Bully!") - { - giftBox.AddLoot(person.NewItemWithType("AthenaCharacter:CID_002_Athena_Commando_F_Default", 1, "athena")) - } - user.CommonCoreProfile.Gifts.AddGift(giftBox) -} -user.Save() +daily := person.NewDailyQuest("Quest:Quest_3") +daily.AddObjective("quest_objective_place_top10", 0) +user.AthenaProfile.Quests.AddQuest(daily) ``` ### Profile Changes diff --git a/storage/cache.go b/storage/cache.go index 6103d5a..43c3c5a 100644 --- a/storage/cache.go +++ b/storage/cache.go @@ -27,14 +27,14 @@ func (m *PersonsCache) CacheKiller() { Cache.Range(func(key, value interface{}) bool { cacheEntry := value.(*CacheEntry) - if time.Since(cacheEntry.LastAccessed) >= 5 * time.Minute { + if time.Since(cacheEntry.LastAccessed) >= 30 * time.Minute { Cache.Delete(key) } return true }) - time.Sleep(5 * time.Minute) + time.Sleep(5000 * time.Minute) } } diff --git a/storage/tables.go b/storage/tables.go index 5fac9dc..d85bda3 100644 --- a/storage/tables.go +++ b/storage/tables.go @@ -94,6 +94,7 @@ type DB_Quest struct { ID string `gorm:"primary_key"` ProfileID string TemplateID string + State string Objectives pq.StringArray `gorm:"type:text[]"` ObjectiveCounts pq.Int64Array `gorm:"type:bigint[]"` BundleID string