#include "game/options.hpp" #include #include // Para ifstream, ofstream #include // Para cout, cerr #include // Para string #include // Para unordered_map #include "core/input/input_types.hpp" // Para BUTTON_TO_STRING, STRING_TO_BUTTON #include "external/fkyaml_node.hpp" // Para fkyaml::node #include "game/defaults.hpp" // Para GameDefaults::VERSION namespace Options { // --- Funciones helper de conversión --- // Mapa de nombres de filtro const std::unordered_map FILTER_TO_STRING = { {Screen::Filter::NEAREST, "nearest"}, {Screen::Filter::LINEAR, "linear"}}; const std::unordered_map STRING_TO_FILTER = { {"nearest", Screen::Filter::NEAREST}, {"linear", Screen::Filter::LINEAR}}; // Mapa de scancodes comunes (los más usados para controles de juego) const std::unordered_map SCANCODE_TO_STRING = { // Flechas {SDL_SCANCODE_LEFT, "LEFT"}, {SDL_SCANCODE_RIGHT, "RIGHT"}, {SDL_SCANCODE_UP, "UP"}, {SDL_SCANCODE_DOWN, "DOWN"}, // Letras {SDL_SCANCODE_A, "A"}, {SDL_SCANCODE_B, "B"}, {SDL_SCANCODE_C, "C"}, {SDL_SCANCODE_D, "D"}, {SDL_SCANCODE_E, "E"}, {SDL_SCANCODE_F, "F"}, {SDL_SCANCODE_G, "G"}, {SDL_SCANCODE_H, "H"}, {SDL_SCANCODE_I, "I"}, {SDL_SCANCODE_J, "J"}, {SDL_SCANCODE_K, "K"}, {SDL_SCANCODE_L, "L"}, {SDL_SCANCODE_M, "M"}, {SDL_SCANCODE_N, "N"}, {SDL_SCANCODE_O, "O"}, {SDL_SCANCODE_P, "P"}, {SDL_SCANCODE_Q, "Q"}, {SDL_SCANCODE_R, "R"}, {SDL_SCANCODE_S, "S"}, {SDL_SCANCODE_T, "T"}, {SDL_SCANCODE_U, "U"}, {SDL_SCANCODE_V, "V"}, {SDL_SCANCODE_W, "W"}, {SDL_SCANCODE_X, "X"}, {SDL_SCANCODE_Y, "Y"}, {SDL_SCANCODE_Z, "Z"}, // Números {SDL_SCANCODE_0, "0"}, {SDL_SCANCODE_1, "1"}, {SDL_SCANCODE_2, "2"}, {SDL_SCANCODE_3, "3"}, {SDL_SCANCODE_4, "4"}, {SDL_SCANCODE_5, "5"}, {SDL_SCANCODE_6, "6"}, {SDL_SCANCODE_7, "7"}, {SDL_SCANCODE_8, "8"}, {SDL_SCANCODE_9, "9"}, // Teclas especiales {SDL_SCANCODE_SPACE, "SPACE"}, {SDL_SCANCODE_RETURN, "RETURN"}, {SDL_SCANCODE_ESCAPE, "ESCAPE"}, {SDL_SCANCODE_TAB, "TAB"}, {SDL_SCANCODE_BACKSPACE, "BACKSPACE"}, {SDL_SCANCODE_LSHIFT, "LSHIFT"}, {SDL_SCANCODE_RSHIFT, "RSHIFT"}, {SDL_SCANCODE_LCTRL, "LCTRL"}, {SDL_SCANCODE_RCTRL, "RCTRL"}, {SDL_SCANCODE_LALT, "LALT"}, {SDL_SCANCODE_RALT, "RALT"}}; const std::unordered_map STRING_TO_SCANCODE = { // Flechas {"LEFT", SDL_SCANCODE_LEFT}, {"RIGHT", SDL_SCANCODE_RIGHT}, {"UP", SDL_SCANCODE_UP}, {"DOWN", SDL_SCANCODE_DOWN}, // Letras {"A", SDL_SCANCODE_A}, {"B", SDL_SCANCODE_B}, {"C", SDL_SCANCODE_C}, {"D", SDL_SCANCODE_D}, {"E", SDL_SCANCODE_E}, {"F", SDL_SCANCODE_F}, {"G", SDL_SCANCODE_G}, {"H", SDL_SCANCODE_H}, {"I", SDL_SCANCODE_I}, {"J", SDL_SCANCODE_J}, {"K", SDL_SCANCODE_K}, {"L", SDL_SCANCODE_L}, {"M", SDL_SCANCODE_M}, {"N", SDL_SCANCODE_N}, {"O", SDL_SCANCODE_O}, {"P", SDL_SCANCODE_P}, {"Q", SDL_SCANCODE_Q}, {"R", SDL_SCANCODE_R}, {"S", SDL_SCANCODE_S}, {"T", SDL_SCANCODE_T}, {"U", SDL_SCANCODE_U}, {"V", SDL_SCANCODE_V}, {"W", SDL_SCANCODE_W}, {"X", SDL_SCANCODE_X}, {"Y", SDL_SCANCODE_Y}, {"Z", SDL_SCANCODE_Z}, // Números {"0", SDL_SCANCODE_0}, {"1", SDL_SCANCODE_1}, {"2", SDL_SCANCODE_2}, {"3", SDL_SCANCODE_3}, {"4", SDL_SCANCODE_4}, {"5", SDL_SCANCODE_5}, {"6", SDL_SCANCODE_6}, {"7", SDL_SCANCODE_7}, {"8", SDL_SCANCODE_8}, {"9", SDL_SCANCODE_9}, // Teclas especiales {"SPACE", SDL_SCANCODE_SPACE}, {"RETURN", SDL_SCANCODE_RETURN}, {"ESCAPE", SDL_SCANCODE_ESCAPE}, {"TAB", SDL_SCANCODE_TAB}, {"BACKSPACE", SDL_SCANCODE_BACKSPACE}, {"LSHIFT", SDL_SCANCODE_LSHIFT}, {"RSHIFT", SDL_SCANCODE_RSHIFT}, {"LCTRL", SDL_SCANCODE_LCTRL}, {"RCTRL", SDL_SCANCODE_RCTRL}, {"LALT", SDL_SCANCODE_LALT}, {"RALT", SDL_SCANCODE_RALT}}; // Mapa extendido de botones de gamepad (incluye ejes como botones) const std::unordered_map GAMEPAD_BUTTON_TO_STRING = { {SDL_GAMEPAD_BUTTON_WEST, "WEST"}, {SDL_GAMEPAD_BUTTON_NORTH, "NORTH"}, {SDL_GAMEPAD_BUTTON_EAST, "EAST"}, {SDL_GAMEPAD_BUTTON_SOUTH, "SOUTH"}, {SDL_GAMEPAD_BUTTON_START, "START"}, {SDL_GAMEPAD_BUTTON_BACK, "BACK"}, {SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, "LEFT_SHOULDER"}, {SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, "RIGHT_SHOULDER"}, {SDL_GAMEPAD_BUTTON_DPAD_UP, "DPAD_UP"}, {SDL_GAMEPAD_BUTTON_DPAD_DOWN, "DPAD_DOWN"}, {SDL_GAMEPAD_BUTTON_DPAD_LEFT, "DPAD_LEFT"}, {SDL_GAMEPAD_BUTTON_DPAD_RIGHT, "DPAD_RIGHT"}, {100, "L2_AS_BUTTON"}, {101, "R2_AS_BUTTON"}, {200, "LEFT_STICK_LEFT"}, {201, "LEFT_STICK_RIGHT"}}; const std::unordered_map STRING_TO_GAMEPAD_BUTTON = { {"WEST", SDL_GAMEPAD_BUTTON_WEST}, {"NORTH", SDL_GAMEPAD_BUTTON_NORTH}, {"EAST", SDL_GAMEPAD_BUTTON_EAST}, {"SOUTH", SDL_GAMEPAD_BUTTON_SOUTH}, {"START", SDL_GAMEPAD_BUTTON_START}, {"BACK", SDL_GAMEPAD_BUTTON_BACK}, {"LEFT_SHOULDER", SDL_GAMEPAD_BUTTON_LEFT_SHOULDER}, {"RIGHT_SHOULDER", SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER}, {"DPAD_UP", SDL_GAMEPAD_BUTTON_DPAD_UP}, {"DPAD_DOWN", SDL_GAMEPAD_BUTTON_DPAD_DOWN}, {"DPAD_LEFT", SDL_GAMEPAD_BUTTON_DPAD_LEFT}, {"DPAD_RIGHT", SDL_GAMEPAD_BUTTON_DPAD_RIGHT}, {"L2_AS_BUTTON", 100}, {"R2_AS_BUTTON", 101}, {"LEFT_STICK_LEFT", 200}, {"LEFT_STICK_RIGHT", 201}}; // Lista de paletas válidas const std::vector VALID_PALETTES = { "black-and-white", "green-phosphor", "island-joy-16", "lost-century", "na16", "orange-screen", "pico-8", "ruzx-spectrum", "ruzx-spectrum-revision-2", "steam-lords", "sweetie-16", "sweetie-16_bona", "zx-spectrum", "zx-spectrum-adjusted", "zxarne-5-2"}; // Funciones helper de conversión auto filterToString(Screen::Filter filter) -> std::string { auto it = FILTER_TO_STRING.find(filter); if (it != FILTER_TO_STRING.end()) { return it->second; } return "nearest"; } auto stringToFilter(const std::string& str) -> Screen::Filter { auto it = STRING_TO_FILTER.find(str); if (it != STRING_TO_FILTER.end()) { return it->second; } return Defaults::Video::FILTER; } auto scancodeToString(SDL_Scancode scancode) -> std::string { auto it = SCANCODE_TO_STRING.find(scancode); if (it != SCANCODE_TO_STRING.end()) { return it->second; } // Fallback: devolver el código numérico como string return std::to_string(static_cast(scancode)); } auto stringToScancode(const std::string& str, SDL_Scancode default_value) -> SDL_Scancode { auto it = STRING_TO_SCANCODE.find(str); if (it != STRING_TO_SCANCODE.end()) { return it->second; } // Intentar parsear como número (compatibilidad hacia atrás) try { int val = std::stoi(str); return static_cast(val); } catch (...) { return default_value; } } auto gamepadButtonToString(int button) -> std::string { auto it = GAMEPAD_BUTTON_TO_STRING.find(button); if (it != GAMEPAD_BUTTON_TO_STRING.end()) { return it->second; } // Fallback: devolver el código numérico como string return std::to_string(button); } auto stringToGamepadButton(const std::string& str, int default_value) -> int { auto it = STRING_TO_GAMEPAD_BUTTON.find(str); if (it != STRING_TO_GAMEPAD_BUTTON.end()) { return it->second; } // Intentar parsear como número (compatibilidad hacia atrás) try { return std::stoi(str); } catch (...) { return default_value; } } auto isValidPalette(const std::string& palette) -> bool { return std::ranges::any_of(VALID_PALETTES, [&palette](const auto& valid) { return valid == palette; }); } // --- Funciones helper para loadFromFile() --- // Carga configuración de ventana desde YAML void loadWindowConfigFromYaml(const fkyaml::node& yaml) { if (yaml.contains("window")) { const auto& win = yaml["window"]; if (win.contains("zoom")) { try { int val = win["zoom"].get_value(); window.zoom = (val > 0) ? val : Defaults::Window::ZOOM; } catch (...) { window.zoom = Defaults::Window::ZOOM; } } } } // Carga configuración de borde desde YAML void loadBorderConfigFromYaml(const fkyaml::node& border) { if (border.contains("enabled")) { try { video.border.enabled = border["enabled"].get_value(); } catch (...) { video.border.enabled = Defaults::Border::ENABLED; } } if (border.contains("width")) { try { auto val = border["width"].get_value(); video.border.width = (val > 0) ? val : Defaults::Border::WIDTH; } catch (...) { video.border.width = Defaults::Border::WIDTH; } } if (border.contains("height")) { try { auto val = border["height"].get_value(); video.border.height = (val > 0) ? val : Defaults::Border::HEIGHT; } catch (...) { video.border.height = Defaults::Border::HEIGHT; } } } // Carga los campos básicos de configuración de video void loadBasicVideoFieldsFromYaml(const fkyaml::node& vid) { // fullscreen (antes era "mode") if (vid.contains("fullscreen")) { try { video.fullscreen = vid["fullscreen"].get_value(); } catch (...) { video.fullscreen = Defaults::Video::FULLSCREEN; } } // filter (ahora es string) if (vid.contains("filter")) { try { auto filter_str = vid["filter"].get_value(); video.filter = stringToFilter(filter_str); } catch (...) { video.filter = Defaults::Video::FILTER; } } if (vid.contains("shaders")) { try { video.shaders = vid["shaders"].get_value(); } catch (...) { video.shaders = Defaults::Video::SHADERS; } } if (vid.contains("vertical_sync")) { try { video.vertical_sync = vid["vertical_sync"].get_value(); } catch (...) { video.vertical_sync = Defaults::Video::VERTICAL_SYNC; } } if (vid.contains("integer_scale")) { try { video.integer_scale = vid["integer_scale"].get_value(); } catch (...) { video.integer_scale = Defaults::Video::INTEGER_SCALE; } } if (vid.contains("keep_aspect")) { try { video.keep_aspect = vid["keep_aspect"].get_value(); } catch (...) { video.keep_aspect = Defaults::Video::KEEP_ASPECT; } } if (vid.contains("palette")) { try { auto palette_str = vid["palette"].get_value(); if (isValidPalette(palette_str)) { video.palette = palette_str; } else { video.palette = Defaults::Video::PALETTE_NAME; } } catch (...) { video.palette = Defaults::Video::PALETTE_NAME; } } } // Carga configuración de video desde YAML void loadVideoConfigFromYaml(const fkyaml::node& yaml) { if (yaml.contains("video")) { const auto& vid = yaml["video"]; loadBasicVideoFieldsFromYaml(vid); // Lee border if (vid.contains("border")) { loadBorderConfigFromYaml(vid["border"]); } } } // Carga controles de teclado desde YAML void loadKeyboardControlsFromYaml(const fkyaml::node& yaml) { if (yaml.contains("keyboard_controls")) { const auto& ctrl = yaml["keyboard_controls"]; if (ctrl.contains("key_left")) { try { auto key_str = ctrl["key_left"].get_value(); keyboard_controls.key_left = stringToScancode(key_str, Defaults::Controls::KEY_LEFT); } catch (...) { keyboard_controls.key_left = Defaults::Controls::KEY_LEFT; } } if (ctrl.contains("key_right")) { try { auto key_str = ctrl["key_right"].get_value(); keyboard_controls.key_right = stringToScancode(key_str, Defaults::Controls::KEY_RIGHT); } catch (...) { keyboard_controls.key_right = Defaults::Controls::KEY_RIGHT; } } if (ctrl.contains("key_jump")) { try { auto key_str = ctrl["key_jump"].get_value(); keyboard_controls.key_jump = stringToScancode(key_str, Defaults::Controls::KEY_JUMP); } catch (...) { keyboard_controls.key_jump = Defaults::Controls::KEY_JUMP; } } } } // Carga controles de gamepad desde YAML void loadGamepadControlsFromYaml(const fkyaml::node& yaml) { if (yaml.contains("gamepad_controls")) { const auto& gp = yaml["gamepad_controls"]; if (gp.contains("button_left")) { try { auto button_str = gp["button_left"].get_value(); gamepad_controls.button_left = stringToGamepadButton(button_str, Defaults::Controls::GAMEPAD_BUTTON_LEFT); } catch (...) { gamepad_controls.button_left = Defaults::Controls::GAMEPAD_BUTTON_LEFT; } } if (gp.contains("button_right")) { try { auto button_str = gp["button_right"].get_value(); gamepad_controls.button_right = stringToGamepadButton(button_str, Defaults::Controls::GAMEPAD_BUTTON_RIGHT); } catch (...) { gamepad_controls.button_right = Defaults::Controls::GAMEPAD_BUTTON_RIGHT; } } if (gp.contains("button_jump")) { try { auto button_str = gp["button_jump"].get_value(); gamepad_controls.button_jump = stringToGamepadButton(button_str, Defaults::Controls::GAMEPAD_BUTTON_JUMP); } catch (...) { gamepad_controls.button_jump = Defaults::Controls::GAMEPAD_BUTTON_JUMP; } } } } // Crea e inicializa las opciones del programa void init() { #ifdef _DEBUG console = true; #else console = false; #endif } // Establece la ruta del fichero de configuración void setConfigFile(const std::string& path) { config_file_path = path; } // Carga las opciones desde el fichero configurado auto loadFromFile() -> bool { // Versión esperada del fichero const std::string CONFIG_VERSION = Texts::VERSION; version = ""; // Intenta abrir y leer el fichero std::ifstream file(config_file_path); if (!file.good()) { if (console) { std::cout << "Config file not found, creating default: " << config_file_path << '\n'; } saveToFile(); return true; } // Lee todo el contenido del fichero std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); file.close(); try { if (console) { std::cout << "Reading config file: " << config_file_path << '\n'; } // Parsea el YAML auto yaml = fkyaml::node::deserialize(content); // Lee la versión if (yaml.contains("version")) { version = yaml["version"].get_value(); } // Si la versión no coincide, crea un fichero nuevo con valores por defecto if (CONFIG_VERSION != version) { if (console) { std::cout << "Config version mismatch (expected: " << CONFIG_VERSION << ", got: " << version << "), creating new config\n"; } init(); saveToFile(); return true; } // Carga las diferentes secciones de configuración usando funciones helper loadWindowConfigFromYaml(yaml); loadVideoConfigFromYaml(yaml); loadKeyboardControlsFromYaml(yaml); loadGamepadControlsFromYaml(yaml); if (console) { std::cout << "Config file loaded successfully\n\n"; } return true; } catch (const fkyaml::exception& e) { if (console) { std::cerr << "Error parsing YAML config: " << e.what() << '\n'; std::cerr << "Creating new config with defaults\n"; } init(); saveToFile(); return true; } } // Guarda las opciones al fichero configurado auto saveToFile() -> bool { // Abre el fichero para escritura std::ofstream file(config_file_path); if (!file.is_open()) { if (console) { std::cerr << "Error: Unable to open file " << config_file_path << " for writing\n"; } return false; } if (console) { std::cout << "Writing config file: " << config_file_path << '\n'; } // Escribe el fichero manualmente para controlar el orden y los comentarios file << "# JailDoctor's Dilemma - 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 \n"; file << "version: \"" << Texts::VERSION << "\"\n"; file << "\n"; // WINDOW file << "# WINDOW\n"; file << "window:\n"; file << " zoom: " << window.zoom << "\n"; file << "\n"; // VIDEO file << "# VIDEO \n"; file << "video:\n"; file << " fullscreen: " << (video.fullscreen ? "true" : "false") << "\n"; file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n"; file << " shaders: " << (video.shaders ? "true" : "false") << "\n"; file << " vertical_sync: " << (video.vertical_sync ? "true" : "false") << "\n"; file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n"; file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n"; file << " palette: " << video.palette << "\n"; file << " border:\n"; file << " enabled: " << (video.border.enabled ? "true" : "false") << "\n"; file << " width: " << video.border.width << "\n"; file << " height: " << video.border.height << "\n"; file << "\n"; // KEYBOARD CONTROLS file << "# KEYBOARD CONTROLS\n"; file << "keyboard_controls:\n"; file << " key_left: " << scancodeToString(keyboard_controls.key_left) << "\n"; file << " key_right: " << scancodeToString(keyboard_controls.key_right) << "\n"; file << " key_jump: " << scancodeToString(keyboard_controls.key_jump) << "\n"; file << "\n"; // GAMEPAD CONTROLS file << "# GAMEPAD CONTROLS\n"; file << "gamepad_controls:\n"; file << " button_left: " << gamepadButtonToString(gamepad_controls.button_left) << "\n"; file << " button_right: " << gamepadButtonToString(gamepad_controls.button_right) << "\n"; file << " button_jump: " << gamepadButtonToString(gamepad_controls.button_jump) << "\n"; file.close(); if (console) { std::cout << "Config file saved successfully\n\n"; } return true; } } // namespace Options