Add other locker (s12+)

This commit is contained in:
Eccentric 2024-02-03 02:33:54 +00:00
parent b3394099a8
commit c33abe7d6f
7 changed files with 258 additions and 38 deletions

View File

@ -138,7 +138,7 @@ func NewFortnitePersonWithId(id string, displayName string, everything bool) *p.
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("allowed_to_send_gifts", true)).Save()
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("gift_history", aid.JSON{})).Save()
loadout := p.NewLoadout("sandbox_loadout", person.AthenaProfile)
loadout := p.NewLoadoutWithID("sandbox_loadout", "", person.AthenaProfile)
person.AthenaProfile.Loadouts.AddLoadout(loadout).Save()
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("loadouts", []string{loadout.ID})).Save()
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("last_applied_loadout", loadout.ID)).Save()

View File

@ -9,6 +9,7 @@ import (
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/fortnite"
p "github.com/ectrc/snow/person"
"github.com/google/uuid"
"github.com/gofiber/fiber/v2"
)
@ -23,6 +24,9 @@ var (
"SetBattleRoyaleBanner": clientSetBattleRoyaleBannerAction,
"SetCosmeticLockerSlot": clientSetCosmeticLockerSlotAction,
"SetCosmeticLockerBanner": clientSetCosmeticLockerBannerAction,
"SetCosmeticLockerName": clientSetCosmeticLockerNameAction,
"CopyCosmeticLoadout": clientCopyCosmeticLoadoutAction,
"DeleteCosmeticLoadout": clientDeleteCosmeticLoadoutAction,
"PurchaseCatalogEntry": clientPurchaseCatalogEntryAction,
"GiftCatalogEntry": clientGiftCatalogEntryAction,
"RemoveGiftBox": clientRemoveGiftBoxAction,
@ -188,8 +192,8 @@ func clientEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person,
default:
attr.ValueJSON = aid.JSONStringify(item.ID)
}
go attr.Save()
return nil
}
@ -225,11 +229,9 @@ func clientSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *
iconAttr.ValueJSON = aid.JSONStringify(strings.Split(iconItem.TemplateID, ":")[1])
colorAttr.ValueJSON = aid.JSONStringify(strings.Split(colorItem.TemplateID, ":")[1])
go func() {
iconAttr.Save()
colorAttr.Save()
}()
return nil
}
@ -321,6 +323,7 @@ func clientSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *
}
go currentLocker.Save()
return nil
}
@ -351,11 +354,194 @@ func clientSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile
if currentLocker == nil {
return fmt.Errorf("current locker not found")
}
currentLocker.BannerColorID = color.ID
currentLocker.BannerID = icon.ID
go currentLocker.Save()
return nil
}
func clientSetCosmeticLockerNameAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
LockerItem string `json:"lockerItem" binding:"required"`
Name string `json:"name" binding:"required"`
}
if err := c.BodyParser(&body); err != nil {
return fmt.Errorf("invalid Body")
}
loadoutsAttribute := profile.Attributes.GetAttributeByKey("loadouts")
if loadoutsAttribute == nil {
return fmt.Errorf("loadouts not found")
}
loadouts := p.AttributeConvertToSlice[string](loadoutsAttribute)
currentLocker := profile.Loadouts.GetLoadout(body.LockerItem)
if currentLocker == nil {
return fmt.Errorf("current locker not found")
}
if loadouts[0] == currentLocker.ID {
return fmt.Errorf("cannot rename default locker")
}
currentLocker.LockerName = body.Name
go currentLocker.Save()
return nil
}
func clientCopyCosmeticLoadoutAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
OptNewNameForTarget string `json:"optNewNameForTarget" binding:"required"`
SourceIndex int `json:"sourceIndex" binding:"required"`
TargetIndex int `json:"targetIndex" binding:"required"`
}
if err := c.BodyParser(&body); err != nil {
return fmt.Errorf("invalid Body")
}
lastAppliedLoadoutAttribute := profile.Attributes.GetAttributeByKey("last_applied_loadout")
if lastAppliedLoadoutAttribute == nil {
return fmt.Errorf("last_applied_loadout not found")
}
activeLoadoutIndexAttribute := profile.Attributes.GetAttributeByKey("active_loadout_index")
if activeLoadoutIndexAttribute == nil {
return fmt.Errorf("active_loadout_index not found")
}
loadoutsAttribute := profile.Attributes.GetAttributeByKey("loadouts")
if loadoutsAttribute == nil {
return fmt.Errorf("loadouts not found")
}
loadouts := p.AttributeConvertToSlice[string](loadoutsAttribute)
if body.SourceIndex >= len(loadouts) {
return fmt.Errorf("source index out of range")
}
sandboxLoadout := profile.Loadouts.GetLoadout("sandbox_loadout")
if sandboxLoadout == nil {
return fmt.Errorf("sandbox loadout not found")
}
lastAppliedLoadout := profile.Loadouts.GetLoadout(aid.JSONParse(lastAppliedLoadoutAttribute.ValueJSON).(string))
if lastAppliedLoadout == nil {
return fmt.Errorf("last applied loadout not found")
}
if body.TargetIndex >= len(loadouts) {
clone := lastAppliedLoadout.Copy()
clone.ID = uuid.New().String()
clone.LockerName = body.OptNewNameForTarget
profile.Loadouts.AddLoadout(&clone).Save()
lastAppliedLoadout.CopyFrom(sandboxLoadout)
go lastAppliedLoadout.Save()
sandboxLoadout.CopyFrom(&clone)
go sandboxLoadout.Save()
loadouts = append(loadouts, clone.ID)
loadoutsAttribute.ValueJSON = aid.JSONStringify(loadouts)
go loadoutsAttribute.Save()
lastAppliedLoadoutAttribute.ValueJSON = aid.JSONStringify(clone.ID)
activeLoadoutIndexAttribute.ValueJSON = aid.JSONStringify(body.TargetIndex)
go lastAppliedLoadoutAttribute.Save()
go activeLoadoutIndexAttribute.Save()
return nil
}
if body.SourceIndex > 0 {
sourceLoadout := profile.Loadouts.GetLoadout(loadouts[body.SourceIndex])
if sourceLoadout == nil {
return fmt.Errorf("target loadout not found")
}
sandboxLoadout.CopyFrom(sourceLoadout)
go sandboxLoadout.Save()
lastAppliedLoadoutAttribute.ValueJSON = aid.JSONStringify(sourceLoadout.ID)
activeLoadoutIndexAttribute.ValueJSON = aid.JSONStringify(body.SourceIndex)
go lastAppliedLoadoutAttribute.Save()
go activeLoadoutIndexAttribute.Save()
return nil
}
activeLoadout := profile.Loadouts.GetLoadout(loadouts[body.TargetIndex])
if activeLoadout == nil {
return fmt.Errorf("target loadout not found")
}
sandboxLoadout.CopyFrom(activeLoadout)
go sandboxLoadout.Save()
if len(profile.Changes) == 0{
// dance ids and item wrap ids are registered as changes in the client so force a fix
profile.CreateLoadoutChangedChange(sandboxLoadout, "DanceID")
}
return nil
}
func clientDeleteCosmeticLoadoutAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct {
FallbackLoadoutIndex int `json:"fallbackLoadoutIndex" binding:"required"`
LoadoutIndex int `json:"index" binding:"required"`
}
if err := c.BodyParser(&body); err != nil {
return fmt.Errorf("invalid Body")
}
lastAppliedLoadoutAttribute := profile.Attributes.GetAttributeByKey("last_applied_loadout")
if lastAppliedLoadoutAttribute == nil {
return fmt.Errorf("last_applied_loadout not found")
}
activeLoadoutIndexAttribute := profile.Attributes.GetAttributeByKey("active_loadout_index")
if activeLoadoutIndexAttribute == nil {
return fmt.Errorf("active_loadout_index not found")
}
loadoutsAttribute := profile.Attributes.GetAttributeByKey("loadouts")
if loadoutsAttribute == nil {
return fmt.Errorf("loadouts not found")
}
loadouts := p.AttributeConvertToSlice[string](loadoutsAttribute)
if body.LoadoutIndex >= len(loadouts) {
return fmt.Errorf("loadout index out of range")
}
if body.LoadoutIndex == 0 {
return fmt.Errorf("cannot delete default loadout")
}
if body.FallbackLoadoutIndex == -1 {
body.FallbackLoadoutIndex = 0
}
fallbackLoadout := profile.Loadouts.GetLoadout(loadouts[body.FallbackLoadoutIndex])
if fallbackLoadout == nil {
return fmt.Errorf("fallback loadout not found")
}
lastAppliedLoadoutAttribute.ValueJSON = aid.JSONStringify(fallbackLoadout.ID)
activeLoadoutIndexAttribute.ValueJSON = aid.JSONStringify(body.FallbackLoadoutIndex)
lastAppliedLoadoutAttribute.Save()
activeLoadoutIndexAttribute.Save()
profile.Loadouts.DeleteLoadout(loadouts[body.LoadoutIndex])
loadouts = append(loadouts[:body.LoadoutIndex], loadouts[body.LoadoutIndex+1:]...)
loadoutsAttribute.ValueJSON = aid.JSONStringify(loadouts)
loadoutsAttribute.Save()
return nil
}
@ -394,12 +580,9 @@ func clientPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p
}
vbucks.Quantity -= body.ExpectedTotalPrice
go func() {
profile0Vbucks.Quantity = vbucks.Quantity
vbucks.Save()
profile0Vbucks.Save()
}()
if offer.ProfileType != "athena" {
return fmt.Errorf("save the world not implemeted yet")
@ -481,12 +664,9 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
}
vbucks.Quantity -= price
go func() {
profile0Vbucks.Quantity = price
vbucks.Save()
profile0Vbucks.Save()
}()
for _, receiverAccountId := range body.ReceiverAccountIds {
receiverPerson := p.Find(receiverAccountId)
@ -501,8 +681,7 @@ func clientGiftCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Pro
gift.AddLoot(item)
}
receiverPerson.CommonCoreProfile.Gifts.AddGift(gift)
receiverPerson.CommonCoreProfile.Save()
receiverPerson.CommonCoreProfile.Gifts.AddGift(gift).Save()
}
return nil

