modernitzat el sistema d'opcions
This commit is contained in:
342
source/game/options.cpp
Normal file
342
source/game/options.cpp
Normal 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
|
||||
Reference in New Issue
Block a user