redefinir tecles

This commit is contained in:
2026-04-05 00:41:04 +02:00
parent a328681365
commit f8b60cb641
11 changed files with 167 additions and 24 deletions

View File

@@ -31,6 +31,7 @@ set(APP_SOURCES
# Core - Input (nova capa)
source/core/input/gamepad.cpp
source/core/input/global_inputs.cpp
source/core/input/key_remap.cpp
source/core/input/mouse.cpp
# Core - System (nova capa)

View File

@@ -74,10 +74,10 @@ namespace Gamepad {
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);
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);
}
}
}
@@ -140,16 +140,16 @@ namespace Gamepad {
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);
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, up);
JI_SetVirtualKey(SDL_SCANCODE_DOWN, dn);
JI_SetVirtualKey(SDL_SCANCODE_LEFT, lt);
JI_SetVirtualKey(SDL_SCANCODE_RIGHT, rt);
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);
// Botó A al joc: emet Enter per avançar seqüències (JI_AnyKey)
if (a && !prev_a_) pushKey(SDL_SCANCODE_RETURN);
}

View File

@@ -0,0 +1,27 @@
#include "core/input/key_remap.hpp"
#include <SDL3/SDL.h>
#include "core/jail/jinput.hpp"
#include "game/options.hpp"
namespace KeyRemap {
static void mirror(SDL_Scancode custom, SDL_Scancode standard, const bool* ks) {
if (custom == standard || custom == SDL_SCANCODE_UNKNOWN) {
JI_SetVirtualKey(standard, JI_VSRC_REMAP, false);
return;
}
JI_SetVirtualKey(standard, JI_VSRC_REMAP, ks[custom]);
}
void update() {
const bool* ks = SDL_GetKeyboardState(nullptr);
if (!ks) return;
mirror(Options::keys_game.up, SDL_SCANCODE_UP, ks);
mirror(Options::keys_game.down, SDL_SCANCODE_DOWN, ks);
mirror(Options::keys_game.left, SDL_SCANCODE_LEFT, ks);
mirror(Options::keys_game.right, SDL_SCANCODE_RIGHT, ks);
}
} // namespace KeyRemap

View File

@@ -0,0 +1,8 @@
#pragma once
// Remapeja tecles del joc: llig les tecles personalitzades de l'usuari
// (Options::keys_game) i les reflecteix a les tecles estàndard virtuals
// que el joc polla (SDL_SCANCODE_UP/DOWN/LEFT/RIGHT).
namespace KeyRemap {
void update(); // cridat cada frame des del Director
}

View File

