aboutsummaryrefslogtreecommitdiff
path: root/internal/player
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-15 15:35:52 -0400
committerhistoria <[not public]>2026-09-15 15:35:52 -0400
commit0b0b25222b3131ada79ff78c28eb8bf07d977deb (patch)
tree2ceaef5203595044f66a8f00064cd8e017f8f48d /internal/player
parent396c3facb8287b9a34076cae8f3d7b300d366d99 (diff)
downloadthehouseoficarus-0b0b25222b3131ada79ff78c28eb8bf07d977deb.tar.gz
feat: single-owner game state, transactional lifecycle, durable money on server stop
Diffstat (limited to 'internal/player')
-rw-r--r--internal/player/player.go5
-rw-r--r--internal/player/store.go32
2 files changed, 26 insertions, 11 deletions
diff --git a/internal/player/player.go b/internal/player/player.go
index 81ef056..ae147b2 100644
--- a/internal/player/player.go
+++ b/internal/player/player.go
@@ -159,6 +159,11 @@ func GetOptionDef(name string) *OptionDef {
type Player struct {
Name string `yaml:"name"`
+ // ID is a stable identifier assigned at creation. Filenames are
+ // renameable; the ID is not, so cached references authorize by ID.
+ ID string `yaml:"id,omitempty"`
+ // Account is the owning account name (case-canonical at write time).
+ Account string `yaml:"account,omitempty"`
Skills map[SkillName]int `yaml:"skills"`
Inventory map[int]*InventorySlot `yaml:"inventory"`
Bank map[int]*InventorySlot `yaml:"bank"`
diff --git a/internal/player/store.go b/internal/player/store.go
index d0dbd0f..d461485 100644
--- a/internal/player/store.go
+++ b/internal/player/store.go
@@ -37,12 +37,17 @@ func (s *AccountStore) LoadAccount(name string) (*Account, error) {
}
func (s *AccountStore) SaveAccount(acc *Account) error {
- path := s.AccountPath(acc.Name)
- data, err := yaml.Marshal(acc)
+ data, err := s.MarshalAccount(acc)
if err != nil {
return err
}
- return persistence.WriteFile(path, data, 0600)
+ return persistence.WriteFile(s.AccountPath(acc.Name), data, 0600)
+}
+
+// MarshalAccount serializes an account; callers that own the account (the
+// game owner, or transaction code holding it) may use this directly.
+func (s *AccountStore) MarshalAccount(acc *Account) ([]byte, error) {
+ return yaml.Marshal(acc)
}
func (s *AccountStore) AccountExists(name string) bool {
@@ -103,9 +108,18 @@ func (s *AccountStore) LoadCharacter(name string) (*Player, error) {
}
func (s *AccountStore) SaveCharacter(p *Player) error {
- path := s.CharPath(p.Name)
- // Serialize a value copy so saving god-mode stats never temporarily
- // changes the live player. The caller still owns all referenced maps.
+ data, err := s.MarshalCharacter(p)
+ if err != nil {
+ return err
+ }
+ return persistence.WriteFile(s.CharPath(p.Name), data, 0600)
+}
+
+// MarshalCharacter serializes a character for a same-directory temporary
+// atomic write. It serializes a value snapshot so saving god-mode stats
+// never temporarily changes the live player; the caller still owns all
+// referenced maps (serialize on the owner goroutine).
+func (s *AccountStore) MarshalCharacter(p *Player) ([]byte, error) {
snapshot := *p
if snapshot.GodMode && snapshot.GodBackup != nil {
snapshot.Skills = snapshot.GodBackup
@@ -113,11 +127,7 @@ func (s *AccountStore) SaveCharacter(p *Player) error {
snapshot.HP = snapshot.MaxHP()
}
}
- data, err := yaml.Marshal(&snapshot)
- if err != nil {
- return err
- }
- return persistence.WriteFile(path, data, 0600)
+ return yaml.Marshal(&snapshot)
}
func (s *AccountStore) CharacterExists(name string) bool {