diff --git a/source/core/config/engine_config.hpp b/source/core/config/engine_config.hpp index a9a3f7d..29680a2 100644 --- a/source/core/config/engine_config.hpp +++ b/source/core/config/engine_config.hpp @@ -48,12 +48,15 @@ namespace Config { int button_right{SDL_GAMEPAD_BUTTON_DPAD_RIGHT}; int button_thrust{SDL_GAMEPAD_BUTTON_WEST}; // X button int button_shoot{SDL_GAMEPAD_BUTTON_SOUTH}; // A button + int button_start{SDL_GAMEPAD_BUTTON_START}; // Start button + int button_menu{SDL_GAMEPAD_BUTTON_BACK}; // Select/Back -> obre menu servei }; struct PlayerBindings { KeyboardBindings keyboard{}; GamepadBindings gamepad{}; std::string gamepad_name; // Empty = auto-assign by index + std::string gamepad_path; // Prioritari sobre name per distingir mateixos models }; struct AudioConfig { diff --git a/source/core/input/input.hpp b/source/core/input/input.hpp index 541c3b4..9a5134a 100644 --- a/source/core/input/input.hpp +++ b/source/core/input/input.hpp @@ -62,7 +62,9 @@ class Input { {Action::LEFT, ButtonState{.button = static_cast(SDL_GAMEPAD_BUTTON_DPAD_LEFT)}}, {Action::RIGHT, ButtonState{.button = static_cast(SDL_GAMEPAD_BUTTON_DPAD_RIGHT)}}, {Action::THRUST, ButtonState{.button = static_cast(SDL_GAMEPAD_BUTTON_WEST)}}, - {Action::SHOOT, ButtonState{.button = static_cast(SDL_GAMEPAD_BUTTON_SOUTH)}}} {} + {Action::SHOOT, ButtonState{.button = static_cast(SDL_GAMEPAD_BUTTON_SOUTH)}}, + {Action::START, ButtonState{.button = static_cast(SDL_GAMEPAD_BUTTON_START)}}, + {Action::MENU, ButtonState{.button = static_cast(SDL_GAMEPAD_BUTTON_BACK)}}} {} ~Gamepad() { if (pad != nullptr) { @@ -107,6 +109,10 @@ class Input { auto checkActionPlayer1(Action action, bool repeat = true) -> bool; auto checkActionPlayer2(Action action, bool repeat = true) -> bool; + // Accés al gamepad assignat per jugador (0=P1, 1=P2). nullptr si no n'hi + // ha cap d'assignat o connectat. Usat per la UI de redefinició de botons. + [[nodiscard]] auto getPlayerGamepad(int player_index) const -> std::shared_ptr; + // Check if any player pressed any action from a list auto checkAnyPlayerAction(const std::span& actions, bool repeat = DO_NOT_ALLOW_REPEAT) -> bool; @@ -142,6 +148,8 @@ class Input { auto removeGamepad(SDL_JoystickID id) -> std::string; void addGamepadMappingsFromFile(); void discoverGamepads(); + auto resolvePlayerGamepad(const Config::PlayerBindings& bindings, + std::size_t fallback_index) -> std::shared_ptr; // --- Variables miembro --- static Input* instance; // Instancia única del singleton diff --git a/source/core/input/input_types.cpp b/source/core/input/input_types.cpp index d4d4008..a425b59 100644 --- a/source/core/input/input_types.cpp +++ b/source/core/input/input_types.cpp @@ -6,6 +6,8 @@ const std::unordered_map ACTION_TO_STRING = { {InputAction::RIGHT, "RIGHT"}, {InputAction::THRUST, "THRUST"}, {InputAction::SHOOT, "SHOOT"}, + {InputAction::START, "START"}, + {InputAction::MENU, "MENU"}, {InputAction::WINDOW_INC_ZOOM, "WINDOW_INC_ZOOM"}, {InputAction::WINDOW_DEC_ZOOM, "WINDOW_DEC_ZOOM"}, {InputAction::TOGGLE_FULLSCREEN, "TOGGLE_FULLSCREEN"}, @@ -18,6 +20,8 @@ const std::unordered_map STRING_TO_ACTION = { {"RIGHT", InputAction::RIGHT}, {"THRUST", InputAction::THRUST}, {"SHOOT", InputAction::SHOOT}, + {"START", InputAction::START}, + {"MENU", InputAction::MENU}, {"WINDOW_INC_ZOOM", InputAction::WINDOW_INC_ZOOM}, {"WINDOW_DEC_ZOOM", InputAction::WINDOW_DEC_ZOOM}, {"TOGGLE_FULLSCREEN", InputAction::TOGGLE_FULLSCREEN}, diff --git a/source/core/input/input_types.hpp b/source/core/input/input_types.hpp index 40a7997..0f5d0db 100644 --- a/source/core/input/input_types.hpp +++ b/source/core/input/input_types.hpp @@ -15,6 +15,7 @@ enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el j THRUST, // Acelerar SHOOT, // Disparar START, // Empezar match + MENU, // Abrir/cerrar menu de servicio (equivalent a F12) // Inputs de sistema (globales) WINDOW_INC_ZOOM, // F2 diff --git a/source/core/system/global_events.cpp b/source/core/system/global_events.cpp index 6b06f25..3b38575 100644 --- a/source/core/system/global_events.cpp +++ b/source/core/system/global_events.cpp @@ -43,6 +43,37 @@ namespace GlobalEvents { return true; } + // Botó MENU al mando d'algun jugador → alterna el menú de servei + // (mateix comportament que F12 al teclat). Retorna true si l'event és + // un GAMEPAD_BUTTON_DOWN consumit. + auto handleGamepadMenuButton(const SDL_Event& event) -> bool { + if (event.type != SDL_EVENT_GAMEPAD_BUTTON_DOWN) { + return false; + } + auto* input = Input::get(); + if (input == nullptr) { + return false; + } + auto match_player = [&](int player_index) { + auto pad = input->getPlayerGamepad(player_index); + if (!pad || pad->instance_id != event.gbutton.which) { + return false; + } + auto it = pad->bindings.find(InputAction::MENU); + if (it == pad->bindings.end()) { + return false; + } + return it->second.button == static_cast(event.gbutton.button); + }; + if (!match_player(0) && !match_player(1)) { + return false; + } + if (auto* menu = System::ServiceMenu::get(); menu != nullptr) { + menu->toggle(); + } + return true; + } + } // namespace auto handle(const SDL_Event& event, SDLManager& sdl, SceneContext& context) -> bool { @@ -62,6 +93,11 @@ namespace GlobalEvents { // 3. Gestió del ratolí (auto-ocultar) Mouse::handleEvent(event); + // 3b. Botó MENU al mando (equivalent a F12) + if (handleGamepadMenuButton(event)) { + return true; + } + // 4. Service Menu (F12): consumeix tot KEY_DOWN excepte tecles de // funció (F1-F12) i ESC, que continuen sent globals (zoom, fullscreen, // vsync, AA, postfx, locale, exit prompt). Aixi el menu captura diff --git a/source/game/config_yaml.cpp b/source/game/config_yaml.cpp index 4c1d9ce..43a821a 100644 --- a/source/game/config_yaml.cpp +++ b/source/game/config_yaml.cpp @@ -373,12 +373,21 @@ namespace ConfigYaml { if (gp.contains("button_shoot")) { player1.gamepad.button_shoot = stringToButton(gp["button_shoot"].get_value()); } + if (gp.contains("button_start")) { + player1.gamepad.button_start = stringToButton(gp["button_start"].get_value()); + } + if (gp.contains("button_menu")) { + player1.gamepad.button_menu = stringToButton(gp["button_menu"].get_value()); + } } - // Carregar nom del gamepad + // Carregar nom i path del gamepad assignat if (p1.contains("gamepad_name")) { player1.gamepad_name = p1["gamepad_name"].get_value(); } + if (p1.contains("gamepad_path")) { + player1.gamepad_path = p1["gamepad_path"].get_value(); + } } // Carregar controls del player 2 desde YAML @@ -421,12 +430,21 @@ namespace ConfigYaml { if (gp.contains("button_shoot")) { player2.gamepad.button_shoot = stringToButton(gp["button_shoot"].get_value()); } + if (gp.contains("button_start")) { + player2.gamepad.button_start = stringToButton(gp["button_start"].get_value()); + } + if (gp.contains("button_menu")) { + player2.gamepad.button_menu = stringToButton(gp["button_menu"].get_value()); + } } - // Carregar nom del gamepad + // Carregar nom i path del gamepad assignat if (p2.contains("gamepad_name")) { player2.gamepad_name = p2["gamepad_name"].get_value(); } + if (p2.contains("gamepad_path")) { + player2.gamepad_path = p2["gamepad_path"].get_value(); + } } // Carregar configuración des del file YAML @@ -531,7 +549,10 @@ namespace ConfigYaml { file << " button_right: " << buttonToString(player1.gamepad.button_right) << "\n"; file << " button_thrust: " << buttonToString(player1.gamepad.button_thrust) << "\n"; file << " button_shoot: " << buttonToString(player1.gamepad.button_shoot) << "\n"; - file << " gamepad_name: \"" << player1.gamepad_name << "\" # Buit = primer disponible\n\n"; + file << " button_start: " << buttonToString(player1.gamepad.button_start) << "\n"; + file << " button_menu: " << buttonToString(player1.gamepad.button_menu) << "\n"; + file << " gamepad_name: \"" << player1.gamepad_name << "\" # Buit = primer disponible\n"; + file << " gamepad_path: \"" << player1.gamepad_path << "\" # Prioritari sobre name\n\n"; } // Guardar controls del player 2 a YAML @@ -548,7 +569,10 @@ namespace ConfigYaml { file << " button_right: " << buttonToString(player2.gamepad.button_right) << "\n"; file << " button_thrust: " << buttonToString(player2.gamepad.button_thrust) << "\n"; file << " button_shoot: " << buttonToString(player2.gamepad.button_shoot) << "\n"; - file << " gamepad_name: \"" << player2.gamepad_name << "\" # Buit = segon disponible\n\n"; + file << " button_start: " << buttonToString(player2.gamepad.button_start) << "\n"; + file << " button_menu: " << buttonToString(player2.gamepad.button_menu) << "\n"; + file << " gamepad_name: \"" << player2.gamepad_name << "\" # Buit = segon disponible\n"; + file << " gamepad_path: \"" << player2.gamepad_path << "\" # Prioritari sobre name\n\n"; } // Guardar configuración al file YAML diff --git a/source/game/config_yaml.hpp b/source/game/config_yaml.hpp index 79ae832..27e00b4 100644 --- a/source/game/config_yaml.hpp +++ b/source/game/config_yaml.hpp @@ -28,6 +28,7 @@ namespace ConfigYaml { .key_start = SDL_SCANCODE_2, }, .gamepad_name = "", + .gamepad_path = "", }, };