#include "game/options.hpp" #include #include #include #include "core/jail/jail_audio.hpp" #include "external/fkyaml_node.hpp" #include "game/defaults.hpp" #include "game/defines.hpp" namespace Options { // Estableix la ruta del fitxer de configuració void setConfigFile(const std::string& path) { config_file_path = path; } void applyAudio() { const float master = audio.enabled ? audio.volume : 0.0F; JA_EnableMusic(audio.music_enabled); JA_EnableSound(audio.sound_enabled); JA_SetMusicVolume(master * audio.music_volume); JA_SetSoundVolume(master * audio.sound_volume); } // --- Funcions helper de càrrega --- static void loadAudioConfigFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("audio")) return; const auto& node = yaml["audio"]; if (node.contains("enabled")) audio.enabled = node["enabled"].get_value(); if (node.contains("volume")) audio.volume = node["volume"].get_value(); if (node.contains("music")) { const auto& music = node["music"]; if (music.contains("enabled")) audio.music_enabled = music["enabled"].get_value(); if (music.contains("volume")) audio.music_volume = music["volume"].get_value(); } if (node.contains("sound")) { const auto& sound = node["sound"]; if (sound.contains("enabled")) audio.sound_enabled = sound["enabled"].get_value(); if (sound.contains("volume")) audio.sound_volume = sound["volume"].get_value(); } } static void loadVideoConfigFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("video")) return; const auto& node = yaml["video"]; if (node.contains("gpu_acceleration")) video.gpu_acceleration = node["gpu_acceleration"].get_value(); if (node.contains("shader_enabled")) video.shader_enabled = node["shader_enabled"].get_value(); if (node.contains("supersampling")) video.supersampling = node["supersampling"].get_value(); if (node.contains("integer_scale")) video.integer_scale = node["integer_scale"].get_value(); if (node.contains("aspect_ratio_4_3")) video.aspect_ratio_4_3 = node["aspect_ratio_4_3"].get_value(); if (node.contains("stretch_filter_linear")) video.stretch_filter_linear = node["stretch_filter_linear"].get_value(); if (node.contains("downscale_algo")) video.downscale_algo = node["downscale_algo"].get_value(); if (node.contains("linear_upscale")) video.linear_upscale = node["linear_upscale"].get_value(); if (node.contains("current_shader")) video.current_shader = node["current_shader"].get_value(); if (node.contains("current_postfx_preset")) video.current_postfx_preset = node["current_postfx_preset"].get_value(); if (node.contains("current_crtpi_preset")) video.current_crtpi_preset = node["current_crtpi_preset"].get_value(); } static void loadRenderInfoFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("render_info")) return; const auto& node = yaml["render_info"]; if (node.contains("position")) { auto pos = node["position"].get_value(); if (pos == "top") render_info.position = RenderInfoPosition::TOP; else if (pos == "bottom") render_info.position = RenderInfoPosition::BOTTOM; else render_info.position = RenderInfoPosition::OFF; } if (node.contains("text_color")) render_info.text_color = static_cast(node["text_color"].get_value()); if (node.contains("shadow_color")) render_info.shadow_color = static_cast(node["shadow_color"].get_value()); } static void loadWindowConfigFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("window")) return; const auto& node = yaml["window"]; if (node.contains("zoom")) window.zoom = node["zoom"].get_value(); if (node.contains("fullscreen")) window.fullscreen = node["fullscreen"].get_value(); } // Helper: carrega una SDL_Scancode des d'un string (nom SDL de la tecla). static void loadScancodeField(const fkyaml::node& node, const std::string& key, SDL_Scancode& target) { if (!node.contains(key)) return; auto name = node[key].get_value(); SDL_Scancode sc = SDL_GetScancodeFromName(name.c_str()); if (sc != SDL_SCANCODE_UNKNOWN) target = sc; } static void loadControlsFromYaml(const fkyaml::node& yaml) { if (yaml.contains("controls")) { const auto& node = yaml["controls"]; loadScancodeField(node, "up", keys_game.up); loadScancodeField(node, "down", keys_game.down); loadScancodeField(node, "left", keys_game.left); loadScancodeField(node, "right", keys_game.right); loadScancodeField(node, "menu_toggle", keys_gui.menu_toggle); loadScancodeField(node, "pause_toggle", keys_gui.pause_toggle); } } static void loadGameConfigFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("game")) return; const auto& node = yaml["game"]; if (node.contains("habitacio_inicial")) game.habitacio_inicial = node["habitacio_inicial"].get_value(); if (node.contains("piramide_inicial")) game.piramide_inicial = node["piramide_inicial"].get_value(); if (node.contains("vides")) game.vides = node["vides"].get_value(); } // Carrega les opcions des del fitxer configurat auto loadFromFile() -> bool { const std::string CONFIG_VERSION = Texts::VERSION; version = ""; std::ifstream file(config_file_path); if (!file.good()) { std::cout << "Config file not found, creating default: " << config_file_path << '\n'; saveToFile(); return true; } std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); file.close(); try { std::cout << "Reading config file: " << config_file_path << '\n'; auto yaml = fkyaml::node::deserialize(content); if (yaml.contains("version")) { version = yaml["version"].get_value(); } if (CONFIG_VERSION != version) { std::cout << "Config version mismatch (expected: " << CONFIG_VERSION << ", got: " << version << "), creating new config\n"; saveToFile(); return true; } loadVideoConfigFromYaml(yaml); loadRenderInfoFromYaml(yaml); loadWindowConfigFromYaml(yaml); loadAudioConfigFromYaml(yaml); loadGameConfigFromYaml(yaml); loadControlsFromYaml(yaml); std::cout << "Config file loaded successfully\n\n"; return true; } catch (const fkyaml::exception& e) { std::cerr << "Error parsing YAML config: " << e.what() << '\n'; std::cerr << "Creating new config with defaults\n"; saveToFile(); return true; } } // Guarda les opcions al fitxer configurat auto saveToFile() -> bool { std::ofstream file(config_file_path); if (!file.is_open()) { std::cerr << "Error: Unable to open file " << config_file_path << " for writing\n"; return false; } std::cout << "Writing config file: " << config_file_path << '\n'; file << "# Aventures En Egipte - Configuration File\n"; file << "# \n"; file << "# This file is automatically generated and managed by the game.\n"; file << "# Manual edits are preserved if valid.\n"; file << "\n"; // VERSION file << "version: \"" << Texts::VERSION << "\"\n"; file << "\n"; // VIDEO file << "# VIDEO\n"; file << "video:\n"; file << " gpu_acceleration: " << (video.gpu_acceleration ? "true" : "false") << "\n"; file << " shader_enabled: " << (video.shader_enabled ? "true" : "false") << "\n"; file << " supersampling: " << (video.supersampling ? "true" : "false") << "\n"; file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n"; file << " aspect_ratio_4_3: " << (video.aspect_ratio_4_3 ? "true" : "false") << "\n"; file << " stretch_filter_linear: " << (video.stretch_filter_linear ? "true" : "false") << " # filtre 4:3: false=nearest, true=linear\n"; file << " downscale_algo: " << video.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n"; file << " linear_upscale: " << (video.linear_upscale ? "true" : "false") << "\n"; file << " current_shader: " << video.current_shader << "\n"; file << " current_postfx_preset: " << video.current_postfx_preset << "\n"; file << " current_crtpi_preset: " << video.current_crtpi_preset << "\n"; file << "\n"; // RENDER INFO file << "# RENDER INFO\n"; file << "render_info:\n"; { const char* pos = "off"; if (render_info.position == RenderInfoPosition::TOP) pos = "top"; else if (render_info.position == RenderInfoPosition::BOTTOM) pos = "bottom"; file << " position: " << pos << " # off/top/bottom\n"; } file << " text_color: " << render_info.text_color << "\n"; file << " shadow_color: " << render_info.shadow_color << "\n"; file << "\n"; // WINDOW file << "# WINDOW\n"; file << "window:\n"; file << " zoom: " << window.zoom << "\n"; file << " fullscreen: " << (window.fullscreen ? "true" : "false") << "\n"; file << "\n"; // AUDIO file << "# AUDIO\n"; file << "audio:\n"; file << " enabled: " << (audio.enabled ? "true" : "false") << "\n"; file << " volume: " << audio.volume << "\n"; file << " music:\n"; file << " enabled: " << (audio.music_enabled ? "true" : "false") << "\n"; file << " volume: " << audio.music_volume << "\n"; file << " sound:\n"; file << " enabled: " << (audio.sound_enabled ? "true" : "false") << "\n"; file << " volume: " << audio.sound_volume << "\n"; file << "\n"; // GAME file << "# GAME\n"; file << "game:\n"; file << " habitacio_inicial: " << game.habitacio_inicial << "\n"; file << " piramide_inicial: " << game.piramide_inicial << "\n"; file << " vides: " << game.vides << "\n"; file << "\n"; // CONTROLS file << "# CONTROLS (noms SDL: \"Up\", \"Down\", \"W\", \"Space\", \"F12\", etc.)\n"; file << "controls:\n"; file << " up: \"" << SDL_GetScancodeName(keys_game.up) << "\"\n"; file << " down: \"" << SDL_GetScancodeName(keys_game.down) << "\"\n"; file << " left: \"" << SDL_GetScancodeName(keys_game.left) << "\"\n"; file << " right: \"" << SDL_GetScancodeName(keys_game.right) << "\"\n"; file << " menu_toggle: \"" << SDL_GetScancodeName(keys_gui.menu_toggle) << "\"\n"; file << " pause_toggle: \"" << SDL_GetScancodeName(keys_gui.pause_toggle) << "\"\n"; file.close(); std::cout << "Config file saved successfully\n\n"; return true; } // --- Helper per a parsejar floats de YAML --- static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) { if (node.contains(key)) { try { target = node[key].get_value(); } catch (...) {} } } // --- Presets PostFX --- void setPostFXFile(const std::string& path) { postfx_file_path = path; } auto loadPostFXFromFile() -> bool { postfx_presets.clear(); std::ifstream file(postfx_file_path); if (!file.good()) { std::cout << "PostFX file not found, creating defaults: " << postfx_file_path << '\n'; // Escriure defaults std::ofstream out(postfx_file_path); if (out.is_open()) { out << "# Aventures En Egipte - PostFX Shader Presets\n\n"; out << "presets:\n"; out << " - name: \"CRT\"\n vignette: 0.6\n scanlines: 0.7\n chroma: 0.15\n mask: 0.6\n gamma: 0.8\n"; out << " - name: \"NTSC\"\n vignette: 0.4\n scanlines: 0.5\n chroma: 0.2\n mask: 0.4\n gamma: 0.5\n bleeding: 0.6\n"; out << " - name: \"CURVED\"\n vignette: 0.5\n scanlines: 0.6\n chroma: 0.1\n mask: 0.5\n gamma: 0.7\n curvature: 0.8\n"; out << " - name: \"SCANLINES\"\n scanlines: 0.8\n vignette: 0.0\n chroma: 0.0\n"; out << " - name: \"SUBTLE\"\n vignette: 0.3\n scanlines: 0.4\n chroma: 0.05\n gamma: 0.3\n"; out << " - name: \"CRT LIVE\"\n vignette: 0.5\n scanlines: 0.6\n chroma: 0.3\n mask: 0.3\n gamma: 0.4\n curvature: 0.3\n bleeding: 0.4\n flicker: 0.8\n"; out.close(); } postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F, 0.6F, 0.8F}); postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.4F, 0.5F, 0.0F, 0.6F}); postfx_presets.push_back({"CURVED", 0.5F, 0.6F, 0.1F, 0.5F, 0.7F, 0.8F}); postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F}); postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F}); postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F}); current_postfx_preset = 0; return true; } std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); file.close(); try { auto yaml = fkyaml::node::deserialize(content); if (yaml.contains("presets")) { for (const auto& p : yaml["presets"]) { PostFXPreset preset; if (p.contains("name")) preset.name = p["name"].get_value(); parseFloatField(p, "vignette", preset.vignette); parseFloatField(p, "scanlines", preset.scanlines); parseFloatField(p, "chroma", preset.chroma); parseFloatField(p, "mask", preset.mask); parseFloatField(p, "gamma", preset.gamma); parseFloatField(p, "curvature", preset.curvature); parseFloatField(p, "bleeding", preset.bleeding); parseFloatField(p, "flicker", preset.flicker); postfx_presets.push_back(preset); } } current_postfx_preset = 0; std::cout << "PostFX loaded: " << postfx_presets.size() << " preset(s)\n"; return true; } catch (const fkyaml::exception& e) { std::cerr << "Error parsing PostFX YAML: " << e.what() << '\n'; postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F, 0.6F, 0.8F}); current_postfx_preset = 0; return false; } } // --- Presets CrtPi --- void setCrtPiFile(const std::string& path) { crtpi_file_path = path; } auto loadCrtPiFromFile() -> bool { crtpi_presets.clear(); std::ifstream file(crtpi_file_path); if (!file.good()) { std::cout << "CrtPi file not found, creating defaults: " << crtpi_file_path << '\n'; std::ofstream out(crtpi_file_path); if (out.is_open()) { out << "# Aventures En Egipte - CrtPi Shader Presets\n\n"; out << "presets:\n"; out << " - name: \"DEFAULT\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: true\n enable_gamma: true\n enable_curvature: false\n enable_sharper: false\n"; out << " - name: \"CURVED\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: true\n enable_gamma: true\n enable_curvature: true\n enable_sharper: false\n"; out << " - name: \"SHARP\"\n scanline_weight: 6.0\n scanline_gap_brightness: 0.12\n bloom_factor: 3.5\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 0.80\n curvature_x: 0.05\n curvature_y: 0.10\n mask_type: 2\n enable_scanlines: true\n enable_multisample: false\n enable_gamma: true\n enable_curvature: false\n enable_sharper: true\n"; out << " - name: \"MINIMAL\"\n scanline_weight: 8.0\n scanline_gap_brightness: 0.05\n bloom_factor: 2.0\n input_gamma: 2.4\n output_gamma: 2.2\n mask_brightness: 1.00\n curvature_x: 0.0\n curvature_y: 0.0\n mask_type: 0\n enable_scanlines: true\n enable_multisample: false\n enable_gamma: false\n enable_curvature: false\n enable_sharper: false\n"; out.close(); } crtpi_presets.push_back({"DEFAULT", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false}); crtpi_presets.push_back({"CURVED", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false}); crtpi_presets.push_back({"SHARP", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true}); crtpi_presets.push_back({"MINIMAL", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false}); current_crtpi_preset = 0; return true; } std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); file.close(); try { auto yaml = fkyaml::node::deserialize(content); if (yaml.contains("presets")) { for (const auto& p : yaml["presets"]) { CrtPiPreset preset; if (p.contains("name")) preset.name = p["name"].get_value(); parseFloatField(p, "scanline_weight", preset.scanline_weight); parseFloatField(p, "scanline_gap_brightness", preset.scanline_gap_brightness); parseFloatField(p, "bloom_factor", preset.bloom_factor); parseFloatField(p, "input_gamma", preset.input_gamma); parseFloatField(p, "output_gamma", preset.output_gamma); parseFloatField(p, "mask_brightness", preset.mask_brightness); parseFloatField(p, "curvature_x", preset.curvature_x); parseFloatField(p, "curvature_y", preset.curvature_y); if (p.contains("mask_type")) try { preset.mask_type = p["mask_type"].get_value(); } catch (...) {} if (p.contains("enable_scanlines")) try { preset.enable_scanlines = p["enable_scanlines"].get_value(); } catch (...) {} if (p.contains("enable_multisample")) try { preset.enable_multisample = p["enable_multisample"].get_value(); } catch (...) {} if (p.contains("enable_gamma")) try { preset.enable_gamma = p["enable_gamma"].get_value(); } catch (...) {} if (p.contains("enable_curvature")) try { preset.enable_curvature = p["enable_curvature"].get_value(); } catch (...) {} if (p.contains("enable_sharper")) try { preset.enable_sharper = p["enable_sharper"].get_value(); } catch (...) {} crtpi_presets.push_back(preset); } } current_crtpi_preset = 0; std::cout << "CrtPi loaded: " << crtpi_presets.size() << " preset(s)\n"; return true; } catch (const fkyaml::exception& e) { std::cerr << "Error parsing CrtPi YAML: " << e.what() << '\n'; crtpi_presets.push_back({"DEFAULT", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false}); current_crtpi_preset = 0; return false; } } } // namespace Options