feat(input): notifica connexio/desconnexio de mandos via Notifier

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 19:55:42 +02:00
parent b3271b17a2
commit e5a91825b1
3 changed files with 77 additions and 18 deletions
+2
View File
@@ -14,6 +14,8 @@ notification:
postfx_on: "POSTPROCESSAT ACTIU" postfx_on: "POSTPROCESSAT ACTIU"
postfx_off: "POSTPROCESSAT INACTIU" postfx_off: "POSTPROCESSAT INACTIU"
locale_switched: "IDIOMA: {lang}" locale_switched: "IDIOMA: {lang}"
gamepad_connected: "{name} CONNECTAT"
gamepad_disconnected: "{name} DESCONNECTAT"
language: language:
ca: "CATALA" ca: "CATALA"
+2
View File
@@ -13,6 +13,8 @@ notification:
postfx_on: "POSTPROCESS ON" postfx_on: "POSTPROCESS ON"
postfx_off: "POSTPROCESS OFF" postfx_off: "POSTPROCESS OFF"
locale_switched: "LANGUAGE: {lang}" locale_switched: "LANGUAGE: {lang}"
gamepad_connected: "{name} CONNECTED"
gamepad_disconnected: "{name} DISCONNECTED"
language: language:
ca: "CATALAN" ca: "CATALAN"
+73 -18
View File
@@ -8,6 +8,9 @@
#include <unordered_map> // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator #include <unordered_map> // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator
#include <utility> // Para move #include <utility> // Para move
#include "core/locale/locale.hpp"
#include "core/system/notifier.hpp"
// Singleton // Singleton
Input* Input::instance = nullptr; Input* Input::instance = nullptr;
@@ -407,6 +410,16 @@ auto Input::addGamepad(int device_index) -> std::string {
auto name = gamepad->name; auto name = gamepad->name;
std::cout << "Gamepad connected (" << name << ")" << '\n'; std::cout << "Gamepad connected (" << name << ")" << '\n';
gamepads_.push_back(std::move(gamepad)); 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"; return name + " CONNECTED";
} }
@@ -419,6 +432,14 @@ auto Input::removeGamepad(SDL_JoystickID id) -> std::string {
std::string name = (*it)->name; std::string name = (*it)->name;
std::cout << "Gamepad disconnected (" << name << ")" << '\n'; std::cout << "Gamepad disconnected (" << name << ")" << '\n';
gamepads_.erase(it); 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"; return name + " DISCONNECTED";
} }
std::cerr << "No se encontró el gamepad con ID " << id << '\n'; 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) ========== // ========== 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<Gamepad> {
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 // Aplica configuración de controles del player 1
void Input::applyPlayer1Bindings(const Config::PlayerBindings& bindings) { void Input::applyPlayer1Bindings(const Config::PlayerBindings& bindings) {
// 1. Aplicar bindings de teclado (NO usar bindKey, llenar mapa específico) // 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::SHOOT].scancode = bindings.keyboard.key_shoot;
player1_keyboard_bindings_[Action::START].scancode = bindings.keyboard.key_start; player1_keyboard_bindings_[Action::START].scancode = bindings.keyboard.key_start;
// 2. Encontrar gamepad por nombre (o usar primer gamepad como fallback) // 2. Resoldre gamepad per path/name/slot
std::shared_ptr<Gamepad> gamepad = nullptr; std::shared_ptr<Gamepad> gamepad = resolvePlayerGamepad(bindings, 0);
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);
}
if (!gamepad) { if (!gamepad) {
player1_gamepad_ = nullptr; 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::RIGHT].button = bindings.gamepad.button_right;
gamepad->bindings[Action::THRUST].button = bindings.gamepad.button_thrust; gamepad->bindings[Action::THRUST].button = bindings.gamepad.button_thrust;
gamepad->bindings[Action::SHOOT].button = bindings.gamepad.button_shoot; 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 // 4. Cachear referencia
player1_gamepad_ = gamepad; 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::SHOOT].scancode = bindings.keyboard.key_shoot;
player2_keyboard_bindings_[Action::START].scancode = bindings.keyboard.key_start; player2_keyboard_bindings_[Action::START].scancode = bindings.keyboard.key_start;
// 2. Encontrar gamepad por nombre (o usar segundo gamepad como fallback) // 2. Resoldre gamepad per path/name/slot
std::shared_ptr<Gamepad> gamepad = nullptr; std::shared_ptr<Gamepad> gamepad = resolvePlayerGamepad(bindings, 1);
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);
}
if (!gamepad) { if (!gamepad) {
player2_gamepad_ = nullptr; 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::RIGHT].button = bindings.gamepad.button_right;
gamepad->bindings[Action::THRUST].button = bindings.gamepad.button_thrust; gamepad->bindings[Action::THRUST].button = bindings.gamepad.button_thrust;
gamepad->bindings[Action::SHOOT].button = bindings.gamepad.button_shoot; 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 // 4. Cachear referencia
player2_gamepad_ = gamepad; player2_gamepad_ = gamepad;
@@ -555,6 +599,17 @@ auto Input::checkActionPlayer1(Action action, bool repeat) -> bool {
return keyboard_active || gamepad_active; 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<Input::Gamepad> {
if (player_index == 0) {
return player1_gamepad_;
}
if (player_index == 1) {
return player2_gamepad_;
}
return nullptr;
}
// Consulta de input para player 2 // Consulta de input para player 2
auto Input::checkActionPlayer2(Action action, bool repeat) -> bool { auto Input::checkActionPlayer2(Action action, bool repeat) -> bool {
// Comprobar teclado con el mapa específico de P2 // Comprobar teclado con el mapa específico de P2