correccions en la detecció de mandos

This commit is contained in:
2026-04-14 17:09:09 +02:00
parent 1a2298963d
commit c694781f38
8 changed files with 121 additions and 36 deletions

View File

@@ -303,20 +303,6 @@ void Input::addGamepadMappingsFromFile() {
}
}
void Input::discoverGamepads() {
// Enumera los gamepads ya conectados sin drenar la cola de eventos de SDL
// (necesario con SDL_MAIN_USE_CALLBACKS, que entrega los eventos por SDL_AppEvent).
int count = 0;
SDL_JoystickID* joysticks = SDL_GetGamepads(&count);
if (joysticks == nullptr) {
return;
}
for (int i = 0; i < count; ++i) {
addGamepad(joysticks[i]);
}
SDL_free(joysticks);
}
void Input::initSDLGamePad() {
if (SDL_WasInit(SDL_INIT_GAMEPAD) != 1) {
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
@@ -324,7 +310,9 @@ void Input::initSDLGamePad() {
} else {
addGamepadMappingsFromFile();
loadGamepadConfigs();
discoverGamepads();
// Los mandos ya conectados llegan como SDL_EVENT_GAMEPAD_ADDED en el
// primer pase del pump de eventos (antes del primer SDL_AppIterate),
// por lo que no hace falta enumerarlos aquí a mano.
}
}
}
@@ -444,6 +432,27 @@ void Input::applyGamepadConfig(std::shared_ptr<Gamepad> gamepad) {
return config.path == gamepad->path;
});
// Fallback por nombre: si el mismo dispositivo se enchufa a otro puerto, su
// path cambia pero el nombre suele mantenerse. Recuperamos su configuración
// y actualizamos el path guardado al actual. Solo se acepta un match cuyo
// path guardado NO corresponda a otro mando ya conectado, para no arruinar
// setups con varios mandos idénticos en puertos distintos (cabinet arcade).
if (config_it == gamepad_configs_.end() && !gamepad->name.empty()) {
auto is_path_active = [this](const std::string& query_path) -> bool {
return std::ranges::any_of(gamepads_, [&query_path](const std::shared_ptr<Gamepad>& g) -> bool {
return g && g->path == query_path;
});
};
config_it = std::ranges::find_if(gamepad_configs_, [&gamepad, &is_path_active](const GamepadConfig& config) -> bool {
return config.name == gamepad->name && !is_path_active(config.path);
});
if (config_it != gamepad_configs_.end()) {
std::cout << "Gamepad '" << gamepad->name << "' found by name, refreshing path to: " << gamepad->path << '\n';
config_it->path = gamepad->path;
saveGamepadConfigs();
}
}
if (config_it != gamepad_configs_.end()) {
// Se encontró una configuración específica para este puerto/dispositivo. La aplicamos.
std::cout << "Applying custom config for gamepad at path: " << gamepad->path << '\n';
@@ -453,7 +462,6 @@ void Input::applyGamepadConfig(std::shared_ptr<Gamepad> gamepad) {
}
}
}
// Opcional: Podrías añadir un fallback para buscar por nombre si no se encuentra por ruta.
}
void Input::saveGamepadConfigFromGamepad(std::shared_ptr<Gamepad> gamepad) {

View File

@@ -206,7 +206,6 @@ class Input {
auto addGamepad(int device_index) -> std::string;
auto removeGamepad(SDL_JoystickID id) -> std::string;
void addGamepadMappingsFromFile();
void discoverGamepads();
// --- Métodos para integración con GamepadConfigManager ---
void loadGamepadConfigs();

View File

@@ -9,20 +9,20 @@
#include <string> // Para basic_string, operator+, char_traits, to_string, string
#include <vector> // Para vector
#include "core/input/mouse.hpp" // Para updateCursorVisibility
#include "core/input/mouse.hpp" // Para updateCursorVisibility
#ifndef NO_SHADERS
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
#endif
#include "core/rendering/text.hpp" // Para Text
#include "core/rendering/texture.hpp" // Para Texture
#include "core/resources/asset.hpp" // Para Asset
#include "core/resources/resource.hpp" // Para Resource
#include "core/system/director.hpp" // Para Director::debug_config
#include "game/options.hpp" // Para Video, video, Window, window
#include "game/ui/notifier.hpp" // Para Notifier
#include "game/ui/service_menu.hpp" // Para ServiceMenu
#include "utils/param.hpp" // Para Param, param, ParamGame, ParamDebug
#include "utils/utils.hpp" // Para toLower
#include "core/rendering/text.hpp" // Para Text
#include "core/rendering/texture.hpp" // Para Texture
#include "core/resources/asset.hpp" // Para Asset
#include "core/resources/resource.hpp" // Para Resource
#include "core/system/director.hpp" // Para Director::debug_config
#include "game/options.hpp" // Para Video, video, Window, window
#include "game/ui/notifier.hpp" // Para Notifier
#include "game/ui/service_menu.hpp" // Para ServiceMenu
#include "utils/param.hpp" // Para Param, param, ParamGame, ParamDebug
#include "utils/utils.hpp" // Para toLower
// Singleton
Screen* Screen::instance = nullptr;

View File

@@ -125,8 +125,8 @@ void Director::init() {
Options::video.fullscreen = true;
Options::video.integer_scale = true;
#endif
loadParams(); // Carga los parámetros del programa
loadScoreFile(); // Carga el archivo de puntuaciones
loadParams(); // Carga los parámetros del programa
loadScoreFile(); // Carga el archivo de puntuaciones
// Inicialización de subsistemas principales
Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma
@@ -435,6 +435,15 @@ auto Director::iterate() -> SDL_AppResult {
return SDL_APP_SUCCESS;
}
// En el primer frame, SDL ya ha drenado los SDL_EVENT_GAMEPAD_ADDED de los
// mandos conectados al iniciar. A partir de ahora los eventos sí son hotplug
// real y deben mostrar notificación.
static bool first_iterate = true;
if (first_iterate) {
first_iterate = false;
GlobalEvents::markStartupComplete();
}
// Gestiona las transiciones entre secciones (destruye la anterior y construye la nueva)
handleSectionTransition();

View File

@@ -17,12 +17,28 @@
#include "game/ui/service_menu.hpp" // Para ServiceMenu
namespace GlobalEvents {
namespace {
// Durante el arranque se drenan los SDL_EVENT_GAMEPAD_ADDED de los mandos
// ya conectados. Esos eventos sí deben reasignar mandos a jugadores, pero
// no deben mostrar notificación: no son hotplug, son detección inicial.
bool startup_in_progress = true;
} // namespace
// Comprueba los eventos de Input y muestra notificaciones
void handleInputEvents(const SDL_Event& event) {
if (event.type != SDL_EVENT_GAMEPAD_ADDED && event.type != SDL_EVENT_GAMEPAD_REMOVED) {
return;
}
static auto* input_ = Input::get();
auto message = input_->handleEvent(event);
if (message.empty()) {
// Reasignar siempre: tanto en arranque como en hotplug en caliente.
Options::gamepad_manager.assignAndLinkGamepads();
Options::gamepad_manager.resyncGamepadsWithPlayers();
ServiceMenu::get()->refresh();
if (startup_in_progress || message.empty()) {
return;
}
@@ -35,10 +51,11 @@ namespace GlobalEvents {
message.replace(pos, std::string(" DISCONNECTED").length(), " " + Lang::getText("[NOTIFICATIONS] DISCONNECTED"));
}
Options::gamepad_manager.assignAndLinkGamepads();
Options::gamepad_manager.resyncGamepadsWithPlayers();
Notifier::get()->show({message});
ServiceMenu::get()->refresh();
}
void markStartupComplete() {
startup_in_progress = false;
}
// Comprueba los eventos que se pueden producir en cualquier sección del juego

View File

@@ -6,4 +6,10 @@
namespace GlobalEvents {
// --- Funciones ---
void handle(const SDL_Event& event); // Comprueba los eventos que se pueden producir en cualquier sección del juego
// Marca el fin del arranque: a partir de aquí, los eventos de mando
// generan notificaciones en pantalla. Se debe llamar desde Director::iterate
// en el primer fotograma, después de que SDL haya drenado los
// SDL_EVENT_GAMEPAD_ADDED de los mandos ya conectados al iniciar.
void markStartupComplete();
} // namespace GlobalEvents

View File

@@ -763,18 +763,30 @@ namespace Options {
}
// Asigna los mandos físicos basándose en la configuración actual.
//
// Tres pases en orden decreciente de prioridad:
// 1) Match por path exacto: resuelve el caso arcade (dos sticks idénticos
// enchufados siempre a los mismos puertos USB).
// 2) Match por nombre cuando el path no coincidió: recoloca un mando
// conocido que se ha reenchufado a otro puerto (ej. DualSense). En ese
// caso se refresca el path guardado al nuevo.
// 3) Reparto en orden de los mandos físicos sobrantes a los slots que
// sigan libres.
void GamepadManager::assignAndLinkGamepads() {
auto physical_gamepads = Input::get()->getGamepads();
std::array<std::string, MAX_PLAYERS> desired_paths;
std::array<std::string, MAX_PLAYERS> desired_names;
for (size_t i = 0; i < MAX_PLAYERS; ++i) {
desired_paths[i] = gamepads_[i].path;
desired_names[i] = gamepads_[i].name;
gamepads_[i].instance = nullptr;
}
std::vector<std::shared_ptr<Input::Gamepad>> assigned_instances;
assignGamepadsByPath(desired_paths, physical_gamepads, assigned_instances);
assignGamepadsByName(desired_names, physical_gamepads, assigned_instances);
assignRemainingGamepads(physical_gamepads, assigned_instances);
clearUnassignedGamepadSlots();
}
@@ -802,7 +814,37 @@ namespace Options {
}
}
// --- SEGUNDA PASADA: Asigna los mandos físicos restantes a los jugadores libres ---
// --- SEGUNDA PASADA: Match por nombre para slots cuyo path guardado no casó ---
// Si un slot tiene nombre guardado pero el path no ha coincidido (mando reenchufado
// a otro puerto, o path del sistema cambiado), lo enlazamos por nombre y
// refrescamos el path guardado al del dispositivo físico actual.
void GamepadManager::assignGamepadsByName(
const std::array<std::string, MAX_PLAYERS>& desired_names,
const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads, // NOLINT(readability-named-parameter)
std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) {
for (size_t i = 0; i < MAX_PLAYERS; ++i) {
if (gamepads_[i].instance != nullptr) {
continue;
}
const std::string& desired_name = desired_names[i];
if (desired_name.empty()) {
continue;
}
for (const auto& physical_gamepad : physical_gamepads) {
if (physical_gamepad->name == desired_name && !isGamepadAssigned(physical_gamepad, assigned_instances)) {
gamepads_[i].instance = physical_gamepad;
gamepads_[i].name = physical_gamepad->name;
gamepads_[i].path = physical_gamepad->path;
assigned_instances.push_back(physical_gamepad);
break;
}
}
}
}
// --- TERCERA PASADA: Asigna los mandos físicos restantes a los jugadores libres ---
void GamepadManager::assignRemainingGamepads(
const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads, // NOLINT(readability-named-parameter)
std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) {
@@ -824,7 +866,7 @@ namespace Options {
}
}
// --- TERCERA PASADA: Limpia la información "fantasma" de los slots no asignados ---
// --- LIMPIEZA FINAL: Limpia la información "fantasma" de los slots no asignados ---
void GamepadManager::clearUnassignedGamepadSlots() {
for (auto& gamepad_config : gamepads_) {
if (gamepad_config.instance == nullptr) {

View File

@@ -293,6 +293,10 @@ namespace Options {
const std::array<std::string, MAX_PLAYERS>& desired_paths,
const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads, // NOLINT(readability-avoid-const-params-in-decls)
std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances);
void assignGamepadsByName(
const std::array<std::string, MAX_PLAYERS>& desired_names,
const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads, // NOLINT(readability-avoid-const-params-in-decls)
std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances);
void assignRemainingGamepads(
const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads, // NOLINT(readability-avoid-const-params-in-decls)
std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances);