#pragma once #include #include #include #include #include // Gestor global de gamepads. Descobreix els mandos presents a `init()` // sense emetre notificacions (el jugador ja els té posats al llançar el // joc). Durant el runtime, `handleEvent()` processa els events de hot-plug // i emet notificacions visibles via `Notification::notify`. // // El polling real de botons i eixos NO es fa a través d'aquesta classe: // els consumidors obtenen el `SDL_Gamepad*` primari (o l'enèsim per a // multi-jugador futur) i el passen directament a // `SDL_GetGamepadButton`/`SDL_GetGamepadAxis`. class GamepadManager { public: static auto get() -> GamepadManager&; GamepadManager(const GamepadManager&) = delete; GamepadManager(GamepadManager&&) = delete; auto operator=(const GamepadManager&) -> GamepadManager& = delete; auto operator=(GamepadManager&&) -> GamepadManager& = delete; // Inicialitza el subsistema SDL de gamepad, intenta carregar un // `gamecontrollerdb.txt` opcional i obri silenciosament tots els // mandos ja connectats. Cridat una sola vegada des de Director. void init(); // Tanca tots els gamepads oberts i treu el subsistema SDL. Cridat // des de `~Director` abans de `SDL_Quit` per evitar handles orfes. void shutdown(); // Processa SDL_EVENT_GAMEPAD_ADDED i SDL_EVENT_GAMEPAD_REMOVED // emesos durant el runtime. Emet notificacions " connectat" o // " desconnectat". El Director decideix quins events li arriben // via `processGamepadEvents()`. void handleEvent(const SDL_Event& ev); // Primary = primer gamepad al vector. `nullptr` si no n'hi ha cap. // Ús típic: navegació del ServiceMenu. Per al gameplay els jugadors // usen `getForPlayer(id)`. [[nodiscard]] auto getPrimary() const -> SDL_Gamepad*; [[nodiscard]] auto getCount() const -> std::size_t; // Retorna el mand del jugador `idx`-èsim (0-indexed) en l'ordre de // connexió. Per al gameplay, `getForPlayer` fa servir l'assignació // persistida, no l'ordre de connexió. [[nodiscard]] auto getByPlayerIndex(std::size_t idx) const -> SDL_Gamepad*; // Informació exposada al menú de servei (llista de mandos // disponibles). `path` pot estar buit si SDL no l'exposa (p. ex. a // alguns backends web). struct Info { SDL_Gamepad* pad{nullptr}; std::string name; std::string path; }; [[nodiscard]] auto getAll() const -> std::vector; // Retorna el mand assignat al jugador `player_id` (0 = P1, 1 = P2) // o `nullptr` si no n'hi ha cap. L'assignació la fixa // `assignToPlayer` (cridat per Options en carregar `config.yaml` o // des del ServiceMenu quan l'usuari tria un mando). [[nodiscard]] auto getForPlayer(int player_id) const -> SDL_Gamepad*; // Assigna un mando per nom + ruta física. Si ja hi ha un mando // connectat que coincideix per ruta (o per nom com a fallback), el // vincula immediatament; si no, l'assignació queda pendent i // s'aplicarà si el mando es connecta més tard (hot-plug). // Política last-wins: si l'altre jugador tenia el mateix mando // vinculat, se'l desvincula silenciosament. void assignToPlayer(int player_id, const std::string& name, const std::string& path); // Esborra l'assignació (per si l'usuari trie "Cap" al menú). void unassignFromPlayer(int player_id); // Per a cada slot buit, hi vincula el primer mando connectat que no // estigui reclamat per un altre slot. Equivalent al patró de // `coffee_crisis_arcade_edition`: les assignacions persistides tenen // prioritat; els slots que queden buits després s'emplenen per // ordre. Retorna la llista de parells (player_id, name, path) que // s'han vinculat, per a que Options pugui persistir-los. struct AutoAssigned { int player_id; std::string name; std::string path; }; auto autoAssignFreeSlots() -> std::vector; private: struct Entry { SDL_Gamepad* pad{nullptr}; SDL_JoystickID instance_id{0}; std::string pretty_name; std::string path; }; // Assignació persistent per jugador. `name` i `path` venen d'Options // (o del menú de servei) i poden referenciar un mando encara no // connectat; en aquest cas `pad` és `nullptr` i el hot-plug el // vincularà quan aparegui. struct Assignment { std::string name; std::string path; SDL_Gamepad* pad{nullptr}; }; static constexpr int MAX_PLAYERS = 2; std::array assignments_{}; // Busca a `entries_` el millor match per a una assignació (primer // per path, després per nom). Retorna l'iterador o `entries_.end()`. // També accepta assignar `path` nou si ha coincidit per nom // (cas mando reenchufat a un altre port USB). void rebindAssignment(Assignment& a); GamepadManager() = default; ~GamepadManager() = default; // Retalla `raw` al primer '(' o '[' i elimina espais finals per a // llevar metadata verbosa ("Retroid Controller (vendor: 1001) ..."). // Fallback "Gamepad" si queda buit. No limita l'amplada final — el // Notifier multilinia ja gestiona noms llargs. [[nodiscard]] static auto prettyName(const char* raw) -> std::string; // Si `base` ja existeix al vector, afegeix " #2", " #3", ... fins que // trobi un nom lliure. Així dos mandos idèntics es distingeixen a les // notificacions. [[nodiscard]] auto uniqueName(const std::string& base) const -> std::string; void addGamepad(SDL_JoystickID jid, bool notify); void removeGamepad(SDL_JoystickID jid); std::vector entries_; bool initialized_{false}; };