package player import ( "fmt" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" "thehouseoficarus/internal/item" ) type AccountStore struct { dataDir string } func NewAccountStore(dataDir string) *AccountStore { return &AccountStore{dataDir: dataDir} } func (s *AccountStore) AccountPath(name string) string { return filepath.Join(s.dataDir, "players", "accounts", name+".yaml") } func (s *AccountStore) LoadAccount(name string) (*Account, error) { path := s.AccountPath(name) data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("account not found: %s", name) } var acc Account if err := yaml.Unmarshal(data, &acc); err != nil { return nil, fmt.Errorf("parse account: %w", err) } return &acc, nil } func (s *AccountStore) SaveAccount(acc *Account) error { path := s.AccountPath(acc.Name) data, err := yaml.Marshal(acc) if err != nil { return err } return os.WriteFile(path, data, 0644) } func (s *AccountStore) AccountExists(name string) bool { _, err := s.FindAccount(name) return err == nil } func (s *AccountStore) FindAccount(name string) (string, error) { path := s.AccountPath(name) if _, err := os.Stat(path); err == nil { return name, nil } dir := filepath.Join(s.dataDir, "players", "accounts") entries, err := os.ReadDir(dir) if err != nil { return "", fmt.Errorf("account not found: %s", name) } lower := strings.ToLower(name) for _, e := range entries { if e.IsDir() { continue } base := strings.TrimSuffix(e.Name(), ".yaml") if strings.ToLower(base) == lower { return base, nil } } return "", fmt.Errorf("account not found: %s", name) } func (s *AccountStore) CharPath(name string) string { return filepath.Join(s.dataDir, "players", "characters", name+".yaml") } func (s *AccountStore) LoadCharacter(name string) (*Player, error) { path := s.CharPath(name) data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("character not found: %s", name) } var p Player if err := yaml.Unmarshal(data, &p); err != nil { return nil, fmt.Errorf("parse character: %w", err) } if p.Skills == nil { p.Skills = make(map[SkillName]int) } if p.Equipment == nil { p.Equipment = make(map[item.EquipSlot]string) } if p.Inventory == nil { p.Inventory = make(map[int]*InventorySlot) } if p.MapSymbols == nil { p.MapSymbols = make(map[int]MapSymbolData) } return &p, nil } func (s *AccountStore) SaveCharacter(p *Player) error { path := s.CharPath(p.Name) if p.GodMode && p.GodBackup != nil { buffedSkills := p.Skills buffedHP := p.HP p.Skills = p.GodBackup if p.HP > p.MaxHP() { p.HP = p.MaxHP() } defer func() { p.Skills = buffedSkills p.HP = buffedHP }() } data, err := yaml.Marshal(p) if err != nil { return err } return os.WriteFile(path, data, 0644) } func (s *AccountStore) CharacterExists(name string) bool { _, err := s.FindCharacter(name) return err == nil } func (s *AccountStore) FindCharacter(name string) (string, error) { path := s.CharPath(name) if _, err := os.Stat(path); err == nil { return name, nil } dir := filepath.Join(s.dataDir, "players", "characters") entries, err := os.ReadDir(dir) if err != nil { return "", fmt.Errorf("character not found: %s", name) } lower := strings.ToLower(name) for _, e := range entries { if e.IsDir() { continue } base := strings.TrimSuffix(e.Name(), ".yaml") if strings.ToLower(base) == lower { return base, nil } } return "", fmt.Errorf("character not found: %s", name) } // ListCharacterNames returns the stem (character name) of every // data/players/characters/*.yaml file. Used by room-ID migration to walk and // rewrite offline characters. Online characters are excluded by the caller // (their in-memory state is updated directly). func (s *AccountStore) ListAccountNames() ([]string, error) { dir := filepath.Join(s.dataDir, "players", "accounts") entries, err := os.ReadDir(dir) if err != nil { return nil, err } var names []string for _, e := range entries { if e.IsDir() { continue } name := strings.TrimSuffix(e.Name(), ".yaml") if name == "" { continue } names = append(names, name) } return names, nil } func (s *AccountStore) ListCharacterNames() ([]string, error) { dir := filepath.Join(s.dataDir, "players", "characters") entries, err := os.ReadDir(dir) if err != nil { return nil, err } var names []string for _, e := range entries { if e.IsDir() { continue } name := strings.TrimSuffix(e.Name(), ".yaml") if name == "" { continue } names = append(names, name) } return names, nil }