package save import ( "fmt" "os" "path/filepath" "time" "github.com/BurntSushi/toml" ) // File path for save data. const ( dataFile = "data.toml" ) // Stores all persistent game data in a single file. type Data struct { Settings Settings `toml:"settings"` GameState GameState `toml:"game_state"` } // Stores user preferences. type Settings struct { FPSMonitor bool `toml:"fps_monitor"` FPSCap string `toml:"fps_cap"` // "60", "120", or "uncapped" } // Stores the current game progress. type GameState struct { HasSave bool `toml:"has_save"` SavedAt time.Time `toml:"saved_at"` HeroX float64 `toml:"hero_x"` HeroY float64 `toml:"hero_y"` HeroStamina float64 `toml:"hero_stamina"` PlayTimeMS int64 `toml:"play_time_ms"` // Total play time in milliseconds } // Handles saving and loading of settings and game state. type Manager struct { dataPath string } // Creates a new save manager. func NewManager() (*Manager, error) { // Get executable path exePath, err := os.Executable() if err != nil { return nil, fmt.Errorf("failed to get executable path: %w", err) } // Get directory containing the executable exeDir := filepath.Dir(exePath) return &Manager{ dataPath: filepath.Join(exeDir, dataFile), }, nil } // Loads all data from disk. func (m *Manager) LoadData() (*Data, error) { // Check if file exists if _, err := os.Stat(m.dataPath); os.IsNotExist(err) { // Return default data return &Data{ Settings: Settings{ FPSMonitor: false, FPSCap: "60", }, GameState: GameState{ HasSave: false, }, }, nil } var data Data if _, err := toml.DecodeFile(m.dataPath, &data); err != nil { return nil, fmt.Errorf("failed to parse data file: %w", err) } return &data, nil } // Writes all data to disk. func (m *Manager) SaveData(data *Data) error { file, err := os.Create(m.dataPath) if err != nil { return fmt.Errorf("failed to create data file: %w", err) } defer file.Close() encoder := toml.NewEncoder(file) if err := encoder.Encode(data); err != nil { return fmt.Errorf("failed to write data file: %w", err) } return nil } // Loads user settings from disk. func (m *Manager) LoadSettings() (*Settings, error) { data, err := m.LoadData() if err != nil { return nil, err } return &data.Settings, nil } // Writes user settings to disk. func (m *Manager) SaveSettings(settings *Settings) error { data, err := m.LoadData() if err != nil { // If load fails, create new data with these settings data = &Data{ Settings: *settings, GameState: GameState{ HasSave: false, }, } } else { data.Settings = *settings } return m.SaveData(data) } // Loads game state from disk. func (m *Manager) LoadGameState() (*GameState, error) { data, err := m.LoadData() if err != nil { return nil, err } if !data.GameState.HasSave { return nil, nil // No save exists } return &data.GameState, nil } // Writes game state to disk. func (m *Manager) SaveGameState(state *GameState) error { data, err := m.LoadData() if err != nil { // If load fails, create new data with this game state data = &Data{ Settings: Settings{ FPSMonitor: false, FPSCap: "60", }, GameState: *state, } } else { data.GameState = *state } data.GameState.HasSave = true data.GameState.SavedAt = time.Now() return m.SaveData(data) } // Checks if a saved game exists. func (m *Manager) HasSavedGame() bool { data, err := m.LoadData() if err != nil { return false } return data.GameState.HasSave } // Removes the saved game. func (m *Manager) DeleteGameState() error { data, err := m.LoadData() if err != nil { return err } data.GameState = GameState{ HasSave: false, } return m.SaveData(data) } // Returns the path to the save file. func (m *Manager) GetSaveFilePath() string { return m.dataPath }