SaveData
The player's settings and the progress of three players, kept as key=value lines in savedata.txt.
Location
| Header | src/GalaxyEggbert/Game/SaveData.hpp |
|---|---|
| Sources | src/GalaxyEggbert/Game/SaveData.cpp; the folder it writes to: src/GalaxyEggbert/Game/UserFolder.{hpp,cpp} |
| Namespace | GalaxyEggbert::Game |
| Engine dependency | None. Standard library only (<fstream>, <array>); it can be tested without a window. |
Purpose
SaveData is the game's persistent profile. It holds the global settings of the Setup
screen (sound, volumes, controls, 3D terrain, 3D effects level, frame rate, texture size, full
screen) and three independent player slots, shown in the main menu as players A, B and C. Each
slot remembers the lives, the mission and world last played, whether there is progress to
resume, and 200 door flags: one per mission number, set when that mission has been won, which
is what opens the hubs' mission bars and the main hub's golden posts.
The original game stored a fixed 640-byte binary blob. Galaxy Eggbert deliberately does not copy
that layout: savedata.txt is plain text, one key=value per line, readable
and hand-editable. Each player's own galaxy has a second, separate
progress record (galaxyN.* keys), so playing a galaxy never touches the campaign's
doors and lives.
Collaborators
Uses
UserFolder(UserPathString("savedata.txt")) for the file's path.
Used by
GalaxyEggbertGame(membersaveData_): loads it in its constructor (the full-screen preference is needed before the window opens), checkpoints progress at Win and Lost, stores the mission on everyLoadMission, applies the Setup's toggles, and reads the door flags when it adapts a hub's doors (HubDoors).- InputPad: the Setup and gamer-select screens report which toggle or
slider changed; the host writes it into
SaveData. - ResumeLog and DemoReplay: a resume log
and a demo carry the output of
Write()as their start settings and feed it back throughRead(). TextureScale.hppreadsGetTextureScale()at start.
Key members
A selection of the public API declared in src/GalaxyEggbert/Game/SaveData.hpp. Most
setters have a matching getter that is not listed separately.
| Member | Signature | What it does |
|---|---|---|
kGamerCount | static constexpr int kGamerCount = 3 | Number of player slots. |
Load | void Load() | Reads savedata.txt from the user folder. A missing or unreadable file leaves every field at its default. |
Save | void Save() const | Overwrites savedata.txt with the current state. |
Read / Write | void Read(std::istream& in)void Write(std::ostream& out) const | The same text format on any stream; used for the file and for the settings carried inside resume logs and demos. |
Reset | void Reset() noexcept | Every field back to its default, all three slots included (the galaxy mode flag is kept). The host then saves. |
SetSelectedGamer | void SetSelectedGamer(int gamer) noexcept | Chooses the slot the progress accessors work on; clamped to 0..2. |
SetLives, SetMissionNumber, SetLastWorld, SetHasProgress | void SetLives(int lives) noexcept (and similar) | Progress of the selected slot, or of its galaxy record while galaxy mode is on. |
UnlockMissionDoor | void UnlockMissionDoor(int mission) noexcept | Marks a mission won; the index is the mission number (0..199). Out-of-range numbers are ignored. |
IsMissionDoorUnlocked | bool IsMissionDoorUnlocked(int mission) const noexcept | Whether that door is open for the current slot. |
GetMainGatesForGamer, GetSecondaryGatesForGamer | int GetMainGatesForGamer(int gamer) const noexcept | Counts for the menu's player panel: doors 180..199 are main gates, 0..179 secondary. |
SetGalaxyMode | void SetGalaxyMode(bool on) noexcept | While on, the progress accessors read and write the selected player's galaxy record. The mode itself is never saved. |
GalaxyProgress, GetGalaxyGatesForGamer, ResetGalaxyProgress | const GamerSlot& GalaxyProgress(int gamer) const noexcept | Galaxy progress of any slot, whatever the mode; reset when a galaxy is imported in place of the old one. |
SetEffectsLevel | void SetEffectsLevel(int level) noexcept | 3D effects 0..4 (Off, Low, Medium, High, Ultra), clamped. |
SetFrameRate, NextFrameRate | void SetFrameRate(int rate) noexceptstatic int NextFrameRate(int rate) noexcept | One of kFrameRates = {60, 120, 144, 240}; any other value becomes 60. |
SetTextureScale | void SetTextureScale(int scale) noexcept | 0 auto, 1 or 2; anything else is auto. Applies at the next start. |
SetEffectsVolume, SetMusicVolume, SetAccelSensitivity | void SetEffectsVolume(int volume) noexcept | 0..100, clamped by ClampVolume. |
The file: savedata.txt
Write() produces the lines in this order (shown here with the default values; the door
strings are 200 characters of 0/1 and are shortened):
soundEnabled=1
jumpRight=1
autoZoom=1
accelActive=0
effectsVolume=100
musicVolume=100
accelSensitivity=50
fullscreen=0
terrainRelief=0
effectsLevel=0
frameRate=60
textureScale=0
selectedGamer=0
gamer0.lives=3
gamer0.missionNumber=0
gamer0.lastWorld=1
gamer0.hasProgress=0
gamer0.doorsUnlocked=0000...0000
gamer1.lives=3
...
gamer2.doorsUnlocked=0000...0000
| Key | Values (default) | Meaning |
|---|---|---|
soundEnabled | 0/1 (1) | Sounds on or off. |
jumpRight, autoZoom, accelActive | 0/1 (1, 1, 0) | The original's Setup toggles: which side the on-screen action buttons sit on, automatic zoom, accelerometer. |
effectsVolume, musicVolume | 0..100 (100) | The Setup's volume sliders (a Galaxy addition; 100 is the original loudness). |
accelSensitivity | 0..100 (50) | Accelerometer sensitivity. |
fullscreen | 0/1 (0) | Desktop window mode; F11 and the menu's Fullscreen / Window button store it. |
terrainRelief | 0/1 (0) | The Setup's "3D terrain". |
effectsLevel | 0..4 (0) | The Setup's "3D effects". An older key effects3d=1 is still read, as level 3 (High). |
frameRate | 60, 120, 144, 240 (60) | Frames drawn per second; the simulation still steps 60 times a second. |
textureScale | 0, 1, 2 (0) | The Setup's "Textures": Auto, 1x, 2x. |
selectedGamer | 0..2 (0) | The player chosen in the main menu. |
gamerN.lives, .missionNumber, .lastWorld, .hasProgress, .doorsUnlocked | N = 0..2 | Campaign progress of player N (lives 3, mission 0, last world 1 by default). |
galaxyN.* | same fields | Progress in player N's own galaxy. Written only once that record differs from the default, so a file of a player who never played a galaxy has no such lines. |
Reading is tolerant: lines without = and unknown keys are skipped, an out-of-range
selectedGamer is ignored, volumes are clamped, and an unknown frame rate falls back to 60.
Where the file lives
Since 2026-11-10 the player's files (savedata.txt, resume.txt,
demos/, customworlds/) live in a per-user folder chosen at start
(ChooseUserFolder() in src/GalaxyEggbertCNA/main.cpp):
- Desktop: CNA's storage root for "Galaxy Eggbert", i.e.
$XDG_DATA_HOMEor~/.local/share/Galaxy Eggberton Linux and%LOCALAPPDATA%\Galaxy Eggberton Windows.--user DIRor the environment variableGALAXY_USER_DIRoverrides it (the test scripts use their run folder). - Web:
/save, the browser-persisted (IDBFS) mount. - Android: the app's private storage (its working folder).
A player of an earlier build, which kept these files in the working folder, has them copied
(never moved or overwritten) the first time, when the new folder has no savedata.txt yet
(CopyOldUserFiles()).
How it is used
The host never calls Save() directly but goes through
GalaxyEggbertGame::SavePlayerData(), which refuses to write while a resume log is being
reconstructed or a main-menu demo plays: both run the normal simulation with someone else's
settings, and neither may change the player's profile. The checkpoint at the end of a mission
looks like this (src/GalaxyEggbertCNA/GalaxyEggbertGameUpdate.cpp):
if (interaction_.GameOverCount() > gameOverCountBeforeUpdate)
{
// Real MemorizeGamerProgress() checkpoint ...
if (!editorPlayTestActive_)
{
saveData_.SetLives(interaction_.Lives());
saveData_.SetMissionNumber(worldRuntime_.GetMissionNumber());
saveData_.SetHasProgress(true);
SavePlayerData();
}
SetPhase(GalaxyEggbert::Def::GamePhase::Lost);
}
At start-up the Resume screen is offered when resume.txt holds a valid
resume log or when the selected slot's hasProgress is set;
the log wins, the checkpoint (mission number and lives) is the fallback.
Rules and invariants
- Settings are global; progress is per slot. The single-slot accessors (
GetLives()...) always act ongamers_[selectedGamer_], or on the galaxy record in galaxy mode, which is whySetSelectedGamer()andRead()both keep the index in range. - An editor play-test never writes progress: a custom world has no real mission number.
- Door flags are indexed by mission number directly, as the original's
m_doors[]. - The text format is the contract shared with resume logs and demos: they embed the full
Write()output, so adding a key is safe (old readers skip it), renaming one is not.
Tests
VerifyGESaveData(tools/VerifyGESaveData.cpp): defaults without a file, round trips of every setting (full screen, sound, 3D terrain, the Setup toggles, volumes and their clamping),Reset(), progress and door flags, three independent slots, rejection of an out-of-rangeselectedGamer, and the galaxy records (nogalaxylines until a galaxy is played, campaign and galaxy progress kept apart, gate counts). It runs in its own directory of the build tree (verify-savedata/) because it creates and deletessavedata.txtwhere it runs.
Related pages
- Save Data & Resume
- ResumeLog, DemoReplay
- GalaxyEggbertGame, InputPad, HubDoors
- CustomWorldStorage (the galaxy's worlds; its progress is here)
- Configuration, Your Own Galaxy