feat(service_menu): slot 'sense mando' al cycle i swap automatic en conflicte

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 21:22:23 +02:00
parent 85050c8da4
commit 99d0f62ab5
2 changed files with 63 additions and 18 deletions
+5 -3
View File
@@ -55,13 +55,15 @@ namespace {
} }
// Scancodes que MAI capturem com a binding (reservats per a navegacio o // 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 { auto isReservedScancode(SDL_Scancode sc) -> bool {
if (sc == SDL_SCANCODE_ESCAPE) { if (sc == SDL_SCANCODE_ESCAPE) {
return true; // ESC sempre cancel·la return true;
} }
if (sc >= SDL_SCANCODE_F1 && sc <= SDL_SCANCODE_F12) { 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) { if (sc == SDL_SCANCODE_RETURN || sc == SDL_SCANCODE_KP_ENTER) {
return true; return true;
+58 -15
View File
@@ -84,8 +84,8 @@ namespace {
return Utils::toUpperAscii(pad->name); return Utils::toUpperAscii(pad->name);
} }
// Index actual del pad assignat dins de la llista de mandos detectats. // Index del pad assignat dins de la llista. Retorna pads.size() per
// Prioritat path > name. Si no n'hi ha cap match, retorna 0. // representar el slot virtual SENSE MANDO (al final del cycle).
auto findAssignedIndex(const std::vector<std::shared_ptr<Input::Gamepad>>& pads, auto findAssignedIndex(const std::vector<std::shared_ptr<Input::Gamepad>>& pads,
const Config::PlayerBindings& pcfg) -> std::size_t { const Config::PlayerBindings& pcfg) -> std::size_t {
for (std::size_t i = 0; i < pads.size(); ++i) { for (std::size_t i = 0; i < pads.size(); ++i) {
@@ -98,12 +98,35 @@ namespace {
return i; 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. // 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) { void cyclePlayerPad(int player_index, int dir) {
auto* input = Input::get(); const auto* input = Input::get();
if (input == nullptr) { if (input == nullptr) {
return; return;
} }
@@ -114,21 +137,41 @@ namespace {
auto& pcfg = (player_index == 0) auto& pcfg = (player_index == 0)
? ConfigYaml::engine_config.player1 ? ConfigYaml::engine_config.player1
: ConfigYaml::engine_config.player2; : 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 N = pads.size();
const std::size_t STEP = (dir > 0) ? 1 : (N - 1); const std::size_t SLOTS = N + 1; // N pads + slot "sense mando"
const std::size_t NEXT = (CURRENT + STEP) % N; const std::size_t CURRENT = findAssignedIndex(pads, pcfg);
if (!pads[NEXT]) { const std::size_t STEP = (dir > 0) ? 1 : (SLOTS - 1);
return; 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; // Detecta conflicte amb l'altre jugador per fer swap.
if (player_index == 0) { const bool CONFLICT = !new_path.empty() && other.gamepad_path == new_path;
input->applyPlayer1Bindings(ConfigYaml::engine_config.player1); const bool CONFLICT_BY_NAME = !new_name.empty() && new_path.empty() &&
} else { other.gamepad_name == new_name;
input->applyPlayer2Bindings(ConfigYaml::engine_config.player2); 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(); ConfigYaml::saveToFile();
} }