diff --git a/source/core/input/input.cpp b/source/core/input/input.cpp index 8d844b29..5231a081 100644 --- a/source/core/input/input.cpp +++ b/source/core/input/input.cpp @@ -51,8 +51,7 @@ Input::Input(std::string game_controller_db_path) {Action::TOGGLE_BORDER, KeyState{SDL_SCANCODE_F9}}, {Action::TOGGLE_VSYNC, KeyState{SDL_SCANCODE_F10}}, {Action::PAUSE, KeyState{SDL_SCANCODE_F11}}, - {Action::TOGGLE_DEBUG, KeyState{SDL_SCANCODE_F12}} - }; + {Action::TOGGLE_DEBUG, KeyState{SDL_SCANCODE_F12}}}; initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD } @@ -64,9 +63,9 @@ void Input::bindKey(Action action, SDL_Scancode code) { // Aplica las teclas configuradas desde Options void Input::applyKeyboardBindingsFromOptions() { - bindKey(Action::LEFT, Options::controls.key_left); - bindKey(Action::RIGHT, Options::controls.key_right); - bindKey(Action::JUMP, Options::controls.key_jump); + bindKey(Action::LEFT, Options::keyboard_controls.key_left); + bindKey(Action::RIGHT, Options::keyboard_controls.key_right); + bindKey(Action::JUMP, Options::keyboard_controls.key_jump); } // Aplica configuración de botones del gamepad desde Options al primer gamepad conectado diff --git a/source/game/defaults.hpp b/source/game/defaults.hpp index 651d458a..d0229837 100644 --- a/source/game/defaults.hpp +++ b/source/game/defaults.hpp @@ -21,7 +21,7 @@ constexpr int GAME_HEIGHT = 192; // Alto de la ventana por defecto constexpr int WINDOW_ZOOM = 2; // Zoom de la ventana por defecto // VIDEO -constexpr bool VIDEO_MODE = false; // Modo de pantalla completa por defecto (false = ventana) +constexpr bool VIDEO_FULLSCREEN = false; // Modo de pantalla completa por defecto (false = ventana) constexpr Screen::Filter VIDEO_FILTER = Screen::Filter::NEAREST; // Filtro por defecto constexpr bool VIDEO_VERTICAL_SYNC = true; // Vsync activado por defecto constexpr bool VIDEO_SHADERS = false; // Shaders desactivados por defecto @@ -50,6 +50,27 @@ constexpr bool SOUND_ENABLED = true; // Sonido habilitado por defecto constexpr bool NOTIFICATION_SOUND = true; // Sonido de las notificaciones por defecto const Uint8 NOTIFICATION_COLOR = static_cast(PaletteColor::BLUE); // Color de las notificaciones por defecto +// CHEATS +constexpr bool CHEAT_INFINITE_LIVES = false; // Vidas infinitas desactivadas por defecto +constexpr bool CHEAT_INVINCIBLE = false; // Invencibilidad desactivada por defecto +constexpr bool CHEAT_JAIL_IS_OPEN = false; // Jail abierta desactivada por defecto +constexpr bool CHEAT_ALTERNATE_SKIN = false; // Skin alternativa desactivada por defecto + +// STATS +constexpr int STATS_ROOMS = 0; // Habitaciones visitadas por defecto +constexpr int STATS_ITEMS = 0; // Items obtenidos por defecto +constexpr const char* STATS_WORST_NIGHTMARE = ""; // Habitación con más muertes por defecto + +// CONTROLS +constexpr SDL_Scancode CONTROL_KEY_LEFT = SDL_SCANCODE_LEFT; // Tecla izquierda por defecto +constexpr SDL_Scancode CONTROL_KEY_RIGHT = SDL_SCANCODE_RIGHT; // Tecla derecha por defecto +constexpr SDL_Scancode CONTROL_KEY_JUMP = SDL_SCANCODE_UP; // Tecla salto por defecto + +// GAMEPAD CONTROLS +const int GAMEPAD_BUTTON_LEFT = SDL_GAMEPAD_BUTTON_DPAD_LEFT; // Botón izquierda por defecto +const int GAMEPAD_BUTTON_RIGHT = SDL_GAMEPAD_BUTTON_DPAD_RIGHT; // Botón derecha por defecto +const int GAMEPAD_BUTTON_JUMP = SDL_GAMEPAD_BUTTON_WEST; // Botón salto por defecto + // OTHER constexpr bool CONSOLE = false; // Consola desactivada por defecto constexpr const char* VERSION = "1.10"; // Versión por defecto diff --git a/source/game/options.cpp b/source/game/options.cpp index ea164a6b..58a29a99 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -2,15 +2,269 @@ #include -#include // Para ifstream, ofstream -#include // Para cout, cerr -#include // Para string +#include // Para ifstream, ofstream +#include // Para cout, cerr +#include // Para string +#include // Para unordered_map -#include "external/fkyaml_node.hpp" // Para fkyaml::node -#include "game/defaults.hpp" // Para GameDefaults::VERSION +#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 GameDefaults::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 { + for (const auto& valid : VALID_PALETTES) { + if (valid == palette) { + return true; + } + } + return false; +} + // Crea e inicializa las opciones del programa void init() { #ifdef _DEBUG @@ -72,8 +326,12 @@ auto loadFromFile() -> bool { if (yaml.contains("window")) { const auto& win = yaml["window"]; if (win.contains("zoom")) { - int val = win["zoom"].get_value(); - window.zoom = (val > 0) ? val : GameDefaults::WINDOW_ZOOM; + try { + int val = win["zoom"].get_value(); + window.zoom = (val > 0) ? val : GameDefaults::WINDOW_ZOOM; + } catch (...) { + window.zoom = GameDefaults::WINDOW_ZOOM; + } } } @@ -81,35 +339,68 @@ auto loadFromFile() -> bool { if (yaml.contains("video")) { const auto& vid = yaml["video"]; - if (vid.contains("mode")) { - video.fullscreen = vid["mode"].get_value(); + // fullscreen (antes era "mode") + if (vid.contains("fullscreen")) { + try { + video.fullscreen = vid["fullscreen"].get_value(); + } catch (...) { + video.fullscreen = GameDefaults::VIDEO_FULLSCREEN; + } } + // filter (ahora es string) if (vid.contains("filter")) { - int val = vid["filter"].get_value(); - if (val == static_cast(Screen::Filter::NEAREST) || val == static_cast(Screen::Filter::LINEAR)) { - video.filter = static_cast(val); + try { + std::string filter_str = vid["filter"].get_value(); + video.filter = stringToFilter(filter_str); + } catch (...) { + video.filter = GameDefaults::VIDEO_FILTER; } } if (vid.contains("shaders")) { - video.shaders = vid["shaders"].get_value(); + try { + video.shaders = vid["shaders"].get_value(); + } catch (...) { + video.shaders = GameDefaults::VIDEO_SHADERS; + } } if (vid.contains("vertical_sync")) { - video.vertical_sync = vid["vertical_sync"].get_value(); + try { + video.vertical_sync = vid["vertical_sync"].get_value(); + } catch (...) { + video.vertical_sync = GameDefaults::VIDEO_VERTICAL_SYNC; + } } if (vid.contains("integer_scale")) { - video.integer_scale = vid["integer_scale"].get_value(); + try { + video.integer_scale = vid["integer_scale"].get_value(); + } catch (...) { + video.integer_scale = GameDefaults::VIDEO_INTEGER_SCALE; + } } if (vid.contains("keep_aspect")) { - video.keep_aspect = vid["keep_aspect"].get_value(); + try { + video.keep_aspect = vid["keep_aspect"].get_value(); + } catch (...) { + video.keep_aspect = GameDefaults::VIDEO_KEEP_ASPECT; + } } if (vid.contains("palette")) { - video.palette = vid["palette"].get_value(); + try { + std::string palette_str = vid["palette"].get_value(); + if (isValidPalette(palette_str)) { + video.palette = palette_str; + } else { + video.palette = GameDefaults::PALETTE_NAME; + } + } catch (...) { + video.palette = GameDefaults::PALETTE_NAME; + } } // Lee border @@ -117,38 +408,62 @@ auto loadFromFile() -> bool { const auto& border = vid["border"]; if (border.contains("enabled")) { - video.border.enabled = border["enabled"].get_value(); + try { + video.border.enabled = border["enabled"].get_value(); + } catch (...) { + video.border.enabled = GameDefaults::BORDER_ENABLED; + } } if (border.contains("width")) { - float val = border["width"].get_value(); - video.border.width = (val > 0) ? val : GameDefaults::BORDER_WIDTH; + try { + float val = border["width"].get_value(); + video.border.width = (val > 0) ? val : GameDefaults::BORDER_WIDTH; + } catch (...) { + video.border.width = GameDefaults::BORDER_WIDTH; + } } if (border.contains("height")) { - float val = border["height"].get_value(); - video.border.height = (val > 0) ? val : GameDefaults::BORDER_HEIGHT; + try { + float val = border["height"].get_value(); + video.border.height = (val > 0) ? val : GameDefaults::BORDER_HEIGHT; + } catch (...) { + video.border.height = GameDefaults::BORDER_HEIGHT; + } } } } - // Lee controls - if (yaml.contains("controls")) { - const auto& ctrl = yaml["controls"]; + // Lee keyboard_controls (antes era "controls") + if (yaml.contains("keyboard_controls")) { + const auto& ctrl = yaml["keyboard_controls"]; - if (ctrl.contains("left")) { - int val = ctrl["left"].get_value(); - controls.key_left = static_cast(val); + if (ctrl.contains("key_left")) { + try { + std::string key_str = ctrl["key_left"].get_value(); + keyboard_controls.key_left = stringToScancode(key_str, GameDefaults::CONTROL_KEY_LEFT); + } catch (...) { + keyboard_controls.key_left = GameDefaults::CONTROL_KEY_LEFT; + } } - if (ctrl.contains("right")) { - int val = ctrl["right"].get_value(); - controls.key_right = static_cast(val); + if (ctrl.contains("key_right")) { + try { + std::string key_str = ctrl["key_right"].get_value(); + keyboard_controls.key_right = stringToScancode(key_str, GameDefaults::CONTROL_KEY_RIGHT); + } catch (...) { + keyboard_controls.key_right = GameDefaults::CONTROL_KEY_RIGHT; + } } - if (ctrl.contains("jump")) { - int val = ctrl["jump"].get_value(); - controls.key_jump = static_cast(val); + if (ctrl.contains("key_jump")) { + try { + std::string key_str = ctrl["key_jump"].get_value(); + keyboard_controls.key_jump = stringToScancode(key_str, GameDefaults::CONTROL_KEY_JUMP); + } catch (...) { + keyboard_controls.key_jump = GameDefaults::CONTROL_KEY_JUMP; + } } } @@ -156,16 +471,31 @@ auto loadFromFile() -> bool { if (yaml.contains("gamepad_controls")) { const auto& gp = yaml["gamepad_controls"]; - if (gp.contains("left")) { - gamepad_controls.button_left = gp["left"].get_value(); + if (gp.contains("button_left")) { + try { + std::string button_str = gp["button_left"].get_value(); + gamepad_controls.button_left = stringToGamepadButton(button_str, GameDefaults::GAMEPAD_BUTTON_LEFT); + } catch (...) { + gamepad_controls.button_left = GameDefaults::GAMEPAD_BUTTON_LEFT; + } } - if (gp.contains("right")) { - gamepad_controls.button_right = gp["right"].get_value(); + if (gp.contains("button_right")) { + try { + std::string button_str = gp["button_right"].get_value(); + gamepad_controls.button_right = stringToGamepadButton(button_str, GameDefaults::GAMEPAD_BUTTON_RIGHT); + } catch (...) { + gamepad_controls.button_right = GameDefaults::GAMEPAD_BUTTON_RIGHT; + } } - if (gp.contains("jump")) { - gamepad_controls.button_jump = gp["jump"].get_value(); + if (gp.contains("button_jump")) { + try { + std::string button_str = gp["button_jump"].get_value(); + gamepad_controls.button_jump = stringToGamepadButton(button_str, GameDefaults::GAMEPAD_BUTTON_JUMP); + } catch (...) { + gamepad_controls.button_jump = GameDefaults::GAMEPAD_BUTTON_JUMP; + } } } @@ -188,53 +518,6 @@ auto loadFromFile() -> bool { // Guarda las opciones al fichero configurado auto saveToFile() -> bool { - // Crea el nodo YAML raíz como mapping - fkyaml::node yaml(fkyaml::node_type::MAPPING); - - // Establece la versión - yaml["version"] = GameDefaults::VERSION; - - // Window - fkyaml::node window_node(fkyaml::node_type::MAPPING); - window_node["zoom"] = window.zoom; - yaml["window"] = window_node; - - // Video - fkyaml::node video_node(fkyaml::node_type::MAPPING); - video_node["mode"] = video.fullscreen; - video_node["filter"] = static_cast(video.filter); - video_node["shaders"] = video.shaders; - video_node["vertical_sync"] = video.vertical_sync; - video_node["integer_scale"] = video.integer_scale; - video_node["keep_aspect"] = video.keep_aspect; - video_node["palette"] = video.palette; - - // Video border - fkyaml::node border_node(fkyaml::node_type::MAPPING); - border_node["enabled"] = video.border.enabled; - border_node["width"] = video.border.width; - border_node["height"] = video.border.height; - video_node["border"] = border_node; - - yaml["video"] = video_node; - - // Controls - fkyaml::node controls_node(fkyaml::node_type::MAPPING); - controls_node["left"] = static_cast(controls.key_left); - controls_node["right"] = static_cast(controls.key_right); - controls_node["jump"] = static_cast(controls.key_jump); - yaml["controls"] = controls_node; - - // Gamepad controls - fkyaml::node gamepad_node(fkyaml::node_type::MAPPING); - gamepad_node["left"] = gamepad_controls.button_left; - gamepad_node["right"] = gamepad_controls.button_right; - gamepad_node["jump"] = gamepad_controls.button_jump; - yaml["gamepad_controls"] = gamepad_node; - - // Serializa a string - std::string yaml_content = fkyaml::node::serialize(yaml); - // Abre el fichero para escritura std::ofstream file(config_file_path_); if (!file.is_open()) { @@ -248,17 +531,54 @@ auto saveToFile() -> bool { std::cout << "Writing config file: " << config_file_path_ << '\n'; } - // Escribe el encabezado con comentarios + // Escribe el fichero manualmente para controlar el orden y los comentarios file << "# JailDoctor's Dilemma - Configuration File\n"; - file << "# This file is automatically generated and managed by the game\n"; - file << "#\n"; - file << "# Video filters: 0 = Nearest (pixel perfect), 1 = Linear (smooth)\n"; - file << "# SDL_Scancode values for keyboard controls (see SDL documentation)\n"; - file << "# Gamepad button values: 0-20+ = SDL_GamepadButton, 100 = L2, 101 = R2, 200+ = Axes\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"; - // Escribe el contenido YAML - file << yaml_content; + // VERSION + file << "# VERSION \n"; + file << "version: \"" << GameDefaults::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(); diff --git a/source/game/options.hpp b/source/game/options.hpp index a7b8bde0..27461a76 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -7,55 +7,18 @@ #include #include "core/rendering/screen.hpp" // Para Screen::Filter -#include "utils/defines.hpp" // Para WINDOW_CAPTION -#include "utils/utils.hpp" // Para Color, Palette +#include "game/defaults.hpp" +#include "utils/defines.hpp" // Para WINDOW_CAPTION +#include "utils/utils.hpp" // Para Color, Palette // --- Namespace Options: gestión de configuración y opciones del juego --- namespace Options { -// Tipos de control de teclado -/* -enum class ControlScheme { - CURSOR, - OPQA, - WASD -}; -*/ - -} // namespace Options - -// Incluir constantes por defecto después de declarar los enums -#include "game/defaults.hpp" - -namespace Options { - -// Estructura para las opciones de las notificaciones -struct Notification { - bool sound{GameDefaults::NOTIFICATION_SOUND}; // Indica si las notificaciones suenan - Uint8 color{GameDefaults::NOTIFICATION_COLOR}; // Color de las notificaciones - - // Constructor por defecto - Notification() = default; - - // Constructor - Notification(bool s, Uint8 c) - : sound(s), - color(c) {} -}; // Estructura para las opciones de control de teclado -struct ControlScheme { - SDL_Scancode key_left{SDL_SCANCODE_LEFT}; // Tecla para mover a la izquierda - SDL_Scancode key_right{SDL_SCANCODE_RIGHT}; // Tecla para mover a la derecha - SDL_Scancode key_jump{SDL_SCANCODE_UP}; // Tecla para saltar - - // Constructor por defecto - ControlScheme() = default; - - // Constructor - ControlScheme(SDL_Scancode left, SDL_Scancode right, SDL_Scancode jump) - : key_left(left), - key_right(right), - key_jump(jump) {} +struct KeyboardControls { + SDL_Scancode key_left{GameDefaults::CONTROL_KEY_LEFT}; // Tecla para mover a la izquierda + SDL_Scancode key_right{GameDefaults::CONTROL_KEY_RIGHT}; // Tecla para mover a la derecha + SDL_Scancode key_jump{GameDefaults::CONTROL_KEY_JUMP}; // Tecla para saltar }; // Estructura para las opciones de control del gamepad/joystick @@ -65,19 +28,10 @@ struct ControlScheme { // - 101: R2 trigger // - 200: Left stick X axis (negativo = izquierda) // - 201: Left stick X axis (positivo = derecha) -struct GamepadControlScheme { - int button_left{static_cast(SDL_GAMEPAD_BUTTON_DPAD_LEFT)}; // Botón para mover a la izquierda (por defecto: DPAD_LEFT) - int button_right{static_cast(SDL_GAMEPAD_BUTTON_DPAD_RIGHT)}; // Botón para mover a la derecha (por defecto: DPAD_RIGHT) - int button_jump{static_cast(SDL_GAMEPAD_BUTTON_WEST)}; // Botón para saltar (por defecto: WEST/X button) - - // Constructor por defecto - GamepadControlScheme() = default; - - // Constructor - GamepadControlScheme(int left, int right, int jump) - : button_left(left), - button_right(right), - button_jump(jump) {} +struct GamepadControls { + int button_left{GameDefaults::GAMEPAD_BUTTON_LEFT}; // Botón para mover a la izquierda (por defecto: DPAD_LEFT) + int button_right{GameDefaults::GAMEPAD_BUTTON_RIGHT}; // Botón para mover a la derecha (por defecto: DPAD_RIGHT) + int button_jump{GameDefaults::GAMEPAD_BUTTON_JUMP}; // Botón para saltar (por defecto: WEST/X button) }; // Estructura para albergar trucos @@ -87,20 +41,10 @@ struct Cheat { ENABLED = true }; - State infinite_lives{State::DISABLED}; // Indica si el jugador dispone de vidas infinitas - State invincible{State::DISABLED}; // Indica si el jugador puede morir - State jail_is_open{State::DISABLED}; // Indica si la Jail está abierta - State alternate_skin{State::DISABLED}; // Indica si se usa una skin diferente para el jugador - - // Constructor por defecto - Cheat() = default; - - // Constructor - Cheat(State inf_lives, State is_invincible, State jail_enabled, State alt_skin) - : infinite_lives(inf_lives), - invincible(is_invincible), - jail_is_open(jail_enabled), - alternate_skin(alt_skin) {} + State infinite_lives{GameDefaults::CHEAT_INFINITE_LIVES ? State::ENABLED : State::DISABLED}; // Indica si el jugador dispone de vidas infinitas + State invincible{GameDefaults::CHEAT_INVINCIBLE ? State::ENABLED : State::DISABLED}; // Indica si el jugador puede morir + State jail_is_open{GameDefaults::CHEAT_JAIL_IS_OPEN ? State::ENABLED : State::DISABLED}; // Indica si la Jail está abierta + State alternate_skin{GameDefaults::CHEAT_ALTERNATE_SKIN ? State::ENABLED : State::DISABLED}; // Indica si se usa una skin diferente para el jugador // Método para comprobar si alguno de los tres primeros trucos está activo [[nodiscard]] auto enabled() const -> bool { @@ -112,18 +56,9 @@ struct Cheat { // Estructura para almacenar estadísticas struct Stats { - int rooms{0}; // Cantidad de habitaciones visitadas - int items{0}; // Cantidad de items obtenidos - std::string worst_nightmare; // Habitación con más muertes acumuladas - - // Constructor por defecto - Stats() = default; - - // Constructor - Stats(int room_count, int item_count, std::string worst_nightmare_room) - : rooms(room_count), - items(item_count), - worst_nightmare(std::move(worst_nightmare_room)) {} + int rooms{GameDefaults::STATS_ROOMS}; // Cantidad de habitaciones visitadas + int items{GameDefaults::STATS_ITEMS}; // Cantidad de items obtenidos + std::string worst_nightmare{GameDefaults::STATS_WORST_NIGHTMARE}; // Habitación con más muertes acumuladas }; // Estructura con opciones de la ventana @@ -131,15 +66,6 @@ struct Window { std::string caption{WINDOW_CAPTION}; // Texto que aparece en la barra de título de la ventana int zoom{GameDefaults::WINDOW_ZOOM}; // Zoom de la ventana int max_zoom{GameDefaults::WINDOW_ZOOM}; // Máximo tamaño de zoom para la ventana - - // Constructor por defecto - Window() = default; - - // Constructor - Window(int window_zoom, int maximum_zoom) - : caption(WINDOW_CAPTION), - zoom(window_zoom), - max_zoom(maximum_zoom) {} }; // Estructura para gestionar el borde de la pantalla @@ -147,124 +73,66 @@ struct Border { bool enabled{GameDefaults::BORDER_ENABLED}; // Indica si se ha de mostrar el borde float width{GameDefaults::BORDER_WIDTH}; // Ancho del borde float height{GameDefaults::BORDER_HEIGHT}; // Alto del borde - - // Constructor por defecto - Border() = default; - - // Constructor - Border(bool is_enabled, float border_width, float border_height) - : enabled(is_enabled), - width(border_width), - height(border_height) {} }; // Estructura para las opciones de video struct Video { - bool fullscreen{GameDefaults::VIDEO_MODE}; // Contiene el valor del modo de pantalla completa + bool fullscreen{GameDefaults::VIDEO_FULLSCREEN}; // Contiene el valor del modo de pantalla completa Screen::Filter filter{GameDefaults::VIDEO_FILTER}; // Filtro usado para el escalado de la imagen bool vertical_sync{GameDefaults::VIDEO_VERTICAL_SYNC}; // Indica si se quiere usar vsync o no bool shaders{GameDefaults::VIDEO_SHADERS}; // Indica si se van a usar shaders o no bool integer_scale{GameDefaults::VIDEO_INTEGER_SCALE}; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa bool keep_aspect{GameDefaults::VIDEO_KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa - Border border; // Borde de la pantalla + Border border{}; // Borde de la pantalla std::string palette{GameDefaults::PALETTE_NAME}; // Paleta de colores a usar en el juego - std::string info; // Información sobre el modo de vídeo - - // Constructor por defecto - Video() = default; - - // Constructor - Video(bool is_fullscreen, Screen::Filter screen_filter, bool vsync, bool use_shaders, bool int_scale, bool keep_aspect_ratio, Border video_border, std::string palette_name) - : fullscreen(is_fullscreen), - filter(screen_filter), - vertical_sync(vsync), - shaders(use_shaders), - integer_scale(int_scale), - keep_aspect(keep_aspect_ratio), - border(video_border), - palette(std::move(palette_name)) {} + std::string info{}; // Información sobre el modo de vídeo }; // Estructura para las opciones de musica struct Music { bool enabled{GameDefaults::MUSIC_ENABLED}; // Indica si la música suena o no float volume{GameDefaults::MUSIC_VOLUME}; // Volumen al que suena la música - - // Constructor por defecto - Music() = default; - - // Constructor con parámetros - Music(bool is_enabled, float volume_percent) - : enabled(is_enabled), - volume(volume_percent) {} }; // Estructura para las opciones de sonido struct Sound { bool enabled{GameDefaults::SOUND_ENABLED}; // Indica si los sonidos suenan o no float volume{GameDefaults::SOUND_VOLUME}; // Volumen al que suenan los sonidos (0 a 128 internamente) - - // Constructor por defecto - Sound() = default; - - // Constructor con parámetros - Sound(bool is_enabled, float volume_percent) - : enabled(is_enabled), - volume(volume_percent) {} }; // Estructura para las opciones de audio struct Audio { - Music music; // Opciones para la música - Sound sound; // Opciones para los efectos de sonido + Music music{}; // Opciones para la música + Sound sound{}; // Opciones para los efectos de sonido bool enabled{GameDefaults::AUDIO_ENABLED}; // Indica si el audio está activo o no float volume{GameDefaults::AUDIO_VOLUME}; // Volumen al que suenan el audio (0-128 internamente) - - // Constructor por defecto - Audio() = default; - - // Constructor - Audio(Music audio_music, Sound audio_sound, bool is_enabled, float volume_percent) - : music(audio_music), - sound(audio_sound), - enabled(is_enabled), - volume(volume_percent) {} }; // Estructura para las opciones de juego struct Game { float width{GameDefaults::GAME_WIDTH}; // Ancho de la resolucion del juego float height{GameDefaults::GAME_HEIGHT}; // Alto de la resolucion del juego - - // Constructor por defecto - Game() = default; - - // Constructor - Game(float game_width, float game_height) - : width(game_width), - height(game_height) {} }; // --- Variables globales (inline C++17+) --- -inline std::string version{}; // Versión del fichero de configuración. Sirve para saber si las opciones son compatibles -inline bool console{false}; // Indica si ha de mostrar información por la consola de texto -inline Cheat cheats{}; // Contiene trucos y ventajas para el juego -inline Game game{}; // Opciones de juego -inline Video video{}; // Opciones de video -inline Stats stats{}; // Datos con las estadisticas de juego -inline Notification notifications{}; // Opciones relativas a las notificaciones; -inline Window window{}; // Opciones relativas a la ventana -inline Audio audio{}; // Opciones relativas al audio -inline ControlScheme controls{}; // Teclas usadas para jugar -inline GamepadControlScheme gamepad_controls{}; // Botones del gamepad usados para jugar +inline std::string version{}; // Versión del fichero de configuración. Sirve para saber si las opciones son compatibles +inline bool console{false}; // Indica si ha de mostrar información por la consola de texto +inline Cheat cheats{}; // Contiene trucos y ventajas para el juego +inline Game game{}; // Opciones de juego +inline Video video{}; // Opciones de video +inline Stats stats{}; // Datos con las estadisticas de juego +inline Window window{}; // Opciones relativas a la ventana +inline Audio audio{}; // Opciones relativas al audio +inline KeyboardControls keyboard_controls{}; // Teclas usadas para jugar +inline GamepadControls gamepad_controls{}; // Botones del gamepad usados para jugar // Ruta completa del fichero de configuración (establecida mediante setConfigFile) inline std::string config_file_path_{}; // --- Funciones --- -void init(); // Crea e inicializa las opciones del programa -void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración -auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado -auto saveToFile() -> bool; // Guarda las opciones al fichero configurado +void init(); // Crea e inicializa las opciones del programa +void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración +auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado +auto saveToFile() -> bool; // Guarda las opciones al fichero configurado } // namespace Options \ No newline at end of file diff --git a/source/game/scenes/title.cpp b/source/game/scenes/title.cpp index bdccf742..7103cc9d 100644 --- a/source/game/scenes/title.cpp +++ b/source/game/scenes/title.cpp @@ -645,9 +645,9 @@ auto Title::getActionName(int step) -> std::string { // Aplica y guarda las teclas redefinidas void Title::applyKeyboardRemap() { // Guardar las nuevas teclas en Options::controls - Options::controls.key_left = temp_keys_[0]; - Options::controls.key_right = temp_keys_[1]; - Options::controls.key_jump = temp_keys_[2]; + Options::keyboard_controls.key_left = temp_keys_[0]; + Options::keyboard_controls.key_right = temp_keys_[1]; + Options::keyboard_controls.key_jump = temp_keys_[2]; // Aplicar los bindings al sistema de Input Input::get()->applyKeyboardBindingsFromOptions();