#include "options.h" #include // Para SDL_ScaleMode, SDL_GamepadButton, SDL_LogCategory, SDL_LogInfo, SDL_LogError, SDL_LogWarn #include // Para size_t #include // Para clamp, max #include // Para basic_ostream, operator<<, basic_ostream::operator<<, basic_ofstream, basic_istream, basic_ifstream, ifstream, ofstream #include // Para function #include // Para map, operator==, _Rb_tree_const_iterator #include // Para invalid_argument, out_of_range #include // Para char_traits, stoi, operator==, operator<<, allocator, string, basic_string, operator<=>, getline #include // Para swap, pair #include // Para vector #include "difficulty.h" // Para Code, init #include "input.h" // Para InputDevice #include "lang.h" // Para Code #include "utils.h" // Para boolToString, stringToBool, getFileName namespace Options { // --- Variables globales --- WindowOptions window; // Opciones de la ventana SettingsOptions settings; // Opciones del juego VideoOptions video; // Opciones de vídeo AudioOptions audio; // Opciones de audio std::vector controllers; // Opciones de mando para cada jugador PendingChanges pending_changes; // Opciones que se aplican al cerrar // Declaraciones auto set(const std::string& var, const std::string& value) -> bool; // Establece el fichero de configuración void setFile(const std::string& file_path) { settings.config_file = file_path; }; // Inicializa las opciones del programa void init() { // Dificultades Difficulty::init(); // Opciones de control controllers.clear(); controllers.resize(2); controllers.at(0).player_id = 1; controllers.at(1).player_id = 2; setKeyboardToPlayer(1); // Opciones pendientes pending_changes.new_language = settings.language; pending_changes.new_difficulty = settings.difficulty; pending_changes.has_pending_changes = false; } // Carga el fichero de configuración auto loadFromFile() -> bool { // Inicializa las opciones del programa init(); // Indicador de éxito en la carga bool success = true; // Variables para manejar el fichero std::ifstream file(settings.config_file); // Si el fichero se puede abrir if (file.good()) { // Procesa el fichero línea a línea SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\nReading file: %s", getFileName(settings.config_file).c_str()); std::string line; while (std::getline(file, line)) { // Comprueba que la línea no sea un comentario if (line.substr(0, 1) != "#") { // Encuentra la posición del carácter '=' int pos = line.find("="); // Procesa las dos subcadenas if (!set(line.substr(0, pos), line.substr(pos + 1, line.length()))) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unknown parameter: %s", line.substr(0, pos).c_str()); success = false; } } } file.close(); } // El fichero no existe else { saveToFile(); // Crea el fichero con los valores por defecto } // Normaliza los valores if (settings.language != Lang::Code::ENGLISH && settings.language != Lang::Code::VALENCIAN && settings.language != Lang::Code::SPANISH) { settings.language = Lang::Code::ENGLISH; } return success; } // Guarda el fichero de configuración auto saveToFile() -> bool { std::ofstream file(settings.config_file); if (!file.good()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: %s can't be opened", getFileName(settings.config_file).c_str()); return false; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Writing file: %s", getFileName(settings.config_file).c_str()); applyPendingChanges(); // Opciones de ventana file << "## WINDOW\n"; file << "\n"; file << "window.zoom=" << window.zoom << "\n"; // Opciones de video file << "## VIDEO\n"; file << "## video.scale_mode [" << static_cast(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": lineal]\n"; file << "\n"; file << "video.fullscreen=" << boolToString(video.fullscreen) << "\n"; file << "video.scale_mode=" << static_cast(video.scale_mode) << "\n"; file << "video.vsync=" << boolToString(video.vsync) << "\n"; file << "video.integer_scale=" << boolToString(video.integer_scale) << "\n"; file << "video.shaders=" << boolToString(video.shaders) << "\n"; // Opciones de audio file << "\n\n## AUDIO\n"; file << "## volume [0 .. 100]\n"; file << "\n"; file << "audio.enabled=" << boolToString(audio.enabled) << "\n"; file << "audio.volume=" << audio.volume << "\n"; file << "audio.music.enabled=" << boolToString(audio.music.enabled) << "\n"; file << "audio.music.volume=" << audio.music.volume << "\n"; file << "audio.sound.enabled=" << boolToString(audio.sound.enabled) << "\n"; file << "audio.sound.volume=" << audio.sound.volume << "\n"; // Opciones del juego file << "\n\n## GAME\n"; file << "## game.language [0: spanish, 1: valencian, 2: english]\n"; file << "## game.difficulty [" << static_cast(Difficulty::Code::EASY) << ": easy, " << static_cast(Difficulty::Code::NORMAL) << ": normal, " << static_cast(Difficulty::Code::HARD) << ": hard]\n"; file << "\n"; file << "game.language=" << static_cast(settings.language) << "\n"; file << "game.difficulty=" << static_cast(settings.difficulty) << "\n"; file << "game.autofire=" << boolToString(settings.autofire) << "\n"; file << "game.shutdown_enabled=" << boolToString(settings.shutdown_enabled) << "\n"; // Opciones de mandos file << "\n\n## CONTROLLERS\n"; int controller_index = 0; for (const auto& controller : controllers) { file << "\n"; file << "controller." << controller_index << ".name=" << controller.name << "\n"; file << "controller." << controller_index << ".player=" << controller.player_id << "\n"; file << "controller." << controller_index << ".type=" << static_cast(controller.type) << "\n"; file << "controller." << controller_index << ".button.fire_left=" << controller.buttons.at(0) << "\n"; file << "controller." << controller_index << ".button.fire_center=" << controller.buttons.at(1) << "\n"; file << "controller." << controller_index << ".button.fire_right=" << controller.buttons.at(2) << "\n"; file << "controller." << controller_index << ".button.start=" << controller.buttons.at(3) << "\n"; file << "controller." << controller_index << ".button.service=" << controller.buttons.at(4) << "\n"; // Incrementa el índice ++controller_index; } // Cierra el fichero file.close(); return true; } // Función auxiliar para analizar la configuración del mando y reducir duplicación void parseAndSetController(const std::string& var, const std::string& value) { // Lógica básica de análisis (puede hacerse más robusta) size_t first_dot = var.find('.'); size_t second_dot = var.find('.', first_dot + 1); int controller_index = std::stoi(var.substr(first_dot + 1, second_dot - first_dot - 1)); std::string setting_key = var.substr(second_dot + 1); auto& controller = controllers.at(controller_index); if (setting_key == "name") { controller.name = value; } else if (setting_key == "player") { controller.player_id = std::clamp(std::stoi(value), 1, 2); } else if (setting_key == "type") { controller.type = static_cast(std::stoi(value)); } else if (setting_key == "button.fire_left") { controller.buttons.at(0) = static_cast(std::stoi(value)); } else if (setting_key == "button.fire_center") { controller.buttons.at(1) = static_cast(std::stoi(value)); } else if (setting_key == "button.fire_right") { controller.buttons.at(2) = static_cast(std::stoi(value)); } else if (setting_key == "button.start") { controller.buttons.at(3) = static_cast(std::stoi(value)); } else if (setting_key == "button.service") { controller.buttons.at(4) = static_cast(std::stoi(value)); } } auto set(const std::string& var, const std::string& value) -> bool { // Clausula de protección: ignora líneas vacías o comentarios if (var.empty() || var.starts_with("#")) { return false; } // Un mapa estático asegura que se inicializa solo una vez static const std::map> SETTINGS_MAP = { // Ventana {"window.zoom", [](const auto& val) { window.zoom = std::stoi(val); }}, // Vídeo {"video.fullscreen", [](const auto& val) { video.fullscreen = stringToBool(val); }}, {"video.scale_mode", [](const auto& val) { video.scale_mode = static_cast(std::stoi(val)); }}, {"video.shaders", [](const auto& val) { video.shaders = stringToBool(val); }}, {"video.integer_scale", [](const auto& val) { video.integer_scale = stringToBool(val); }}, {"video.vsync", [](const auto& val) { video.vsync = stringToBool(val); }}, // Audio {"audio.enabled", [](const auto& val) { audio.enabled = stringToBool(val); }}, {"audio.volume", [](const auto& val) { audio.volume = std::clamp(std::stoi(val), 0, 100); }}, {"audio.music.enabled", [](const auto& val) { audio.music.enabled = stringToBool(val); }}, {"audio.music.volume", [](const auto& val) { audio.music.volume = std::clamp(std::stoi(val), 0, 100); }}, {"audio.sound.enabled", [](const auto& val) { audio.sound.enabled = stringToBool(val); }}, {"audio.sound.volume", [](const auto& val) { audio.sound.volume = std::clamp(std::stoi(val), 0, 100); }}, // Juego {"game.language", [](const auto& val) { settings.language = static_cast(std::stoi(val)); pending_changes.new_language = settings.language; }}, {"game.difficulty", [](const auto& val) { settings.difficulty = static_cast(std::stoi(val)); pending_changes.new_difficulty = settings.difficulty; }}, {"game.autofire", [](const auto& val) { settings.autofire = stringToBool(val); }}, {"game.shutdown_enabled", [](const auto& val) { settings.shutdown_enabled = stringToBool(val); }}}; // Maneja por separado la configuración general de los mandos if (var.starts_with("controller.")) { try { parseAndSetController(var, value); return true; } catch (const std::out_of_range& e) { // Error: por ejemplo, índice de mando fuera de rango return false; } catch (const std::invalid_argument& e) { // Error: por ejemplo, fallo en std::stoi return false; } } // Busca el nombre de la variable en el mapa if (auto it = SETTINGS_MAP.find(var); it != SETTINGS_MAP.end()) { try { // Ejecuta la función lambda asociada it->second(value); return true; } catch (const std::invalid_argument& e) { // Maneja casos donde std::stoi falla por entrada inválida return false; } } // Si la clave no se encontró en el mapa ni en la lógica de mandos return false; } // Asigna el teclado al jugador void setKeyboardToPlayer(int player_id) { for (auto& controller : controllers) { if (controller.player_id == player_id) { controller.type = InputDevice::ANY; } else { controller.type = InputDevice::CONTROLLER; } } } // Intercambia el teclado de jugador void swapKeyboard() { std::swap(controllers.at(0).type, controllers.at(1).type); } // Intercambia los jugadores asignados a los dos primeros mandos void swapControllers() { std::swap(controllers.at(0).player_id, controllers.at(1).player_id); std::swap(controllers.at(0).type, controllers.at(1).type); } // Averigua quien está usando el teclado auto getPlayerWhoUsesKeyboard() -> int { for (const auto& controller : controllers) { if (controller.type == InputDevice::ANY) { return controller.player_id; } } return 0; } // Aplica los cambios pendientes copiando los valores a sus variables void applyPendingChanges() { if (pending_changes.has_pending_changes) { settings.language = pending_changes.new_language; settings.difficulty = pending_changes.new_difficulty; pending_changes.has_pending_changes = false; } } void checkPendingChanges() { pending_changes.has_pending_changes = settings.language != pending_changes.new_language || settings.difficulty != pending_changes.new_difficulty; } } // namespace Options