#pragma once #include #include #include #include #include "core/audio/audio.hpp" #include "core/rendering/render_info.hpp" #include "core/rendering/screen.hpp" #include "core/rendering/shader_backend.hpp" #include "utils/defaults.hpp" // Gestor de configuració del joc. És l'ÚNIC lloc que coneix YAML i la ruta // del config.yaml. Els subsistemes (Screen, Audio, ...) reben structs tipats // a través dels build*Config(), de manera que no queden acoblats a aquest // namespace ni al format concret del fitxer. // // L'estat mutable viu en un anonymous namespace dins d'`options.cpp`: els // callers el llegeixen via getters const-ref (`window()`, `video()`, ...) i // el muten via setters específics que persisteixen a config.yaml. Els // setters NO notifiquen els subsistemes (Screen/Audio): el caller (típic. // `core/input/global_inputs.cpp`) crida després el setter del subsistema. // Aquesta asimetria preserva la direcció `Director → Screen/Audio` i evita // que Options aprenga del layout dels subsistemes (decisió WONTFIX d'aquest // refactor: veure docs/audit-game.md → COUP-03). namespace Options { struct Window { int zoom{Defaults::Window::ZOOM}; bool fullscreen{Defaults::Window::FULLSCREEN}; }; struct Gpu { bool acceleration{Defaults::Video::GPU_ACCELERATION}; std::string preferred_driver{Defaults::Video::GPU_PREFERRED_DRIVER}; }; struct Shader { bool enabled{Defaults::Video::SHADER_ENABLED}; std::string current_shader{Defaults::Video::CURRENT_SHADER}; // "postfx" o "crtpi" std::string current_postfx_preset{Defaults::Video::POSTFX_DEFAULT_PRESET}; std::string current_crtpi_preset{Defaults::Video::CRTPI_DEFAULT_PRESET}; }; struct Video { bool vsync{Defaults::Video::VSYNC}; Defaults::ScalingMode scaling_mode{Defaults::Video::SCALING_MODE}; Defaults::TextureFilter texture_filter{Defaults::Video::TEXTURE_FILTER}; Defaults::RenderInfoPosition render_info_position{Defaults::Video::RENDER_INFO_POSITION}; bool render_info_show_time{Defaults::Video::RENDER_INFO_SHOW_TIME}; Gpu gpu{}; Shader shader{}; }; // Preset d'efectes PostFX (un per entrada a postfx.yaml). struct PostFXPreset { std::string name; float vignette{0.0F}; float scanlines{0.0F}; float chroma{0.0F}; float mask{0.0F}; float gamma{0.0F}; float curvature{0.0F}; float bleeding{0.0F}; float flicker{0.0F}; // Forma de les scanlines (3 subpíxels per fila lògica per defecte). float scan_dark_ratio{0.333F}; float scan_dark_floor{0.42F}; float scan_edge_soft{1.0F}; }; // Preset del shader CRT-Pi (un per entrada a crtpi.yaml). struct CrtPiPreset { std::string name; float scanline_weight{6.0F}; float scanline_gap_brightness{0.12F}; float bloom_factor{3.5F}; float input_gamma{2.4F}; float output_gamma{2.2F}; float mask_brightness{0.80F}; float curvature_x{0.05F}; float curvature_y{0.10F}; int mask_type{2}; bool enable_scanlines{true}; bool enable_multisample{true}; bool enable_gamma{true}; bool enable_curvature{false}; bool enable_sharper{false}; }; struct Music { bool enabled{Defaults::Audio::MUSIC_ENABLED}; float volume{Defaults::Audio::MUSIC_VOLUME}; }; struct Sound { bool enabled{Defaults::Audio::SOUND_ENABLED}; float volume{Defaults::Audio::SOUND_VOLUME}; }; struct Audio { bool enabled{Defaults::Audio::ENABLED}; float volume{Defaults::Audio::VOLUME}; Music music{}; Sound sound{}; }; // Assignació d'un mando a un jugador: nom visible (el que retorna // `GamepadManager::Info::name`) + ruta física (de `SDL_GetGamepadPath`). // Tots dos buits = sense mando. La ruta serveix per re-vincular // automàticament el mateix mando al mateix port; el nom és el // fallback quan l'usuari el canvia de port USB. struct GamepadAssignment { std::string name; std::string path; }; // Tecles de joc d'un jugador (vinculacions remapeables des del menú). // Cada camp és un `SDL_Scancode` literal; els defaults venen de // `Defaults::KeysGame*` segons el slot (P1 fletxes, P2 WASD). struct Keyboard { SDL_Scancode up{SDL_SCANCODE_UNKNOWN}; SDL_Scancode down{SDL_SCANCODE_UNKNOWN}; SDL_Scancode left{SDL_SCANCODE_UNKNOWN}; SDL_Scancode right{SDL_SCANCODE_UNKNOWN}; SDL_Scancode jump{SDL_SCANCODE_UNKNOWN}; SDL_Scancode attack{SDL_SCANCODE_UNKNOWN}; }; // Identificadors d'acció per als setters genèrics: una sola funció // `setKeyboardBinding(player_id, action, scancode)` cobreix les 12 // possibles vinculacions sense haver de generar 12 setters individuals. enum class KeyboardAction : std::uint8_t { UP, DOWN, LEFT, RIGHT, JUMP, ATTACK, }; struct Controls { GamepadAssignment gamepad_p1{}; GamepadAssignment gamepad_p2{}; Keyboard keyboard_p1{ .up = Defaults::KeysGame::UP, .down = Defaults::KeysGame::DOWN, .left = Defaults::KeysGame::LEFT, .right = Defaults::KeysGame::RIGHT, .jump = Defaults::KeysGame::JUMP, .attack = Defaults::KeysGame::ATTACK, }; Keyboard keyboard_p2{ .up = Defaults::KeysGameP2::UP, .down = Defaults::KeysGameP2::DOWN, .left = Defaults::KeysGameP2::LEFT, .right = Defaults::KeysGameP2::RIGHT, .jump = Defaults::KeysGameP2::JUMP, .attack = Defaults::KeysGameP2::ATTACK, }; }; // --- Getters de l'estat (read-only) --- [[nodiscard]] auto window() -> const Window&; [[nodiscard]] auto video() -> const Video&; [[nodiscard]] auto audio() -> const Audio&; [[nodiscard]] auto controls() -> const Controls&; [[nodiscard]] auto locale() -> const std::string&; [[nodiscard]] auto configVersion() -> const std::string&; [[nodiscard]] auto postfxPresets() -> const std::vector&; [[nodiscard]] auto crtpiPresets() -> const std::vector&; // --- Setters: muten + persisteixen a config.yaml --- // No notifiquen els subsistemes; el caller ha de propagar el canvi al // subsistema corresponent (p.ex. Screen::setWindowZoom) per separat. void setWindowZoom(int value); void setWindowFullscreen(bool value); void setVideoVSync(bool value); void setVideoScalingMode(Defaults::ScalingMode value); void setVideoRenderInfoPosition(Defaults::RenderInfoPosition value); void setVideoRenderInfoShowTime(bool value); void setShaderEnabled(bool value); void setActiveShaderName(const std::string& value); // "postfx" | "crtpi" void setPostFXPreset(const std::string& value); void setCrtPiPreset(const std::string& value); void setAudioEnabled(bool value); void setAudioVolume(float value); void setAudioMusicEnabled(bool value); void setAudioMusicVolume(float value); void setAudioSoundEnabled(bool value); void setAudioSoundVolume(float value); // Fixa l'assignació mando→jugador. Persisteix a `config.yaml` i // propaga a `GamepadManager::assignToPlayer`. `name` i `path` buits // = desassignació. `player_id`: 0 = P1, 1 = P2. void setGamepadAssignment(int player_id, const std::string& name, const std::string& path); // Assigna un nou scancode a una acció d'un jugador i persisteix a // `config.yaml`. `player_id`: 0 = P1, 1 = P2. Sense validació de // conflictes — replicant la referència AEE, el mateix scancode pot // quedar associat a múltiples accions si l'usuari ho elegeix. void setKeyboardBinding(int player_id, KeyboardAction action, SDL_Scancode scancode); // --- API pública: càrrega/desament de fitxers --- void setConfigFile(const std::string& path); auto loadFromFile() -> bool; auto saveToFile() -> bool; void setPostFXFile(const std::string& path); auto loadPostFXFromFile() -> bool; auto savePostFXToFile() -> bool; void setCrtPiFile(const std::string& path); auto loadCrtPiFromFile() -> bool; auto saveCrtPiToFile() -> bool; // --- Builders per als subsistemes --- [[nodiscard]] auto buildScreenConfig() -> Screen::Config; [[nodiscard]] auto buildPostFXParams() -> Rendering::PostFXParams; [[nodiscard]] auto buildCrtPiParams() -> Rendering::CrtPiParams; [[nodiscard]] auto buildActiveShader() -> Rendering::ShaderType; // Els colors de text/ombra els decideix la UI del joc (no Options), per // això es passen com a paràmetres. [[nodiscard]] auto buildRenderInfoFrame(Uint8 text_color, Uint8 shadow_color) -> RenderInfo::Frame; // `::Audio` és la classe global d'àudio (no `Options::Audio`, que és la // struct serialitzable). Retornem la seua Config per passar-la a init(). [[nodiscard]] auto buildAudioConfig() -> ::Audio::Config; } // namespace Options