#include "save.h" #include #include #include #ifdef SDL_PLATFORM_EMSCRIPTEN #include #endif constexpr const char* KEY_DISCOVERED = "discovered"; constexpr const char* KEY_ITEM = "item"; constexpr const char* SETTINGS_FILE = "settings.txt"; static void value_read(const char* text, float& value) { value = (float)SDL_atof(text); } static void value_read(const char* text, int& value) { value = SDL_atoi(text); } static void value_read(const char* text, bool& value) { value = SDL_atoi(text); } static void value_read(const char* text, Measurement& value) { value = (Measurement)SDL_atoi(text); } static void value_read(const char* text, std::string& value) { value = text; } static void value_read(const char* text, Vec2& value) { SDL_sscanf(text, "%f %f", &value.x, &value.y); } static void value_read(const char* text, Color& value) { SDL_sscanf(text, "%f %f %f", &value.r, &value.g, &value.b); } static std::string value_write(float value) { return std::to_string(value); } static std::string value_write(int value) { return std::to_string(value); } static std::string value_write(const std::string& value) { return value; } static std::string value_write(Vec2 value) { return value_write(value.x) + " " + value_write(value.y); } static std::string value_write(Color value) { return value_write(value.r) + " " + value_write(value.g) + " " + value_write(value.b); } // Calls line with each key and value of a key=value text file. Returns whether it was there. template static bool lines_read(const std::string& path, Line&& line) { char* text = (char*)SDL_LoadFile(path.c_str(), nullptr); if (!text) return false; char* rest = nullptr; for (char* entry = SDL_strtok_r(text, "\r\n", &rest); entry; entry = SDL_strtok_r(nullptr, "\r\n", &rest)) { char* value = SDL_strchr(entry, '='); if (!value) continue; *value++ = '\0'; line(std::string(entry), value); } SDL_free(text); return true; } static void file_write(const std::string& path, const std::string& text) { SDL_CreateDirectory(path.substr(0, path.find_last_of('/')).c_str()); if (!SDL_SaveFile(path.c_str(), text.data(), text.size())) SDL_Log("Failed to save %s: %s", path.c_str(), SDL_GetError()); #ifdef SDL_PLATFORM_EMSCRIPTEN EM_ASM(FS.syncfs(false, () => {})); #endif } // Reads every field a visitable struct names from a key=value file, and writes them back. template static bool fields_read(const std::string& path, Fields& fields) { return lines_read(path, [&](const std::string& key, const char* value) { fields.visit([&](const char* name, auto& field) { if (key == name) value_read(value, field); }); }); } template static std::string fields_write(Fields& fields) { std::string text; fields.visit([&](const char* name, auto& field) { using Type = std::decay_t; if constexpr (std::is_same_v || std::is_enum_v) text += std::string(name) + "=" + value_write((int)field) + "\n"; else text += std::string(name) + "=" + value_write(field) + "\n"; }); return text; } template void Settings::visit(Visitor&& visitor) { visitor("character_color", isCharacterColor); visitor("color", color); visitor("console", isConsole); visitor("height", height); visitor("last_character", lastCharacter); visitor("measurement", measurement); visitor("mute", isMuted); visitor("volume", volume); visitor("width", width); visitor("x", x); visitor("y", y); } void Settings::load() { fields_read(pref_path_get(SETTINGS_FILE), *this); } void Settings::save() { file_write(pref_path_get(SETTINGS_FILE), fields_write(*this)); } // Puts back the defaults of what the settings menu shows. void Settings::reset() { Settings defaults; color = defaults.color; measurement = defaults.measurement; volume = defaults.volume; isCharacterColor = defaults.isCharacterColor; isMuted = defaults.isMuted; } template void Progress::visit(Visitor&& visitor) { visitor("alternate", isAlternate); visitor("calories", calories); visitor("capacity", capacity); visitor("digesting", isDigesting); visitor("digestion_progress", digestionProgress); visitor("digestion_rate", digestionRate); visitor("digestion_timer", digestionTimer); visitor("pan", pan); visitor("playtime", playtime); visitor("postgame", isPostgame); visitor("spawn_delay", spawnDelay); visitor("total_calories", totalCalories); visitor("total_consumed", totalConsumed); visitor("total_spawned", totalSpawned); visitor("weight", weight); visitor("zoom", zoom); } // Items are kept one per line as their position, velocity, rotation and then name. void Progress::load(const std::string& path) { *this = {}; isValid = fields_read(path, *this); lines_read(path, [&](const std::string& key, const char* value) { if (key == KEY_DISCOVERED) discovered.emplace_back(value); SavedItem item; int length = 0; if (key == KEY_ITEM && std::sscanf(value, "%f %f %f %f %f %n", &item.position.x, &item.position.y, &item.velocity.x, &item.velocity.y, &item.rotation, &length) == 5) { item.name = value + length; items.push_back(item); } }); } void Progress::save(const std::string& path) { std::string text = fields_write(*this); for (const std::string& name : discovered) text += std::string(KEY_DISCOVERED) + "=" + name + "\n"; for (const SavedItem& item : items) text += std::string(KEY_ITEM) + "=" + value_write(item.position) + " " + value_write(item.velocity) + " " + value_write(item.rotation) + " " + item.name + "\n"; file_write(path, text); }