modernitzat el sistema d'opcions

This commit is contained in:
2026-04-17 19:36:40 +02:00
parent 1bb0ebdef8
commit 7f703390f9
12 changed files with 690 additions and 553 deletions

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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();

342
source/game/options.cpp Normal file
View File

@@ -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

105
source/game/options.hpp Normal file
View File

@@ -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

View File

@@ -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;
}

View File

@@ -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();