17
main.go
View File

@ -45,14 +45,19 @@ func init() {
fortnite.GenerateRandomStorefront()
fortnite.GeneratePlaylistImages()
if found := person.FindByDisplay("god"); found == nil {
god := fortnite.NewFortnitePersonWithId("god", "god", true)
god.AddPermission("all")
angel := fortnite.NewFortnitePersonWithId("angel", "angel", true)
angel.AddPermission("all")
for _, username := range aid.Config.Accounts.Gods {
found := person.FindByDisplay(username)
if found == nil {
found = fortnite.NewFortnitePersonWithId(username, username, true)
}
for _, perm := range found.Permissions {
found.RemovePermission(perm)
}
found.AddPermission("all")
aid.Print("(snow) max account " + username + " loaded")
}
}
func main() {
r := fiber.New(fiber.Config{

View File

@ -55,3 +55,17 @@ func (a *Attribute) Save() {
}
storage.Repo.SaveAttribute(a.ToDatabase(a.ProfileID))
}
func AttributeConvertToSlice[T any](attribute *Attribute) []T {
valuesRaw := aid.JSONParse(attribute.ValueJSON).([]interface{})
values := make([]T, len(valuesRaw))
for i, value := range valuesRaw {
values[i] = value.(T)
}
return values
}
func AttributeConvert[T any](attribute *Attribute) T {
return aid.JSONParse(attribute.ValueJSON).(T)
}

View File

@ -68,7 +68,8 @@ func NewLoadout(name string, athena *Profile) *Loadout {
return &Loadout{
ID: uuid.New().String(),
PersonID: athena.ID,
PersonID: athena.PersonID,
ProfileID: athena.ID,
TemplateID: "CosmeticLocker:CosmeticLocker_Athena",
LockerName: name,
CharacterID: aid.JSONParse(character.ValueJSON).(string),
@ -85,6 +86,12 @@ func NewLoadout(name string, athena *Profile) *Loadout {
}
}
func NewLoadoutWithID(id string, name string, athena *Profile) *Loadout {
loadout := NewLoadout(name, athena)
loadout.ID = id
return loadout
}
func FromDatabaseLoadout(loadout *storage.DB_Loadout) *Loadout {
return &Loadout{
ID: loadout.ID,
@ -229,14 +236,7 @@ func (l *Loadout) GetItemsSlotData(itemIds []string) aid.JSON {
items := json["items"].([]string)
items[pos] = item.TemplateID
activeVariants := json["activeVariants"].([]aid.JSON)
activeVariants[pos] = aid.JSON{
"variants": []aid.JSON{},
}
json["items"] = items
json["activeVariants"] = activeVariants
}
return json
@ -269,3 +269,24 @@ func (l *Loadout) ToDatabase(profileId string) *storage.DB_Loadout {
func (q *Loadout) Save() {
storage.Repo.SaveLoadout(q.ToDatabase(q.ProfileID))
}
func (l *Loadout) Copy() Loadout {
return *l
}
func (l *Loadout) CopyFrom(loadout *Loadout) {
l.ProfileID = loadout.ProfileID
l.BannerID = loadout.BannerID
l.BannerColorID = loadout.BannerColorID
l.CharacterID = loadout.CharacterID
l.PickaxeID = loadout.PickaxeID
l.BackpackID = loadout.BackpackID
l.GliderID = loadout.GliderID
copy(l.DanceID, loadout.DanceID)
copy(l.ItemWrapID, loadout.ItemWrapID)
l.ContrailID = loadout.ContrailID
l.LoadingScreenID = loadout.LoadingScreenID
l.MusicPackID = loadout.MusicPackID
l.Save()
}

View File

@ -255,11 +255,11 @@ func (m *LoadoutMutex) AddLoadout(loadout *Loadout) *Loadout {
loadout.PersonID = m.PersonID
loadout.ProfileID = m.ProfileID
m.Store(loadout.ID, loadout)
// storage.Repo.SaveLoadout(loadout.ToDatabase(m.ProfileID))
storage.Repo.SaveLoadout(loadout.ToDatabase(m.ProfileID))
return loadout
}
func (m *LoadoutMutex) DeleteItem(id string) {
func (m *LoadoutMutex) DeleteLoadout(id string) {
loadout := m.GetLoadout(id)
if loadout == nil {
return

View File

@ -13,7 +13,7 @@ Performance first, universal Fortnite private server backend written in Go.
## What's next?
- More profile actions like `RefundMtxPurchase` and `CopyCosmeticLoadout`.
- More profile actions like `RefundMtxPurchase` and more.
- 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).
@ -23,6 +23,7 @@ Performance first, universal Fortnite private server backend written in Go.
### Supported
- **_Chapter 1 Season 2_** `Fortnite+Release-2.5-CL-3889387-Windows`
- **_Chapter 1 Season 3_** `Fortnite+Release-3.6-CL-4019403-Windows`
- **_Chapter 1 Season 5_** `Fortnite+Release-5.41-CL-4363240-Windows`
- **_Chapter 1 Season 8_** `Fortnite+Release-8.51-CL-6165369-Windows`
- **_Chapter 2 Season 2_** `Fortnite+Release-12.41-CL-12905909-Windows`