Automatic Profile Changes

This commit is contained in:
eccentric 2023-11-02 17:50:52 +00:00
parent 4607e42476
commit 347e1f6c02
13 changed files with 196 additions and 100 deletions

View File

@ -10,6 +10,7 @@ type CS struct {
Database struct { Database struct {
URI string URI string
Type string Type string
DropAllTables bool
} }
Output struct { Output struct {
Level string Level string
@ -33,6 +34,7 @@ func LoadConfig() {
panic(err) panic(err)
} }
Config.Database.DropAllTables = cfg.Section("database").Key("drop").MustBool(false)
Config.Database.URI = cfg.Section("database").Key("uri").String() Config.Database.URI = cfg.Section("database").Key("uri").String()
if Config.Database.URI == "" { if Config.Database.URI == "" {
panic("Database URI is empty") panic("Database URI is empty")

View File

@ -6,6 +6,14 @@ import (
"time" "time"
) )
func Print(v ...interface{}) {
if Config.Output.Level == "prod" {
return
}
fmt.Println(v...)
}
func PrintJSON(v interface{}) { func PrintJSON(v interface{}) {
if Config.Output.Level == "prod" || Config.Output.Level == "time" { if Config.Output.Level == "prod" || Config.Output.Level == "time" {
return return

View File

@ -3,7 +3,12 @@
uri="host=localhost user=postgres password=pass dbname=snow port=5432 sslmode=disable" uri="host=localhost user=postgres password=pass dbname=snow port=5432 sslmode=disable"
; postgres ; postgres
type="postgres" type="postgres"
; drop all tables at start of program
drop=false
[output] [output]
; level of logging ; level of logging
level="dev" # dev, prod ; info = everything
; time = only time taken
; prod = only errors
level="info"

63
main.go
View File

@ -5,21 +5,16 @@ import (
"github.com/ectrc/snow/person" "github.com/ectrc/snow/person"
"github.com/ectrc/snow/storage" "github.com/ectrc/snow/storage"
) )
const (
DROP_TABLES = true
)
func init() { func init() {
aid.LoadConfig() aid.LoadConfig()
var device storage.Storage var device storage.Storage
switch aid.Config.Database.Type { switch aid.Config.Database.Type {
case "postgres": case "postgres":
postgresStorage := storage.NewPostgresStorage() postgresStorage := storage.NewPostgresStorage()
if DROP_TABLES { if aid.Config.Database.DropAllTables {
aid.Print("Dropping all tables")
postgresStorage.DropTables() postgresStorage.DropTables()
} }
@ -41,63 +36,17 @@ func init() {
} }
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() go storage.Cache.CacheKiller()
} }
func main() { func main() {
var users []*person.Person
aid.PrintTime("Fetching Persons", func() { aid.PrintTime("Fetching Persons", func() {
users = person.AllFromDatabase() users := person.AllFromDatabase()
}) aid.Print("Found", len(users), "users")
for _, user := range users { for _, user := range users {
aid.PrintJSON(user.Snapshot()) aid.Print(user.ID)
} }
})
// aid.WaitForExit() // aid.WaitForExit()
} }

View File

@ -2,10 +2,6 @@ package person
import "github.com/ectrc/snow/aid" import "github.com/ectrc/snow/aid"
type ProfileChange struct {
ChangeType string `json:"changeType"`
}
type FullProfileUpdate struct { type FullProfileUpdate struct {
ChangeType string `json:"changeType"` ChangeType string `json:"changeType"`
Profile aid.JSON `json:"profile"` Profile aid.JSON `json:"profile"`

View File

@ -3,6 +3,7 @@ package person
import ( import (
"time" "time"
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage" "github.com/ectrc/snow/storage"
"github.com/google/uuid" "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) { func (g *Gift) AddLoot(loot *Item) {
g.Loot = append(g.Loot, loot) g.Loot = append(g.Loot, loot)
//storage.Repo.SaveGiftLoot(g.ID, loot) //storage.Repo.SaveGiftLoot(g.ID, loot)

View File

@ -70,10 +70,10 @@ func FromDatabaseLoot(item *storage.DB_Loot) *Item {
} }
func (i *Item) GenerateFortniteItemEntry() aid.JSON { func (i *Item) GenerateFortniteItemEntry() aid.JSON {
varaints := []aid.JSON{} variants := []aid.JSON{}
for _, variant := range i.Variants { for _, variant := range i.Variants {
varaints = append(varaints, aid.JSON{ variants = append(variants, aid.JSON{
"channel": variant.Channel, "channel": variant.Channel,
"owned": variant.Owned, "owned": variant.Owned,
"active": variant.Active, "active": variant.Active,
@ -83,7 +83,7 @@ func (i *Item) GenerateFortniteItemEntry() aid.JSON {
return aid.JSON{ return aid.JSON{
"templateId": i.TemplateID, "templateId": i.TemplateID,
"attributes": aid.JSON{ "attributes": aid.JSON{
"variants": varaints, "variants": variants,
"favorite": i.Favorite, "favorite": i.Favorite,
"item_seen": i.HasSeen, "item_seen": i.HasSeen,
}, },

View File

@ -28,6 +28,7 @@ func NewProfile(profile string) *Profile {
Attributes: NewAttributeMutex(), Attributes: NewAttributeMutex(),
Type: profile, Type: profile,
Revision: 0, Revision: 0,
Changes: []interface{}{},
} }
} }
@ -118,8 +119,6 @@ func (p *Profile) Diff(snapshot *ProfileSnapshot) []diff.Change {
return nil return nil
} }
// aid.PrintJSON(changes)
for _, change := range changes { for _, change := range changes {
switch change.Path[0] { switch change.Path[0] {
case "Items": case "Items":
@ -138,12 +137,75 @@ func (p *Profile) Diff(snapshot *ProfileSnapshot) []diff.Change {
if change.Type == "update" && change.Path[2] != "Quantity" { if change.Type == "update" && change.Path[2] != "Quantity" {
p.CreateItemAttributeChangedChange(p.Items.GetItem(change.Path[1]), change.Path[2]) 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 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) { func (p *Profile) CreateItemAddedChange(item *Item) {
if item == nil { if item == nil {
fmt.Println("error getting item from profile", item.ID) fmt.Println("error getting item from profile", item.ID)

View File

@ -1,6 +1,7 @@
package person package person
import ( import (
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage" "github.com/ectrc/snow/storage"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -8,6 +9,7 @@ import (
type Quest struct { type Quest struct {
ID string ID string
TemplateID string TemplateID string
State string
Objectives []string Objectives []string
ObjectiveCounts []int64 ObjectiveCounts []int64
BundleID string BundleID string
@ -18,6 +20,7 @@ func NewQuest(templateID string, bundleID string, scheduleID string) *Quest {
return &Quest{ return &Quest{
ID: uuid.New().String(), ID: uuid.New().String(),
TemplateID: templateID, TemplateID: templateID,
State: "Active",
Objectives: []string{}, Objectives: []string{},
ObjectiveCounts: []int64{}, ObjectiveCounts: []int64{},
BundleID: bundleID, 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 { func FromDatabaseQuest(quest *storage.DB_Quest, profileType *string) *Quest {
return &Quest{ return &Quest{
ID: quest.ID, ID: quest.ID,
TemplateID: quest.TemplateID, TemplateID: quest.TemplateID,
State: quest.State,
Objectives: quest.Objectives, Objectives: quest.Objectives,
ObjectiveCounts: quest.ObjectiveCounts, ObjectiveCounts: quest.ObjectiveCounts,
BundleID: quest.BundleID, 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() { func (q *Quest) Delete() {
storage.Repo.DeleteQuest(q.ID) storage.Repo.DeleteQuest(q.ID)
} }
@ -98,6 +129,7 @@ func (q *Quest) ToDatabase(profileId string) *storage.DB_Quest {
ProfileID: profileId, ProfileID: profileId,
ID: q.ID, ID: q.ID,
TemplateID: q.TemplateID, TemplateID: q.TemplateID,
State: q.State,
Objectives: q.Objectives, Objectives: q.Objectives,
ObjectiveCounts: q.ObjectiveCounts, ObjectiveCounts: q.ObjectiveCounts,
BundleID: q.BundleID, BundleID: q.BundleID,

View File

@ -165,17 +165,17 @@ func NewAttributeMutex() *AttributeMutex {
} }
func (m *AttributeMutex) AddAttribute(attribute *Attribute) { func (m *AttributeMutex) AddAttribute(attribute *Attribute) {
m.Store(attribute.Key, attribute) m.Store(attribute.ID, attribute)
// storage.Repo.SaveAttribute(key, value) // storage.Repo.SaveAttribute(key, value)
} }
func (m *AttributeMutex) DeleteAttribute(key string) { func (m *AttributeMutex) DeleteAttribute(id string) {
m.Delete(key) m.Delete(id)
// storage.Repo.DeleteAttribute(key) // storage.Repo.DeleteAttribute(key)
} }
func (m *AttributeMutex) GetAttribute(key string) *Attribute { func (m *AttributeMutex) GetAttribute(id string) *Attribute {
value, ok := m.Load(key) value, ok := m.Load(id)
if !ok { if !ok {
return nil return nil
} }
@ -183,8 +183,32 @@ func (m *AttributeMutex) GetAttribute(key string) *Attribute {
return value.(*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 { m.Range(func(key, value interface{}) bool {
return f(key.(string), value.(*Attribute)) 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
}

View File

@ -11,34 +11,22 @@ Performance first, universal Fortnite backend written in Go.
## Examples ## Examples
### Person Structure ### Quests
```golang ```golang
user := person.NewPerson() schedule := person.NewItem("ChallengeBundleSchedule:Paid_1", 1)
{ user.AthenaProfile.Items.AddItem(schedule)
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") bundle := person.NewItem("ChallengeBundle:Daily_1", 1)
{ user.AthenaProfile.Items.AddItem(bundle)
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 := person.NewQuest("Quest:Quest_2", bundle.ID, schedule.ID)
quest.UpdateObjectiveCount("quest_objective_place_top10", -3) quest.AddObjective("quest_objective_eliminateplayers", 0)
user.AthenaProfile.Quests.AddQuest(quest)
quest.RemoveObjective("quest_objective_top1") daily := person.NewDailyQuest("Quest:Quest_3")
} daily.AddObjective("quest_objective_place_top10", 0)
user.AthenaProfile.Quests.AddQuest(quest) user.AthenaProfile.Quests.AddQuest(daily)
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()
``` ```
### Profile Changes ### Profile Changes

View File

@ -27,14 +27,14 @@ func (m *PersonsCache) CacheKiller() {
Cache.Range(func(key, value interface{}) bool { Cache.Range(func(key, value interface{}) bool {
cacheEntry := value.(*CacheEntry) cacheEntry := value.(*CacheEntry)
if time.Since(cacheEntry.LastAccessed) >= 5 * time.Minute { if time.Since(cacheEntry.LastAccessed) >= 30 * time.Minute {
Cache.Delete(key) Cache.Delete(key)
} }
return true return true
}) })
time.Sleep(5 * time.Minute) time.Sleep(5000 * time.Minute)
} }
} }

View File

@ -94,6 +94,7 @@ type DB_Quest struct {
ID string `gorm:"primary_key"` ID string `gorm:"primary_key"`
ProfileID string ProfileID string
TemplateID string TemplateID string
State string
Objectives pq.StringArray `gorm:"type:text[]"` Objectives pq.StringArray `gorm:"type:text[]"`
ObjectiveCounts pq.Int64Array `gorm:"type:bigint[]"` ObjectiveCounts pq.Int64Array `gorm:"type:bigint[]"`
BundleID string BundleID string