#pragma once #include #include #include #include #include // Vinculacions remapeables d'accions del joc a botons d'un mando concret, // indexades per `(name, path)` del mando — replica el patró // `GamepadConfig` + `GamepadConfigManager` de coffee_crisis_arcade_edition. // // Persisteix en un YAML aïllat al system folder (no a `config.yaml`) perquè // és per dispositiu, no per usuari/perfil: dos mandos diferents poden // tenir mappings diferents i cap d'ells va lligat a un slot de jugador. // // El layer runtime (consultat per Player/Director cada frame) és // directament aquesta classe; no hi ha caché a `GamepadManager::Entry`. El // nombre d'entries persistides és típicament <10, i les accions són 3 // (JUMP/ATTACK/SERVICE), així que les consultes són trivialment ràpides. class GamepadBindings { public: enum class Action : std::uint8_t { JUMP, ATTACK, SERVICE, START, }; // Triggers L2/R2 representats com a "botons virtuals" — replica la // convenció de coffee_crisis_arcade_edition (`TRIGGER_L2_AS_BUTTON`, // `TRIGGER_R2_AS_BUTTON`). El threshold viu a `isPressed`. static constexpr int TRIGGER_L2_AS_BUTTON = 100; static constexpr int TRIGGER_R2_AS_BUTTON = 101; static auto get() -> GamepadBindings&; GamepadBindings(const GamepadBindings&) = delete; GamepadBindings(GamepadBindings&&) = delete; auto operator=(const GamepadBindings&) -> GamepadBindings& = delete; auto operator=(GamepadBindings&&) -> GamepadBindings& = delete; // Fixa la ruta del fitxer persistent + el carrega. Si no existix // encara, l'estat queda buit (les consultes retornaran defaults) i // `save()` el crearà al primer `setBindings`. void load(const std::string& file_path); // Persistix l'estat actual (totes les Config conegudes). Cridat // automàticament dins de `setBindings` — no cal cridar-la a mà. auto save() -> bool; // Retorna el botó (o trigger virtual 100/101) lligat a `action` per al // mando identificat per (name, path). Si no hi ha entrada per aquest // mando, retorna el default global. La cerca és primer per `path` // (re-vincular el mateix mando al mateix port és prioritari) i, si no // troba, fallback per `name` (mando connectat a un altre port USB). [[nodiscard]] auto getButton(const std::string& name, const std::string& path, Action action) const -> int; // Reescriu totes les vinculacions del mando (name, path). Si no hi // havia config per aquest dispositiu, en crea una nova. Persisteix a // disc immediatament. Replicació de `Input::saveGamepadConfigFromGamepad`. void setBindings(const std::string& name, const std::string& path, const std::unordered_map& bindings); // Wrapper de polling: torna `true` si el "botó" (o trigger virtual) // està premut ara mateix al mando `pad`. Encapsula el threshold per // a L2/R2 (~50% del rang). [[nodiscard]] static auto isPressed(SDL_Gamepad* pad, int button) -> bool; // Default de fàbrica per a una acció — usat tant pel fallback de // `getButton` com per inicialitzar la seqüència del DefineButtons. [[nodiscard]] static auto defaultButton(Action action) -> int; private: struct Config { std::string name; std::string path; std::unordered_map bindings; }; GamepadBindings() = default; ~GamepadBindings() = default; // Localitza la Config que coincideix amb (name, path); retorna -1 si // no la troba. Match prioritari per path, fallback per nom. [[nodiscard]] auto findConfig(const std::string& name, const std::string& path) const -> int; std::vector configs_; std::string file_path_; };