#include "core/input/gamepad.hpp" #include #include #include "core/input/key_config.hpp" #include "core/jail/jinput.hpp" #include "core/locale/locale.hpp" #include "core/rendering/menu.hpp" #include "core/rendering/overlay.hpp" namespace Gamepad { static SDL_Gamepad* pad_ = nullptr; static SDL_JoystickID pad_id_ = 0; // Dead-zone del stick esquerre (rang Sint16: -32768..32767) static constexpr Sint16 STICK_DEADZONE = 12000; // Estat previ per a detecció de flanc (edge-triggered) static bool prev_up_ = false; static bool prev_down_ = false; static bool prev_left_ = false; static bool prev_right_ = false; static bool prev_south_ = false; static bool prev_east_ = false; static bool prev_west_ = false; static bool prev_north_ = false; static bool prev_start_ = false; static bool prev_back_ = false; static void notify(const char* name, const char* status_key) { std::string msg = (name && *name) ? name : "Gamepad"; msg += ' '; msg += Locale::get(status_key); Overlay::showNotification(msg.c_str(), 2.5F); } static void notifyConnected(const char* name) { notify(name, "notifications.gamepad_connected"); } static void notifyDisconnected(const char* name) { notify(name, "notifications.gamepad_disconnected"); } static void openFirstGamepad() { int count = 0; SDL_JoystickID* ids = SDL_GetGamepads(&count); if (ids && count > 0) { pad_ = SDL_OpenGamepad(ids[0]); if (pad_) { pad_id_ = ids[0]; SDL_Log("Gamepad connectat: %s", SDL_GetGamepadName(pad_)); } } if (ids) SDL_free(ids); } void init() { if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) { SDL_Log("No s'ha pogut inicialitzar SDL_INIT_GAMEPAD: %s", SDL_GetError()); return; } int added = SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt"); if (added < 0) { SDL_Log("No s'ha pogut carregar gamecontrollerdb.txt: %s", SDL_GetError()); } else { SDL_Log("Carregats %d mappings de gamepad", added); } openFirstGamepad(); } void destroy() { if (pad_) { SDL_CloseGamepad(pad_); pad_ = nullptr; pad_id_ = 0; } SDL_QuitSubSystem(SDL_INIT_GAMEPAD); } auto isConnected() -> bool { return pad_ != nullptr; } void handleEvent(const SDL_Event& event) { if (event.type == SDL_EVENT_GAMEPAD_ADDED) { if (!pad_) { pad_ = SDL_OpenGamepad(event.gdevice.which); if (pad_) { pad_id_ = event.gdevice.which; const char* name = SDL_GetGamepadName(pad_); SDL_Log("Gamepad connectat: %s", name ? name : ""); notifyConnected(name); } } } else if (event.type == SDL_EVENT_GAMEPAD_REMOVED) { if (pad_ && event.gdevice.which == pad_id_) { const char* name = SDL_GetGamepadName(pad_); std::string saved_name = name ? name : ""; SDL_Log("Gamepad desconnectat: %s", saved_name.c_str()); SDL_CloseGamepad(pad_); pad_ = nullptr; pad_id_ = 0; // Neteja qualsevol tecla virtual que poguera estar premuda JI_SetVirtualKey(SDL_SCANCODE_UP, JI_VSRC_GAMEPAD, false); JI_SetVirtualKey(SDL_SCANCODE_DOWN, JI_VSRC_GAMEPAD, false); JI_SetVirtualKey(SDL_SCANCODE_LEFT, JI_VSRC_GAMEPAD, false); JI_SetVirtualKey(SDL_SCANCODE_RIGHT, JI_VSRC_GAMEPAD, false); notifyDisconnected(saved_name.c_str()); } } } // Emet un parell de KEY_DOWN + KEY_UP sintètics que passaran pel handleEvents // del Director (menú, ESC, F12, etc.) static void pushKey(SDL_Scancode sc) { SDL_Event e; SDL_zero(e); e.type = SDL_EVENT_KEY_DOWN; e.key.scancode = sc; e.key.repeat = false; e.key.down = true; SDL_PushEvent(&e); e.type = SDL_EVENT_KEY_UP; e.key.down = false; SDL_PushEvent(&e); } void update() { if (!pad_) return; // D-pad bool dup = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_DPAD_UP); bool ddn = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_DPAD_DOWN); bool dlt = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_DPAD_LEFT); bool drt = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); // Stick esquerre amb dead-zone Sint16 lx = SDL_GetGamepadAxis(pad_, SDL_GAMEPAD_AXIS_LEFTX); Sint16 ly = SDL_GetGamepadAxis(pad_, SDL_GAMEPAD_AXIS_LEFTY); bool sup = ly < -STICK_DEADZONE; bool sdn = ly > STICK_DEADZONE; bool slt = lx < -STICK_DEADZONE; bool srt = lx > STICK_DEADZONE; bool up = dup || sup; bool dn = ddn || sdn; bool lt = dlt || slt; bool rt = drt || srt; // Botons frontals (layout SDL: SOUTH=A/Cross, EAST=B/Circle, WEST=X/Square, NORTH=Y/Triangle) bool south = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_SOUTH); bool east = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_EAST); bool west = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_WEST); bool north = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_NORTH); bool start = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_START); bool back = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_BACK); // Select (Back) → obre/tanca menú de servei (flanc) if (back && !prev_back_) pushKey(KeyConfig::scancode("menu_toggle")); // Start → pausa (flanc) if (start && !prev_start_) pushKey(KeyConfig::scancode("pause_toggle")); if (Menu::isOpen()) { // Navegació del menú per flanc if (up && !prev_up_) pushKey(SDL_SCANCODE_UP); if (dn && !prev_down_) pushKey(SDL_SCANCODE_DOWN); if (lt && !prev_left_) pushKey(SDL_SCANCODE_LEFT); if (rt && !prev_right_) pushKey(SDL_SCANCODE_RIGHT); // EAST accepta, SOUTH cancela / endarrere if (east && !prev_east_) pushKey(SDL_SCANCODE_RETURN); if (south && !prev_south_) pushKey(SDL_SCANCODE_BACKSPACE); // Assegura que el joc no rep tecles de moviment mentre el menú està obert JI_SetVirtualKey(SDL_SCANCODE_UP, JI_VSRC_GAMEPAD, false); JI_SetVirtualKey(SDL_SCANCODE_DOWN, JI_VSRC_GAMEPAD, false); JI_SetVirtualKey(SDL_SCANCODE_LEFT, JI_VSRC_GAMEPAD, false); JI_SetVirtualKey(SDL_SCANCODE_RIGHT, JI_VSRC_GAMEPAD, false); } else { // Moviment al joc — level-triggered (polling) JI_SetVirtualKey(SDL_SCANCODE_UP, JI_VSRC_GAMEPAD, up); JI_SetVirtualKey(SDL_SCANCODE_DOWN, JI_VSRC_GAMEPAD, dn); JI_SetVirtualKey(SDL_SCANCODE_LEFT, JI_VSRC_GAMEPAD, lt); JI_SetVirtualKey(SDL_SCANCODE_RIGHT, JI_VSRC_GAMEPAD, rt); // Qualsevol dels 4 botons frontals avança escenes (JI_AnyKey via Enter sintètic) if ((south && !prev_south_) || (east && !prev_east_) || (west && !prev_west_) || (north && !prev_north_)) { pushKey(SDL_SCANCODE_RETURN); } } prev_up_ = up; prev_down_ = dn; prev_left_ = lt; prev_right_ = rt; prev_south_ = south; prev_east_ = east; prev_west_ = west; prev_north_ = north; prev_start_ = start; prev_back_ = back; } } // namespace Gamepad