From e5a91825b1b3e8308c5b9345c58117094a034315 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 24 May 2026 19:55:42 +0200 Subject: [PATCH] feat(input): notifica connexio/desconnexio de mandos via Notifier Co-Authored-By: Claude Opus 4.7 (1M context) --- data/locale/ca.yaml | 2 + data/locale/en.yaml | 2 + source/core/input/input.cpp | 91 +++++++++++++++++++++++++++++-------- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/data/locale/ca.yaml b/data/locale/ca.yaml index 71ccc66..4bc25f2 100644 --- a/data/locale/ca.yaml +++ b/data/locale/ca.yaml @@ -14,6 +14,8 @@ notification: postfx_on: "POSTPROCESSAT ACTIU" postfx_off: "POSTPROCESSAT INACTIU" locale_switched: "IDIOMA: {lang}" + gamepad_connected: "{name} CONNECTAT" + gamepad_disconnected: "{name} DESCONNECTAT" language: ca: "CATALA" diff --git a/data/locale/en.yaml b/data/locale/en.yaml index f5fc757..77755c8 100644 --- a/data/locale/en.yaml +++ b/data/locale/en.yaml @@ -13,6 +13,8 @@ notification: postfx_on: "POSTPROCESS ON" postfx_off: "POSTPROCESS OFF" locale_switched: "LANGUAGE: {lang}" + gamepad_connected: "{name} CONNECTED" + gamepad_disconnected: "{name} DISCONNECTED" language: ca: "CATALAN" diff --git a/source/core/input/input.cpp b/source/core/input/input.cpp index a668690..e0e9e67 100644 --- a/source/core/input/input.cpp +++ b/source/core/input/input.cpp @@ -8,6 +8,9 @@ #include // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator #include // Para move +#include "core/locale/locale.hpp" +#include "core/system/notifier.hpp" + // Singleton Input* Input::instance = nullptr; @@ -407,6 +410,16 @@ auto Input::addGamepad(int device_index) -> std::string { auto name = gamepad->name; std::cout << "Gamepad connected (" << name << ")" << '\n'; gamepads_.push_back(std::move(gamepad)); + + // Toast a pantalla. Pot ser nullptr durant discoverGamepads() inicial + // (l'Input::init() es crida abans que el Director instanciï el Notifier). + if (auto* notifier = System::Notifier::get(); notifier != nullptr) { + notifier->notifyInfo(localeSubstitute( + Locale::get().text("notification.gamepad_connected"), + "{name}", + name)); + } + return name + " CONNECTED"; } @@ -419,6 +432,14 @@ auto Input::removeGamepad(SDL_JoystickID id) -> std::string { std::string name = (*it)->name; std::cout << "Gamepad disconnected (" << name << ")" << '\n'; gamepads_.erase(it); + + if (auto* notifier = System::Notifier::get(); notifier != nullptr) { + notifier->notifyInfo(localeSubstitute( + Locale::get().text("notification.gamepad_disconnected"), + "{name}", + name)); + } + return name + " DISCONNECTED"; } std::cerr << "No se encontró el gamepad con ID " << id << '\n'; @@ -465,6 +486,39 @@ auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std:: // ========== MÉTODOS ESPECÍFICOS POR JUGADOR (ORNI) ========== +// Cerca el gamepad assignat a un jugador. Prioritat: path > name > slot +// per índex (fallback). Retorna nullptr si no n'hi ha cap de connectat. +auto Input::resolvePlayerGamepad(const Config::PlayerBindings& bindings, + std::size_t fallback_index) -> std::shared_ptr { + if (gamepads_.empty()) { + return nullptr; + } + + if (!bindings.gamepad_path.empty()) { + for (const auto& pad : gamepads_) { + if (pad && pad->path == bindings.gamepad_path) { + return pad; + } + } + } + + if (!bindings.gamepad_name.empty()) { + for (const auto& pad : gamepads_) { + if (pad && pad->name == bindings.gamepad_name) { + return pad; + } + } + } + + // Fallback: pad pel slot del jugador (P1=0, P2=1). Si no hi ha pad per al + // slot, retornem nullptr en lloc de robar-li el pad a l'altre jugador. + if (fallback_index < gamepads_.size() && bindings.gamepad_path.empty() && bindings.gamepad_name.empty()) { + return gamepads_[fallback_index]; + } + + return nullptr; +} + // Aplica configuración de controles del player 1 void Input::applyPlayer1Bindings(const Config::PlayerBindings& bindings) { // 1. Aplicar bindings de teclado (NO usar bindKey, llenar mapa específico) @@ -474,15 +528,8 @@ void Input::applyPlayer1Bindings(const Config::PlayerBindings& bindings) { player1_keyboard_bindings_[Action::SHOOT].scancode = bindings.keyboard.key_shoot; player1_keyboard_bindings_[Action::START].scancode = bindings.keyboard.key_start; - // 2. Encontrar gamepad por nombre (o usar primer gamepad como fallback) - std::shared_ptr gamepad = nullptr; - if (bindings.gamepad_name.empty()) { - // Fallback: usar primer gamepad disponible - gamepad = (!gamepads_.empty()) ? gamepads_[0] : nullptr; - } else { - // Buscar por nombre - gamepad = findAvailableGamepadByName(bindings.gamepad_name); - } + // 2. Resoldre gamepad per path/name/slot + std::shared_ptr gamepad = resolvePlayerGamepad(bindings, 0); if (!gamepad) { player1_gamepad_ = nullptr; @@ -494,6 +541,8 @@ void Input::applyPlayer1Bindings(const Config::PlayerBindings& bindings) { gamepad->bindings[Action::RIGHT].button = bindings.gamepad.button_right; gamepad->bindings[Action::THRUST].button = bindings.gamepad.button_thrust; gamepad->bindings[Action::SHOOT].button = bindings.gamepad.button_shoot; + gamepad->bindings[Action::START].button = bindings.gamepad.button_start; + gamepad->bindings[Action::MENU].button = bindings.gamepad.button_menu; // 4. Cachear referencia player1_gamepad_ = gamepad; @@ -508,15 +557,8 @@ void Input::applyPlayer2Bindings(const Config::PlayerBindings& bindings) { player2_keyboard_bindings_[Action::SHOOT].scancode = bindings.keyboard.key_shoot; player2_keyboard_bindings_[Action::START].scancode = bindings.keyboard.key_start; - // 2. Encontrar gamepad por nombre (o usar segundo gamepad como fallback) - std::shared_ptr gamepad = nullptr; - if (bindings.gamepad_name.empty()) { - // Fallback: usar segundo gamepad disponible - gamepad = (gamepads_.size() > 1) ? gamepads_[1] : nullptr; - } else { - // Buscar por nombre - gamepad = findAvailableGamepadByName(bindings.gamepad_name); - } + // 2. Resoldre gamepad per path/name/slot + std::shared_ptr gamepad = resolvePlayerGamepad(bindings, 1); if (!gamepad) { player2_gamepad_ = nullptr; @@ -528,6 +570,8 @@ void Input::applyPlayer2Bindings(const Config::PlayerBindings& bindings) { gamepad->bindings[Action::RIGHT].button = bindings.gamepad.button_right; gamepad->bindings[Action::THRUST].button = bindings.gamepad.button_thrust; gamepad->bindings[Action::SHOOT].button = bindings.gamepad.button_shoot; + gamepad->bindings[Action::START].button = bindings.gamepad.button_start; + gamepad->bindings[Action::MENU].button = bindings.gamepad.button_menu; // 4. Cachear referencia player2_gamepad_ = gamepad; @@ -555,6 +599,17 @@ auto Input::checkActionPlayer1(Action action, bool repeat) -> bool { return keyboard_active || gamepad_active; } +// Retorna el pad assignat (0=P1, 1=P2). Pot ser nullptr. +auto Input::getPlayerGamepad(int player_index) const -> std::shared_ptr { + if (player_index == 0) { + return player1_gamepad_; + } + if (player_index == 1) { + return player2_gamepad_; + } + return nullptr; +} + // Consulta de input para player 2 auto Input::checkActionPlayer2(Action action, bool repeat) -> bool { // Comprobar teclado con el mapa específico de P2