diff --git a/runtime/include/psx_keybinds.h b/runtime/include/psx_keybinds.h index 4f9d6884..9d9072bd 100644 --- a/runtime/include/psx_keybinds.h +++ b/runtime/include/psx_keybinds.h @@ -93,7 +93,13 @@ const char *psx_keybinds_button_name(int button); /* "up".."rs_right" const char *psx_keybinds_button_label(int button); /* pretty, e.g. "Cross" */ SDL_Scancode psx_keybinds_get_button(int player, int button); void psx_keybinds_set_button(int player, int button, SDL_Scancode sc); -/* Reset one player's bindings to the built-in defaults (same map for every slot). */ +/* Alternate (secondary) binding per control: both the primary and the alt + * assert the input (e.g. cross = X, Mouse1). Stored in keybinds.ini as a + * comma-separated second name; SDL_SCANCODE_UNKNOWN = no alt. */ +SDL_Scancode psx_keybinds_get_button_alt(int player, int button); +void psx_keybinds_set_button_alt(int player, int button, SDL_Scancode sc); +/* Reset one player's bindings to the shared built-in primary defaults and + * clear that player's alternate bindings. */ void psx_keybinds_reset_player(int player); /* Persist the current bindings to keybinds.ini (path resolved by * psx_keybinds_init; call that first). */ diff --git a/runtime/src/psx_keybinds.c b/runtime/src/psx_keybinds.c index b224a631..2dcd893c 100644 --- a/runtime/src/psx_keybinds.c +++ b/runtime/src/psx_keybinds.c @@ -82,6 +82,10 @@ static PsxKeyBinds s_binds = PSXKB_DEFAULTS; static const PsxKeyBinds s_default_binds = PSXKB_DEFAULTS; +/* Alternate bindings: zero-initialised = SDL_SCANCODE_UNKNOWN = no alt bound. + * Primary OR alt asserts the input; both persist to keybinds.ini as + * "primary, alt" on one line. */ +static PsxKeyBinds s_alt_binds; typedef struct { const char *name; /* ini key */ @@ -212,11 +216,18 @@ static void derive_ini_path(const char *exe_path) { snprintf(s_ini_path, sizeof(s_ini_path), "%skeybinds.ini", dir); } -static void write_player_section(FILE *f, const char *section, const PsxPlayerBinds *pb) { +static void write_player_section(FILE *f, const char *section, + const PsxPlayerBinds *pb, + const PsxPlayerBinds *alt) { fprintf(f, "[%s]\n", section); for (int i = 0; i < PSXKB_N; i++) { - SDL_Scancode sc = *(const SDL_Scancode *)((const char *)pb + s_buttons[i].offset); - fprintf(f, "%-9s = %s\n", s_buttons[i].name, scancode_to_name(sc)); + SDL_Scancode sc = *(const SDL_Scancode *)((const char *)pb + s_buttons[i].offset); + SDL_Scancode al = *(const SDL_Scancode *)((const char *)alt + s_buttons[i].offset); + if (al != SDL_SCANCODE_UNKNOWN) + fprintf(f, "%-9s = %s, %s\n", s_buttons[i].name, + scancode_to_name(sc), scancode_to_name(al)); + else + fprintf(f, "%-9s = %s\n", s_buttons[i].name, scancode_to_name(sc)); } fprintf(f, "\n"); } @@ -232,6 +243,8 @@ static void write_ini(const char *path) { "# Backspace, Escape, Backslash. Use \"None\" to leave an input unbound.\n" "# Mouse buttons also bind: Mouse1 (left), Mouse2 (middle), Mouse3 (right),\n" "# Mouse4/Mouse5 (side); aliases LMB, MMB, RMB.\n" + "# Each input accepts an optional SECOND binding after a comma - both\n" + "# assert the input: cross = X, Mouse1\n" "#\n" "# Buttons: up/down/left/right, cross/circle/square/triangle, l1/r1/l2/r2,\n" "# l3/r3 (stick clicks), start/select. ls_* / rs_* are the left/right\n" @@ -243,7 +256,8 @@ static void write_ini(const char *path) { for (int p = 0; p < PSXKB_MAX_PLAYERS; ++p) { char section[16]; snprintf(section, sizeof(section), "player%d", p + 1); - write_player_section(f, section, &s_binds.player[p]); + write_player_section(f, section, &s_binds.player[p], + &s_alt_binds.player[p]); } fclose(f); printf("[Keybinds] Wrote %s\n", path); @@ -269,7 +283,7 @@ static void promote_empty_players(void) { static void load_ini(const char *path) { FILE *f = fopen(path, "r"); if (!f) return; - PsxPlayerBinds *current = NULL; + PsxPlayerBinds *current = NULL, *current_alt = NULL; char line[256]; while (fgets(line, sizeof(line), f)) { trim(line); @@ -278,11 +292,13 @@ static void load_ini(const char *path) { char *end = strchr(line, ']'); if (end) *end = '\0'; const char *section = line + 1; - current = NULL; + current = NULL; current_alt = NULL; if (!strncmp(section, "player", 6)) { int n = atoi(section + 6); - if (n >= 1 && n <= PSXKB_MAX_PLAYERS) + if (n >= 1 && n <= PSXKB_MAX_PLAYERS) { current = &s_binds.player[n - 1]; + current_alt = &s_alt_binds.player[n - 1]; + } } continue; } @@ -293,9 +309,16 @@ static void load_ini(const char *path) { trim(key); trim(val); for (char *c = key; *c; c++) *c = (char)tolower((unsigned char)*c); if (!current) continue; + /* Optional alternate binding after a comma: "cross = X, Mouse1". */ + char *comma = strchr(val, ','); + char *alt_val = NULL; + if (comma) { *comma = '\0'; alt_val = comma + 1; trim(val); trim(alt_val); } for (int i = 0; i < PSXKB_N; i++) { if (!strcmp(key, s_buttons[i].name)) { - *(SDL_Scancode *)((char *)current + s_buttons[i].offset) = name_to_scancode(val); + *(SDL_Scancode *)((char *)current + s_buttons[i].offset) = + name_to_scancode(val); + *(SDL_Scancode *)((char *)current_alt + s_buttons[i].offset) = + alt_val ? name_to_scancode(alt_val) : SDL_SCANCODE_UNKNOWN; break; } } @@ -325,11 +348,10 @@ static PsxPlayerBinds *player_binds(int player) { return &s_binds.player[player - 1]; } -/* Is the scancode at button-def index i currently held for this player? +/* Is this single scancode (keyboard or mouse pseudo-code) currently held? * Mouse pseudo-scancodes MUST be checked before indexing keys[] — they sit * beyond the keyboard state array (SDL_NUM_SCANCODES entries). */ -static int held(const uint8_t *keys, const PsxPlayerBinds *pb, int i) { - SDL_Scancode sc = *(const SDL_Scancode *)((const char *)pb + s_buttons[i].offset); +static int held_sc(const uint8_t *keys, SDL_Scancode sc) { if (PSXKB_IS_MOUSE_SC(sc)) { Uint32 m = SDL_GetMouseState(NULL, NULL); return (m & SDL_BUTTON((int)sc - PSXKB_MOUSE_SC_BASE)) != 0; @@ -337,6 +359,18 @@ static int held(const uint8_t *keys, const PsxPlayerBinds *pb, int i) { return sc != SDL_SCANCODE_UNKNOWN && (int)sc < SDL_NUM_SCANCODES && keys[sc]; } +/* Is the input at button-def index i held for this player, via either its + * primary or its alternate binding? The alternate table uses the same player + * slot as the primary table. */ +static int held(const uint8_t *keys, const PsxPlayerBinds *pb, int i) { + ptrdiff_t slot = pb - &s_binds.player[0]; + if (slot < 0 || slot >= PSXKB_MAX_PLAYERS) slot = 0; + const PsxPlayerBinds *alt = &s_alt_binds.player[slot]; + SDL_Scancode p = *(const SDL_Scancode *)((const char *)pb + s_buttons[i].offset); + SDL_Scancode a = *(const SDL_Scancode *)((const char *)alt + s_buttons[i].offset); + return held_sc(keys, p) || held_sc(keys, a); +} + uint16_t psx_keybinds_pad_word(const uint8_t *keys, int player) { if (!keys || player < 1 || player > PSXKB_MAX_PLAYERS) return 0xFFFF; const PsxPlayerBinds *pb = player_binds_c(player); @@ -393,9 +427,26 @@ void psx_keybinds_set_button(int player, int button, SDL_Scancode sc) { *(SDL_Scancode *)((char *)player_binds(player) + s_buttons[button].offset) = sc; } +static PsxPlayerBinds *player_alt_binds(int player) { + if (player < 1 || player > PSXKB_MAX_PLAYERS) + return &s_alt_binds.player[0]; + return &s_alt_binds.player[player - 1]; +} + +SDL_Scancode psx_keybinds_get_button_alt(int player, int button) { + if (button < 0 || button >= PSXKB_N) return SDL_SCANCODE_UNKNOWN; + return *(SDL_Scancode *)((char *)player_alt_binds(player) + s_buttons[button].offset); +} + +void psx_keybinds_set_button_alt(int player, int button, SDL_Scancode sc) { + if (button < 0 || button >= PSXKB_N) return; + *(SDL_Scancode *)((char *)player_alt_binds(player) + s_buttons[button].offset) = sc; +} + void psx_keybinds_reset_player(int player) { if (player < 1 || player > PSXKB_MAX_PLAYERS) return; *player_binds(player) = s_default_binds.player[0]; + memset(player_alt_binds(player), 0, sizeof(PsxPlayerBinds)); /* alts: unbound */ } void psx_keybinds_save(void) {