@@ -20,12 +20,12 @@ void JI_SetInputBlocked(bool blocked) {
input_blocked = blocked;
}
static Uint8 virtual_keystates[SDL_SCANCODE_COUNT] = {0};
static Uint8 virtual_keystates[JI_VSRC_COUNT][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_SetVirtualKey(int scancode, int source, bool pressed) {
if (scancode < 0 || scancode >= SDL_SCANCODE_COUNT) return;
if (source < 0 || source >= JI_VSRC_COUNT) return;
virtual_keystates[source][scancode] = pressed ? 1 : 0;
}
void JI_moveCheats(Uint8 new_key) {
@@ -56,7 +56,11 @@ bool JI_KeyPressed(int key) {
// ESC bloquejada pel Director (primera pulsació mostra notificació)
if (key == SDL_SCANCODE_ESCAPE && Director::get()->isEscBlocked()) return false;
if (key < 0 || key >= SDL_SCANCODE_COUNT) return false;
return keystates[key] != 0 || virtual_keystates[key] != 0;
if (keystates[key] != 0) return true;
for (int src = 0; src < JI_VSRC_COUNT; src++) {
if (virtual_keystates[src][key] != 0) return true;
}
return false;
}
bool JI_CheatActivated(const char* cheat_code) {

View File

@@ -6,9 +6,15 @@ 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);
// Estableix l'estat d'una tecla virtual. Múltiples fonts (gamepad, remap)
// s'agrupen per OR. JI_KeyPressed retorna true si el teclat real O qualsevol
// font virtual està premuda.
enum JI_VirtualSource {
JI_VSRC_GAMEPAD = 0,
JI_VSRC_REMAP = 1,
JI_VSRC_COUNT
};
void JI_SetVirtualKey(int scancode, int source, bool pressed);
void JI_Update();

View File

@@ -41,7 +41,8 @@ namespace Menu {
enum class ItemKind { Toggle,
Cycle,
IntRange,
Submenu };
Submenu,
KeyBind };
struct Item {
const char* label;
@@ -49,6 +50,7 @@ namespace Menu {
std::function<std::string()> getValue; // opcional
std::function<void(int dir)> change; // per Toggle/Cycle/IntRange
std::function<void()> enter; // per Submenu
SDL_Scancode* scancode{nullptr}; // per KeyBind
};
struct Page {
@@ -62,6 +64,7 @@ namespace Menu {
static std::unique_ptr<Text> font_;
static float open_anim_{0.0F}; // 0 = tancat, 1 = obert
static Uint32 last_ticks_{0};
static SDL_Scancode* capturing_{nullptr}; // != null → esperant tecla per assignar
// --- Helpers ---
@@ -72,11 +75,13 @@ namespace Menu {
static Page buildVideo();
static Page buildAudio();
static Page buildControls();
static Page buildRoot() {
Page p{"OPCIONS", {}, 0};
p.items.push_back({"VIDEO", ItemKind::Submenu, nullptr, nullptr, [] { stack_.push_back(buildVideo()); }});
p.items.push_back({"AUDIO", ItemKind::Submenu, nullptr, nullptr, [] { stack_.push_back(buildAudio()); }});
p.items.push_back({"VIDEO", ItemKind::Submenu, nullptr, nullptr, [] { stack_.push_back(buildVideo()); }, nullptr});
p.items.push_back({"AUDIO", ItemKind::Submenu, nullptr, nullptr, [] { stack_.push_back(buildAudio()); }, nullptr});
p.items.push_back({"CONTROLS", ItemKind::Submenu, nullptr, nullptr, [] { stack_.push_back(buildControls()); }, nullptr});
return p;
}
@@ -137,6 +142,16 @@ namespace Menu {
Options::applyAudio();
}
static Page buildControls() {
Page p{"CONTROLS", {}, 0};
p.items.push_back({"MOU AMUNT", ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.up});
p.items.push_back({"MOU AVALL", ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.down});
p.items.push_back({"MOU ESQUERRA", ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.left});
p.items.push_back({"MOU DRETA", ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.right});
p.items.push_back({"TECLA MENU", ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_gui.menu_toggle});
return p;
}
static Page buildAudio() {
Page p{"AUDIO", {}, 0};
@@ -242,6 +257,22 @@ namespace Menu {
void close() {
stack_.clear();
open_anim_ = 0.0F;
capturing_ = nullptr;
}
auto isCapturing() -> bool {
return capturing_ != nullptr;
}
void captureKey(SDL_Scancode sc) {
if (!capturing_) return;
if (sc == SDL_SCANCODE_ESCAPE) {
// Cancel·la
capturing_ = nullptr;
return;
}
*capturing_ = sc;
capturing_ = nullptr;
}
void handleKey(SDL_Scancode sc) {
@@ -279,6 +310,8 @@ namespace Menu {
case SDL_SCANCODE_KP_ENTER:
if (page.items[page.cursor].kind == ItemKind::Submenu) {
if (page.items[page.cursor].enter) page.items[page.cursor].enter();
} else if (page.items[page.cursor].kind == ItemKind::KeyBind) {
capturing_ = page.items[page.cursor].scancode;
} else if (page.items[page.cursor].change) {
page.items[page.cursor].change(+1);
}
@@ -363,6 +396,13 @@ namespace Menu {
int aw = font_->width(arrow);
Uint32 ac = selected ? CURSOR_COLOR : VALUE_COLOR;
font_->draw(pixel_data, box_x + BOX_W - ITEM_PAD_X - aw, y, arrow, ac);
} else if (item.kind == ItemKind::KeyBind) {
bool this_capturing = (capturing_ == item.scancode);
const char* text = this_capturing ? "<PREM TECLA>" : (item.scancode ? SDL_GetScancodeName(*item.scancode) : "");
if (!text || !*text) text = "---";
int tw = font_->width(text);
Uint32 tc = this_capturing ? 0xFF00FFFF : (selected ? CURSOR_COLOR : VALUE_COLOR);
font_->draw(pixel_data, box_x + BOX_W - ITEM_PAD_X - tw, y, text, tc);
} else if (item.getValue) {
std::string value = item.getValue();
int value_w = font_->width(value.c_str());

View File

@@ -15,4 +15,8 @@ namespace Menu {
// Gestió d'input — cridat des del Director en KEY_DOWN
void handleKey(SDL_Scancode sc);
// Mode de captura de tecla (per al menú de remapeig)
[[nodiscard]] auto isCapturing() -> bool;
void captureKey(SDL_Scancode sc); // assigna la tecla capturada (ESC cancel·la)
} // namespace Menu

View File

@@ -5,6 +5,7 @@
#include "core/input/gamepad.hpp"
#include "core/input/global_inputs.hpp"
#include "core/input/key_remap.hpp"
#include "core/input/mouse.hpp"
#include "core/jail/jgame.hpp"
#include "core/jail/jinput.hpp"
@@ -54,6 +55,7 @@ void Director::run() {
handleEvents();
Gamepad::update();
KeyRemap::update();
GlobalInputs::handle();
Mouse::updateCursorVisibility();
@@ -117,11 +119,24 @@ void Director::handleEvents() {
Gamepad::handleEvent(event);
continue;
}
// Empassar-se el KEY_UP de qualsevol tecla que el menú va consumir en KEY_DOWN
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode >= 0 &&
event.key.scancode < SDL_SCANCODE_COUNT && menu_keys_held_[event.key.scancode]) {
menu_keys_held_[event.key.scancode] = false;
continue;
}
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot
if (Menu::isCapturing() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
Menu::captureKey(event.key.scancode);
menu_keys_held_[event.key.scancode] = true;
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) {
Menu::toggle();
JI_SetInputBlocked(Menu::isOpen());
menu_keys_held_[event.key.scancode] = true;
continue;
}
// Si el menú està obert, consumeix tot l'input de teclat
@@ -133,7 +148,12 @@ void Director::handleEvents() {
esc_swallow_until_release_ = true;
} else {
Menu::handleKey(event.key.scancode);
// El menú pot haver-se tancat (p.ex. Backspace al nivell arrel)
if (!Menu::isOpen()) {
JI_SetInputBlocked(false);
}
}
menu_keys_held_[event.key.scancode] = true;
continue;
}
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {

View File

@@ -59,4 +59,7 @@ class Director {
// Quan el menú tanca amb ESC, empassem-nos l'ESC fins que l'usuari la deixe anar,
// per no fer eixir el joc al proper poll de JI_KeyPressed.
std::atomic<bool> esc_swallow_until_release_{false};
// Tecles consumides pel menú (KEY_DOWN): el KEY_UP associat cal empassar-lo
// per evitar que el joc (JI_AnyKey / JI_moveCheats) les veja quan el menú tanca.
bool menu_keys_held_[SDL_SCANCODE_COUNT]{};
};

View File

@@ -110,6 +110,25 @@ namespace Options {
window.fullscreen = node["fullscreen"].get_value<bool>();
}
// Helper: carrega una SDL_Scancode des d'un string (nom SDL de la tecla).
static void loadScancodeField(const fkyaml::node& node, const std::string& key, SDL_Scancode& target) {
if (!node.contains(key)) return;
auto name = node[key].get_value<std::string>();
SDL_Scancode sc = SDL_GetScancodeFromName(name.c_str());
if (sc != SDL_SCANCODE_UNKNOWN) target = sc;
}
static void loadControlsFromYaml(const fkyaml::node& yaml) {
if (yaml.contains("controls")) {
const auto& node = yaml["controls"];
loadScancodeField(node, "up", keys_game.up);
loadScancodeField(node, "down", keys_game.down);
loadScancodeField(node, "left", keys_game.left);
loadScancodeField(node, "right", keys_game.right);
loadScancodeField(node, "menu_toggle", keys_gui.menu_toggle);
}
}
static void loadGameConfigFromYaml(const fkyaml::node& yaml) {
if (!yaml.contains("game")) return;
const auto& node = yaml["game"];
@@ -158,6 +177,7 @@ namespace Options {
loadWindowConfigFromYaml(yaml);
loadAudioConfigFromYaml(yaml);
loadGameConfigFromYaml(yaml);
loadControlsFromYaml(yaml);
std::cout << "Config file loaded successfully\n\n";
return true;
@@ -247,6 +267,16 @@ namespace Options {
file << " habitacio_inicial: " << game.habitacio_inicial << "\n";
file << " piramide_inicial: " << game.piramide_inicial << "\n";
file << " vides: " << game.vides << "\n";
file << "\n";
// CONTROLS
file << "# CONTROLS (noms SDL: \"Up\", \"Down\", \"W\", \"Space\", \"F12\", etc.)\n";
file << "controls:\n";
file << " up: \"" << SDL_GetScancodeName(keys_game.up) << "\"\n";
file << " down: \"" << SDL_GetScancodeName(keys_game.down) << "\"\n";
file << " left: \"" << SDL_GetScancodeName(keys_game.left) << "\"\n";
file << " right: \"" << SDL_GetScancodeName(keys_game.right) << "\"\n";
file << " menu_toggle: \"" << SDL_GetScancodeName(keys_gui.menu_toggle) << "\"\n";
file.close();