Start to track profile changes.

This commit is contained in:
eccentric 2023-11-01 01:24:42 +00:00
parent 6b09cd5e82
commit 8e3cd016d9
8 changed files with 233 additions and 78 deletions

36
main.go
View File

@ -44,7 +44,9 @@ func init() {
func init() {
if DROP_TABLES {
user := person.NewPerson()
snapshot := user.AthenaProfile.Snapshot()
{
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")
{
@ -64,28 +66,26 @@ func init() {
giftBox.AddLoot(person.NewItemWithType("AthenaCharacter:CID_002_Athena_Commando_F_Default", 1, "athena"))
}
user.CommonCoreProfile.Gifts.AddGift(giftBox)
currency := person.NewItem("Currency:MtxPurchased", 100)
user.AthenaProfile.Items.AddItem(currency)
}
user.Save()
user.AthenaProfile.Diff(snapshot)
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)
aid.PrintJSON(user.CommonCoreProfile.Changes)
}
go storage.Cache.CacheKiller()
}
func main() {
persons := person.AllFromDatabase()
for _, p := range persons {
p.AthenaProfile.Items.RangeItems(func(id string, item *person.Item) bool {
aid.PrintJSON(item)
return true
})
aid.PrintJSON(p.Snapshot())
}
aid.WaitForExit()
// aid.WaitForExit()
}

View File

@ -1,6 +1,7 @@
package person
import (
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage"
"github.com/google/uuid"
)
@ -68,6 +69,41 @@ func FromDatabaseLoot(item *storage.DB_Loot) *Item {
}
}
func (i *Item) GenerateFortniteItemEntry() aid.JSON {
varaints := []aid.JSON{}
for _, variant := range i.Variants {
varaints = append(varaints, aid.JSON{
"channel": variant.Channel,
"owned": variant.Owned,
"active": variant.Active,
})
}
return aid.JSON{
"templateId": i.TemplateID,
"attributes": aid.JSON{
"variants": varaints,
"favorite": i.Favorite,
"item_seen": i.HasSeen,
},
"quantity": i.Quantity,
}
}
func (i *Item) GetAttribute(attribute string) interface{} {
switch attribute {
case "Favorite":
return i.Favorite
case "HasSeen":
return i.HasSeen
case "Variants":
return i.Variants
}
return nil
}
func (i *Item) Delete() {
//storage.Repo.DeleteItem(i.ID)
i.Quantity = 0

View File

@ -21,7 +21,7 @@ type Option struct {
func NewPerson() *Person {
return &Person{
ID: uuid.New().String(),
DisplayName: "Hello, Bully!",
DisplayName: uuid.New().String(),
AthenaProfile: NewProfile("athena"),
CommonCoreProfile: NewProfile("common_core"),
Loadout: NewLoadout(),

View File

@ -3,6 +3,7 @@ package person
import (
"fmt"
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage"
"github.com/google/uuid"
"github.com/r3labs/diff/v3"
@ -15,7 +16,8 @@ type Profile struct {
Quests *QuestMutex
Attributes *AttributeMutex
Type string
Changes []diff.Change
Revision int
Changes []interface{}
}
func NewProfile(profile string) *Profile {
@ -26,6 +28,7 @@ func NewProfile(profile string) *Profile {
Quests: NewQuestMutex(),
Attributes: NewAttributeMutex(),
Type: profile,
Revision: 0,
}
}
@ -64,6 +67,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile {
Quests: quests,
Attributes: attributes,
Type: profile.Type,
Revision: profile.Revision,
}
}
@ -92,8 +96,8 @@ func (p *Profile) Snapshot() *ProfileSnapshot {
return true
})
p.Attributes.RangeAttributes(func(key string, value *Attribute) bool {
attributes[key] = *value
p.Attributes.RangeAttributes(func(key string, attribute *Attribute) bool {
attributes[key] = *attribute
return true
})
@ -103,15 +107,97 @@ func (p *Profile) Snapshot() *ProfileSnapshot {
Gifts: gifts,
Quests: quests,
Attributes: attributes,
Type: p.Type,
Revision: p.Revision,
}
}
func (p *Profile) Diff(snapshot *ProfileSnapshot) []diff.Change {
changes, _ := diff.Diff(p.Snapshot(), snapshot)
p.Changes = changes
changes, err := diff.Diff(snapshot, p.Snapshot())
if err != nil {
fmt.Printf("error diffing profile: %v\n", err)
return nil
}
aid.PrintJSON(changes)
for _, change := range changes {
switch change.Path[0] {
case "Items":
if change.Type == "create" && change.Path[2] == "ID" {
p.CreateItemAddedChange(p.Items.GetItem(change.Path[1]))
}
if change.Type == "delete" && change.Path[2] == "ID" {
p.CreateItemRemovedChange(change.Path[1])
}
if change.Type == "update" && change.Path[2] == "Quantity" {
p.CreateItemQuantityChangedChange(p.Items.GetItem(change.Path[1]))
}
if change.Type == "update" && change.Path[2] != "Quantity" {
p.CreateItemAttributeChangedChange(p.Items.GetItem(change.Path[1]), change.Path[2])
}
}
}
return changes
}
func (p *Profile) CreateItemAddedChange(item *Item) {
if item == nil {
fmt.Println("error getting item from profile", item.ID)
return
}
p.Changes = append(p.Changes, ItemAdded{
ChangeType: "itemAdded",
ItemId: item.ID,
Item: item.GenerateFortniteItemEntry(),
})
}
func (p *Profile) CreateItemRemovedChange(itemId string) {
p.Changes = append(p.Changes, ItemRemoved{
ChangeType: "itemRemoved",
ItemId: itemId,
})
}
func (p *Profile) CreateItemQuantityChangedChange(item *Item) {
if item == nil {
fmt.Println("error getting item from profile", item.ID)
return
}
p.Changes = append(p.Changes, ItemQuantityChanged{
ChangeType: "itemQuantityChanged",
ItemId: item.ID,
Quantity: item.Quantity,
})
}
func (p *Profile) CreateItemAttributeChangedChange(item *Item, attribute string) {
if item == nil {
fmt.Println("error getting item from profile", item.ID)
return
}
lookup := map[string]string{
"Favorite": "favorite",
"HasSeen": "item_seen",
"Variants": "variants",
}
p.Changes = append(p.Changes, ItemAttributeChanged{
ChangeType: "itemAttributeChanged",
ItemId: item.ID,
AttributeName: lookup[attribute],
AttributeValue: item.GetAttribute(attribute),
})
}
type Loadout struct {
ID string
Character string
@ -134,8 +220,8 @@ func NewLoadout() *Loadout {
Backpack: "",
Pickaxe: "",
Glider: "",
Dances: []string{"", "", "", "", "", ""},
ItemWraps: []string{"", "", "", "", "", "", ""},
Dances: make([]string, 6),
ItemWraps: make([]string, 7),
LoadingScreen: "",
SkyDiveContrail: "",
MusicPack: "",
@ -144,20 +230,20 @@ func NewLoadout() *Loadout {
}
}
func FromDatabaseLoadout(loadout *storage.DB_Loadout) *Loadout {
func FromDatabaseLoadout(l *storage.DB_Loadout) *Loadout {
return &Loadout{
ID: loadout.ID,
Character: loadout.Character,
Backpack: loadout.Backpack,
Pickaxe: loadout.Pickaxe,
Glider: loadout.Glider,
Dances: loadout.Dances,
ItemWraps: loadout.ItemWraps,
LoadingScreen: loadout.LoadingScreen,
SkyDiveContrail: loadout.SkyDiveContrail,
MusicPack: loadout.MusicPack,
BannerIcon: loadout.BannerIcon,
BannerColor: loadout.BannerColor,
ID: l.ID,
Character: l.Character,
Backpack: l.Backpack,
Pickaxe: l.Pickaxe,
Glider: l.Glider,
Dances: l.Dances,
ItemWraps: l.ItemWraps,
LoadingScreen: l.LoadingScreen,
SkyDiveContrail: l.SkyDiveContrail,
MusicPack: l.MusicPack,
BannerIcon: l.BannerIcon,
BannerColor: l.BannerColor,
}
}

View File

@ -14,6 +14,8 @@ type ProfileSnapshot struct {
Gifts map[string]GiftSnapshot
Quests map[string]Quest
Attributes map[string]Attribute
Revision int
Type string
}
type ItemSnapshot struct {

View File

@ -35,6 +35,21 @@ func (m *ItemMutex) GetItem(id string) *Item {
return item.(*Item)
}
func (m *ItemMutex) GetItemByTemplateID(templateID string) *Item {
var item *Item
m.Range(func(key, value interface{}) bool {
if value.(*Item).TemplateID == templateID {
item = value.(*Item)
return false
}
return true
})
return item
}
func (m *ItemMutex) RangeItems(f func(key string, value *Item) bool) {
m.Range(func(key, value interface{}) bool {
return f(key.(string), value.(*Item))

View File

@ -11,12 +11,16 @@ Performance first, universal Fortnite backend written in Go.
## Examples
### Person Structure
```golang
user := person.NewPerson()
snapshot := user.AthenaProfile.Snapshot()
quest := person.NewQuest("Quest:Quest_1", "ChallengeBundle:Daily_1", "ChallengeBundleSchedule:Paid_1")
{
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)
@ -25,28 +29,39 @@ quest := person.NewQuest("Quest:Quest_1", "ChallengeBundle:Daily_1", "ChallengeB
quest.UpdateObjectiveCount("quest_objective_place_top10", -3)
quest.RemoveObjective("quest_objective_top1")
}
user.AthenaProfile.Quests.AddQuest(quest)
}
user.AthenaProfile.Quests.AddQuest(quest)
giftBox := person.NewGift("GiftBox:GB_Default", 1, user.ID, "Hello, Bully!")
{
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.CommonCoreProfile.Gifts.AddGift(giftBox)
currency := person.NewItem("Currency:MtxPurchased", 100)
user.CommonCoreProfile.Items.AddItem(currency)
user.Save()
user.AthenaProfile.Diff(snapshot)
```
### Profile Changes
```golang
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)
```
## What's next?
- Be able to convert my person structures into the required format for the game. This mainly targets the profiles and their changes.
- Be able to track my person profile structures changes, convert into the required responses for the game, and send back to the client.
- Implement the HTTP API for the game to communicate with the backend. This is the most important part of the project as it needs to handle thousands of requests per second. _Should I use Fiber?_
- Person Authentication for the game to determine if the person is valid or not. Fortnite uses JWT tokens for this which makes it easy to implement.
- Embed game assets into the backend e.g. Game XP Curve, Quest Data etc. _This would mean a single binary that can be run anywhere without the need of external files._
- Implement the HTTP API for the game to communicate with the backend. This is the most important part of the project as it needs to handle thousands of requests per second. _Should I use Fiber?_
- Interact with external Buckets to save player data externally.
- A way to interact with persons outside of the game. This is mainly for a web app and other services to interact with the backend.
- Game Server Communication. This would mean a websocket server that communicates with the game servers to send and receive data.

View File

@ -45,6 +45,7 @@ type DB_Profile struct {
Quests []DB_Quest `gorm:"foreignkey:ProfileID"`
Attributes []DB_PAttribute `gorm:"foreignkey:ProfileID"`
Type string
Revision int
}
func (DB_Profile) TableName() string {