Files
aee/source/core/input/gamepad.cpp
2026-04-16 18:46:58 +02:00

202 lines
7.7 KiB
C++

#include "core/input/gamepad.hpp"
#include <cstdio>
#include <string>
#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