From 7f703390f94f967d7ece89862234494aa675b5c3 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 17 Apr 2026 19:36:40 +0200 Subject: [PATCH] modernitzat el sistema d'opcions --- source/core/rendering/screen.cpp | 127 +++++------- source/core/rendering/screen.h | 9 +- source/core/system/director.cpp | 336 +++--------------------------- source/core/system/director.h | 17 -- source/game/defaults.hpp | 50 +++++ source/game/game.cpp | 50 ++--- source/game/game.h | 3 +- source/game/options.cpp | 342 +++++++++++++++++++++++++++++++ source/game/options.hpp | 105 ++++++++++ source/game/scenes/title.cpp | 155 ++++++-------- source/game/scenes/title.h | 12 +- source/utils/utils.h | 37 ---- 12 files changed, 690 insertions(+), 553 deletions(-) create mode 100644 source/game/options.cpp create mode 100644 source/game/options.hpp diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index c92a59e..63426ab 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -11,6 +11,8 @@ #include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE #include "core/resources/asset.h" // for Asset #include "core/resources/resource.h" +#include "game/defaults.hpp" // for GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT +#include "game/options.hpp" // for Options::video, Options::settings #ifndef NO_SHADERS #include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // for Rendering::SDL3GPUShader @@ -59,17 +61,14 @@ namespace { #endif // __EMSCRIPTEN__ // Constructor -Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options_t *options) { +Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset) { // Inicializa variables this->window = window; this->renderer = renderer; - this->options = options; this->asset = asset; - gameCanvasWidth = options->gameWidth; - gameCanvasHeight = options->gameHeight; - borderWidth = options->borderWidth * 2; - borderHeight = options->borderHeight * 2; + gameCanvasWidth = GAMECANVAS_WIDTH; + gameCanvasHeight = GAMECANVAS_HEIGHT; // Define el color del borde para el modo de pantalla completa borderColor = {0x00, 0x00, 0x00}; @@ -80,7 +79,7 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options // Mirror del pattern de jaildoctors_dilemma (que usa exactament 256×192 i // funciona) on `initSDLVideo` configura la presentation abans de crear cap // textura. - setVideoMode(options->videoMode != 0); + setVideoMode(Options::video.fullscreen); // Força al window manager a completar el resize/posicionat abans de passar // la ventana al dispositiu GPU. Sense açò en Linux/X11 hi ha un race @@ -92,10 +91,10 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options // ARGB8888 per simplificar el readback cap al pipeline SDL3 GPU. gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight); if (gameCanvas != nullptr) { - SDL_SetTextureScaleMode(gameCanvas, options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR); + SDL_SetTextureScaleMode(gameCanvas, Options::video.scale_mode); } if (gameCanvas == nullptr) { - if (options->console) { + if (Options::settings.console) { std::cout << "gameCanvas could not be created!\nSDL Error: " << SDL_GetError() << std::endl; } } @@ -182,7 +181,7 @@ void Screen::blit() { } SDL_SetRenderTarget(renderer, nullptr); - if (options->videoShaderEnabled) { + if (Options::video.shader.enabled) { // Ruta normal: shader amb els seus params. shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight); shader_backend_->render(); @@ -237,54 +236,54 @@ void Screen::setVideoMode(bool fullscreen) { // Cambia entre pantalla completa y ventana void Screen::toggleVideoMode() { - setVideoMode(options->videoMode == 0); + setVideoMode(!Options::video.fullscreen); } // Reduce el zoom de la ventana auto Screen::decWindowZoom() -> bool { - if (options->videoMode != 0) { return false; } - const int PREV = options->windowSize; - options->windowSize = std::max(options->windowSize - 1, WINDOW_ZOOM_MIN); - if (options->windowSize == PREV) { return false; } + if (Options::video.fullscreen) { return false; } + const int PREV = Options::window.zoom; + Options::window.zoom = std::max(Options::window.zoom - 1, WINDOW_ZOOM_MIN); + if (Options::window.zoom == PREV) { return false; } setVideoMode(false); return true; } // Aumenta el zoom de la ventana auto Screen::incWindowZoom() -> bool { - if (options->videoMode != 0) { return false; } - const int PREV = options->windowSize; - options->windowSize = std::min(options->windowSize + 1, WINDOW_ZOOM_MAX); - if (options->windowSize == PREV) { return false; } + if (Options::video.fullscreen) { return false; } + const int PREV = Options::window.zoom; + Options::window.zoom = std::min(Options::window.zoom + 1, WINDOW_ZOOM_MAX); + if (Options::window.zoom == PREV) { return false; } setVideoMode(false); return true; } // Establece el zoom de la ventana directamente auto Screen::setWindowZoom(int zoom) -> bool { - if (options->videoMode != 0) { return false; } + if (Options::video.fullscreen) { return false; } if (zoom < WINDOW_ZOOM_MIN || zoom > WINDOW_ZOOM_MAX) { return false; } - if (zoom == options->windowSize) { return false; } - options->windowSize = zoom; + if (zoom == Options::window.zoom) { return false; } + Options::window.zoom = zoom; setVideoMode(false); return true; } // Establece el escalado entero void Screen::setIntegerScale(bool enabled) { - if (options->integerScale == enabled) { return; } - options->integerScale = enabled; - setVideoMode(options->videoMode != 0); + if (Options::video.integer_scale == enabled) { return; } + Options::video.integer_scale = enabled; + setVideoMode(Options::video.fullscreen); } // Alterna el escalado entero void Screen::toggleIntegerScale() { - setIntegerScale(!options->integerScale); + setIntegerScale(!Options::video.integer_scale); } // Establece el V-Sync void Screen::setVSync(bool enabled) { - options->vSync = enabled; + Options::video.vsync = enabled; SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED); #ifndef NO_SHADERS if (shader_backend_) { @@ -295,7 +294,7 @@ void Screen::setVSync(bool enabled) { // Alterna el V-Sync void Screen::toggleVSync() { - setVSync(!options->vSync); + setVSync(!Options::video.vsync); } // Cambia el color del borde @@ -322,15 +321,9 @@ void Screen::applyFullscreen(bool fullscreen) { // Calcula windowWidth/Height/dest para el modo ventana y aplica SDL_SetWindowSize void Screen::applyWindowedLayout() { - if (options->borderEnabled) { - windowWidth = gameCanvasWidth + borderWidth; - windowHeight = gameCanvasHeight + borderHeight; - dest = {0 + (borderWidth / 2), 0 + (borderHeight / 2), gameCanvasWidth, gameCanvasHeight}; - } else { - windowWidth = gameCanvasWidth; - windowHeight = gameCanvasHeight; - dest = {0, 0, gameCanvasWidth, gameCanvasHeight}; - } + windowWidth = gameCanvasWidth; + windowHeight = gameCanvasHeight; + dest = {0, 0, gameCanvasWidth, gameCanvasHeight}; #ifdef __EMSCRIPTEN__ windowWidth *= WASM_RENDER_SCALE; @@ -340,7 +333,7 @@ void Screen::applyWindowedLayout() { #endif // Modifica el tamaño de la ventana - SDL_SetWindowSize(window, windowWidth * options->windowSize, windowHeight * options->windowSize); + SDL_SetWindowSize(window, windowWidth * Options::window.zoom, windowHeight * Options::window.zoom); SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); } @@ -350,9 +343,9 @@ void Screen::applyFullscreenLayout() { computeFullscreenGameRect(); } -// Calcula el rectángulo dest para fullscreen: integerScale / keepAspect / stretched +// Calcula el rectángulo dest para fullscreen: integer_scale / aspect ratio void Screen::computeFullscreenGameRect() { - if (options->integerScale) { + if (Options::video.integer_scale) { // Calcula el tamaño de la escala máxima int scale = 0; while (((gameCanvasWidth * (scale + 1)) <= windowWidth) && ((gameCanvasHeight * (scale + 1)) <= windowHeight)) { @@ -363,7 +356,8 @@ void Screen::computeFullscreenGameRect() { dest.h = gameCanvasHeight * scale; dest.x = (windowWidth - dest.w) / 2; dest.y = (windowHeight - dest.h) / 2; - } else if (options->keepAspect) { + } else { + // Manté la relació d'aspecte sense escalat enter (letterbox/pillarbox). float ratio = (float)gameCanvasWidth / (float)gameCanvasHeight; if ((windowWidth - gameCanvasWidth) >= (windowHeight - gameCanvasHeight)) { dest.h = windowHeight; @@ -376,21 +370,13 @@ void Screen::computeFullscreenGameRect() { dest.x = (windowWidth - dest.w) / 2; dest.y = (windowHeight - dest.h) / 2; } - } else { - dest.w = windowWidth; - dest.h = windowHeight; - dest.x = dest.y = 0; } } // Aplica la logical presentation y persiste el estado en options void Screen::applyLogicalPresentation(bool fullscreen) { SDL_SetRenderLogicalPresentation(renderer, windowWidth, windowHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX); - - // Actualiza las opciones - options->videoMode = fullscreen ? SDL_WINDOW_FULLSCREEN : 0; - options->screen.windowWidth = windowWidth; - options->screen.windowHeight = windowHeight; + Options::video.fullscreen = fullscreen; } // ============================================================================ @@ -436,13 +422,13 @@ void Screen::handleCanvasResized() { // La crida a SDL_SetWindowFullscreen + SDL_SetRenderLogicalPresentation // que fa setVideoMode és l'única manera de resincronitzar l'estat intern // de SDL amb el canvas HTML real. - setVideoMode(options->videoMode != 0); + setVideoMode(Options::video.fullscreen); #endif } void Screen::syncFullscreenFlagFromBrowser(bool isFullscreen) { #ifdef __EMSCRIPTEN__ - options->videoMode = isFullscreen ? SDL_WINDOW_FULLSCREEN : 0; + Options::video.fullscreen = isFullscreen; #else (void)isFullscreen; #endif @@ -474,10 +460,7 @@ void Screen::applyShaderParams() { if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) { return; } - const Rendering::ShaderType ACTIVE = options->videoShaderType == 1 - ? Rendering::ShaderType::CRTPI - : Rendering::ShaderType::POSTFX; - shader_backend_->setActiveShader(ACTIVE); + shader_backend_->setActiveShader(Options::video.shader.current_shader); // Preset per defecte (carregador YAML pendent). Valors estil "CRT" de CCAE. Rendering::PostFXParams POSTFX; @@ -497,17 +480,17 @@ void Screen::initShaders() { shader_backend_ = std::make_unique(); const std::string FALLBACK_DRIVER = "none"; shader_backend_->setPreferredDriver( - options->videoGpuAcceleration ? options->videoGpuPreferredDriver : FALLBACK_DRIVER); + Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER); } if (!shader_backend_->isHardwareAccelerated()) { const bool ok = shader_backend_->init(window, gameCanvas, "", ""); - if (options->console) { + if (Options::settings.console) { std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n'; } } if (shader_backend_->isHardwareAccelerated()) { - shader_backend_->setScaleMode(options->integerScale); - shader_backend_->setVSync(options->vSync); + shader_backend_->setScaleMode(Options::video.integer_scale); + shader_backend_->setVSync(Options::video.vsync); applyShaderParams(); // aplica preset del shader actiu } #endif @@ -526,8 +509,8 @@ void Screen::shutdownShaders() { } void Screen::setGpuAcceleration(bool enabled) { - if (options->videoGpuAcceleration == enabled) { return; } - options->videoGpuAcceleration = enabled; + if (Options::video.gpu.acceleration == enabled) { return; } + Options::video.gpu.acceleration = enabled; // Soft toggle: el backend es manté viu (vegeu shutdownShaders). El canvi // s'aplica al proper arrencada. S'emet una notificació perquè l'usuari // sap que ha tocat la tecla però el canvi no és immediat. @@ -538,7 +521,7 @@ void Screen::setGpuAcceleration(bool enabled) { } void Screen::toggleGpuAcceleration() { - setGpuAcceleration(!options->videoGpuAcceleration); + setGpuAcceleration(!Options::video.gpu.acceleration); } auto Screen::isGpuAccelerated() const -> bool { @@ -550,8 +533,8 @@ auto Screen::isGpuAccelerated() const -> bool { } void Screen::setShaderEnabled(bool enabled) { - if (options->videoShaderEnabled == enabled) { return; } - options->videoShaderEnabled = enabled; + if (Options::video.shader.enabled == enabled) { return; } + Options::video.shader.enabled = enabled; #ifndef NO_SHADERS if (enabled) { applyShaderParams(); // restaura preset del shader actiu @@ -566,17 +549,17 @@ void Screen::setShaderEnabled(bool enabled) { } void Screen::toggleShaderEnabled() { - setShaderEnabled(!options->videoShaderEnabled); + setShaderEnabled(!Options::video.shader.enabled); } auto Screen::isShaderEnabled() const -> bool { - return options->videoShaderEnabled; + return Options::video.shader.enabled; } #ifndef NO_SHADERS void Screen::setActiveShader(Rendering::ShaderType type) { - options->videoShaderType = type == Rendering::ShaderType::CRTPI ? 1 : 0; - if (options->videoShaderEnabled) { + Options::video.shader.current_shader = type; + if (Options::video.shader.enabled) { applyShaderParams(); } const color_t MAGENTA = {0xFF, 0x00, 0xFF}; @@ -586,7 +569,7 @@ void Screen::setActiveShader(Rendering::ShaderType type) { } auto Screen::getActiveShader() const -> Rendering::ShaderType { - return options->videoShaderType == 1 ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX; + return Options::video.shader.current_shader; } #endif @@ -597,6 +580,8 @@ void Screen::toggleActiveShader() { : Rendering::ShaderType::POSTFX; setActiveShader(NEXT); #else - options->videoShaderType = options->videoShaderType == 1 ? 0 : 1; + Options::video.shader.current_shader = Options::video.shader.current_shader == Rendering::ShaderType::POSTFX + ? Rendering::ShaderType::CRTPI + : Rendering::ShaderType::POSTFX; #endif } diff --git a/source/core/rendering/screen.h b/source/core/rendering/screen.h index 25cbffa..5865257 100644 --- a/source/core/rendering/screen.h +++ b/source/core/rendering/screen.h @@ -18,10 +18,6 @@ namespace Rendering { class Asset; class Text; -// Tipos de filtro -constexpr int FILTER_NEAREST = 0; -constexpr int FILTER_LINEAL = 1; - class Screen { public: // Constantes @@ -35,7 +31,7 @@ class Screen { #endif // Constructor y destructor - Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options_t *options); + Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset); ~Screen(); // Render loop @@ -104,15 +100,12 @@ class Screen { SDL_Renderer *renderer; // El renderizador de la ventana Asset *asset; // Objeto con el listado de recursos SDL_Texture *gameCanvas; // Textura para completar la ventana de juego hasta la pantalla completa - options_t *options; // Variable con todas las opciones del programa // Variables int windowWidth; // Ancho de la pantalla o ventana int windowHeight; // Alto de la pantalla o ventana int gameCanvasWidth; // Resolución interna del juego. Es el ancho de la textura donde se dibuja el juego int gameCanvasHeight; // Resolución interna del juego. Es el alto de la textura donde se dibuja el juego - int borderWidth; // Anchura del borde - int borderHeight; // Altura del borde SDL_Rect dest; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana color_t borderColor; // Color del borde añadido a la textura de juego para rellenar la pantalla diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 6111959..5876464 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -21,17 +21,18 @@ #include "core/input/input.h" // for Input, inputs_e, INPUT_USE_GAME... #include "core/input/mouse.hpp" // for Mouse::handleEvent, Mouse::upda... #include "core/locale/lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK -#include "core/rendering/screen.h" // for FILTER_NEAREST, Screen, FILTER_... +#include "core/rendering/screen.h" // for Screen #include "core/rendering/texture.h" // for Texture #include "core/resources/asset.h" // for Asset, assetType #include "core/resources/resource.h" #include "core/resources/resource_helper.h" #include "game/defaults.hpp" // for SECTION_PROG_LOGO, GAMECANVAS_H... #include "game/game.h" // for Game +#include "game/options.hpp" // for Options::init, loadFromFile... #include "game/scenes/intro.h" // for Intro #include "game/scenes/logo.h" // for Logo #include "game/scenes/title.h" // for Title -#include "utils/utils.h" // for options_t, input_t, boolToString +#include "utils/utils.h" // for input_t, boolToString #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) #include @@ -44,10 +45,10 @@ Director::Director(int argc, const char *argv[]) { section = new section_t(); section->name = SECTION_PROG_LOGO; - // Inicializa las opciones del programa - initOptions(); + // Inicializa las opciones del programa (defaults + dispositivos d'entrada) + Options::init(); - // Comprueba los parametros del programa + // Comprueba los parametros del programa (pot activar console) checkProgramArguments(argc, argv); // Crea la carpeta del sistema donde guardar datos @@ -58,6 +59,11 @@ Director::Director(int argc, const char *argv[]) { createSystemFolder("jailgames/coffee_crisis_debug"); #endif + // Estableix el fitxer de configuració i carrega les opcions (o crea el + // YAML amb defaults si no existeix). + Options::setConfigFile(systemFolder + "/config.yaml"); + Options::loadFromFile(); + // Inicializa el sistema de recursos (pack + fallback). // En wasm siempre se usa filesystem (MEMFS) porque el propio --preload-file // de emscripten ya empaqueta data/ — no hay resources.pack. @@ -77,16 +83,13 @@ Director::Director(int argc, const char *argv[]) { // Crea el objeto que controla los ficheros de recursos asset = new Asset(executablePath); - asset->setVerbose(options->console); + asset->setVerbose(Options::settings.console); // Si falta algún fichero no inicia el programa if (!setFileList()) { exit(EXIT_FAILURE); } - // Carga el fichero de configuración - loadConfigFile(); - // Inicializa SDL initSDL(); @@ -94,11 +97,11 @@ Director::Director(int argc, const char *argv[]) { initJailAudio(); // Establece el modo de escalado de texturas - Texture::setGlobalScaleMode(options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR); + Texture::setGlobalScaleMode(Options::video.scale_mode); // Crea los objetos lang = new Lang(asset); - lang->setLang(options->language); + lang->setLang(Options::settings.language); #ifdef __EMSCRIPTEN__ input = new Input("/gamecontrollerdb.txt"); @@ -122,10 +125,10 @@ Director::Director(int argc, const char *argv[]) { // // Por eso el constructor de Screen NO carga notificationText desde // Resource; se enlaza después vía `screen->initNotifications()`. - screen = new Screen(window, renderer, asset, options); + screen = new Screen(window, renderer, asset); #ifndef NO_SHADERS - if (options->videoGpuAcceleration) { + if (Options::video.gpu.acceleration) { screen->initShaders(); } #endif @@ -142,7 +145,7 @@ Director::Director(int argc, const char *argv[]) { } Director::~Director() { - saveConfigFile(); + Options::saveToFile(); // Libera las secciones primero: sus destructores tocan audio/render SDL // (p.ej. Intro::~Intro llama a JA_DeleteMusic) y deben ejecutarse antes @@ -162,7 +165,6 @@ Director::~Director() { delete asset; delete input; delete lang; - delete options; delete section; SDL_DestroyRenderer(renderer); @@ -178,7 +180,7 @@ Director::~Director() { // Inicializa el objeto input void Director::initInput() { // Establece si ha de mostrar mensajes - input->setVerbose(options->console); + input->setVerbose(Options::settings.console); // Busca si hay un mando conectado input->discoverGameController(); @@ -237,7 +239,7 @@ bool Director::initSDL() { // Inicializa SDL if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) { - if (options->console) { + if (Options::settings.console) { std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl; } success = false; @@ -246,15 +248,13 @@ bool Director::initSDL() { std::srand(static_cast(SDL_GetTicks())); // Crea la ventana - int incW = 0; - int incH = 0; - if (options->borderEnabled) { - incW = options->borderWidth * 2; - incH = options->borderHeight * 2; - } - window = SDL_CreateWindow(WINDOW_CAPTION, (options->gameWidth + incW) * options->windowSize, (options->gameHeight + incH) * options->windowSize, 0); + window = SDL_CreateWindow( + Options::window.caption.c_str(), + GAMECANVAS_WIDTH * Options::window.zoom, + GAMECANVAS_HEIGHT * Options::window.zoom, + 0); if (window == nullptr) { - if (options->console) { + if (Options::settings.console) { std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl; } success = false; @@ -265,7 +265,7 @@ bool Director::initSDL() { renderer = SDL_CreateRenderer(window, NULL); if (renderer == nullptr) { - if (options->console) { + if (Options::settings.console) { std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl; } success = false; @@ -275,7 +275,7 @@ bool Director::initSDL() { SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); // Activa vsync si es necesario - if (options->vSync) { + if (Options::video.vsync) { SDL_SetRenderVSync(renderer, 1); } @@ -283,7 +283,7 @@ bool Director::initSDL() { SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF); // Establece el tamaño del buffer de renderizado - SDL_SetRenderLogicalPresentation(renderer, options->gameWidth, options->gameHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX); + SDL_SetRenderLogicalPresentation(renderer, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); // Establece el modo de mezcla SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); @@ -291,7 +291,7 @@ bool Director::initSDL() { } } - if (options->console) { + if (Options::settings.console) { std::cout << std::endl; } return success; @@ -306,7 +306,6 @@ bool Director::setFileList() { #endif // Ficheros de configuración - asset->add(systemFolder + "/config.txt", t_data, false, true); asset->add(systemFolder + "/score.bin", t_data, false, true); asset->add(prefix + "/data/demo/demo.bin", t_data); @@ -426,52 +425,6 @@ bool Director::setFileList() { return asset->check(); } -// Inicializa las opciones del programa -void Director::initOptions() { - // Crea el puntero a la estructura de opciones - options = new options_t; - - // Pone unos valores por defecto para las opciones de control - options->input.clear(); - - input_t inp; - inp.id = 0; - inp.name = "KEYBOARD"; - inp.deviceType = INPUT_USE_KEYBOARD; - options->input.push_back(inp); - - inp.id = 0; - inp.name = "GAME CONTROLLER"; - inp.deviceType = INPUT_USE_GAMECONTROLLER; - options->input.push_back(inp); - - // Opciones de video - options->gameWidth = GAMECANVAS_WIDTH; - options->gameHeight = GAMECANVAS_HEIGHT; - options->videoMode = 0; - options->windowSize = 3; - options->filter = FILTER_NEAREST; - options->vSync = true; - options->integerScale = true; - options->keepAspect = true; - options->borderWidth = 0; - options->borderHeight = 0; - options->borderEnabled = false; - - // Opciones varios - options->playerSelected = 0; - options->difficulty = DIFFICULTY_NORMAL; - options->language = ba_BA; - options->console = false; - -#ifdef __EMSCRIPTEN__ - // En Emscripten la ventana la gestiona el navegador - options->windowSize = 4; - options->videoMode = 0; - options->integerScale = true; -#endif -} - // Comprueba los parametros del programa void Director::checkProgramArguments(int argc, const char *argv[]) { // Establece la ruta del programa @@ -480,7 +433,7 @@ void Director::checkProgramArguments(int argc, const char *argv[]) { // Comprueba el resto de parametros for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "--console") == 0) { - options->console = true; + Options::settings.console = true; } } } @@ -548,127 +501,6 @@ void Director::createSystemFolder(const std::string &folder) { #endif } -// Carga el fichero de configuración -bool Director::loadConfigFile() { - // Indicador de éxito en la carga - bool success = true; - - // Variables para manejar el fichero - const std::string filePath = "config.txt"; - std::string line; - std::ifstream file(asset->get(filePath)); - - // Si el fichero se puede abrir - if (file.good()) { - // Procesa el fichero linea a linea - if (options->console) { - std::cout << "Reading file " << filePath << std::endl; - } - while (std::getline(file, line)) { - // Comprueba que la linea no sea un comentario - if (line.substr(0, 1) != "#") { - // Encuentra la posición del caracter '=' - int pos = line.find("="); - // Procesa las dos subcadenas - if (!setOptions(options, line.substr(0, pos), line.substr(pos + 1, line.length()))) { - if (options->console) { - std::cout << "Warning: file " << filePath << std::endl; - std::cout << "Unknown parameter " << line.substr(0, pos).c_str() << std::endl; - } - success = false; - } - } - } - - // Cierra el fichero - if (options->console) { - std::cout << "Closing file " << filePath << std::endl; - } - file.close(); - } - - // El fichero no existe - else { // Crea el fichero con los valores por defecto - saveConfigFile(); - } - - // Normaliza los valores - if (options->videoMode != 0 && options->videoMode != SDL_WINDOW_FULLSCREEN) { - options->videoMode = 0; - } - - if (options->windowSize < 1 || options->windowSize > 4) { - options->windowSize = 3; - } - - if (options->language < 0 || options->language > MAX_LANGUAGES) { - options->language = en_UK; - } - - return success; -} - -// Guarda el fichero de configuración -bool Director::saveConfigFile() { - bool success = true; - - // Crea y abre el fichero de texto - std::ofstream file(asset->get("config.txt")); - - if (file.good()) { - if (options->console) { - std::cout << asset->get("config.txt") << " open for writing" << std::endl; - } - } else { - if (options->console) { - std::cout << asset->get("config.txt") << " can't be opened" << std::endl; - } - } - - // Opciones g´raficas - file << "## VISUAL OPTIONS\n"; - if (options->videoMode == 0) { - file << "videoMode=0\n"; - } - - else if (options->videoMode == SDL_WINDOW_FULLSCREEN) { - file << "videoMode=SDL_WINDOW_FULLSCREEN\n"; - } - - file << "windowSize=" + std::to_string(options->windowSize) + "\n"; - - if (options->filter == FILTER_NEAREST) { - file << "filter=FILTER_NEAREST\n"; - } else { - file << "filter=FILTER_LINEAL\n"; - } - - file << "vSync=" + boolToString(options->vSync) + "\n"; - file << "integerScale=" + boolToString(options->integerScale) + "\n"; - file << "keepAspect=" + boolToString(options->keepAspect) + "\n"; - file << "borderEnabled=" + boolToString(options->borderEnabled) + "\n"; - file << "borderWidth=" + std::to_string(options->borderWidth) + "\n"; - file << "borderHeight=" + std::to_string(options->borderHeight) + "\n"; - - // Opciones de GPU / shaders (post-procesado) - file << "videoGpuAcceleration=" + boolToString(options->videoGpuAcceleration) + "\n"; - file << "videoGpuPreferredDriver=" + options->videoGpuPreferredDriver + "\n"; - file << "videoShaderEnabled=" + boolToString(options->videoShaderEnabled) + "\n"; - file << "videoShaderType=" + std::to_string(options->videoShaderType) + "\n"; - - // Otras opciones del programa - file << "\n## OTHER OPTIONS\n"; - file << "language=" + std::to_string(options->language) + "\n"; - file << "difficulty=" + std::to_string(options->difficulty) + "\n"; - file << "input0=" + std::to_string(options->input[0].deviceType) + "\n"; - file << "input1=" + std::to_string(options->input[1].deviceType) + "\n"; - - // Cierra el fichero - file.close(); - - return success; -} - // Gestiona las transiciones entre secciones void Director::handleSectionTransition() { // Determina qué sección debería estar activa @@ -707,11 +539,11 @@ void Director::handleSectionTransition() { intro = std::make_unique(renderer, screen, asset, input, lang, section); break; case ActiveSection::Title: - title = std::make_unique(renderer, screen, input, asset, options, lang, section); + title = std::make_unique<Title>(renderer, screen, input, asset, lang, section); break; case ActiveSection::Game: { const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2; - game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, options, section); + game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, section); break; } case ActiveSection::None: @@ -733,7 +565,7 @@ SDL_AppResult Director::iterate() { #endif // Actualiza la visibilidad del cursor del ratón - Mouse::updateCursorVisibility(options->videoMode != 0); + Mouse::updateCursorVisibility(Options::video.fullscreen); // Gestiona las transiciones entre secciones handleSectionTransition(); @@ -789,7 +621,7 @@ SDL_AppResult Director::handleEvent(SDL_Event *event) { } // Gestiona la visibilidad del cursor según el movimiento del ratón - Mouse::handleEvent(*event, options->videoMode != 0); + Mouse::handleEvent(*event, Options::video.fullscreen); // Reenvía el evento a la sección activa switch (activeSection) { @@ -812,103 +644,3 @@ SDL_AppResult Director::handleEvent(SDL_Event *event) { return SDL_APP_CONTINUE; } -// Asigna variables a partir de dos cadenas -bool Director::setOptions(options_t *options, std::string var, std::string value) { - // Indicador de éxito en la asignación - bool success = true; - - // Opciones de video - if (var == "videoMode") { - if (value == "SDL_WINDOW_FULLSCREEN" || value == "SDL_WINDOW_FULLSCREEN_DESKTOP") { - options->videoMode = SDL_WINDOW_FULLSCREEN; - } else { - options->videoMode = 0; - } - } - - else if (var == "windowSize") { - options->windowSize = std::stoi(value); - if ((options->windowSize < 1) || (options->windowSize > 4)) { - options->windowSize = 3; - } - } - - else if (var == "filter") { - if (value == "FILTER_LINEAL") { - options->filter = FILTER_LINEAL; - } else { - options->filter = FILTER_NEAREST; - } - } - - else if (var == "vSync") { - options->vSync = stringToBool(value); - } - - else if (var == "integerScale") { - options->integerScale = stringToBool(value); - } - - else if (var == "keepAspect") { - options->keepAspect = stringToBool(value); - } - - else if (var == "borderEnabled") { - options->borderEnabled = stringToBool(value); - } - - else if (var == "borderWidth") { - options->borderWidth = std::stoi(value); - } - - else if (var == "borderHeight") { - options->borderHeight = std::stoi(value); - } - - // Opciones de GPU / shaders - else if (var == "videoGpuAcceleration") { - options->videoGpuAcceleration = stringToBool(value); - } - - else if (var == "videoGpuPreferredDriver") { - options->videoGpuPreferredDriver = value; - } - - else if (var == "videoShaderEnabled") { - options->videoShaderEnabled = stringToBool(value); - } - - else if (var == "videoShaderType") { - options->videoShaderType = std::stoi(value); - if (options->videoShaderType < 0 || options->videoShaderType > 1) { - options->videoShaderType = 0; - } - } - - // Opciones varias - else if (var == "language") { - options->language = std::stoi(value); - } - - else if (var == "difficulty") { - options->difficulty = std::stoi(value); - } - - else if (var == "input0") { - options->input[0].deviceType = std::stoi(value); - } - - else if (var == "input1") { - options->input[1].deviceType = std::stoi(value); - } - - // Lineas vacias o que empiezan por comentario - else if (var == "" || var.substr(0, 1) == "#") { - } - - else { - success = false; - } - - return success; -} \ No newline at end of file diff --git a/source/core/system/director.h b/source/core/system/director.h index 31b5c15..13bc3b9 100644 --- a/source/core/system/director.h +++ b/source/core/system/director.h @@ -12,12 +12,8 @@ class Lang; class Logo; class Screen; class Title; -struct options_t; struct section_t; -// Textos -constexpr const char *WINDOW_CAPTION = "© 2020 Coffee Crisis — JailDesigner"; - // Secciones activas del Director enum class ActiveSection { None, Logo, @@ -44,7 +40,6 @@ class Director { std::unique_ptr<Game> game; // Variables - struct options_t *options; // Variable con todas las opciones del programa std::string executablePath; // Path del ejecutable std::string systemFolder; // Carpeta del sistema donde guardar datos @@ -57,21 +52,9 @@ class Director { // Inicializa el objeto input void initInput(); - // Inicializa las opciones del programa - void initOptions(); - - // Asigna variables a partir de dos cadenas - bool setOptions(options_t *options, std::string var, std::string value); - // Crea el indice de ficheros bool setFileList(); - // Carga el fichero de configuración - bool loadConfigFile(); - - // Guarda el fichero de configuración - bool saveConfigFile(); - // Comprueba los parametros del programa void checkProgramArguments(int argc, const char *argv[]); diff --git a/source/game/defaults.hpp b/source/game/defaults.hpp index f0df0be..ec9e76a 100644 --- a/source/game/defaults.hpp +++ b/source/game/defaults.hpp @@ -5,6 +5,56 @@ #include "core/locale/lang.h" #include "utils/utils.h" +// ============================================================================= +// Defaults per a Options (alineats amb coffee_crisis_arcade_edition). +// ============================================================================= + +namespace Defaults::Window { + constexpr const char *CAPTION = "© 2020 Coffee Crisis — JailDesigner"; + constexpr int ZOOM = 3; + constexpr int MAX_ZOOM = 4; +} // namespace Defaults::Window + +namespace Defaults::Video { + constexpr SDL_ScaleMode SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST; + constexpr bool FULLSCREEN = false; + constexpr bool VSYNC = true; + constexpr bool INTEGER_SCALE = true; + constexpr bool GPU_ACCELERATION = true; + constexpr const char *GPU_PREFERRED_DRIVER = ""; + constexpr bool SHADER_ENABLED = false; + constexpr bool SUPERSAMPLING = false; + constexpr bool LINEAR_UPSCALE = false; + constexpr int DOWNSCALE_ALGO = 1; +} // namespace Defaults::Video + +namespace Defaults::Audio { + constexpr bool ENABLED = true; + constexpr int VOLUME = 100; +} // namespace Defaults::Audio + +namespace Defaults::Music { + constexpr bool ENABLED = true; + constexpr int VOLUME = 100; +} // namespace Defaults::Music + +namespace Defaults::Sound { + constexpr bool ENABLED = true; + constexpr int VOLUME = 100; +} // namespace Defaults::Sound + +namespace Defaults::Loading { + constexpr bool SHOW = false; + constexpr bool SHOW_RESOURCE_NAME = true; + constexpr bool WAIT_FOR_INPUT = false; +} // namespace Defaults::Loading + +namespace Defaults::Settings { + constexpr int DIFFICULTY = DIFFICULTY_NORMAL; + constexpr int LANGUAGE = ba_BA; + constexpr palette_e PALETTE = p_zxspectrum; +} // namespace Defaults::Settings + // Tamaño de bloque constexpr int BLOCK = 8; constexpr int HALF_BLOCK = BLOCK / 2; diff --git a/source/game/game.cpp b/source/game/game.cpp index 56aadaf..4c5cd04 100644 --- a/source/game/game.cpp +++ b/source/game/game.cpp @@ -24,18 +24,18 @@ #include "game/entities/bullet.h" // for Bullet, BULLET_LEFT, BULLET_RIGHT, BULLE... #include "game/entities/item.h" // for Item, ITEM_COFFEE_MACHINE, ITEM_CLOCK #include "game/entities/player.h" // for Player, DEATH_COUNTER +#include "game/options.hpp" // for Options #include "game/ui/menu.h" // for Menu struct JA_Sound_t; // Constructor -Game::Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *screen, Asset *asset, Lang *lang, Input *input, bool demo, options_t *options, section_t *section) { +Game::Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *screen, Asset *asset, Lang *lang, Input *input, bool demo, section_t *section) { // Copia los punteros this->renderer = renderer; this->screen = screen; this->asset = asset; this->lang = lang; this->input = input; - this->options = options; this->section = section; // Pasa variables @@ -48,10 +48,10 @@ Game::Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *scr #endif lastStageReached = currentStage; if (numPlayers == 1) { // Si solo juega un jugador, permite jugar tanto con teclado como con mando - onePlayerControl = options->input[0].deviceType; - options->input[0].deviceType = INPUT_USE_ANY; + onePlayerControl = Options::inputs[0].deviceType; + Options::inputs[0].deviceType = INPUT_USE_ANY; } - difficulty = options->difficulty; + difficulty = Options::settings.difficulty; // Crea los objetos fade = new Fade(renderer); @@ -96,7 +96,7 @@ Game::~Game() { // Restaura el metodo de control if (numPlayers == 1) { - options->input[0].deviceType = onePlayerControl; + Options::inputs[0].deviceType = onePlayerControl; } // Elimina todos los objetos contenidos en vectores (jugadores, balas, etc.) @@ -143,7 +143,7 @@ void Game::init() { // Crea los jugadores if (numPlayers == 1) { - Player *player = new Player(PLAY_AREA_CENTER_X - 11, PLAY_AREA_BOTTOM - 24, renderer, playerTextures[options->playerSelected], playerAnimations); + Player *player = new Player(PLAY_AREA_CENTER_X - 11, PLAY_AREA_BOTTOM - 24, renderer, playerTextures[Options::settings.player_selected], playerAnimations); players.push_back(player); } @@ -327,7 +327,7 @@ void Game::init() { // Carga los recursos necesarios para la sección 'Game' void Game::loadMedia() { - if (options->console) { + if (Options::settings.console) { std::cout << std::endl << "** LOADING RESOURCES FOR GAME SECTION" << std::endl; } @@ -433,7 +433,7 @@ void Game::loadMedia() { // Musicas gameMusic = R->getMusic("playing.ogg"); - if (options->console) { + if (Options::settings.console) { std::cout << "** RESOURCES FOR GAME SECTION LOADED" << std::endl << std::endl; } @@ -449,14 +449,14 @@ bool Game::loadScoreFile() { // El fichero no existe if (file == nullptr) { - if (options->console) { + if (Options::settings.console) { std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl; } // Creamos el fichero para escritura file = SDL_IOFromFile(p.c_str(), "w+b"); if (file != nullptr) { - if (options->console) { + if (Options::settings.console) { std::cout << "New file (" << filename.c_str() << ") created!" << std::endl; } @@ -469,7 +469,7 @@ bool Game::loadScoreFile() { // Cerramos el fichero SDL_CloseIO(file); } else { - if (options->console) { + if (Options::settings.console) { std::cout << "Error: Unable to create file " << filename.c_str() << std::endl; } success = false; @@ -478,7 +478,7 @@ bool Game::loadScoreFile() { // El fichero existe else { // Cargamos los datos - if (options->console) { + if (Options::settings.console) { std::cout << "Reading file " << filename.c_str() << std::endl; } for (int i = 0; i < TOTAL_SCORE_DATA; ++i) @@ -511,12 +511,12 @@ bool Game::loadDemoFile() { for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { memcpy(&demo.dataFile[i], bytes.data() + i * sizeof(demoKeys_t), sizeof(demoKeys_t)); } - if (options->console) { + if (Options::settings.console) { std::cout << "Demo data loaded (" << bytes.size() << " bytes)" << std::endl; } } else { // Si no hay datos (bytes vacíos o tamaño inválido), inicializamos a cero. - if (options->console) { + if (Options::settings.console) { std::cout << "Warning: demo data missing or too small, initializing to zero" << std::endl; } for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { @@ -544,14 +544,14 @@ bool Game::saveScoreFile() { SDL_WriteIO(file, &scoreDataFile[i], sizeof(Uint32)); } - if (options->console) { + if (Options::settings.console) { std::cout << "Writing file " << filename.c_str() << std::endl; } // Cerramos el fichero SDL_CloseIO(file); } else { - if (options->console) { + if (Options::settings.console) { std::cout << "Error: Unable to save " << filename.c_str() << " file! " << SDL_GetError() << std::endl; } } @@ -571,14 +571,14 @@ bool Game::saveDemoFile() { SDL_WriteIO(file, &demo.dataFile[i], sizeof(demoKeys_t)); } - if (options->console) { + if (Options::settings.console) { std::cout << "Writing file " << filename.c_str() << std::endl; } // Cerramos el fichero SDL_CloseIO(file); } else { - if (options->console) { + if (Options::settings.console) { std::cout << "Error: Unable to save " << filename.c_str() << " file! " << SDL_GetError() << std::endl; } } @@ -2600,12 +2600,12 @@ void Game::checkGameInput() { for (auto player : players) { if (player->isAlive()) { // Input a la izquierda - if (input->checkInput(input_left, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { + if (input->checkInput(input_left, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) { player->setInput(input_left); demo.keys.left = 1; } else { // Input a la derecha - if (input->checkInput(input_right, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { + if (input->checkInput(input_right, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) { player->setInput(input_right); demo.keys.right = 1; } else { @@ -2615,7 +2615,7 @@ void Game::checkGameInput() { } } // Comprueba el input de disparar al centro - if (input->checkInput(input_fire_center, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { + if (input->checkInput(input_fire_center, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) { if (player->canFire()) { player->setInput(input_fire_center); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_UP, player->isPowerUp(), i); @@ -2629,7 +2629,7 @@ void Game::checkGameInput() { } // Comprueba el input de disparar a la izquierda - if (input->checkInput(input_fire_left, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { + if (input->checkInput(input_fire_left, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) { if (player->canFire()) { player->setInput(input_fire_left); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_LEFT, player->isPowerUp(), i); @@ -2643,7 +2643,7 @@ void Game::checkGameInput() { } // Comprueba el input de disparar a la derecha - if (input->checkInput(input_fire_right, REPEAT_TRUE, options->input[i].deviceType, options->input[i].id)) { + if (input->checkInput(input_fire_right, REPEAT_TRUE, Options::inputs[i].deviceType, Options::inputs[i].id)) { if (player->canFire()) { player->setInput(input_fire_right); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BULLET_RIGHT, player->isPowerUp(), i); @@ -2657,7 +2657,7 @@ void Game::checkGameInput() { } // Comprueba el input de pausa - if (input->checkInput(input_pause, REPEAT_FALSE, options->input[i].deviceType, options->input[i].id)) { + if (input->checkInput(input_pause, REPEAT_FALSE, Options::inputs[i].deviceType, Options::inputs[i].id)) { section->subsection = SUBSECTION_GAME_PAUSE; } diff --git a/source/game/game.h b/source/game/game.h index e6348a0..7d866e9 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -245,7 +245,6 @@ class Game { Uint8 difficulty; // Dificultad del juego float difficultyScoreMultiplier; // Multiplicador de puntos en función de la dificultad color_t difficultyColor; // Color asociado a la dificultad - struct options_t *options; // Variable con todas las variables de las opciones del programa Uint8 onePlayerControl; // Variable para almacenar el valor de las opciones enemyFormation_t enemyFormation[NUMBER_OF_ENEMY_FORMATIONS]; // Vector con todas las formaciones enemigas enemyPool_t enemyPool[10]; // Variable con los diferentes conjuntos de formaciones enemigas @@ -546,7 +545,7 @@ class Game { public: // Constructor - Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *screen, Asset *asset, Lang *lang, Input *input, bool demo, options_t *options, section_t *section); + Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *screen, Asset *asset, Lang *lang, Input *input, bool demo, section_t *section); // Destructor ~Game(); diff --git a/source/game/options.cpp b/source/game/options.cpp new file mode 100644 index 0000000..432fa29 --- /dev/null +++ b/source/game/options.cpp @@ -0,0 +1,342 @@ +#include "game/options.hpp" + +#include <SDL3/SDL.h> + +#include <algorithm> +#include <fstream> +#include <iostream> +#include <string> + +#include "core/input/input.h" // for INPUT_USE_KEYBOARD, INPUT_USE_GAMECONTROLLER +#include "core/locale/lang.h" // for MAX_LANGUAGES, en_UK +#include "external/fkyaml_node.hpp" // for fkyaml::node +#include "utils/utils.h" // for boolToString + +namespace Options { + + // --- Variables globales --- + Window window; + Video video; + Audio audio; + Loading loading; + Settings settings; + std::vector<input_t> inputs; + + // --- Helpers locals --- + namespace { + void parseBoolField(const fkyaml::node &node, const std::string &key, bool &target) { + if (node.contains(key)) { + try { + target = node[key].get_value<bool>(); + } catch (...) {} + } + } + + void parseIntField(const fkyaml::node &node, const std::string &key, int &target) { + if (node.contains(key)) { + try { + target = node[key].get_value<int>(); + } catch (...) {} + } + } + + void parseStringField(const fkyaml::node &node, const std::string &key, std::string &target) { + if (node.contains(key)) { + try { + target = node[key].get_value<std::string>(); + } catch (...) {} + } + } + + void loadWindowFromYaml(const fkyaml::node &yaml) { + if (!yaml.contains("window")) { return; } + const auto &win = yaml["window"]; + parseIntField(win, "zoom", window.zoom); + if (window.zoom < 1 || window.zoom > window.max_zoom) { + window.zoom = Defaults::Window::ZOOM; + } + } + + void loadVideoFromYaml(const fkyaml::node &yaml) { + if (!yaml.contains("video")) { return; } + const auto &vid = yaml["video"]; + + parseBoolField(vid, "fullscreen", video.fullscreen); + parseBoolField(vid, "vsync", video.vsync); + parseBoolField(vid, "integer_scale", video.integer_scale); + if (vid.contains("scale_mode")) { + try { + video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>()); + } catch (...) {} + } + + if (vid.contains("gpu")) { + const auto &gpu = vid["gpu"]; + parseBoolField(gpu, "acceleration", video.gpu.acceleration); + parseStringField(gpu, "preferred_driver", video.gpu.preferred_driver); + } + + if (vid.contains("supersampling")) { + const auto &ss = vid["supersampling"]; + parseBoolField(ss, "enabled", video.supersampling.enabled); + parseBoolField(ss, "linear_upscale", video.supersampling.linear_upscale); + parseIntField(ss, "downscale_algo", video.supersampling.downscale_algo); + } + + if (vid.contains("shader")) { + const auto &sh = vid["shader"]; + parseBoolField(sh, "enabled", video.shader.enabled); + if (sh.contains("current_shader")) { + try { + auto s = sh["current_shader"].get_value<std::string>(); + video.shader.current_shader = (s == "crtpi" || s == "CRTPI") + ? Rendering::ShaderType::CRTPI + : Rendering::ShaderType::POSTFX; + } catch (...) {} + } + parseStringField(sh, "current_postfx_preset", video.shader.current_postfx_preset_name); + parseStringField(sh, "current_crtpi_preset", video.shader.current_crtpi_preset_name); + } + } + + void loadAudioFromYaml(const fkyaml::node &yaml) { + if (!yaml.contains("audio")) { return; } + const auto &aud = yaml["audio"]; + + parseBoolField(aud, "enabled", audio.enabled); + 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"]; + parseBoolField(mus, "enabled", audio.music.enabled); + 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"]; + parseBoolField(snd, "enabled", audio.sound.enabled); + if (snd.contains("volume")) { + try { + audio.sound.volume = std::clamp(snd["volume"].get_value<int>(), 0, 100); + } catch (...) {} + } + } + } + + void loadLoadingFromYaml(const fkyaml::node &yaml) { + if (!yaml.contains("loading")) { return; } + const auto &ld = yaml["loading"]; + parseBoolField(ld, "show", loading.show); + parseBoolField(ld, "show_resource_name", loading.show_resource_name); + parseBoolField(ld, "wait_for_input", loading.wait_for_input); + } + + void loadSettingsFromYaml(const fkyaml::node &yaml) { + if (!yaml.contains("settings")) { return; } + const auto &st = yaml["settings"]; + parseIntField(st, "difficulty", settings.difficulty); + parseIntField(st, "language", settings.language); + if (settings.language < 0 || settings.language > MAX_LANGUAGES) { + settings.language = en_UK; + } + if (st.contains("palette")) { + try { + settings.palette = static_cast<palette_e>(st["palette"].get_value<int>()); + } catch (...) {} + } + parseIntField(st, "player_selected", settings.player_selected); + } + + void loadInputsFromYaml(const fkyaml::node &yaml) { + if (!yaml.contains("input") || inputs.size() < 2) { return; } + const auto &ins = yaml["input"]; + size_t i = 0; + for (const auto &entry : ins) { + if (i >= inputs.size()) { break; } + if (entry.contains("device_type")) { + try { + inputs[i].deviceType = static_cast<Uint8>(entry["device_type"].get_value<int>()); + } catch (...) {} + } + ++i; + } + } + } // namespace + + // --- Funciones públiques --- + + void setConfigFile(const std::string &file_path) { + settings.config_file = file_path; + } + + void init() { + // Reinicia structs a defaults (els member-initializers ho fan sols). + window = Window{}; + video = Video{}; + audio = Audio{}; + loading = Loading{}; + + // Preserva config_file si ja s'ha establert abans. + const std::string PREV_CONFIG_FILE = settings.config_file; + settings = Settings{}; + settings.config_file = PREV_CONFIG_FILE; + +#ifdef __EMSCRIPTEN__ + // En Emscripten la ventana la gestiona el navegador + window.zoom = 4; + video.fullscreen = false; + video.integer_scale = true; +#endif + + // Dispositius d'entrada per defecte + inputs.clear(); + input_t kb; + kb.id = 0; + kb.name = "KEYBOARD"; + kb.deviceType = INPUT_USE_KEYBOARD; + inputs.push_back(kb); + + input_t gc; + gc.id = 0; + gc.name = "GAME CONTROLLER"; + gc.deviceType = INPUT_USE_GAMECONTROLLER; + inputs.push_back(gc); + } + + auto loadFromFile() -> bool { + init(); + + std::ifstream file(settings.config_file); + if (!file.is_open()) { + // Primera execució: crea el YAML amb defaults. + return saveToFile(); + } + + const std::string CONTENT((std::istreambuf_iterator<char>(file)), + std::istreambuf_iterator<char>()); + file.close(); + + try { + auto yaml = fkyaml::node::deserialize(CONTENT); + + int file_version = 0; + if (yaml.contains("config_version")) { + try { + file_version = yaml["config_version"].get_value<int>(); + } catch (...) {} + } + if (file_version != Settings::CURRENT_CONFIG_VERSION) { + std::cout << "Config version " << file_version + << " != expected " << Settings::CURRENT_CONFIG_VERSION + << ". Recreating defaults.\n"; + init(); + return saveToFile(); + } + + loadWindowFromYaml(yaml); + loadVideoFromYaml(yaml); + loadAudioFromYaml(yaml); + loadLoadingFromYaml(yaml); + loadSettingsFromYaml(yaml); + loadInputsFromYaml(yaml); + + } catch (const fkyaml::exception &e) { + std::cout << "Error parsing YAML config: " << e.what() << ". Using defaults.\n"; + init(); + return saveToFile(); + } + + return true; + } + + auto saveToFile() -> bool { + if (settings.config_file.empty()) { return false; } + std::ofstream file(settings.config_file); + if (!file.is_open()) { + std::cout << "Error: " << settings.config_file << " can't be opened for writing\n"; + return false; + } + + file << "# Coffee Crisis - Configuration file\n"; + file << "# Auto-generated, managed by the game.\n\n"; + + file << "config_version: " << settings.config_version << "\n\n"; + + // WINDOW + file << "# WINDOW\n"; + file << "window:\n"; + file << " zoom: " << window.zoom << "\n"; + file << " max_zoom: " << window.max_zoom << "\n\n"; + + // VIDEO + file << "# VIDEO\n"; + file << "video:\n"; + file << " fullscreen: " << boolToString(video.fullscreen) << "\n"; + file << " vsync: " << boolToString(video.vsync) << "\n"; + file << " integer_scale: " << boolToString(video.integer_scale) << "\n"; + file << " scale_mode: " << static_cast<int>(video.scale_mode) + << " # " << static_cast<int>(SDL_SCALEMODE_NEAREST) << ": nearest, " + << static_cast<int>(SDL_SCALEMODE_LINEAR) << ": linear\n"; + file << " gpu:\n"; + file << " acceleration: " << boolToString(video.gpu.acceleration) << "\n"; + file << " preferred_driver: \"" << video.gpu.preferred_driver << "\"\n"; + file << " supersampling:\n"; + file << " enabled: " << boolToString(video.supersampling.enabled) << "\n"; + file << " linear_upscale: " << boolToString(video.supersampling.linear_upscale) << "\n"; + file << " downscale_algo: " << video.supersampling.downscale_algo << "\n"; + file << " shader:\n"; + file << " enabled: " << boolToString(video.shader.enabled) << "\n"; + file << " current_shader: " + << (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") + << "\n"; + file << " current_postfx_preset: \"" << video.shader.current_postfx_preset_name << "\"\n"; + file << " current_crtpi_preset: \"" << video.shader.current_crtpi_preset_name << "\"\n\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\n"; + + // LOADING + file << "# LOADING SCREEN\n"; + file << "loading:\n"; + file << " show: " << boolToString(loading.show) << "\n"; + file << " show_resource_name: " << boolToString(loading.show_resource_name) << "\n"; + file << " wait_for_input: " << boolToString(loading.wait_for_input) << "\n\n"; + + // SETTINGS + file << "# SETTINGS\n"; + file << "settings:\n"; + file << " difficulty: " << settings.difficulty << "\n"; + file << " language: " << settings.language << "\n"; + file << " palette: " << static_cast<int>(settings.palette) << "\n"; + file << " player_selected: " << settings.player_selected << "\n\n"; + + // INPUT + file << "# INPUT DEVICES (device_type: " + << static_cast<int>(INPUT_USE_KEYBOARD) << "=KEYBOARD, " + << static_cast<int>(INPUT_USE_GAMECONTROLLER) << "=GAMECONTROLLER)\n"; + file << "input:\n"; + for (size_t i = 0; i < inputs.size(); ++i) { + file << " - slot: " << i << "\n"; + file << " device_type: " << static_cast<int>(inputs[i].deviceType) << "\n"; + } + + file.close(); + return true; + } + +} // namespace Options diff --git a/source/game/options.hpp b/source/game/options.hpp new file mode 100644 index 0000000..a162835 --- /dev/null +++ b/source/game/options.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include <SDL3/SDL.h> + +#include <string> +#include <vector> + +#include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType +#include "game/defaults.hpp" +#include "utils/utils.h" // for input_t, palette_e + +// ============================================================================= +// Opciones del programa, alineades amb coffee_crisis_arcade_edition. +// L'estat viu en globals dins el namespace Options:: (window, video, audio, +// loading, settings, inputs). La persistència usa fkyaml i es guarda a +// config.yaml dins la carpeta de configuració del sistema. +// ============================================================================= + +namespace Options { + + struct Window { + std::string caption = Defaults::Window::CAPTION; + int zoom = Defaults::Window::ZOOM; + int max_zoom = Defaults::Window::MAX_ZOOM; + }; + + struct GPU { + bool acceleration = Defaults::Video::GPU_ACCELERATION; + std::string preferred_driver = Defaults::Video::GPU_PREFERRED_DRIVER; + }; + + struct Supersampling { + bool enabled = Defaults::Video::SUPERSAMPLING; + bool linear_upscale = Defaults::Video::LINEAR_UPSCALE; + int downscale_algo = Defaults::Video::DOWNSCALE_ALGO; + }; + + struct ShaderConfig { + bool enabled = Defaults::Video::SHADER_ENABLED; + Rendering::ShaderType current_shader = Rendering::ShaderType::POSTFX; + std::string current_postfx_preset_name = "CRT"; + std::string current_crtpi_preset_name = "Default"; + int current_postfx_preset = 0; + int current_crtpi_preset = 0; + }; + + struct Video { + SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE; + bool fullscreen = Defaults::Video::FULLSCREEN; + bool vsync = Defaults::Video::VSYNC; + bool integer_scale = Defaults::Video::INTEGER_SCALE; + GPU gpu; + Supersampling supersampling; + ShaderConfig shader; + }; + + struct Music { + bool enabled = Defaults::Music::ENABLED; + int volume = Defaults::Music::VOLUME; + }; + + struct Sound { + bool enabled = Defaults::Sound::ENABLED; + int volume = Defaults::Sound::VOLUME; + }; + + struct Audio { + bool enabled = Defaults::Audio::ENABLED; + int volume = Defaults::Audio::VOLUME; + Music music; + Sound sound; + }; + + struct Loading { + bool show = Defaults::Loading::SHOW; + bool show_resource_name = Defaults::Loading::SHOW_RESOURCE_NAME; + bool wait_for_input = Defaults::Loading::WAIT_FOR_INPUT; + }; + + struct Settings { + static constexpr int CURRENT_CONFIG_VERSION = 1; + int config_version = CURRENT_CONFIG_VERSION; + int difficulty = Defaults::Settings::DIFFICULTY; + int language = Defaults::Settings::LANGUAGE; + palette_e palette = Defaults::Settings::PALETTE; + bool console = false; + int player_selected = 0; + std::string config_file; + }; + + // --- Variables globales --- + extern Window window; + extern Video video; + extern Audio audio; + extern Loading loading; + extern Settings settings; + extern std::vector<input_t> inputs; // [0]=KEYBOARD, [1]=GAMECONTROLLER per defecte + + // --- Funciones --- + void init(); // Reinicia a defaults i omple `inputs` + void setConfigFile(const std::string &file_path); // Ruta del config.yaml + auto loadFromFile() -> bool; // Carrega el YAML; si no existeix, crea'l amb defaults + auto saveToFile() -> bool; // Guarda el YAML + +} // namespace Options diff --git a/source/game/scenes/title.cpp b/source/game/scenes/title.cpp index 36f913b..c0775b8 100644 --- a/source/game/scenes/title.cpp +++ b/source/game/scenes/title.cpp @@ -11,7 +11,7 @@ #include "core/locale/lang.h" // for Lang, ba_BA, en_UK, es_ES #include "core/rendering/animatedsprite.h" // for AnimatedSprite #include "core/rendering/fade.h" // for Fade -#include "core/rendering/screen.h" // for Screen, FILTER_LINEAL, FILTER_NEAREST +#include "core/rendering/screen.h" // for Screen #include "core/rendering/smartsprite.h" // for SmartSprite #include "core/rendering/sprite.h" // for Sprite #include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_SHADOW @@ -20,16 +20,16 @@ #include "core/resources/resource.h" #include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT #include "game/game.h" // for Game +#include "game/options.hpp" // for Options #include "game/ui/menu.h" // for Menu // Constructor -Title::Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset, options_t *options, Lang *lang, section_t *section) { +Title::Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset, Lang *lang, section_t *section) { // Copia las direcciones de los punteros this->renderer = renderer; this->screen = screen; this->input = input; this->asset = asset; - this->options = options; this->lang = lang; this->section = section; @@ -113,18 +113,18 @@ void Title::init() { demoThenInstructions = false; // Pone valores por defecto a las opciones de control - options->input.clear(); + Options::inputs.clear(); input_t i; i.id = 0; i.name = "KEYBOARD"; i.deviceType = INPUT_USE_KEYBOARD; - options->input.push_back(i); + Options::inputs.push_back(i); i.id = 0; i.name = "GAME CONTROLLER"; i.deviceType = INPUT_USE_GAMECONTROLLER; - options->input.push_back(i); + Options::inputs.push_back(i); // Comprueba si hay mandos conectados checkInputDevices(); @@ -136,9 +136,9 @@ void Title::init() { // Si ha encontrado un mando se lo asigna al segundo jugador if (input->gameControllerFound()) { - options->input[1].id = availableInputDevices[deviceIndex[1]].id; - options->input[1].name = availableInputDevices[deviceIndex[1]].name; - options->input[1].deviceType = availableInputDevices[deviceIndex[1]].deviceType; + Options::inputs[1].id = availableInputDevices[deviceIndex[1]].id; + Options::inputs[1].name = availableInputDevices[deviceIndex[1]].name; + Options::inputs[1].deviceType = availableInputDevices[deviceIndex[1]].deviceType; } else { // Si no ha encontrado un mando, deshabilita la opción de jugar a 2 jugadores menu.title->setSelectable(1, false); menu.title->setGreyed(1, true); @@ -347,7 +347,10 @@ void Title::update() { case 2: // OPTIONS menu.active = menu.options; - optionsPrevious = *options; + prevVideo = Options::video; + prevWindow = Options::window; + prevSettings = Options::settings; + prevInputs = Options::inputs; break; case 3: // QUIT @@ -369,13 +372,13 @@ void Title::update() { case 1: // BAL1 postFade = 0; - options->playerSelected = 0; + Options::settings.player_selected = 0; fade->activateFade(); break; case 2: // AROUNDER postFade = 0; - options->playerSelected = 1; + Options::settings.player_selected = 1; fade->activateFade(); break; @@ -393,12 +396,12 @@ void Title::update() { if (menu.active->getName() == "OPTIONS") { switch (menu.active->getItemSelected()) { case 0: // Difficulty - if (options->difficulty == DIFFICULTY_EASY) - options->difficulty = DIFFICULTY_NORMAL; - else if (options->difficulty == DIFFICULTY_NORMAL) - options->difficulty = DIFFICULTY_HARD; + if (Options::settings.difficulty == DIFFICULTY_EASY) + Options::settings.difficulty = DIFFICULTY_NORMAL; + else if (Options::settings.difficulty == DIFFICULTY_NORMAL) + Options::settings.difficulty = DIFFICULTY_HARD; else - options->difficulty = DIFFICULTY_EASY; + Options::settings.difficulty = DIFFICULTY_EASY; updateMenuLabels(); break; @@ -413,15 +416,15 @@ void Title::update() { break; case 5: // Language - options->language++; - if (options->language == 3) - options->language = 0; + Options::settings.language++; + if (Options::settings.language == 3) + Options::settings.language = 0; updateMenuLabels(); break; case 6: // Display mode switchFullScreenModeVar(); - if (options->videoMode != 0) { + if (Options::video.fullscreen) { menu.options->setSelectable(8, false); menu.options->setGreyed(8, true); } else { @@ -432,27 +435,23 @@ void Title::update() { break; case 8: // Windows size - options->windowSize++; - if (options->windowSize == 5) - options->windowSize = 1; + Options::window.zoom++; + if (Options::window.zoom > Options::window.max_zoom) + Options::window.zoom = 1; updateMenuLabels(); break; - case 9: // FILTER - if (options->filter == FILTER_LINEAL) - options->filter = FILTER_NEAREST; - else - options->filter = FILTER_LINEAL; - Texture::setGlobalScaleMode(options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR); + case 9: // Scale mode + Options::video.scale_mode = (Options::video.scale_mode == SDL_SCALEMODE_NEAREST) + ? SDL_SCALEMODE_LINEAR + : SDL_SCALEMODE_NEAREST; + Texture::setGlobalScaleMode(Options::video.scale_mode); reLoadTextures(); updateMenuLabels(); break; case 10: // VSYNC - if (options->vSync) - options->vSync = false; - else - options->vSync = true; + Options::video.vsync = !Options::video.vsync; updateMenuLabels(); break; @@ -467,7 +466,10 @@ void Title::update() { break; case 13: // CANCEL - options = &optionsPrevious; + Options::video = prevVideo; + Options::window = prevWindow; + Options::settings = prevSettings; + Options::inputs = prevInputs; updateMenuLabels(); menu.active->reset(); menu.active = menu.title; @@ -682,25 +684,14 @@ void Title::updateBG() { // Cambia el valor de la variable de modo de pantalla completa void Title::switchFullScreenModeVar() { - switch (options->videoMode) { - case 0: - options->videoMode = SDL_WINDOW_FULLSCREEN; - break; - case SDL_WINDOW_FULLSCREEN: - options->videoMode = 0; - break; - - default: - options->videoMode = 0; - break; - } + Options::video.fullscreen = !Options::video.fullscreen; } // Actualiza los elementos de los menus void Title::updateMenuLabels() { int i = 0; // DIFFICULTY - switch (options->difficulty) { + switch (Options::settings.difficulty) { case DIFFICULTY_EASY: menu.options->setItemCaption(i, lang->getText(59) + ": " + lang->getText(66)); // EASY break; @@ -724,7 +715,7 @@ void Title::updateMenuLabels() { i++; // PLAYER 1 CONTROLS - OPTIONS - switch (options->input[0].deviceType) { + switch (Options::inputs[0].deviceType) { case INPUT_USE_KEYBOARD: menu.options->setItemCaption(i, lang->getText(69)); // KEYBOARD menu.options->setGreyed(i, false); @@ -736,7 +727,7 @@ void Title::updateMenuLabels() { menu.options->setGreyed(i, true); else { menu.options->setGreyed(i, false); - menu.options->setItemCaption(i, options->input[0].name); + menu.options->setItemCaption(i, Options::inputs[0].name); } break; @@ -751,7 +742,7 @@ void Title::updateMenuLabels() { i++; // PLAYER 2 CONTROLS - OPTIONS - switch (options->input[1].deviceType) { + switch (Options::inputs[1].deviceType) { case INPUT_USE_KEYBOARD: menu.options->setItemCaption(i, lang->getText(69)); // KEYBOARD menu.options->setGreyed(i, false); @@ -763,7 +754,7 @@ void Title::updateMenuLabels() { menu.options->setGreyed(i, true); else { menu.options->setGreyed(i, false); - menu.options->setItemCaption(i, options->input[1].name); + menu.options->setItemCaption(i, Options::inputs[1].name); } break; @@ -774,7 +765,7 @@ void Title::updateMenuLabels() { i++; // LANGUAGE - switch (options->language) { + switch (Options::settings.language) { case es_ES: menu.options->setItemCaption(i, lang->getText(8) + ": " + lang->getText(24)); // SPANISH break; @@ -798,34 +789,22 @@ void Title::updateMenuLabels() { i++; // DISPLAY MODE - OPTIONS - switch (options->videoMode) { - case 0: - menu.options->setItemCaption(i, lang->getText(4)); // WINDOW - break; - - case SDL_WINDOW_FULLSCREEN: - menu.options->setItemCaption(i, lang->getText(5)); // FULLSCREEN - break; - - default: - menu.options->setItemCaption(i, lang->getText(4)); // WINDOW - break; - } + menu.options->setItemCaption(i, Options::video.fullscreen ? lang->getText(5) : lang->getText(4)); i++; // WINDOW SIZE - menu.options->setItemCaption(i, lang->getText(7) + " x" + std::to_string(options->windowSize)); // WINDOW SIZE + menu.options->setItemCaption(i, lang->getText(7) + " x" + std::to_string(Options::window.zoom)); // WINDOW SIZE i++; - // FILTER - if (options->filter == FILTER_LINEAL) + // SCALE MODE + if (Options::video.scale_mode == SDL_SCALEMODE_LINEAR) menu.options->setItemCaption(i, lang->getText(60) + ": " + lang->getText(71)); // BILINEAL else menu.options->setItemCaption(i, lang->getText(60) + ": " + lang->getText(72)); // LINEAL i++; // VSYNC - if (options->vSync) + if (Options::video.vsync) menu.options->setItemCaption(i, lang->getText(61) + ": " + lang->getText(73)); // ON else menu.options->setItemCaption(i, lang->getText(61) + ": " + lang->getText(74)); // OFF @@ -887,9 +866,11 @@ void Title::updateMenuLabels() { // Aplica las opciones de menu seleccionadas void Title::applyOptions() { - screen->setVideoMode(options->videoMode != 0); + screen->setVideoMode(Options::video.fullscreen); + screen->setWindowZoom(Options::window.zoom); + screen->setVSync(Options::video.vsync); - lang->setLang(options->language); + lang->setLang(Options::settings.language); updateMenuLabels(); createTiledBackground(); @@ -1004,7 +985,7 @@ void Title::runDemoGame() { // Temporalmente ponemos section para que el constructor de Game funcione section->name = SECTION_PROG_GAME; section->subsection = SUBSECTION_GAME_PLAY_1P; - demoGame = new Game(1, 0, renderer, screen, asset, lang, input, true, options, section); + demoGame = new Game(1, 0, renderer, screen, asset, lang, input, true, section); demoGameActive = true; // Restauramos section para que Director no transicione fuera de Title section->name = SECTION_PROG_TITLE; @@ -1018,17 +999,17 @@ bool Title::updatePlayerInputs(int numPlayer) { deviceIndex[0] = 0; deviceIndex[1] = 0; - options->input[0].id = -1; - options->input[0].name = "KEYBOARD"; - options->input[0].deviceType = INPUT_USE_KEYBOARD; + Options::inputs[0].id = -1; + Options::inputs[0].name = "KEYBOARD"; + Options::inputs[0].deviceType = INPUT_USE_KEYBOARD; - options->input[1].id = 0; - options->input[1].name = "GAME CONTROLLER"; - options->input[1].deviceType = INPUT_USE_GAMECONTROLLER; + Options::inputs[1].id = 0; + Options::inputs[1].name = "GAME CONTROLLER"; + Options::inputs[1].deviceType = INPUT_USE_GAMECONTROLLER; return true; } else { // Si hay mas de un dispositivo, se recorre el vector - if (options->console) { + if (Options::settings.console) { std::cout << "numplayer:" << numPlayer << std::endl; std::cout << "deviceindex:" << deviceIndex[numPlayer] << std::endl; } @@ -1039,7 +1020,7 @@ bool Title::updatePlayerInputs(int numPlayer) { } else { deviceIndex[numPlayer] = 0; } - if (options->console) { + if (Options::settings.console) { std::cout << "deviceindex:" << deviceIndex[numPlayer] << std::endl; } @@ -1053,8 +1034,8 @@ bool Title::updatePlayerInputs(int numPlayer) { } // Copia el dispositivo marcado por el indice a la variable de opciones de cada jugador - options->input[0] = availableInputDevices[deviceIndex[0]]; - options->input[1] = availableInputDevices[deviceIndex[1]]; + Options::inputs[0] = availableInputDevices[deviceIndex[0]]; + Options::inputs[1] = availableInputDevices[deviceIndex[1]]; return true; } @@ -1068,7 +1049,7 @@ void Title::createTiledBackground() { SDL_SetTextureScaleMode(background, Texture::currentScaleMode); } if (background == nullptr) { - if (options->console) { + if (Options::settings.console) { std::cout << "TitleSurface could not be created!\nSDL Error: " << SDL_GetError() << std::endl; } } @@ -1105,7 +1086,7 @@ void Title::createTiledBackground() { // El estado de Input lo mantiene al día Director via eventos SDL_EVENT_GAMEPAD_ADDED/REMOVED, // así que aquí solo leemos la lista actual sin reescanear. void Title::checkInputDevices() { - if (options->console) { + if (Options::settings.console) { std::cout << "Filling devices for options menu..." << std::endl; } const int numControllers = input->getNumControllers(); @@ -1119,7 +1100,7 @@ void Title::checkInputDevices() { temp.name = input->getControllerName(i); temp.deviceType = INPUT_USE_GAMECONTROLLER; availableInputDevices.push_back(temp); - if (options->console) { + if (Options::settings.console) { std::cout << "Device " << (int)availableInputDevices.size() << " - " << temp.name.c_str() << std::endl; } } @@ -1129,7 +1110,7 @@ void Title::checkInputDevices() { temp.name = "KEYBOARD"; temp.deviceType = INPUT_USE_KEYBOARD; availableInputDevices.push_back(temp); - if (options->console) { + if (Options::settings.console) { std::cout << "Device " << (int)availableInputDevices.size() << " - " << temp.name.c_str() << std::endl; std::cout << std::endl; } diff --git a/source/game/scenes/title.h b/source/game/scenes/title.h index 434128b..91c97e0 100644 --- a/source/game/scenes/title.h +++ b/source/game/scenes/title.h @@ -4,8 +4,9 @@ #include <vector> // for vector +#include "game/options.hpp" // for Options::Video, Options::Window (per snapshot cancel) #include "game/scenes/instructions.h" // for mode_e -#include "utils/utils.h" // for input_t, options_t, section_t +#include "utils/utils.h" // for input_t, section_t class AnimatedSprite; class Asset; class Fade; @@ -85,8 +86,11 @@ class Title { Uint32 ticksSpeed; // Velocidad a la que se repiten los bucles del programa Uint8 postFade; // Opción a realizar cuando termina el fundido menu_t menu; // Variable con todos los objetos menus y sus variables - struct options_t *options; // Variable con todas las variables de las opciones del programa - options_t optionsPrevious; // Variable de respaldo para las opciones + // Snapshot per a permetre CANCEL al menú d'opcions. + Options::Video prevVideo; + Options::Window prevWindow; + Options::Settings prevSettings; + std::vector<input_t> prevInputs; std::vector<input_t> availableInputDevices; // Vector con todos los metodos de control disponibles std::vector<int> deviceIndex; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles @@ -149,7 +153,7 @@ class Title { public: // Constructor - Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset, options_t *options, Lang *lang, section_t *section); + Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset, Lang *lang, section_t *section); // Destructor ~Title(); diff --git a/source/utils/utils.h b/source/utils/utils.h index 4abc194..a69d76d 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -82,43 +82,6 @@ struct input_t { Uint8 deviceType; // Tipo de dispositivo (teclado o mando) }; -// Estructura con opciones de la pantalla -struct op_screen_t { - int windowWidth; // Ancho de la ventana - int windowHeight; // Alto de la ventana -}; - -// Estructura con todas las opciones de configuración del programa -struct options_t { - Uint8 difficulty; // Dificultad del juego - Uint8 playerSelected; // Jugador seleccionado para el modo 1P - std::vector<input_t> input; // Modo de control (teclado o mando) - Uint8 language; // Idioma usado en el juego - - Uint32 videoMode; // Contiene el valor del modo de pantalla completa - int windowSize; // Contiene el valor por el que se multiplica el tamaño de la ventana - Uint32 filter; // Filtro usado para el escalado de la imagen - bool vSync; // Indica si se quiere usar vsync o no - int gameWidth; // Ancho de la resolucion nativa del juego - int gameHeight; // Alto de la resolucion nativa del juego - bool integerScale; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa - bool keepAspect; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa - bool borderEnabled; // Indica si ha de mostrar el borde en el modo de ventana - int borderWidth; // Cantidad de pixels que se añade en el borde de la ventana - int borderHeight; // Cantidad de pixels que se añade en el borde de la ventana - palette_e palette; // Paleta de colores a usar en el juego - bool console; // Indica si ha de mostrar información por la consola de texto - - // GPU / shaders (persistent; also toggled via F9/F10/F11 hotkeys). - // YAML migration pending — for now they live as flat keys in config.txt. - bool videoGpuAcceleration = true; // Intenta usar SDL3 GPU si está disponible - std::string videoGpuPreferredDriver; // Driver preferido para SDL3 GPU (vacío = auto) - bool videoShaderEnabled = false; // Activa el post-procesado (PostFX / CrtPi) - int videoShaderType = 0; // 0 = POSTFX, 1 = CRTPI - - op_screen_t screen; // Opciones relativas a la clase screen -}; - // Calcula el cuadrado de la distancia entre dos puntos double distanceSquared(int x1, int y1, int x2, int y2);