diff --git a/CMakeLists.txt b/CMakeLists.txt index c1c81ff..d6d7ae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ set(APP_SOURCES source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp # Core - Input (nova capa) + source/core/input/gamepad.cpp source/core/input/global_inputs.cpp source/core/input/mouse.cpp diff --git a/source/core/input/gamepad.cpp b/source/core/input/gamepad.cpp new file mode 100644 index 0000000..4c611e9 --- /dev/null +++ b/source/core/input/gamepad.cpp @@ -0,0 +1,167 @@ +#include "core/input/gamepad.hpp" + +#include + +#include "core/jail/jinput.hpp" +#include "core/rendering/menu.hpp" +#include "game/options.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_a_ = false; + static bool prev_b_ = false; + static bool prev_start_ = false; + static bool prev_back_ = false; + + 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; + } + 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; + SDL_Log("Gamepad connectat: %s", SDL_GetGamepadName(pad_)); + } + } + } else if (event.type == SDL_EVENT_GAMEPAD_REMOVED) { + if (pad_ && event.gdevice.which == pad_id_) { + SDL_Log("Gamepad desconnectat"); + SDL_CloseGamepad(pad_); + pad_ = nullptr; + pad_id_ = 0; + // Neteja qualsevol tecla virtual que poguera estar premuda + JI_SetVirtualKey(SDL_SCANCODE_UP, false); + JI_SetVirtualKey(SDL_SCANCODE_DOWN, false); + JI_SetVirtualKey(SDL_SCANCODE_LEFT, false); + JI_SetVirtualKey(SDL_SCANCODE_RIGHT, false); + } + } + } + + // 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 + bool a = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_SOUTH); // A/Cross + bool b = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_EAST); // B/Circle + bool start = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_START); + bool back = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_BACK); + + // Start → obre/tanca menú (flanc) + if (start && !prev_start_) pushKey(Options::keys_gui.menu_toggle); + // Back → ESC (flanc) + if (back && !prev_back_) pushKey(SDL_SCANCODE_ESCAPE); + + 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); + if (a && !prev_a_) pushKey(SDL_SCANCODE_RETURN); + if (b && !prev_b_) pushKey(SDL_SCANCODE_BACKSPACE); + + // Assegura que el joc no rep tecles de moviment mentre el menú està obert + JI_SetVirtualKey(SDL_SCANCODE_UP, false); + JI_SetVirtualKey(SDL_SCANCODE_DOWN, false); + JI_SetVirtualKey(SDL_SCANCODE_LEFT, false); + JI_SetVirtualKey(SDL_SCANCODE_RIGHT, false); + } else { + // Moviment al joc — level-triggered (polling) + JI_SetVirtualKey(SDL_SCANCODE_UP, up); + JI_SetVirtualKey(SDL_SCANCODE_DOWN, dn); + JI_SetVirtualKey(SDL_SCANCODE_LEFT, lt); + JI_SetVirtualKey(SDL_SCANCODE_RIGHT, rt); + // Botó A al joc: emet Enter per avançar seqüències (JI_AnyKey) + if (a && !prev_a_) pushKey(SDL_SCANCODE_RETURN); + } + + prev_up_ = up; + prev_down_ = dn; + prev_left_ = lt; + prev_right_ = rt; + prev_a_ = a; + prev_b_ = b; + prev_start_ = start; + prev_back_ = back; + } + +} // namespace Gamepad diff --git a/source/core/input/gamepad.hpp b/source/core/input/gamepad.hpp new file mode 100644 index 0000000..92ccfd4 --- /dev/null +++ b/source/core/input/gamepad.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +// Suport de gamepad: mapeja D-pad/stick a les tecles de fletxa virtuals del joc +// i emet events SDL sintètics per a navegació del menú (F12, ESC, Enter, etc.) +namespace Gamepad { + void init(); + void destroy(); + + // Hot-plug: processa events SDL_EVENT_GAMEPAD_ADDED/REMOVED + void handleEvent(const SDL_Event& event); + + // Poll d'estat cada frame — cridat des del Director + void update(); + + [[nodiscard]] auto isConnected() -> bool; +} // namespace Gamepad diff --git a/source/core/jail/jinput.cpp b/source/core/jail/jinput.cpp index 3ba9751..6df9076 100644 --- a/source/core/jail/jinput.cpp +++ b/source/core/jail/jinput.cpp @@ -20,6 +20,14 @@ void JI_SetInputBlocked(bool blocked) { input_blocked = blocked; } +static Uint8 virtual_keystates[SDL_SCANCODE_COUNT] = {0}; + +void JI_SetVirtualKey(int scancode, bool pressed) { + if (scancode >= 0 && scancode < SDL_SCANCODE_COUNT) { + virtual_keystates[scancode] = pressed ? 1 : 0; + } +} + void JI_moveCheats(Uint8 new_key) { cheat[0] = cheat[1]; cheat[1] = cheat[2]; @@ -47,7 +55,8 @@ bool JI_KeyPressed(int key) { if (input_blocked) return false; // ESC bloquejada pel Director (primera pulsació mostra notificació) if (key == SDL_SCANCODE_ESCAPE && Director::get()->isEscBlocked()) return false; - return keystates[key] != 0; + if (key < 0 || key >= SDL_SCANCODE_COUNT) return false; + return keystates[key] != 0 || virtual_keystates[key] != 0; } bool JI_CheatActivated(const char* cheat_code) { diff --git a/source/core/jail/jinput.hpp b/source/core/jail/jinput.hpp index 0e66493..0d2a36f 100644 --- a/source/core/jail/jinput.hpp +++ b/source/core/jail/jinput.hpp @@ -6,6 +6,10 @@ void JI_DisableKeyboard(Uint32 time); // Bloqueja tot l'input cap al joc (JI_KeyPressed retorna false per a tot) void JI_SetInputBlocked(bool blocked); +// Estableix l'estat d'una tecla virtual (p.ex. des del gamepad). +// JI_KeyPressed retorna true si el teclat real O la virtual estan premudes. +void JI_SetVirtualKey(int scancode, bool pressed); + void JI_Update(); bool JI_KeyPressed(int key); diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index d9ff9c4..cfaf975 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -3,6 +3,7 @@ #include #include +#include "core/input/gamepad.hpp" #include "core/input/global_inputs.hpp" #include "core/input/mouse.hpp" #include "core/jail/jgame.hpp" @@ -22,9 +23,11 @@ Director* Director::instance_ = nullptr; void Director::init() { instance_ = new Director(); + Gamepad::init(); } void Director::destroy() { + Gamepad::destroy(); delete instance_; instance_ = nullptr; } @@ -50,6 +53,7 @@ void Director::run() { Uint32 frame_start = SDL_GetTicks(); handleEvents(); + Gamepad::update(); GlobalInputs::handle(); Mouse::updateCursorVisibility(); @@ -108,6 +112,11 @@ void Director::handleEvents() { JG_QuitSignal(); requestQuit(); } + // Hot-plug de gamepad + if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED) { + Gamepad::handleEvent(event); + continue; + } // Menú: F12 (o tecla configurada) obre/tanca el menú flotant if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && event.key.scancode == Options::keys_gui.menu_toggle) {