From 99d0f62ab578f7a7a296911a730c9bb03f04e9f3 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 24 May 2026 21:22:23 +0200 Subject: [PATCH] feat(service_menu): slot 'sense mando' al cycle i swap automatic en conflicte MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El CYCLE de la pagina CONTROLS ara inclou un slot virtual al final que desassigna el mando (gamepad_name + gamepad_path buits → padDisplayName mostra "SENSE MANDO"). Aixi l'usuari pot recuperar el control teclat sense haver d'editar el YAML. A mes, si en assignar un mando l'altre jugador ja el tenia, fem swap automatic: l'altre jugador rep l'assignacio previa d'aquest, evitant que dos jugadors comparteixen el mateix dispositiu. La deteccio prioritza path (mateixa branca que resolvePlayerGamepad). Extracta tambe reapplyBindings per mantenir cyclePlayerPad llegible. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/input/define_inputs.cpp | 8 ++-- source/core/system/service_menu.cpp | 73 +++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/source/core/input/define_inputs.cpp b/source/core/input/define_inputs.cpp index 27e8b9e..9bfb857 100644 --- a/source/core/input/define_inputs.cpp +++ b/source/core/input/define_inputs.cpp @@ -55,13 +55,15 @@ namespace { } // Scancodes que MAI capturem com a binding (reservats per a navegacio o - // global hotkeys). Evita que l'usuari es bloquegi ell mateix. + // global hotkeys). Tornen true → handleEvent les deixa passar al pipeline + // global perque facin la seua feina (ESC obre el prompt d'eixida, F1-F12 + // son hotkeys de sistema, RETURN/BACKSPACE/TAB son navegacio). auto isReservedScancode(SDL_Scancode sc) -> bool { if (sc == SDL_SCANCODE_ESCAPE) { - return true; // ESC sempre cancel·la + return true; } if (sc >= SDL_SCANCODE_F1 && sc <= SDL_SCANCODE_F12) { - return true; // F1-F12 globals + return true; } if (sc == SDL_SCANCODE_RETURN || sc == SDL_SCANCODE_KP_ENTER) { return true; diff --git a/source/core/system/service_menu.cpp b/source/core/system/service_menu.cpp index b3b520b..33198cc 100644 --- a/source/core/system/service_menu.cpp +++ b/source/core/system/service_menu.cpp @@ -84,8 +84,8 @@ namespace { return Utils::toUpperAscii(pad->name); } - // Index actual del pad assignat dins de la llista de mandos detectats. - // Prioritat path > name. Si no n'hi ha cap match, retorna 0. + // Index del pad assignat dins de la llista. Retorna pads.size() per + // representar el slot virtual SENSE MANDO (al final del cycle). auto findAssignedIndex(const std::vector>& pads, const Config::PlayerBindings& pcfg) -> std::size_t { for (std::size_t i = 0; i < pads.size(); ++i) { @@ -98,12 +98,35 @@ namespace { return i; } } - return 0; + return pads.size(); // Slot virtual "sense mando" + } + + // Aplica les noves assignacions a Input. Si ha hagut swap, refresca els + // dos jugadors; en cas contrari nomes el que ha canviat. + void reapplyBindings(int player_index, bool swap_other) { + auto* input = Input::get(); + if (input == nullptr) { + return; + } + if (player_index == 0) { + input->applyPlayer1Bindings(ConfigYaml::engine_config.player1); + if (swap_other) { + input->applyPlayer2Bindings(ConfigYaml::engine_config.player2); + } + } else { + input->applyPlayer2Bindings(ConfigYaml::engine_config.player2); + if (swap_other) { + input->applyPlayer1Bindings(ConfigYaml::engine_config.player1); + } + } } // Avança ciclicament l'assignacio de pad d'un jugador i la persisteix. + // El cycle inclou un slot virtual "sense mando" al final (NEXT == N). + // Si l'altre jugador ja tenia el pad triat, fa swap: l'altre rep la + // assignacio prèvia d'aquest jugador. void cyclePlayerPad(int player_index, int dir) { - auto* input = Input::get(); + const auto* input = Input::get(); if (input == nullptr) { return; } @@ -114,21 +137,41 @@ namespace { auto& pcfg = (player_index == 0) ? ConfigYaml::engine_config.player1 : ConfigYaml::engine_config.player2; + auto& other = (player_index == 0) + ? ConfigYaml::engine_config.player2 + : ConfigYaml::engine_config.player1; - const std::size_t CURRENT = findAssignedIndex(pads, pcfg); const std::size_t N = pads.size(); - const std::size_t STEP = (dir > 0) ? 1 : (N - 1); - const std::size_t NEXT = (CURRENT + STEP) % N; - if (!pads[NEXT]) { - return; + const std::size_t SLOTS = N + 1; // N pads + slot "sense mando" + const std::size_t CURRENT = findAssignedIndex(pads, pcfg); + const std::size_t STEP = (dir > 0) ? 1 : (SLOTS - 1); + const std::size_t NEXT = (CURRENT + STEP) % SLOTS; + + // Determinem el nou nom + path (buits si seleccionem "sense mando"). + std::string new_name; + std::string new_path; + if (NEXT < N && pads[NEXT]) { + new_name = pads[NEXT]->name; + new_path = pads[NEXT]->path; } - pcfg.gamepad_name = pads[NEXT]->name; - pcfg.gamepad_path = pads[NEXT]->path; - if (player_index == 0) { - input->applyPlayer1Bindings(ConfigYaml::engine_config.player1); - } else { - input->applyPlayer2Bindings(ConfigYaml::engine_config.player2); + + // Detecta conflicte amb l'altre jugador per fer swap. + const bool CONFLICT = !new_path.empty() && other.gamepad_path == new_path; + const bool CONFLICT_BY_NAME = !new_name.empty() && new_path.empty() && + other.gamepad_name == new_name; + const bool DO_SWAP = CONFLICT || CONFLICT_BY_NAME; + + const std::string PREV_NAME = pcfg.gamepad_name; + const std::string PREV_PATH = pcfg.gamepad_path; + + pcfg.gamepad_name = new_name; + pcfg.gamepad_path = new_path; + if (DO_SWAP) { + other.gamepad_name = PREV_NAME; + other.gamepad_path = PREV_PATH; } + + reapplyBindings(player_index, DO_SWAP); ConfigYaml::saveToFile(); }