varies coses i detallets

This commit is contained in:
2026-04-16 18:46:58 +02:00
parent fe41919e1e
commit 6394e9afab
17 changed files with 513 additions and 156 deletions

61
.gitignore vendored
View File

@@ -1,8 +1,57 @@
aee
aee.exe
.DS_Store
trick.ini
.vscode/
data.jrf
# --- Build outputs ---
build/
dist/
aee
aee.exe
*.o
*.obj
*.exe
*.app
# --- Generated assets ---
resource.pack
data.jrf
# --- Runtime / debug junk ---
trick.ini
*.log
*.dmp
# --- Editor / IDE ---
.vscode/
.idea/
*.swp
*.swo
*~
.cache/
compile_commands.json
# --- macOS ---
.DS_Store
.AppleDouble
.LSOverride
._*
.Spotlight-V100
.Trashes
.fseventsd
.DocumentRevisions-V100
.TemporaryItems
.VolumeIcon.icns
Icon?
# --- Windows ---
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
Desktop.ini
desktop.ini
$RECYCLE.BIN/
*.lnk
# --- Linux ---
*~
.fuse_hidden*
.directory
.Trash-*
.nfs*

View File

@@ -113,9 +113,10 @@ Flat C-style APIs (no classes), prefixed by subsystem. Being progressively conve
### Input Layer (`source/core/input/`)
- **GlobalInputs** (`global_inputs.hpp/cpp`) — Maps configurable function keys to presentation actions. Uses debounce. Returns whether a key was consumed (to suppress from game layer)
- **KeyConfig** (`key_config.hpp/cpp`) — **Font única de veritat per a les tecles d'UI/sistema**. Carrega `data/input/keys.yaml` al boot (12 entrades: F1-F10 GlobalInputs + F11 pausa + F12 menú de servei) i opcionalment aplica overrides des de `~/.config/jailgames/aee/keys.yaml`. Exposa `KeyConfig::scancode("id")`, `scancodePtr("id")` (per a Menu KeyBind), `setScancode(...)`, `isGuiKey(sc)` (filtre del Director per a no propagar tecles d'UI a `JI_AnyKey`). `saveOverrides()` només persistix les entrades que difereixen del default. Les tecles de moviment del jugador NO viuen ací — es queden a `Options::keys_game`
- **GlobalInputs** (`global_inputs.hpp/cpp`) — Maps function keys (via `KeyConfig::scancode("dec_zoom")`, etc.) to presentation actions. Uses debounce. Returns whether a key was consumed (to suppress from game layer)
- **Mouse** (`mouse.hpp/cpp`) — Auto-hides cursor after 3 seconds of inactivity
- **Gamepad** (`gamepad.hpp/cpp`) — First-gamepad support with hot-plug. Poll-based each frame: D-pad/left stick (deadzone 12000) → virtual arrow keys for game movement; A/B buttons, Start, Back translate to synthetic SDL key events (F12/ESC/Enter/Backspace) when menu is open, so Director handles them exactly like keyboard. Loads extra mappings from `gamecontrollerdb.txt` (next to the executable) at init via `SDL_AddGamepadMappingsFromFile`, extending SDL's built-in controller database
- **Gamepad** (`gamepad.hpp/cpp`) — First-gamepad support with hot-plug + overlay notification with controller name. Poll-based each frame: D-pad/left stick (deadzone 12000) → virtual arrow keys for game movement. Mapeig: SOUTH/EAST/WEST/NORTH (4 botons frontals) → Enter sintètic per avançar escenes; al menú EAST=accept, SOUTH=cancel/back. SELECT → menu_toggle (servei), START → pause_toggle (via `KeyConfig::scancode(...)`). Loads extra mappings from `gamecontrollerdb.txt` at init via `SDL_AddGamepadMappingsFromFile`
- **KeyRemap** (`key_remap.hpp/cpp`) — Each frame, reads `Options::keys_game.*` and mirrors physical keyboard state to virtual standard scancodes (`SDL_SCANCODE_UP`/DOWN/LEFT/RIGHT). Allows full movement key remapping without touching hardcoded game code in `prota.cpp`/`mapa.cpp`
### Locale Layer (`source/core/locale/`)
@@ -127,8 +128,8 @@ Flat C-style APIs (no classes), prefixed by subsystem. Being progressively conve
Follows the pattern from `jaildoctors_dilemma`, persists to YAML:
- **defines.hpp** — Game constants: `Texts::WINDOW_TITLE`, `Texts::VERSION`, `GameScreen::WIDTH/HEIGHT`
- **defaults.hpp** — Default values: `Defaults::KeysGUI`, `Defaults::KeysGame`, `Defaults::Video`, `Defaults::Audio`, `Defaults::Window`, `Defaults::Game`
- **options.hpp/cpp** — `Options` namespace with inline globals and YAML load/save. Structs: `KeysGUI`, `KeysGame`, `Video`, `RenderInfo`, `Audio`, `Window`, `Game`, `PostFXPreset`, `CrtPiPreset`
- **defaults.hpp** — Default values: `Defaults::KeysGame`, `Defaults::Video`, `Defaults::Audio`, `Defaults::Window`, `Defaults::Game`. (Les tecles d'UI viuen a `data/input/keys.yaml` via `KeyConfig`)
- **options.hpp/cpp** — `Options` namespace with inline globals and YAML load/save. Structs: `KeysGame`, `Video`, `RenderInfo`, `Audio`, `Window`, `Game`, `PostFXPreset`, `CrtPiPreset`
### Utilities (`source/utils/`)
@@ -155,7 +156,7 @@ Follows the pattern from `jaildoctors_dilemma`, persists to YAML:
| Backspace | Go up one menu level / close menu if at root |
| ↑↓←→ / Enter | Menu navigation |
All key bindings are configurable via `Options::keys_gui` and stored in `config.yaml` (section `controls:` with SDL scancode names). Game movement keys (`Options::keys_game.up/down/left/right`) can be remapped via the CONTROLS submenu — the `KeyRemap` module mirrors custom physical keys to virtual standard scancodes so hardcoded game code keeps working.
UI/system key bindings are loaded from [data/input/keys.yaml](data/input/keys.yaml) via `KeyConfig`. Overrides fets des del menú es persistixen a `~/.config/jailgames/aee/keys.yaml` (només les que difereixen del default). Game movement keys (`Options::keys_game.up/down/left/right`) viuen separadament a `config.yaml` (secció `controls:`) i es remapejen via la CONTROLS submenu — el `KeyRemap` module mirrors custom physical keys to virtual standard scancodes so hardcoded game code keeps working.
### Execution Model (Single-threaded, Scene-based)
@@ -224,7 +225,8 @@ JD8_Flip produces ABGR byte order: `0xFF000000 + R + (G<<8) + (B<<16)`. SDL text
| File | Content |
|------|---------|
| `~/.config/jailgames/aee/config.yaml` | Main config: video (incl. `vsync`, `integer_scale`), audio (incl. `enabled` master + `music_*` + `sound_*`), window, render_info (incl. `show_time`), game, shader selection, controls (movement keys + menu_toggle + pause_toggle) |
| `~/.config/jailgames/aee/config.yaml` | Main config: video (incl. `vsync`, `integer_scale`), audio (incl. `enabled` master + `music_*` + `sound_*`), window, render_info (incl. `show_time`), game, shader selection, controls (només moviment del jugador) |
| `~/.config/jailgames/aee/keys.yaml` | UI key overrides (només entrades que difereixen del default de [data/input/keys.yaml](data/input/keys.yaml)). Generat per `KeyConfig::saveOverrides()` |
| `~/.config/jailgames/aee/postfx.yaml` | PostFX shader presets (6 defaults: CRT, NTSC, CURVED, SCANLINES, SUBTLE, CRT LIVE) |
| `~/.config/jailgames/aee/crtpi.yaml` | CRT-Pi shader presets (4 defaults: DEFAULT, CURVED, SHARP, MINIMAL) |

View File

@@ -38,6 +38,7 @@ set(APP_SOURCES
# Core - Input (nova capa)
source/core/input/gamepad.cpp
source/core/input/global_inputs.cpp
source/core/input/key_config.cpp
source/core/input/key_remap.cpp
source/core/input/mouse.cpp
@@ -241,6 +242,27 @@ if(NOT EMSCRIPTEN)
)
target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source")
target_compile_options(pack_resources PRIVATE -Wall)
# --- Regeneració automàtica de resource.pack ---
# Cada `cmake --build build` torna a empaquetar `data/` si algun fitxer ha
# canviat. Evita debugar amb un pack obsolet. CONFIGURE_DEPENDS força CMake
# a re-globbar a la pròxima invocació (recull fitxers nous afegits a data/).
file(GLOB_RECURSE DATA_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/data/*")
set(RESOURCE_PACK "${CMAKE_SOURCE_DIR}/resource.pack")
add_custom_command(
OUTPUT ${RESOURCE_PACK}
COMMAND $<TARGET_FILE:pack_resources>
"${CMAKE_SOURCE_DIR}/data"
"${RESOURCE_PACK}"
DEPENDS pack_resources ${DATA_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Empaquetant data/ → resource.pack"
VERBATIM
)
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
add_dependencies(${PROJECT_NAME} resource_pack)
endif()
# --- CLANG-FORMAT TARGETS ---

50
data/input/keys.yaml Normal file
View File

@@ -0,0 +1,50 @@
# Aventures En Egipte — Configuració de tecles d'UI
#
# Font única de veritat per a les tecles de funció / sistema.
# Les tecles de moviment del jugador viuen separades a config.yaml (secció `controls:`).
#
# Si l'usuari remapeja alguna tecla des del menú de servei, la diferència respecte
# aquests valors per defecte es persistix a ~/.config/jailgames/aee/keys.yaml.
#
# Camps:
# id - Identificador usat des del codi via KeyConfig::scancode("id")
# code - Nom SDL del scancode (per SDL_GetScancodeFromName), p.ex. "F1", "Escape"
# desc - Descripció curta (per a HELP / overlays futurs)
keys:
- id: dec_zoom
code: "F1"
desc: "Redueix el zoom de la finestra"
- id: inc_zoom
code: "F2"
desc: "Augmenta el zoom de la finestra"
- id: fullscreen
code: "F3"
desc: "Pantalla completa"
- id: toggle_shader
code: "F4"
desc: "Activa/desactiva shaders"
- id: toggle_aspect_ratio
code: "F5"
desc: "Aspecte 4:3 / pixels quadrats"
- id: toggle_supersampling
code: "F6"
desc: "Activa/desactiva supersampling"
- id: next_shader
code: "F7"
desc: "Tipus de shader (PostFX / CRT-Pi)"
- id: next_shader_preset
code: "F8"
desc: "Pròxim preset del shader"
- id: toggle_stretch_filter
code: "F9"
desc: "Filtre 4:3 (nearest / linear)"
- id: toggle_render_info
code: "F10"
desc: "Mostra info de renderitzat"
- id: pause_toggle
code: "F11"
desc: "Pausa el joc"
- id: menu_toggle
code: "F12"
desc: "Menú de servei"

View File

@@ -4,77 +4,78 @@
menu:
titles:
root: "OPCIONS"
video: "VIDEO"
audio: "AUDIO"
controls: "CONTROLS"
game: "JOC"
root: "Opcions"
video: "Vídeo"
audio: "Àudio"
controls: "Controls"
game: "Joc"
items:
video: "VIDEO"
audio: "AUDIO"
controls: "CONTROLS"
game: "JOC"
use_new_logo: "LOGO NOU"
show_title_credits: "CREDITS DEL PORT"
zoom: "ZOOM"
screen: "PANTALLA"
shader: "SHADER"
aspect_4_3: "ASPECTE 4:3"
supersampling: "SUPERSAMPLING"
vsync: "VSYNC"
integer_scale: "ESCALA ENTERA"
shader_type: "TIPUS SHADER"
preset: "PRESET"
stretch_filter: "FILTRE 4:3"
render_info: "RENDER INFO"
uptime: "TEMPS DE JOC"
master_enable: "AUDIO"
master_volume: "MASTER"
music: "MUSICA"
music_volume: "VOL MUSICA"
sounds: "SONS"
sounds_volume: "VOL SONS"
move_up: "MOU AMUNT"
move_down: "MOU AVALL"
move_left: "MOU ESQUERRA"
move_right: "MOU DRETA"
menu_key: "TECLA MENU"
video: "Vídeo"
audio: "Àudio"
controls: "Controls"
game: "Joc"
use_new_logo: "Logo nou"
show_title_credits: "Crèdits del port"
zoom: "Zoom"
screen: "Pantalla"
shader: "Shader"
aspect_4_3: "Aspecte 4:3"
supersampling: "Supersampling"
vsync: "Vsync"
integer_scale: "Escala entera"
shader_type: "Tipus shader"
preset: "Preset"
stretch_filter: "Filtre 4:3"
render_info: "Render info"
uptime: "Temps de joc"
master_enable: "Àudio"
master_volume: "Màster"
music: "Música"
music_volume: "Vol música"
sounds: "Sons"
sounds_volume: "Vol sons"
move_up: "Mou amunt"
move_down: "Mou avall"
move_left: "Mou esquerra"
move_right: "Mou dreta"
menu_key: "Tecla menú"
values:
"yes": "SI"
"no": "NO"
"on": "ON"
"off": "OFF"
fullscreen: "COMPLETA"
windowed: "FINESTRA"
linear: "LINEAR"
nearest: "NEAREST"
top: "TOP"
bottom: "BOTTOM"
press_key: "<PREM TECLA>"
empty: "(BUIT)"
"yes": "Sí"
"no": "No"
"on": "On"
"off": "Off"
fullscreen: "Completa"
windowed: "Finestra"
linear: "Linear"
nearest: "Nearest"
top: "Top"
bottom: "Bottom"
press_key: "<Prem tecla>"
empty: "(Buit)"
unknown: "---"
window:
title: "© 2000 Aventures en Egipte — JailDesigner"
notifications:
exit_double_esc: "TORNA A PULSAR ESC PER EIXIR"
zoom_fmt: "ZOOM %dX"
fullscreen: "PANTALLA COMPLETA"
windowed: "FINESTRA"
shader_on: "SHADER ON"
shader_off: "SHADER OFF"
exit_double_esc: "Torna a pulsar ESC per a eixir"
zoom_fmt: "Zoom %dX"
fullscreen: "Pantalla completa"
windowed: "Finestra"
shader_on: "Shader on"
shader_off: "Shader off"
aspect_43: "4:3 CRT"
aspect_square: "PIXELS QUADRATS"
ss_on: "SUPERSAMPLING ON"
ss_off: "SUPERSAMPLING OFF"
preset_fmt: "PRESET: %s"
filter_linear: "FILTRE: LINEAR"
filter_nearest: "FILTRE: NEAREST"
pause: "PAUSA"
resume: "REPRES"
aspect_square: "Píxels quadrats"
ss_on: "Supersampling on"
ss_off: "Supersampling off"
preset_fmt: "Preset: %s"
filter_linear: "Filtre: linear"
filter_nearest: "Filtre: nearest"
pause: "Pausa"
gamepad_connected: "connectat"
gamepad_disconnected: "desconnectat"
credits:
port_role: "Conversio a C++ i SDL3"

Binary file not shown.

View File

@@ -1,10 +1,13 @@
#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 "game/options.hpp"
#include "core/rendering/overlay.hpp"
namespace Gamepad {
@@ -19,11 +22,23 @@ namespace Gamepad {
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_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);
@@ -70,12 +85,16 @@ namespace Gamepad {
pad_ = SDL_OpenGamepad(event.gdevice.which);
if (pad_) {
pad_id_ = event.gdevice.which;
SDL_Log("Gamepad connectat: %s", SDL_GetGamepadName(pad_));
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_) {
SDL_Log("Gamepad desconnectat");
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;
@@ -84,6 +103,7 @@ namespace Gamepad {
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());
}
}
}
@@ -125,16 +145,18 @@ namespace Gamepad {
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
// 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);
// 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);
// 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
@@ -142,8 +164,9 @@ namespace Gamepad {
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);
// 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);
@@ -156,16 +179,21 @@ namespace Gamepad {
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);
// 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_a_ = a;
prev_b_ = b;
prev_south_ = south;
prev_east_ = east;
prev_west_ = west;
prev_north_ = north;
prev_start_ = start;
prev_back_ = back;
}

View File

@@ -3,6 +3,7 @@
#include <cstdio>
#include <string>
#include "core/input/key_config.hpp"
#include "core/jail/jinput.hpp"
#include "core/locale/locale.hpp"
#include "core/rendering/overlay.hpp"
@@ -26,7 +27,7 @@ namespace GlobalInputs {
bool consumed = false;
// F1 — Reduir zoom
bool dec_zoom = JI_KeyPressed(Options::keys_gui.dec_zoom);
bool dec_zoom = JI_KeyPressed(KeyConfig::scancode("dec_zoom"));
if (dec_zoom && !dec_zoom_prev) {
Screen::get()->decZoom();
char msg[32];
@@ -37,7 +38,7 @@ namespace GlobalInputs {
dec_zoom_prev = dec_zoom;
// F2 — Augmentar zoom
bool inc_zoom = JI_KeyPressed(Options::keys_gui.inc_zoom);
bool inc_zoom = JI_KeyPressed(KeyConfig::scancode("inc_zoom"));
if (inc_zoom && !inc_zoom_prev) {
Screen::get()->incZoom();
char msg[32];
@@ -48,7 +49,7 @@ namespace GlobalInputs {
inc_zoom_prev = inc_zoom;
// F3 — Toggle pantalla completa
bool fullscreen = JI_KeyPressed(Options::keys_gui.fullscreen);
bool fullscreen = JI_KeyPressed(KeyConfig::scancode("fullscreen"));
if (fullscreen && !fullscreen_prev) {
Screen::get()->toggleFullscreen();
Overlay::showNotification(Screen::get()->isFullscreen() ? Locale::get("notifications.fullscreen") : Locale::get("notifications.windowed"));
@@ -57,7 +58,7 @@ namespace GlobalInputs {
fullscreen_prev = fullscreen;
// F4 — Toggle shaders
bool shader = JI_KeyPressed(Options::keys_gui.toggle_shader);
bool shader = JI_KeyPressed(KeyConfig::scancode("toggle_shader"));
if (shader && !shader_prev) {
Screen::get()->toggleShaders();
Overlay::showNotification(Options::video.shader_enabled ? Locale::get("notifications.shader_on") : Locale::get("notifications.shader_off"));
@@ -66,7 +67,7 @@ namespace GlobalInputs {
shader_prev = shader;
// F5 — Toggle aspect ratio 4:3
bool aspect = JI_KeyPressed(Options::keys_gui.toggle_aspect_ratio);
bool aspect = JI_KeyPressed(KeyConfig::scancode("toggle_aspect_ratio"));
if (aspect && !aspect_prev) {
Screen::get()->toggleAspectRatio();
Overlay::showNotification(Options::video.aspect_ratio_4_3 ? Locale::get("notifications.aspect_43") : Locale::get("notifications.aspect_square"));
@@ -75,7 +76,7 @@ namespace GlobalInputs {
aspect_prev = aspect;
// F6 — Toggle supersampling
bool ss = JI_KeyPressed(Options::keys_gui.toggle_supersampling);
bool ss = JI_KeyPressed(KeyConfig::scancode("toggle_supersampling"));
if (ss && !ss_prev) {
Screen::get()->toggleSupersampling();
Overlay::showNotification(Options::video.supersampling ? Locale::get("notifications.ss_on") : Locale::get("notifications.ss_off"));
@@ -84,7 +85,7 @@ namespace GlobalInputs {
ss_prev = ss;
// F7 — Canviar tipus de shader (PostFX ↔ CrtPi)
bool next_shader = JI_KeyPressed(Options::keys_gui.next_shader);
bool next_shader = JI_KeyPressed(KeyConfig::scancode("next_shader"));
if (next_shader && !next_shader_prev) {
Screen::get()->nextShaderType();
char msg[64];
@@ -95,7 +96,7 @@ namespace GlobalInputs {
next_shader_prev = next_shader;
// F8 — Pròxim preset del shader actiu
bool next_preset = JI_KeyPressed(Options::keys_gui.next_shader_preset);
bool next_preset = JI_KeyPressed(KeyConfig::scancode("next_shader_preset"));
if (next_preset && !next_preset_prev) {
Screen::get()->nextPreset();
char msg[64];
@@ -106,7 +107,7 @@ namespace GlobalInputs {
next_preset_prev = next_preset;
// F9 — Toggle filtre d'estirament 4:3 (NEAREST ↔ LINEAR)
bool stretch_filter = JI_KeyPressed(Options::keys_gui.toggle_stretch_filter);
bool stretch_filter = JI_KeyPressed(KeyConfig::scancode("toggle_stretch_filter"));
if (stretch_filter && !stretch_filter_prev) {
Screen::get()->toggleStretchFilter();
Overlay::showNotification(Options::video.stretch_filter_linear ? Locale::get("notifications.filter_linear") : Locale::get("notifications.filter_nearest"));
@@ -115,7 +116,7 @@ namespace GlobalInputs {
stretch_filter_prev = stretch_filter;
// F10 — Toggle render info (FPS, driver, shader)
bool render_info = JI_KeyPressed(Options::keys_gui.toggle_render_info);
bool render_info = JI_KeyPressed(KeyConfig::scancode("toggle_render_info"));
if (render_info && !render_info_prev) {
Overlay::toggleRenderInfo();
}

View File

@@ -0,0 +1,182 @@
#include "core/input/key_config.hpp"
#include <fstream>
#include <iostream>
#include <utility>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
namespace KeyConfig {
namespace {
std::vector<KeyEntry> entries_;
std::unordered_map<std::string, size_t> index_;
std::string overrides_path_;
auto findIndex(const std::string& id) -> size_t {
auto it = index_.find(id);
if (it == index_.end()) return SIZE_MAX;
return it->second;
}
void loadDefaults(const std::string& defaults_resource_path) {
auto buf = ResourceHelper::loadFile(defaults_resource_path);
if (buf.empty()) {
std::cerr << "KeyConfig: no s'ha pogut llegir " << defaults_resource_path << '\n';
return;
}
std::string content(buf.begin(), buf.end());
try {
auto yaml = fkyaml::node::deserialize(content);
if (!yaml.contains("keys")) return;
for (const auto& node : yaml["keys"]) {
KeyEntry entry;
entry.id = node["id"].get_value<std::string>();
entry.code = node["code"].get_value<std::string>();
if (node.contains("desc")) {
entry.desc = node["desc"].get_value<std::string>();
}
SDL_Scancode sc = SDL_GetScancodeFromName(entry.code.c_str());
if (sc == SDL_SCANCODE_UNKNOWN) {
std::cerr << "KeyConfig: scancode desconegut '" << entry.code
<< "' per '" << entry.id << "'\n";
}
entry.scancode = sc;
entry.default_scancode = sc;
index_[entry.id] = entries_.size();
entries_.push_back(std::move(entry));
}
std::cout << "KeyConfig: " << entries_.size() << " tecles carregades de "
<< defaults_resource_path << '\n';
} catch (const fkyaml::exception& e) {
std::cerr << "KeyConfig: error parsejant YAML: " << e.what() << '\n';
}
}
void applyOverrides(const std::string& disk_path) {
std::ifstream file(disk_path);
if (!file.good()) return;
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
try {
auto yaml = fkyaml::node::deserialize(content);
if (!yaml.contains("overrides")) return;
int applied = 0;
for (const auto& kv : yaml["overrides"].as_map()) {
auto id = kv.first.get_value<std::string>();
auto code = kv.second.get_value<std::string>();
auto idx = findIndex(id);
if (idx == SIZE_MAX) {
std::cerr << "KeyConfig: override per id desconegut '" << id << "'\n";
continue;
}
SDL_Scancode sc = SDL_GetScancodeFromName(code.c_str());
if (sc == SDL_SCANCODE_UNKNOWN) {
std::cerr << "KeyConfig: override amb scancode invàlid '" << code
<< "' per '" << id << "'\n";
continue;
}
entries_[idx].scancode = sc;
entries_[idx].code = code;
applied++;
}
if (applied > 0) {
std::cout << "KeyConfig: aplicats " << applied
<< " overrides de " << disk_path << '\n';
}
} catch (const fkyaml::exception& e) {
std::cerr << "KeyConfig: error parsejant overrides: " << e.what() << '\n';
}
}
} // namespace
void init(const std::string& defaults_resource_path,
const std::string& user_overrides_disk_path) {
entries_.clear();
index_.clear();
overrides_path_ = user_overrides_disk_path;
loadDefaults(defaults_resource_path);
if (!overrides_path_.empty()) {
applyOverrides(overrides_path_);
}
}
void destroy() {
entries_.clear();
index_.clear();
overrides_path_.clear();
}
auto scancode(const std::string& id) -> SDL_Scancode {
auto idx = findIndex(id);
if (idx == SIZE_MAX) return SDL_SCANCODE_UNKNOWN;
return entries_[idx].scancode;
}
auto scancodePtr(const std::string& id) -> SDL_Scancode* {
auto idx = findIndex(id);
if (idx == SIZE_MAX) return nullptr;
return &entries_[idx].scancode;
}
void setScancode(const std::string& id, SDL_Scancode sc) {
auto idx = findIndex(id);
if (idx == SIZE_MAX) return;
entries_[idx].scancode = sc;
const char* name = SDL_GetScancodeName(sc);
entries_[idx].code = (name != nullptr) ? name : "";
}
auto isGuiKey(SDL_Scancode sc) -> bool {
if (sc == SDL_SCANCODE_UNKNOWN) return false;
for (const auto& e : entries_) {
if (e.scancode == sc) return true;
}
return false;
}
auto entries() -> const std::vector<KeyEntry>& {
return entries_;
}
auto saveOverrides() -> bool {
if (overrides_path_.empty()) return false;
// Recull només les entrades remapeades.
std::vector<const KeyEntry*> changed;
for (const auto& e : entries_) {
if (e.scancode != e.default_scancode) changed.push_back(&e);
}
std::ofstream file(overrides_path_);
if (!file.is_open()) {
std::cerr << "KeyConfig: no es pot escriure " << overrides_path_ << '\n';
return false;
}
file << "# AEE - Overrides de tecles d'UI\n";
file << "# Auto-generat. Només llista les tecles modificades respecte\n";
file << "# els valors per defecte de data/input/keys.yaml.\n";
file << "\n";
if (changed.empty()) {
file << "overrides: {}\n";
} else {
file << "overrides:\n";
for (const auto* e : changed) {
file << " " << e->id << ": \"" << e->code << "\"\n";
}
}
return true;
}
} // namespace KeyConfig

View File

@@ -0,0 +1,52 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <unordered_map>
#include <vector>
// KeyConfig: font única de veritat per a les tecles d'UI/sistema.
//
// Llegeix els valors per defecte des de `data/input/keys.yaml` (recurs read-only)
// i opcionalment aplica overrides des d'un fitxer de l'usuari (per a remapejos
// fets des del menú de servei). Els callers consulten per `id` (ex. "menu_toggle").
//
// Les tecles de moviment del jugador NO viuen ací — es queden a Options::keys_game.
struct KeyEntry {
std::string id;
std::string code; // nom SDL del scancode tal com apareix al YAML
std::string desc;
SDL_Scancode scancode{SDL_SCANCODE_UNKNOWN};
SDL_Scancode default_scancode{SDL_SCANCODE_UNKNOWN};
};
namespace KeyConfig {
// Inicialitza KeyConfig llegint defaults des d'un recurs (via ResourceHelper)
// i opcionalment sobreposant overrides des d'un fitxer de disc.
void init(const std::string& defaults_resource_path,
const std::string& user_overrides_disk_path);
void destroy();
// Consulta el scancode actual associat a un id. Torna SDL_SCANCODE_UNKNOWN si no existix.
[[nodiscard]] auto scancode(const std::string& id) -> SDL_Scancode;
// Punter estable al scancode d'un id — útil per a Menu::ItemKind::KeyBind.
// Torna nullptr si l'id no existix.
[[nodiscard]] auto scancodePtr(const std::string& id) -> SDL_Scancode*;
// Estableix el scancode d'un id. No persistix per si sol — cal cridar saveOverrides().
void setScancode(const std::string& id, SDL_Scancode sc);
// True si el scancode coincidix amb alguna tecla d'UI registrada.
// Usat pel Director per a evitar que tecles d'UI activen `key_pressed_` al joc.
[[nodiscard]] auto isGuiKey(SDL_Scancode sc) -> bool;
// Llistat complet de les entrades (per a HELP / debug / iteració).
[[nodiscard]] auto entries() -> const std::vector<KeyEntry>&;
// Persistix al fitxer d'overrides les entrades que difereixen del default.
// Si no s'ha proporcionat user_overrides_disk_path al init, és no-op.
auto saveOverrides() -> bool;
} // namespace KeyConfig

View File

@@ -6,6 +6,7 @@
#include <string>
#include <vector>
#include "core/input/key_config.hpp"
#include "core/locale/locale.hpp"
#include "core/rendering/overlay.hpp"
#include "core/rendering/screen.hpp"
@@ -181,7 +182,7 @@ namespace Menu {
p.items.push_back({Locale::get("menu.items.move_down"), ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.down});
p.items.push_back({Locale::get("menu.items.move_left"), ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.left});
p.items.push_back({Locale::get("menu.items.move_right"), ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.right});
p.items.push_back({Locale::get("menu.items.menu_key"), ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_gui.menu_toggle});
p.items.push_back({Locale::get("menu.items.menu_key"), ItemKind::KeyBind, nullptr, nullptr, nullptr, KeyConfig::scancodePtr("menu_toggle")});
return p;
}

View File

@@ -5,6 +5,7 @@
#include "core/input/gamepad.hpp"
#include "core/input/global_inputs.hpp"
#include "core/input/key_config.hpp"
#include "core/input/key_remap.hpp"
#include "core/input/mouse.hpp"
#include "core/jail/jail_audio.hpp"
@@ -281,17 +282,18 @@ void Director::handleEvent(const SDL_Event& event) {
menu_keys_held_[event.key.scancode] = true;
return;
}
// Pausa: F11 (o tecla configurada) pausa/reprén la simulació
// Pausa: F11 (o tecla configurada) pausa/reprén la simulació.
// No mostrem notificació — l'indicador persistent "Pausa" a la cantonada
// superior dreta (pintat per Overlay) ja comunica l'estat.
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
event.key.scancode == Options::keys_gui.pause_toggle) {
event.key.scancode == KeyConfig::scancode("pause_toggle")) {
togglePause();
Overlay::showNotification(paused_ ? Locale::get("notifications.pause") : Locale::get("notifications.resume"));
menu_keys_held_[event.key.scancode] = true;
return;
}
// 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) {
event.key.scancode == KeyConfig::scancode("menu_toggle")) {
Menu::toggle();
JI_SetInputBlocked(Menu::isOpen());
menu_keys_held_[event.key.scancode] = true;
@@ -346,19 +348,12 @@ void Director::handleEvent(const SDL_Event& event) {
// quan l'overlay faça timeout
return;
} else {
// Comprova si és una tecla GUI (no passa al joc)
// Comprova si és una tecla d'UI registrada (no passa al joc).
// KeyConfig::isGuiKey cobreix totes les tecles GUI a la vegada,
// incloent pause_toggle i menu_toggle (defensa en profunditat:
// aquestes ja s'haurien hagut de menjar al swallow d'amunt).
const auto sc = event.key.scancode;
const bool is_gui_key = (sc == Options::keys_gui.dec_zoom ||
sc == Options::keys_gui.inc_zoom ||
sc == Options::keys_gui.fullscreen ||
sc == Options::keys_gui.toggle_shader ||
sc == Options::keys_gui.toggle_aspect_ratio ||
sc == Options::keys_gui.toggle_supersampling ||
sc == Options::keys_gui.next_shader ||
sc == Options::keys_gui.next_shader_preset ||
sc == Options::keys_gui.toggle_stretch_filter ||
sc == Options::keys_gui.toggle_render_info);
if (!is_gui_key) {
if (!KeyConfig::isGuiKey(sc)) {
key_pressed_ = true;
JI_moveCheats(sc);
}

View File

@@ -2,21 +2,7 @@
#include <SDL3/SDL.h>
// Tecles GUI (capa de presentació — finestra, zoom, shaders, etc.)
namespace Defaults::KeysGUI {
constexpr SDL_Scancode DEC_ZOOM = SDL_SCANCODE_F1;
constexpr SDL_Scancode INC_ZOOM = SDL_SCANCODE_F2;
constexpr SDL_Scancode FULLSCREEN = SDL_SCANCODE_F3;
constexpr SDL_Scancode TOGGLE_SHADER = SDL_SCANCODE_F4;
constexpr SDL_Scancode TOGGLE_ASPECT_RATIO = SDL_SCANCODE_F5;
constexpr SDL_Scancode TOGGLE_SUPERSAMPLING = SDL_SCANCODE_F6;
constexpr SDL_Scancode NEXT_SHADER = SDL_SCANCODE_F7;
constexpr SDL_Scancode NEXT_SHADER_PRESET = SDL_SCANCODE_F8;
constexpr SDL_Scancode TOGGLE_STRETCH_FILTER = SDL_SCANCODE_F9;
constexpr SDL_Scancode TOGGLE_RENDER_INFO = SDL_SCANCODE_F10;
constexpr SDL_Scancode PAUSE_TOGGLE = SDL_SCANCODE_F11;
constexpr SDL_Scancode MENU_TOGGLE = SDL_SCANCODE_F12;
} // namespace Defaults::KeysGUI
// Tecles GUI: viuen a data/input/keys.yaml (font única — KeyConfig).
// Tecles de joc (moviment del personatge, accions)
namespace Defaults::KeysGame {

View File

@@ -58,7 +58,7 @@ void ModuleGame::onEnter() {
// Arranca el fade-in tick-based. El `PaletteFade` avança un pas (de
// 32) per cada tick; durant aquesta fase el gameplay no corre,
// només Draw+fade. Substituïx la crida bloquejant `JD8_FadeToPal`.
fade_.startFadeTo(JD8_LoadPalette(info::ctx.pepe_activat ? "frames2.gif" : "frames.gif"));
fade_.startFadeTo(JD8_LoadPalette(info::ctx.pepe_activat ? "gfx/frames2.gif" : "gfx/frames.gif"));
phase_ = Phase::FadingIn;
}

View File

@@ -189,8 +189,6 @@ namespace Options {
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);
loadScancodeField(node, "pause_toggle", keys_gui.pause_toggle);
}
}
@@ -333,15 +331,14 @@ namespace Options {
file << " show_title_credits: " << (game.show_title_credits ? "true" : "false") << "\n";
file << "\n";
// CONTROLS
file << "# CONTROLS (noms SDL: \"Up\", \"Down\", \"W\", \"Space\", \"F12\", etc.)\n";
// CONTROLS — només moviment del jugador. Les tecles d'UI viuen a
// data/input/keys.yaml (defaults) + ~/.config/jailgames/aee/keys.yaml (overrides).
file << "# CONTROLS (noms SDL: \"Up\", \"Down\", \"W\", \"Space\", 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 << " pause_toggle: \"" << SDL_GetScancodeName(keys_gui.pause_toggle) << "\"\n";
file.close();

View File

@@ -8,23 +8,8 @@
namespace Options {
// Tecles GUI (finestra, zoom, shaders)
struct KeysGUI {
SDL_Scancode dec_zoom{Defaults::KeysGUI::DEC_ZOOM};
SDL_Scancode inc_zoom{Defaults::KeysGUI::INC_ZOOM};
SDL_Scancode fullscreen{Defaults::KeysGUI::FULLSCREEN};
SDL_Scancode toggle_shader{Defaults::KeysGUI::TOGGLE_SHADER};
SDL_Scancode toggle_aspect_ratio{Defaults::KeysGUI::TOGGLE_ASPECT_RATIO};
SDL_Scancode toggle_supersampling{Defaults::KeysGUI::TOGGLE_SUPERSAMPLING};
SDL_Scancode next_shader{Defaults::KeysGUI::NEXT_SHADER};
SDL_Scancode next_shader_preset{Defaults::KeysGUI::NEXT_SHADER_PRESET};
SDL_Scancode toggle_stretch_filter{Defaults::KeysGUI::TOGGLE_STRETCH_FILTER};
SDL_Scancode toggle_render_info{Defaults::KeysGUI::TOGGLE_RENDER_INFO};
SDL_Scancode pause_toggle{Defaults::KeysGUI::PAUSE_TOGGLE};
SDL_Scancode menu_toggle{Defaults::KeysGUI::MENU_TOGGLE};
};
// Tecles de joc (moviment, accions)
// Tecles de joc (moviment, accions). Les tecles d'UI viuen ara a KeyConfig
// (carregades de data/input/keys.yaml).
struct KeysGame {
SDL_Scancode up{Defaults::KeysGame::UP};
SDL_Scancode down{Defaults::KeysGame::DOWN};
@@ -123,7 +108,6 @@ namespace Options {
// --- Variables globals ---
inline std::string version{};
inline KeysGUI keys_gui{};
inline KeysGame keys_game{};
inline Video video{};
inline RenderInfo render_info{};

View File

@@ -11,6 +11,7 @@
#include <ctime>
#include <string>
#include "core/input/key_config.hpp"
#include "core/jail/jail_audio.hpp"
#include "core/jail/jdraw8.hpp"
#include "core/jail/jfile.hpp"
@@ -57,6 +58,10 @@ SDL_AppResult SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) {
Options::setConfigFile(std::string(file_getconfigfolder()) + "config.yaml");
Options::loadFromFile();
// KeyConfig: defaults des de data/input/keys.yaml + overrides de l'usuari
KeyConfig::init("input/keys.yaml",
std::string(file_getconfigfolder()) + "keys.yaml");
#ifndef NDEBUG
// debug.yaml: estat inicial de gameplay per a tests ràpids,
// només en builds de debug.
@@ -119,11 +124,13 @@ void SDL_AppQuit(void* /*appstate*/, SDL_AppResult /*result*/) {
Director::get()->teardown();
Options::saveToFile();
KeyConfig::saveOverrides();
#ifndef NDEBUG
Options::saveDebugToFile();
#endif
Director::destroy();
KeyConfig::destroy();
Menu::destroy();
Overlay::destroy();
JA_Quit();