449 lines
18 KiB
C++
449 lines
18 KiB
C++
#include "options.hpp"
|
|
|
|
#include <SDL3/SDL.h> // Para SDL_ScaleMode, SDL_LogCategory, SDL_LogError, SDL_LogInfo, SDL_LogWarn
|
|
|
|
#include <algorithm> // Para clamp
|
|
#include <cstddef> // Para size_t
|
|
#include <fstream> // Para ifstream, ofstream
|
|
#include <string> // Para string
|
|
#include <vector> // Para vector
|
|
|
|
#include "difficulty.hpp" // Para Code, init
|
|
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
|
#include "input.hpp" // Para Input
|
|
#include "lang.hpp" // Para getText, Code
|
|
#include "ui/logger.hpp" // Para info
|
|
#include "utils.hpp" // Para boolToString, getFileName
|
|
|
|
namespace Options {
|
|
// --- Variables globales ---
|
|
Window window; // Opciones de la ventana
|
|
Settings settings; // Opciones del juego
|
|
Video video; // Opciones de vídeo
|
|
Audio audio; // Opciones de audio
|
|
GamepadManager gamepad_manager; // Opciones de mando para cada jugador
|
|
Keyboard keyboard; // Opciones para el teclado
|
|
PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
|
|
|
// Establece el fichero de configuración
|
|
void setConfigFile(const std::string& file_path) { settings.config_file = file_path; }
|
|
|
|
// Establece el fichero de configuración de mandos
|
|
void setControllersFile(const std::string& file_path) { settings.controllers_file = file_path; }
|
|
|
|
// Inicializa las opciones del programa
|
|
void init() {
|
|
// Dificultades
|
|
Difficulty::init();
|
|
|
|
// Opciones de control
|
|
gamepad_manager.init();
|
|
setKeyboardToPlayer(Player::Id::PLAYER1);
|
|
|
|
// Opciones de cambios pendientes
|
|
pending_changes.new_language = settings.language;
|
|
pending_changes.new_difficulty = settings.difficulty;
|
|
pending_changes.has_pending_changes = false;
|
|
}
|
|
|
|
// --- Funciones helper de carga desde YAML ---
|
|
|
|
void loadWindowFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("window")) { return; }
|
|
const auto& win = yaml["window"];
|
|
if (win.contains("zoom")) {
|
|
try {
|
|
int val = win["zoom"].get_value<int>();
|
|
window.zoom = (val > 0) ? val : window.zoom;
|
|
} catch (...) {}
|
|
}
|
|
}
|
|
|
|
void loadVideoFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("video")) { return; }
|
|
const auto& vid = yaml["video"];
|
|
|
|
if (vid.contains("fullscreen")) {
|
|
try { video.fullscreen = vid["fullscreen"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
if (vid.contains("scale_mode")) {
|
|
try { video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>()); } catch (...) {}
|
|
}
|
|
if (vid.contains("vsync")) {
|
|
try { video.vsync = vid["vsync"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
if (vid.contains("integer_scale")) {
|
|
try { video.integer_scale = vid["integer_scale"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
if (vid.contains("shaders")) {
|
|
try { video.shaders = vid["shaders"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
}
|
|
|
|
void loadAudioFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("audio")) { return; }
|
|
const auto& aud = yaml["audio"];
|
|
|
|
if (aud.contains("enabled")) {
|
|
try { audio.enabled = aud["enabled"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
if (aud.contains("volume")) {
|
|
try { audio.volume = std::clamp(aud["volume"].get_value<int>(), 0, 100); } catch (...) {}
|
|
}
|
|
if (aud.contains("music")) {
|
|
const auto& mus = aud["music"];
|
|
if (mus.contains("enabled")) {
|
|
try { audio.music.enabled = mus["enabled"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
if (mus.contains("volume")) {
|
|
try { audio.music.volume = std::clamp(mus["volume"].get_value<int>(), 0, 100); } catch (...) {}
|
|
}
|
|
}
|
|
if (aud.contains("sound")) {
|
|
const auto& snd = aud["sound"];
|
|
if (snd.contains("enabled")) {
|
|
try { audio.sound.enabled = snd["enabled"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
if (snd.contains("volume")) {
|
|
try { audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100); } catch (...) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
void loadGameFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("game")) { return; }
|
|
const auto& game = yaml["game"];
|
|
|
|
if (game.contains("language")) {
|
|
try {
|
|
auto lang = static_cast<Lang::Code>(game["language"].get_value<int>());
|
|
if (lang == Lang::Code::ENGLISH || lang == Lang::Code::VALENCIAN || lang == Lang::Code::SPANISH) {
|
|
settings.language = lang;
|
|
} else {
|
|
settings.language = Lang::Code::ENGLISH;
|
|
}
|
|
pending_changes.new_language = settings.language;
|
|
} catch (...) {}
|
|
}
|
|
if (game.contains("difficulty")) {
|
|
try {
|
|
settings.difficulty = static_cast<Difficulty::Code>(game["difficulty"].get_value<int>());
|
|
pending_changes.new_difficulty = settings.difficulty;
|
|
} catch (...) {}
|
|
}
|
|
if (game.contains("autofire")) {
|
|
try { settings.autofire = game["autofire"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
if (game.contains("shutdown_enabled")) {
|
|
try { settings.shutdown_enabled = game["shutdown_enabled"].get_value<bool>(); } catch (...) {}
|
|
}
|
|
if (game.contains("params_file")) {
|
|
try { settings.params_file = game["params_file"].get_value<std::string>(); } catch (...) {}
|
|
}
|
|
}
|
|
|
|
void loadControllersFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("controllers")) { return; }
|
|
const auto& controllers = yaml["controllers"];
|
|
|
|
size_t i = 0;
|
|
for (const auto& ctrl : controllers) {
|
|
if (i >= GamepadManager::size()) { break; }
|
|
if (ctrl.contains("name")) {
|
|
try { gamepad_manager[i].name = ctrl["name"].get_value<std::string>(); } catch (...) {}
|
|
}
|
|
if (ctrl.contains("path")) {
|
|
try { gamepad_manager[i].path = ctrl["path"].get_value<std::string>(); } catch (...) {}
|
|
}
|
|
if (ctrl.contains("player")) {
|
|
try {
|
|
int player_int = ctrl["player"].get_value<int>();
|
|
if (player_int == 1) {
|
|
gamepad_manager[i].player_id = Player::Id::PLAYER1;
|
|
} else if (player_int == 2) {
|
|
gamepad_manager[i].player_id = Player::Id::PLAYER2;
|
|
}
|
|
} catch (...) {}
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void loadKeyboardFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("keyboard")) { return; }
|
|
const auto& kb = yaml["keyboard"];
|
|
if (kb.contains("player")) {
|
|
try { keyboard.player_id = static_cast<Player::Id>(kb["player"].get_value<int>()); } catch (...) {}
|
|
}
|
|
}
|
|
|
|
// Carga el fichero de configuración
|
|
auto loadFromFile() -> bool {
|
|
init();
|
|
|
|
std::ifstream file(settings.config_file);
|
|
if (!file.is_open()) {
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Config file not found. Creating default settings.");
|
|
saveToFile();
|
|
return true;
|
|
}
|
|
|
|
Logger::info("Reading file: " + getFileName(settings.config_file));
|
|
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
file.close();
|
|
|
|
try {
|
|
auto yaml = fkyaml::node::deserialize(content);
|
|
|
|
loadWindowFromYaml(yaml);
|
|
loadVideoFromYaml(yaml);
|
|
loadAudioFromYaml(yaml);
|
|
loadGameFromYaml(yaml);
|
|
loadControllersFromYaml(yaml);
|
|
loadKeyboardFromYaml(yaml);
|
|
|
|
} catch (const fkyaml::exception& e) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Error parsing YAML config: %s. Using defaults.", e.what());
|
|
init();
|
|
saveToFile();
|
|
return true;
|
|
}
|
|
|
|
gamepad_manager.assignAndLinkGamepads();
|
|
return true;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
Logger::info("Writing file: " + getFileName(settings.config_file));
|
|
|
|
applyPendingChanges();
|
|
|
|
file << "# Coffee Crisis Arcade Edition - Configuration File\n";
|
|
file << "# This file is automatically generated and managed by the game.\n";
|
|
file << "\n";
|
|
|
|
file << "version: " << settings.config_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: " << boolToString(video.fullscreen) << "\n";
|
|
file << " scale_mode: " << static_cast<int>(video.scale_mode) << " # " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": linear\n";
|
|
file << " vsync: " << boolToString(video.vsync) << "\n";
|
|
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
|
|
file << " shaders: " << boolToString(video.shaders) << "\n";
|
|
file << "\n";
|
|
|
|
// AUDIO
|
|
file << "# AUDIO (volume range: 0..100)\n";
|
|
file << "audio:\n";
|
|
file << " enabled: " << boolToString(audio.enabled) << "\n";
|
|
file << " volume: " << audio.volume << "\n";
|
|
file << " music:\n";
|
|
file << " enabled: " << boolToString(audio.music.enabled) << "\n";
|
|
file << " volume: " << audio.music.volume << "\n";
|
|
file << " sound:\n";
|
|
file << " enabled: " << boolToString(audio.sound.enabled) << "\n";
|
|
file << " volume: " << audio.sound.volume << "\n";
|
|
file << "\n";
|
|
|
|
// GAME
|
|
file << "# GAME\n";
|
|
file << "game:\n";
|
|
file << " language: " << static_cast<int>(settings.language) << " # 0: spanish, 1: valencian, 2: english\n";
|
|
file << " difficulty: " << static_cast<int>(settings.difficulty) << " # " << static_cast<int>(Difficulty::Code::EASY) << ": easy, " << static_cast<int>(Difficulty::Code::NORMAL) << ": normal, " << static_cast<int>(Difficulty::Code::HARD) << ": hard\n";
|
|
file << " autofire: " << boolToString(settings.autofire) << "\n";
|
|
file << " shutdown_enabled: " << boolToString(settings.shutdown_enabled) << "\n";
|
|
file << " params_file: " << settings.params_file << "\n";
|
|
file << "\n";
|
|
|
|
// CONTROLLERS
|
|
file << "# CONTROLLERS\n";
|
|
file << "controllers:\n";
|
|
gamepad_manager.saveToFile(file);
|
|
file << "\n";
|
|
|
|
// KEYBOARD
|
|
file << "# KEYBOARD\n";
|
|
file << "keyboard:\n";
|
|
file << " player: " << static_cast<int>(keyboard.player_id) << "\n";
|
|
|
|
file.close();
|
|
return true;
|
|
}
|
|
|
|
// Asigna el teclado al jugador
|
|
void setKeyboardToPlayer(Player::Id player_id) {
|
|
keyboard.player_id = player_id;
|
|
}
|
|
|
|
// Intercambia el teclado de jugador
|
|
void swapKeyboard() {
|
|
keyboard.player_id = keyboard.player_id == Player::Id::PLAYER1 ? Player::Id::PLAYER2 : Player::Id::PLAYER1;
|
|
}
|
|
|
|
// Intercambia los jugadores asignados a los dos primeros mandos
|
|
void swapControllers() {
|
|
gamepad_manager.swapPlayers();
|
|
}
|
|
|
|
// Averigua quien está usando el teclado
|
|
auto getPlayerWhoUsesKeyboard() -> Player::Id {
|
|
return keyboard.player_id;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Buscar y asignar un mando disponible por nombre
|
|
auto assignGamepadByName(const std::string& gamepad_name_to_find, Player::Id player_id) -> bool {
|
|
auto found_gamepad = Input::get()->findAvailableGamepadByName(gamepad_name_to_find);
|
|
|
|
if (found_gamepad) {
|
|
return gamepad_manager.assignGamepadToPlayer(player_id, found_gamepad, found_gamepad->name);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Obtener información de un gamepad específico
|
|
auto getGamepadInfo(Player::Id player_id) -> std::string {
|
|
try {
|
|
const auto& gamepad = gamepad_manager.getGamepad(player_id);
|
|
return "Player " + std::to_string(static_cast<int>(player_id)) +
|
|
": " + (gamepad.name.empty() ? "No gamepad" : gamepad.name);
|
|
} catch (const std::exception&) {
|
|
return "Invalid player";
|
|
}
|
|
}
|
|
|
|
// Asigna los mandos físicos basándose en la configuración actual.
|
|
void GamepadManager::assignAndLinkGamepads() {
|
|
auto physical_gamepads = Input::get()->getGamepads();
|
|
|
|
std::array<std::string, MAX_PLAYERS> desired_paths;
|
|
for (size_t i = 0; i < MAX_PLAYERS; ++i) {
|
|
desired_paths[i] = gamepads_[i].path;
|
|
gamepads_[i].instance = nullptr;
|
|
}
|
|
|
|
std::vector<std::shared_ptr<Input::Gamepad>> assigned_instances;
|
|
|
|
assignGamepadsByPath(desired_paths, physical_gamepads, assigned_instances);
|
|
assignRemainingGamepads(physical_gamepads, assigned_instances);
|
|
clearUnassignedGamepadSlots();
|
|
}
|
|
|
|
// --- PRIMERA PASADA: Intenta asignar mandos basándose en la ruta guardada ---
|
|
void GamepadManager::assignGamepadsByPath(
|
|
const std::array<std::string, MAX_PLAYERS>& desired_paths,
|
|
const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads, // NOLINT(readability-named-parameter)
|
|
std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) {
|
|
for (size_t i = 0; i < MAX_PLAYERS; ++i) {
|
|
const std::string& desired_path = desired_paths[i];
|
|
if (desired_path.empty()) {
|
|
continue;
|
|
}
|
|
|
|
for (const auto& physical_gamepad : physical_gamepads) {
|
|
if (physical_gamepad->path == desired_path && !isGamepadAssigned(physical_gamepad, assigned_instances)) {
|
|
gamepads_[i].instance = physical_gamepad;
|
|
gamepads_[i].name = physical_gamepad->name;
|
|
|
|
assigned_instances.push_back(physical_gamepad);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- SEGUNDA PASADA: Asigna los mandos físicos restantes a los jugadores libres ---
|
|
void GamepadManager::assignRemainingGamepads(
|
|
const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads, // NOLINT(readability-named-parameter)
|
|
std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) {
|
|
for (size_t i = 0; i < MAX_PLAYERS; ++i) {
|
|
if (gamepads_[i].instance != nullptr) {
|
|
continue;
|
|
}
|
|
|
|
for (const auto& physical_gamepad : physical_gamepads) {
|
|
if (!isGamepadAssigned(physical_gamepad, assigned_instances)) {
|
|
gamepads_[i].instance = physical_gamepad;
|
|
gamepads_[i].name = physical_gamepad->name;
|
|
gamepads_[i].path = physical_gamepad->path;
|
|
|
|
assigned_instances.push_back(physical_gamepad);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- TERCERA PASADA: Limpia la información "fantasma" de los slots no asignados ---
|
|
void GamepadManager::clearUnassignedGamepadSlots() {
|
|
for (auto& gamepad_config : gamepads_) {
|
|
if (gamepad_config.instance == nullptr) {
|
|
gamepad_config.name = Lang::getText("[SERVICE_MENU] NO_CONTROLLER");
|
|
gamepad_config.path = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
auto GamepadManager::isGamepadAssigned(
|
|
const std::shared_ptr<Input::Gamepad>& physical_gamepad,
|
|
const std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) -> bool { // NOLINT(readability-named-parameter)
|
|
return std::ranges::any_of(assigned_instances,
|
|
[&physical_gamepad](const auto& assigned) -> auto {
|
|
return assigned == physical_gamepad;
|
|
});
|
|
}
|
|
|
|
// Convierte un player id a texto segun Lang
|
|
auto playerIdToString(Player::Id player_id) -> std::string {
|
|
switch (player_id) {
|
|
case Player::Id::PLAYER1:
|
|
return Lang::getText("[SERVICE_MENU] PLAYER1");
|
|
case Player::Id::PLAYER2:
|
|
return Lang::getText("[SERVICE_MENU] PLAYER2");
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// Convierte un texto a player id segun Lang
|
|
auto stringToPlayerId(const std::string& name) -> Player::Id {
|
|
if (name == Lang::getText("[SERVICE_MENU] PLAYER1")) {
|
|
return Player::Id::PLAYER1;
|
|
}
|
|
if (name == Lang::getText("[SERVICE_MENU] PLAYER2")) {
|
|
return Player::Id::PLAYER2;
|
|
}
|
|
return Player::Id::NO_PLAYER;
|
|
}
|
|
} // namespace Options
|