bbbb8d47ae
Identifier-naming: rename de métodos públicos y cross-file al inglés
(camelBack), traducción de campos y locales en el proceso (TitleShip,
StageManager, SpawnController, ShipAnimator, helpers de PlayArea, etc.).
Refactor por cognitive-complexity (>25): GameScene::draw (59→3) con 9
helpers de estado, PhysicsWorld::resolveBodyCollisions (35→5) extrayendo
resolveBodyPair, Options::load{Window,Physics,Audio}ConfigFromYaml
(32/49/57→5/2/3) con templates readField, TitleScene::update (68→4) con
5 sub-pasos por estado + handleSkipInput/handleStartInput +
triggerExitForJoinedPlayers, DebrisManager::explode (39→3) con
extractSegments/spawnDebris/applyAngularVelocity/applyVisualRotation.
use-anyofallof: bucles → std::ranges::any_of/all_of en Input,
ShipAnimator y SpawnController.
readability-static-accessed-through-instance: Director::run y
VectorText::getTextWidth/Height invocados por clase.
readability-convert-member-functions-to-static: ResourcePack::decryptData.
unused-includes: eliminación de <utility>, <cstdint>, <vector>,
<iostream>, defaults.hpp y otros no usados directamente en headers y
unidades de traducción. Restablecido core/defaults.hpp en title_scene.cpp
(falsa "unused" del header).
Bug fix: eliminado isActive() duplicado en Bullet (redeclaración tras
rename de esta_activa→isActive que chocaba con el override de Entity).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
640 lines
23 KiB
C++
640 lines
23 KiB
C++
#include "options.hpp"
|
|
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
|
|
#include "core/defaults.hpp"
|
|
#include "external/fkyaml_node.hpp"
|
|
#include "project.h"
|
|
|
|
namespace Options {
|
|
|
|
// ========== FUNCIONS AUXILIARS PER CONVERSIÓ DE CONTROLES ==========
|
|
|
|
// Mapa de SDL_Scancode a string
|
|
static const std::unordered_map<SDL_Scancode, std::string> SCANCODE_TO_STRING = {
|
|
{SDL_SCANCODE_A, "A"},
|
|
{SDL_SCANCODE_B, "B"},
|
|
{SDL_SCANCODE_C, "C"},
|
|
{SDL_SCANCODE_D, "D"},
|
|
{SDL_SCANCODE_E, "E"},
|
|
{SDL_SCANCODE_F, "F"},
|
|
{SDL_SCANCODE_G, "G"},
|
|
{SDL_SCANCODE_H, "H"},
|
|
{SDL_SCANCODE_I, "I"},
|
|
{SDL_SCANCODE_J, "J"},
|
|
{SDL_SCANCODE_K, "K"},
|
|
{SDL_SCANCODE_L, "L"},
|
|
{SDL_SCANCODE_M, "M"},
|
|
{SDL_SCANCODE_N, "N"},
|
|
{SDL_SCANCODE_O, "O"},
|
|
{SDL_SCANCODE_P, "P"},
|
|
{SDL_SCANCODE_Q, "Q"},
|
|
{SDL_SCANCODE_R, "R"},
|
|
{SDL_SCANCODE_S, "S"},
|
|
{SDL_SCANCODE_T, "T"},
|
|
{SDL_SCANCODE_U, "U"},
|
|
{SDL_SCANCODE_V, "V"},
|
|
{SDL_SCANCODE_W, "W"},
|
|
{SDL_SCANCODE_X, "X"},
|
|
{SDL_SCANCODE_Y, "Y"},
|
|
{SDL_SCANCODE_Z, "Z"},
|
|
{SDL_SCANCODE_1, "1"},
|
|
{SDL_SCANCODE_2, "2"},
|
|
{SDL_SCANCODE_3, "3"},
|
|
{SDL_SCANCODE_4, "4"},
|
|
{SDL_SCANCODE_5, "5"},
|
|
{SDL_SCANCODE_6, "6"},
|
|
{SDL_SCANCODE_7, "7"},
|
|
{SDL_SCANCODE_8, "8"},
|
|
{SDL_SCANCODE_9, "9"},
|
|
{SDL_SCANCODE_0, "0"},
|
|
{SDL_SCANCODE_RETURN, "RETURN"},
|
|
{SDL_SCANCODE_ESCAPE, "ESCAPE"},
|
|
{SDL_SCANCODE_BACKSPACE, "BACKSPACE"},
|
|
{SDL_SCANCODE_TAB, "TAB"},
|
|
{SDL_SCANCODE_SPACE, "SPACE"},
|
|
{SDL_SCANCODE_UP, "UP"},
|
|
{SDL_SCANCODE_DOWN, "DOWN"},
|
|
{SDL_SCANCODE_LEFT, "LEFT"},
|
|
{SDL_SCANCODE_RIGHT, "RIGHT"},
|
|
{SDL_SCANCODE_LSHIFT, "LSHIFT"},
|
|
{SDL_SCANCODE_RSHIFT, "RSHIFT"},
|
|
{SDL_SCANCODE_LCTRL, "LCTRL"},
|
|
{SDL_SCANCODE_RCTRL, "RCTRL"},
|
|
{SDL_SCANCODE_LALT, "LALT"},
|
|
{SDL_SCANCODE_RALT, "RALT"}};
|
|
|
|
// Mapa invers: string a SDL_Scancode
|
|
static const std::unordered_map<std::string, SDL_Scancode> STRING_TO_SCANCODE = {
|
|
{"A", SDL_SCANCODE_A},
|
|
{"B", SDL_SCANCODE_B},
|
|
{"C", SDL_SCANCODE_C},
|
|
{"D", SDL_SCANCODE_D},
|
|
{"E", SDL_SCANCODE_E},
|
|
{"F", SDL_SCANCODE_F},
|
|
{"G", SDL_SCANCODE_G},
|
|
{"H", SDL_SCANCODE_H},
|
|
{"I", SDL_SCANCODE_I},
|
|
{"J", SDL_SCANCODE_J},
|
|
{"K", SDL_SCANCODE_K},
|
|
{"L", SDL_SCANCODE_L},
|
|
{"M", SDL_SCANCODE_M},
|
|
{"N", SDL_SCANCODE_N},
|
|
{"O", SDL_SCANCODE_O},
|
|
{"P", SDL_SCANCODE_P},
|
|
{"Q", SDL_SCANCODE_Q},
|
|
{"R", SDL_SCANCODE_R},
|
|
{"S", SDL_SCANCODE_S},
|
|
{"T", SDL_SCANCODE_T},
|
|
{"U", SDL_SCANCODE_U},
|
|
{"V", SDL_SCANCODE_V},
|
|
{"W", SDL_SCANCODE_W},
|
|
{"X", SDL_SCANCODE_X},
|
|
{"Y", SDL_SCANCODE_Y},
|
|
{"Z", SDL_SCANCODE_Z},
|
|
{"1", SDL_SCANCODE_1},
|
|
{"2", SDL_SCANCODE_2},
|
|
{"3", SDL_SCANCODE_3},
|
|
{"4", SDL_SCANCODE_4},
|
|
{"5", SDL_SCANCODE_5},
|
|
{"6", SDL_SCANCODE_6},
|
|
{"7", SDL_SCANCODE_7},
|
|
{"8", SDL_SCANCODE_8},
|
|
{"9", SDL_SCANCODE_9},
|
|
{"0", SDL_SCANCODE_0},
|
|
{"RETURN", SDL_SCANCODE_RETURN},
|
|
{"ESCAPE", SDL_SCANCODE_ESCAPE},
|
|
{"BACKSPACE", SDL_SCANCODE_BACKSPACE},
|
|
{"TAB", SDL_SCANCODE_TAB},
|
|
{"SPACE", SDL_SCANCODE_SPACE},
|
|
{"UP", SDL_SCANCODE_UP},
|
|
{"DOWN", SDL_SCANCODE_DOWN},
|
|
{"LEFT", SDL_SCANCODE_LEFT},
|
|
{"RIGHT", SDL_SCANCODE_RIGHT},
|
|
{"LSHIFT", SDL_SCANCODE_LSHIFT},
|
|
{"RSHIFT", SDL_SCANCODE_RSHIFT},
|
|
{"LCTRL", SDL_SCANCODE_LCTRL},
|
|
{"RCTRL", SDL_SCANCODE_RCTRL},
|
|
{"LALT", SDL_SCANCODE_LALT},
|
|
{"RALT", SDL_SCANCODE_RALT}};
|
|
|
|
// Mapa de botó de gamepad (int) a string
|
|
static const std::unordered_map<int, std::string> BUTTON_TO_STRING = {
|
|
{SDL_GAMEPAD_BUTTON_SOUTH, "SOUTH"}, // A (Xbox), Cross (PS)
|
|
{SDL_GAMEPAD_BUTTON_EAST, "EAST"}, // B (Xbox), Circle (PS)
|
|
{SDL_GAMEPAD_BUTTON_WEST, "WEST"}, // X (Xbox), Square (PS)
|
|
{SDL_GAMEPAD_BUTTON_NORTH, "NORTH"}, // Y (Xbox), Triangle (PS)
|
|
{SDL_GAMEPAD_BUTTON_BACK, "BACK"},
|
|
{SDL_GAMEPAD_BUTTON_START, "START"},
|
|
{SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, "LEFT_SHOULDER"},
|
|
{SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, "RIGHT_SHOULDER"},
|
|
{SDL_GAMEPAD_BUTTON_DPAD_UP, "DPAD_UP"},
|
|
{SDL_GAMEPAD_BUTTON_DPAD_DOWN, "DPAD_DOWN"},
|
|
{SDL_GAMEPAD_BUTTON_DPAD_LEFT, "DPAD_LEFT"},
|
|
{SDL_GAMEPAD_BUTTON_DPAD_RIGHT, "DPAD_RIGHT"},
|
|
{100, "L2_AS_BUTTON"}, // Trigger L2 como a botó digital
|
|
{101, "R2_AS_BUTTON"} // Trigger R2 como a botó digital
|
|
};
|
|
|
|
// Mapa invers: string a botó de gamepad
|
|
static const std::unordered_map<std::string, int> STRING_TO_BUTTON = {
|
|
{"SOUTH", SDL_GAMEPAD_BUTTON_SOUTH},
|
|
{"EAST", SDL_GAMEPAD_BUTTON_EAST},
|
|
{"WEST", SDL_GAMEPAD_BUTTON_WEST},
|
|
{"NORTH", SDL_GAMEPAD_BUTTON_NORTH},
|
|
{"BACK", SDL_GAMEPAD_BUTTON_BACK},
|
|
{"START", SDL_GAMEPAD_BUTTON_START},
|
|
{"LEFT_SHOULDER", SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
|
|
{"RIGHT_SHOULDER", SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
|
|
{"DPAD_UP", SDL_GAMEPAD_BUTTON_DPAD_UP},
|
|
{"DPAD_DOWN", SDL_GAMEPAD_BUTTON_DPAD_DOWN},
|
|
{"DPAD_LEFT", SDL_GAMEPAD_BUTTON_DPAD_LEFT},
|
|
{"DPAD_RIGHT", SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
|
|
{"L2_AS_BUTTON", 100},
|
|
{"R2_AS_BUTTON", 101}};
|
|
|
|
static auto scancodeToString(SDL_Scancode code) -> std::string {
|
|
auto it = SCANCODE_TO_STRING.find(code);
|
|
return (it != SCANCODE_TO_STRING.end()) ? it->second : "UNKNOWN";
|
|
}
|
|
|
|
static auto stringToScancode(const std::string& str) -> SDL_Scancode {
|
|
auto it = STRING_TO_SCANCODE.find(str);
|
|
return (it != STRING_TO_SCANCODE.end()) ? it->second : SDL_SCANCODE_UNKNOWN;
|
|
}
|
|
|
|
static auto buttonToString(int button) -> std::string {
|
|
auto it = BUTTON_TO_STRING.find(button);
|
|
return (it != BUTTON_TO_STRING.end()) ? it->second : "UNKNOWN";
|
|
}
|
|
|
|
static auto stringToButton(const std::string& str) -> int {
|
|
auto it = STRING_TO_BUTTON.find(str);
|
|
return (it != STRING_TO_BUTTON.end()) ? it->second : SDL_GAMEPAD_BUTTON_INVALID;
|
|
}
|
|
|
|
// ========== FI FUNCIONS AUXILIARS ==========
|
|
|
|
// Inicialitzar opciones con valors per defecte de Defaults::
|
|
void init() {
|
|
#ifdef _DEBUG
|
|
console = true;
|
|
#else
|
|
console = false;
|
|
#endif
|
|
|
|
// Window
|
|
window.width = Defaults::Window::WIDTH;
|
|
window.height = Defaults::Window::HEIGHT;
|
|
window.fullscreen = Defaults::Window::FULLSCREEN;
|
|
window.zoom_factor = Defaults::Window::BASE_ZOOM;
|
|
|
|
// Physics
|
|
physics.rotation_speed = Defaults::Physics::ROTATION_SPEED;
|
|
physics.acceleration = Defaults::Physics::ACCELERATION;
|
|
physics.max_velocity = Defaults::Physics::MAX_VELOCITY;
|
|
physics.friction = Defaults::Physics::FRICTION;
|
|
physics.enemy_speed = Defaults::Physics::ENEMY_SPEED;
|
|
physics.bullet_speed = Defaults::Physics::BULLET_SPEED;
|
|
|
|
// Gameplay
|
|
gameplay.max_enemies = Defaults::Entities::MAX_ORNIS;
|
|
gameplay.max_bullets = Defaults::Entities::MAX_BALES;
|
|
|
|
// Rendering
|
|
rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT;
|
|
|
|
// Audio
|
|
audio.enabled = Defaults::Audio::ENABLED;
|
|
audio.volume = Defaults::Audio::VOLUME;
|
|
audio.music.enabled = Defaults::Audio::MUSIC_ENABLED;
|
|
audio.music.volume = Defaults::Audio::MUSIC_VOLUME;
|
|
audio.sound.enabled = Defaults::Audio::SOUND_ENABLED;
|
|
audio.sound.volume = Defaults::Audio::SOUND_VOLUME;
|
|
|
|
// Version
|
|
version = std::string(Project::VERSION);
|
|
}
|
|
|
|
// Establir la ruta del file de configuración
|
|
void setConfigFile(const std::string& path) { config_file_path = path; }
|
|
|
|
// Funciones auxiliars per load seccions del YAML
|
|
|
|
// Lee un campo escalar del YAML aplicando un validador; si la clau no
|
|
// existe, deja `dest` intacto; si la conversió o la validació fallen,
|
|
// asigna `fallback`. Estàtic per quedar dins de la unitat de traducció.
|
|
template <typename T, typename Validator>
|
|
static void readField(const fkyaml::node& parent, const char* key, T& dest,
|
|
T fallback, Validator&& validate) {
|
|
if (!parent.contains(key)) {
|
|
return;
|
|
}
|
|
try {
|
|
auto val = parent[key].template get_value<T>();
|
|
dest = validate(val) ? val : fallback;
|
|
} catch (...) {
|
|
dest = fallback;
|
|
}
|
|
}
|
|
|
|
// Variant sin validador: només lectura amb fallback en cas d'error.
|
|
template <typename T>
|
|
static void readField(const fkyaml::node& parent, const char* key, T& dest, T fallback) {
|
|
if (!parent.contains(key)) {
|
|
return;
|
|
}
|
|
try {
|
|
dest = parent[key].template get_value<T>();
|
|
} catch (...) {
|
|
dest = fallback;
|
|
}
|
|
}
|
|
|
|
static void loadWindowConfigFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("window")) {
|
|
return;
|
|
}
|
|
const auto& win = yaml["window"];
|
|
|
|
readField(win, "width", window.width, Defaults::Window::WIDTH,
|
|
[](int v) { return v >= Defaults::Window::MIN_WIDTH; });
|
|
readField(win, "height", window.height, Defaults::Window::HEIGHT,
|
|
[](int v) { return v >= Defaults::Window::MIN_HEIGHT; });
|
|
readField(win, "fullscreen", window.fullscreen, false);
|
|
|
|
if (win.contains("zoom_factor")) {
|
|
readField(win, "zoom_factor", window.zoom_factor, Defaults::Window::BASE_ZOOM,
|
|
[](float v) { return v >= Defaults::Window::MIN_ZOOM && v <= 10.0F; });
|
|
} else {
|
|
// Legacy config: infer zoom from width
|
|
window.zoom_factor = static_cast<float>(window.width) / Defaults::Window::WIDTH;
|
|
window.zoom_factor = std::max(Defaults::Window::MIN_ZOOM, window.zoom_factor);
|
|
}
|
|
}
|
|
|
|
static void loadPhysicsConfigFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("physics")) {
|
|
return;
|
|
}
|
|
const auto& phys = yaml["physics"];
|
|
constexpr auto POSITIVE = [](float v) { return v > 0.0F; };
|
|
|
|
readField(phys, "rotation_speed", physics.rotation_speed, Defaults::Physics::ROTATION_SPEED, POSITIVE);
|
|
readField(phys, "acceleration", physics.acceleration, Defaults::Physics::ACCELERATION, POSITIVE);
|
|
readField(phys, "max_velocity", physics.max_velocity, Defaults::Physics::MAX_VELOCITY, POSITIVE);
|
|
readField(phys, "friction", physics.friction, Defaults::Physics::FRICTION, POSITIVE);
|
|
readField(phys, "enemy_speed", physics.enemy_speed, Defaults::Physics::ENEMY_SPEED, POSITIVE);
|
|
readField(phys, "bullet_speed", physics.bullet_speed, Defaults::Physics::BULLET_SPEED, POSITIVE);
|
|
}
|
|
|
|
static void loadGameplayConfigFromYaml(const fkyaml::node& yaml) {
|
|
if (yaml.contains("gameplay")) {
|
|
const auto& game = yaml["gameplay"];
|
|
|
|
if (game.contains("max_enemies")) {
|
|
try {
|
|
auto val = game["max_enemies"].get_value<int>();
|
|
gameplay.max_enemies =
|
|
(val > 0 && val <= 50) ? val : Defaults::Entities::MAX_ORNIS;
|
|
} catch (...) {
|
|
gameplay.max_enemies = Defaults::Entities::MAX_ORNIS;
|
|
}
|
|
}
|
|
|
|
if (game.contains("max_bullets")) {
|
|
try {
|
|
auto val = game["max_bullets"].get_value<int>();
|
|
gameplay.max_bullets =
|
|
(val > 0 && val <= 10) ? val : Defaults::Entities::MAX_BALES;
|
|
} catch (...) {
|
|
gameplay.max_bullets = Defaults::Entities::MAX_BALES;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void loadRenderingConfigFromYaml(const fkyaml::node& yaml) {
|
|
if (yaml.contains("rendering")) {
|
|
const auto& rend = yaml["rendering"];
|
|
|
|
if (rend.contains("vsync")) {
|
|
try {
|
|
int val = rend["vsync"].get_value<int>();
|
|
// Validar: solo 0 o 1
|
|
rendering.vsync = (val == 0 || val == 1) ? val : Defaults::Rendering::VSYNC_DEFAULT;
|
|
} catch (...) {
|
|
rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void loadAudioMusicSection(const fkyaml::node& aud) {
|
|
if (!aud.contains("music")) {
|
|
return;
|
|
}
|
|
const auto& mus = aud["music"];
|
|
constexpr auto UNIT_RANGE = [](float v) { return v >= 0.0F && v <= 1.0F; };
|
|
|
|
readField(mus, "enabled", audio.music.enabled, Defaults::Audio::MUSIC_ENABLED);
|
|
readField(mus, "volume", audio.music.volume, Defaults::Audio::MUSIC_VOLUME, UNIT_RANGE);
|
|
}
|
|
|
|
static void loadAudioSoundSection(const fkyaml::node& aud) {
|
|
if (!aud.contains("sound")) {
|
|
return;
|
|
}
|
|
const auto& snd = aud["sound"];
|
|
constexpr auto UNIT_RANGE = [](float v) { return v >= 0.0F && v <= 1.0F; };
|
|
|
|
readField(snd, "enabled", audio.sound.enabled, Defaults::Audio::SOUND_ENABLED);
|
|
readField(snd, "volume", audio.sound.volume, Defaults::Audio::SOUND_VOLUME, UNIT_RANGE);
|
|
}
|
|
|
|
static void loadAudioConfigFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("audio")) {
|
|
return;
|
|
}
|
|
const auto& aud = yaml["audio"];
|
|
constexpr auto UNIT_RANGE = [](float v) { return v >= 0.0F && v <= 1.0F; };
|
|
|
|
readField(aud, "enabled", audio.enabled, Defaults::Audio::ENABLED);
|
|
readField(aud, "volume", audio.volume, Defaults::Audio::VOLUME, UNIT_RANGE);
|
|
loadAudioMusicSection(aud);
|
|
loadAudioSoundSection(aud);
|
|
}
|
|
|
|
// Carregar controls del player 1 desde YAML
|
|
static void loadPlayer1ControlsFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("player1")) {
|
|
return;
|
|
}
|
|
|
|
const auto& p1 = yaml["player1"];
|
|
|
|
// Carregar controls de teclat
|
|
if (p1.contains("keyboard")) {
|
|
const auto& kb = p1["keyboard"];
|
|
if (kb.contains("key_left")) {
|
|
player1.keyboard.key_left = stringToScancode(kb["key_left"].get_value<std::string>());
|
|
}
|
|
if (kb.contains("key_right")) {
|
|
player1.keyboard.key_right = stringToScancode(kb["key_right"].get_value<std::string>());
|
|
}
|
|
if (kb.contains("key_thrust")) {
|
|
player1.keyboard.key_thrust = stringToScancode(kb["key_thrust"].get_value<std::string>());
|
|
}
|
|
if (kb.contains("key_shoot")) {
|
|
player1.keyboard.key_shoot = stringToScancode(kb["key_shoot"].get_value<std::string>());
|
|
}
|
|
}
|
|
|
|
// Carregar controls de gamepad
|
|
if (p1.contains("gamepad")) {
|
|
const auto& gp = p1["gamepad"];
|
|
if (gp.contains("button_left")) {
|
|
player1.gamepad.button_left = stringToButton(gp["button_left"].get_value<std::string>());
|
|
}
|
|
if (gp.contains("button_right")) {
|
|
player1.gamepad.button_right = stringToButton(gp["button_right"].get_value<std::string>());
|
|
}
|
|
if (gp.contains("button_thrust")) {
|
|
player1.gamepad.button_thrust = stringToButton(gp["button_thrust"].get_value<std::string>());
|
|
}
|
|
if (gp.contains("button_shoot")) {
|
|
player1.gamepad.button_shoot = stringToButton(gp["button_shoot"].get_value<std::string>());
|
|
}
|
|
}
|
|
|
|
// Carregar nom del gamepad
|
|
if (p1.contains("gamepad_name")) {
|
|
player1.gamepad_name = p1["gamepad_name"].get_value<std::string>();
|
|
}
|
|
}
|
|
|
|
// Carregar controls del player 2 desde YAML
|
|
static void loadPlayer2ControlsFromYaml(const fkyaml::node& yaml) {
|
|
if (!yaml.contains("player2")) {
|
|
return;
|
|
}
|
|
|
|
const auto& p2 = yaml["player2"];
|
|
|
|
// Carregar controls de teclat
|
|
if (p2.contains("keyboard")) {
|
|
const auto& kb = p2["keyboard"];
|
|
if (kb.contains("key_left")) {
|
|
player2.keyboard.key_left = stringToScancode(kb["key_left"].get_value<std::string>());
|
|
}
|
|
if (kb.contains("key_right")) {
|
|
player2.keyboard.key_right = stringToScancode(kb["key_right"].get_value<std::string>());
|
|
}
|
|
if (kb.contains("key_thrust")) {
|
|
player2.keyboard.key_thrust = stringToScancode(kb["key_thrust"].get_value<std::string>());
|
|
}
|
|
if (kb.contains("key_shoot")) {
|
|
player2.keyboard.key_shoot = stringToScancode(kb["key_shoot"].get_value<std::string>());
|
|
}
|
|
}
|
|
|
|
// Carregar controls de gamepad
|
|
if (p2.contains("gamepad")) {
|
|
const auto& gp = p2["gamepad"];
|
|
if (gp.contains("button_left")) {
|
|
player2.gamepad.button_left = stringToButton(gp["button_left"].get_value<std::string>());
|
|
}
|
|
if (gp.contains("button_right")) {
|
|
player2.gamepad.button_right = stringToButton(gp["button_right"].get_value<std::string>());
|
|
}
|
|
if (gp.contains("button_thrust")) {
|
|
player2.gamepad.button_thrust = stringToButton(gp["button_thrust"].get_value<std::string>());
|
|
}
|
|
if (gp.contains("button_shoot")) {
|
|
player2.gamepad.button_shoot = stringToButton(gp["button_shoot"].get_value<std::string>());
|
|
}
|
|
}
|
|
|
|
// Carregar nom del gamepad
|
|
if (p2.contains("gamepad_name")) {
|
|
player2.gamepad_name = p2["gamepad_name"].get_value<std::string>();
|
|
}
|
|
}
|
|
|
|
// Carregar configuración des del file YAML
|
|
auto loadFromFile() -> bool {
|
|
const std::string CONFIG_VERSION = std::string(Project::VERSION);
|
|
|
|
std::ifstream file(config_file_path);
|
|
if (!file.good()) {
|
|
// El file no existeix → crear-ne un de nuevo con valors per defecte
|
|
if (console) {
|
|
std::cout << "Archivo de config no trobat, creant-ne un de nuevo: "
|
|
<< config_file_path << '\n';
|
|
}
|
|
saveToFile();
|
|
return true;
|
|
}
|
|
|
|
// Llegir todo el contingut del file
|
|
std::string content((std::istreambuf_iterator<char>(file)),
|
|
std::istreambuf_iterator<char>());
|
|
file.close();
|
|
|
|
try {
|
|
// Parsejar YAML
|
|
auto yaml = fkyaml::node::deserialize(content);
|
|
|
|
// Validar versión
|
|
if (yaml.contains("version")) {
|
|
version = yaml["version"].get_value<std::string>();
|
|
}
|
|
|
|
if (CONFIG_VERSION != version) {
|
|
// Versión incompatible → regenerar config
|
|
if (console) {
|
|
std::cout << "Versión de config incompatible (esperada: "
|
|
<< CONFIG_VERSION << ", trobada: " << version
|
|
<< "), regenerant config\n";
|
|
}
|
|
init();
|
|
saveToFile();
|
|
return true;
|
|
}
|
|
|
|
// Carregar seccions
|
|
loadWindowConfigFromYaml(yaml);
|
|
loadPhysicsConfigFromYaml(yaml);
|
|
loadGameplayConfigFromYaml(yaml);
|
|
loadRenderingConfigFromYaml(yaml);
|
|
loadAudioConfigFromYaml(yaml);
|
|
loadPlayer1ControlsFromYaml(yaml);
|
|
loadPlayer2ControlsFromYaml(yaml);
|
|
|
|
if (console) {
|
|
std::cout << "Config carregada correctament desde: " << config_file_path
|
|
<< '\n';
|
|
}
|
|
|
|
return true;
|
|
|
|
} catch (const fkyaml::exception& e) {
|
|
// Error de parsejat YAML → regenerar config
|
|
if (console) {
|
|
std::cerr << "Error parsejant YAML: " << e.what() << '\n';
|
|
std::cerr << "Creant config nuevo con valors per defecte\n";
|
|
}
|
|
init();
|
|
saveToFile();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Guardar controls del player 1 a YAML
|
|
static void savePlayer1ControlsToYaml(std::ofstream& file) {
|
|
file << "# CONTROLS JUGADOR 1\n";
|
|
file << "player1:\n";
|
|
file << " keyboard:\n";
|
|
file << " key_left: " << scancodeToString(player1.keyboard.key_left) << "\n";
|
|
file << " key_right: " << scancodeToString(player1.keyboard.key_right) << "\n";
|
|
file << " key_thrust: " << scancodeToString(player1.keyboard.key_thrust) << "\n";
|
|
file << " key_shoot: " << scancodeToString(player1.keyboard.key_shoot) << "\n";
|
|
file << " gamepad:\n";
|
|
file << " button_left: " << buttonToString(player1.gamepad.button_left) << "\n";
|
|
file << " button_right: " << buttonToString(player1.gamepad.button_right) << "\n";
|
|
file << " button_thrust: " << buttonToString(player1.gamepad.button_thrust) << "\n";
|
|
file << " button_shoot: " << buttonToString(player1.gamepad.button_shoot) << "\n";
|
|
file << " gamepad_name: \"" << player1.gamepad_name << "\" # Buit = primer disponible\n\n";
|
|
}
|
|
|
|
// Guardar controls del player 2 a YAML
|
|
static void savePlayer2ControlsToYaml(std::ofstream& file) {
|
|
file << "# CONTROLS JUGADOR 2\n";
|
|
file << "player2:\n";
|
|
file << " keyboard:\n";
|
|
file << " key_left: " << scancodeToString(player2.keyboard.key_left) << "\n";
|
|
file << " key_right: " << scancodeToString(player2.keyboard.key_right) << "\n";
|
|
file << " key_thrust: " << scancodeToString(player2.keyboard.key_thrust) << "\n";
|
|
file << " key_shoot: " << scancodeToString(player2.keyboard.key_shoot) << "\n";
|
|
file << " gamepad:\n";
|
|
file << " button_left: " << buttonToString(player2.gamepad.button_left) << "\n";
|
|
file << " button_right: " << buttonToString(player2.gamepad.button_right) << "\n";
|
|
file << " button_thrust: " << buttonToString(player2.gamepad.button_thrust) << "\n";
|
|
file << " button_shoot: " << buttonToString(player2.gamepad.button_shoot) << "\n";
|
|
file << " gamepad_name: \"" << player2.gamepad_name << "\" # Buit = segon disponible\n\n";
|
|
}
|
|
|
|
// Guardar configuración al file YAML
|
|
auto saveToFile() -> bool {
|
|
std::ofstream file(config_file_path);
|
|
if (!file.is_open()) {
|
|
if (console) {
|
|
std::cerr << "No s'ha pogut obrir el file de config per escriure: "
|
|
<< config_file_path << '\n';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Escriure manualment per controlar format i comentaris
|
|
file << "# Orni Attack - Archivo de Configuración\n";
|
|
file << "# Auto-generat. Les edicions manuals es preserven si són "
|
|
"vàlides.\n\n";
|
|
|
|
file << "version: \"" << Project::VERSION << "\"\n\n";
|
|
|
|
file << "# FINESTRA\n";
|
|
file << "window:\n";
|
|
file << " width: " << window.width << " # Calculated from zoom_factor\n";
|
|
file << " height: " << window.height << " # Calculated from zoom_factor\n";
|
|
file << " fullscreen: " << (window.fullscreen ? "true" : "false") << "\n";
|
|
file << " zoom_factor: " << window.zoom_factor << " # 0.5x-max (0.1 increments)\n\n";
|
|
|
|
file << "# FÍSICA (todos los valors en px/s, rad/s, etc.)\n";
|
|
file << "physics:\n";
|
|
file << " rotation_speed: " << physics.rotation_speed << " # rad/s\n";
|
|
file << " acceleration: " << physics.acceleration << " # px/s²\n";
|
|
file << " max_velocity: " << physics.max_velocity << " # px/s\n";
|
|
file << " friction: " << physics.friction << " # px/s²\n";
|
|
file << " enemy_speed: " << physics.enemy_speed
|
|
<< " # unitats/frame\n";
|
|
file << " bullet_speed: " << physics.bullet_speed
|
|
<< " # unitats/frame\n\n";
|
|
|
|
file << "# GAMEPLAY\n";
|
|
file << "gameplay:\n";
|
|
file << " max_enemies: " << gameplay.max_enemies << "\n";
|
|
file << " max_bullets: " << gameplay.max_bullets << "\n\n";
|
|
|
|
file << "# RENDERITZACIÓ\n";
|
|
file << "rendering:\n";
|
|
file << " vsync: " << rendering.vsync << " # 0=disabled, 1=enabled\n\n";
|
|
|
|
file << "# AUDIO\n";
|
|
file << "audio:\n";
|
|
file << " enabled: " << (audio.enabled ? "true" : "false") << "\n";
|
|
file << " volume: " << audio.volume << " # 0.0 to 1.0\n";
|
|
file << " music:\n";
|
|
file << " enabled: " << (audio.music.enabled ? "true" : "false") << "\n";
|
|
file << " volume: " << audio.music.volume << " # 0.0 to 1.0\n";
|
|
file << " sound:\n";
|
|
file << " enabled: " << (audio.sound.enabled ? "true" : "false") << "\n";
|
|
file << " volume: " << audio.sound.volume << " # 0.0 to 1.0\n\n";
|
|
|
|
// Guardar controls de jugadors
|
|
savePlayer1ControlsToYaml(file);
|
|
savePlayer2ControlsToYaml(file);
|
|
|
|
file.close();
|
|
|
|
if (console) {
|
|
std::cout << "Config guardada a: " << config_file_path << '\n';
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace Options
|