v0.4.0 M3: GEPD key-layout preset + crouch/reload bindings (D371)

Input.Layout (0=FPS default, 1=GEPD) + Input.CrouchMode (hold/toggle) rows
in F10 INPUT (Turok-standard functional section; the planned MODERN bucket
is dropped per D356). The GEPD preset acts as the per-key effective
default under [Input.Bind] overrides; new IA_RELOAD/IA_CROUCH actions emit
GE_CONT_B (unbound by default, N64-original). Toggle crouch latches B with
a menu-boundary latch drop. optionsoverlay rowSetCommit hook re-derives
the binds via inputLayoutApply(). inputRebuildBinds no longer warns on
intentionally-empty defaults. Boot + ini round-trip + F10 page verified;
in-game feel-check owed (D371).
This commit is contained in:
James Dansereau committed 2026-09-27 16:29:18 -04:00
1 parent c2c6fbdd68
commit 40ef53e395
5 files changed
+160 -3

No files matched your search

+5
View File
@@ -68,6 +68,11 @@ short inputPadAxis(int idx, SDL_GameControllerAxis a);
* ignored -- GE only cycles forward on a bare A edge. */
void inputPostWheel(int notches);
/* v0.4.0 M3: re-derive the keyboard binds after an F10 change to
* Input.Layout / Input.CrouchMode (optionsoverlay rowSetCommit hook).
* Also drops the latched-crouch state. Scheduler thread only. */
void inputLayoutApply(void);
/* D345(b): 1 while the 1:1 menu pointer owns cursor_h/v_pos (in a menu,
* abs pointer available, mouse used within the re-assert window). Port
* screens that teleport the crosshair should skip their snap when set. */
+77 -3
View File
@@ -868,7 +868,12 @@ static int keyDown(const Uint8 *ks, SDL_Scancode sc)
* ---------------------------------------------------------------------- */
enum {
IA_FORWARD, IA_BACK, IA_STRAFE_L, IA_STRAFE_R, IA_TURN_L, IA_TURN_R,
IA_FIRE, IA_AIM, IA_ACTION, IA_CANCEL, IA_LEAN_L, IA_START, IA_COUNT
IA_FIRE, IA_AIM, IA_ACTION, IA_CANCEL, IA_LEAN_L, IA_START,
/* v0.4.0 M3 (modern options wave): GEPD-layout actions. Both map to
* the N64 B button -- the game's own B-context logic
* (bond_interact_object) picks reload (tap, gun in hand, no
* interact target) vs crouch (held) exactly as on the N64. */
IA_RELOAD, IA_CROUCH, IA_COUNT
};
static const struct { const char *key; const char *def; } kBindDefs[IA_COUNT] = {
@@ -884,8 +889,30 @@ static const struct { const char *key; const char *def; } kBindDefs[IA_COUNT] =
[IA_CANCEL] = { "Input.Bind.Cancel", "X,R,F,Escape" },
[IA_LEAN_L] = { "Input.Bind.LeanLeft", "Q" },
[IA_START] = { "Input.Bind.Start", "Return,Tab" },
[IA_RELOAD] = { "Input.Bind.Reload", "" },
[IA_CROUCH] = { "Input.Bind.Crouch", "" },
};
/* v0.4.0 M3: GEPD key-layout preset (docs/dev/notes/GEPORT-REFERENCE-
* DEEPDIVE.md section 7.3). The preset is the EFFECTIVE DEFAULT for any action
* the user has not overridden per-key in [Bind]; a per-key ini value
* layers on top, so switching layouts never clobbers explicit binds.
* Fire loses its Left Ctrl key in this layout (LMB fires, RMB aims;
* Ctrl becomes crouch); LeanLeft is unbound (lean is GEPD-removed).
* NULL entries keep the FPS default. */
static const char *const kGepdPreset[IA_COUNT] = {
[IA_FIRE] = "",
[IA_ACTION] = "Q",
[IA_CANCEL] = "E",
[IA_LEAN_L] = "",
[IA_RELOAD] = "R",
[IA_CROUCH] = "Left Ctrl",
};
static int keyLayout = 0; /* 0 = FPS defaults, 1 = GEPD preset */
static int crouchMode = 0; /* 0 = hold, 1 = toggle (latched B) */
static int s_crouchLatch = 0;
static int s_crouchHeldPrev = 0;
#define BIND_MAX_KEYS 4
static char g_bindStr[IA_COUNT][64];
static SDL_Scancode g_bind[IA_COUNT][BIND_MAX_KEYS];
@@ -896,9 +923,18 @@ static void inputRebuildBinds(void)
for (int k = 0; k < BIND_MAX_KEYS; k++) {
g_bind[a][k] = SDL_SCANCODE_UNKNOWN;
}
/* v0.4.0 M3: the GEPD preset fills the actions the user has not
* individually overridden (empty g_bindStr); FPS layout = the
* kBindDefs defaults, byte-identical to the pre-wave binds. */
const char *def = kBindDefs[a].def;
if (keyLayout == 1) {
const char *preset = kGepdPreset[a];
if (preset) def = preset;
}
char buf[64];
strncpy(buf, g_bindStr[a][0] ? g_bindStr[a] : kBindDefs[a].def, sizeof(buf) - 1);
strncpy(buf, g_bindStr[a][0] ? g_bindStr[a] : def, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
int hadInput = (buf[0] != 0); /* before strtok clobbers buf */
int n = 0;
for (char *tok = strtok(buf, ","); tok && n < BIND_MAX_KEYS; tok = strtok(NULL, ",")) {
@@ -914,13 +950,32 @@ static void inputRebuildBinds(void)
}
g_bind[a][n++] = sc;
}
if (n == 0) {
/* v0.4.0 M3: an intentionally-empty string (a preset or default
* that leaves an action unbound, e.g. GEPD Fire) is silent --
* only a non-empty string that produced no valid keys warns. */
if (n == 0 && hadInput) {
sysLogPrintf(LOG_WARNING, "input: %s has no valid keys; action unbound",
kBindDefs[a].key);
}
}
}
/* v0.4.0 M3: re-derive the binds after an F10 change to Input.Layout /
* Input.CrouchMode (optionsoverlay rowSetCommit hook, scheduler thread).
* g_bind is read per-frame by the game thread's actHeld(): a mid-poll
* rebuild can at worst drop or add one scancode for one frame (the
* D214 startup-rebuild is the same thread-pairing, just at boot).
* Also drops the crouch latch so a latched crouch never carries B into
* a menu (where B = back/cancel). */
void inputLayoutApply(void)
{
s_crouchLatch = 0;
s_crouchHeldPrev = 0;
inputRebuildBinds();
sysLogPrintf(LOG_INFO, "input: key layout %s, crouch %s applied",
keyLayout ? "GEPD" : "FPS", crouchMode ? "toggle" : "hold");
}
static int actHeld(const Uint8 *ks, int act)
{
for (int k = 0; k < BIND_MAX_KEYS; k++) {
@@ -1126,6 +1181,21 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y)
}
if (actHeld(ks, IA_CANCEL)) /* D145: Escape is in the default Cancel bind */
button |= GE_CONT_B;
/* v0.4.0 M3: reload (B tap, same as any B press) and crouch
* (hold, or a latched toggle). Both are unbound by default (the
* N64-original layout), so this is a no-op out of the box; the
* game's B-context logic decides reload-vs-crouch as on N64. */
if (actHeld(ks, IA_RELOAD))
button |= GE_CONT_B;
int crouchNow = actHeld(ks, IA_CROUCH);
if (crouchMode == 1) {
if (crouchNow && !s_crouchHeldPrev) s_crouchLatch ^= 1;
if (menuMode) s_crouchLatch = 0; /* never carry B into a menu */
if (s_crouchLatch) button |= GE_CONT_B;
s_crouchHeldPrev = crouchNow;
} else if (crouchNow) {
button |= GE_CONT_B;
}
if (actHeld(ks, IA_LEAN_L))
button |= GE_CONT_L;
if (actHeld(ks, IA_START))
@@ -1998,6 +2068,10 @@ PD_CONSTRUCTOR static void inputConfigInit(void)
configRegisterInt("Input.PadDeadzone", &padDeadzone, 0, 30000);
configRegisterInt("Input.PadTriggerPct", &padTriggerPct, 1, 99);
configRegisterInt("Input.PadLookInvertY", &padLookInvertY, 0, 1);
/* v0.4.0 M3 (modern options wave): key-layout preset + crouch bind
* mode; both default to the N64-original FPS layout / hold. */
configRegisterInt("Input.Layout", &keyLayout, 0, 1);
configRegisterInt("Input.CrouchMode", &crouchMode, 0, 1);
/* D214: keyboard rebinding. Seed each buffer with its default so the knob
* is visible/editable in a fresh ge007.ini; configLoad() overwrites any the
+29
View File
@@ -91,6 +91,17 @@ static const char *const kTexFilter[] = { "Nearest", "Bilinear", "3-Point", NULL
static const char *const kAimMode[] = { "N64", "Centred (PC)", NULL }; /* D337 */
static const char *const kAimRange[] = { "PC", "N64", NULL }; /* D338 */
static const int kMsaaSeq[] = { 1, 2, 4, 8 };
/* v0.4.0 modern options wave: value names for the new rows (they land
* in the functional sections -- Turok standard, D356 -- not a bucket). */
static const char *const kOnOffRev[] = { "On", "Off", NULL }; /* 0 = On */
/* M2: named crosshair tints; index 0 = the N64 white (no tint applied). */
static const char *const kCrosshairColor[] = {
"White", "Green", "Red", "Blue", "Yellow", "Cyan", "Magenta",
NULL,
};
/* M3: key-layout preset (0 = the FPS default binds, byte-identical to the
* current defaults; 1 = the GEPD mouse-injector layout) + crouch bind mode */
static const char *const kKeyLayout[] = { "FPS (default)", "GEPD", NULL };
/* D186: the sim's own tick pacemaker is hardcoded to the console's native VI
* rate (60Hz NTSC / 50Hz PAL, port/src/libultra.c) -- Video.FpsCap can only
* throttle down from there, never past it, and throttling it below 30
@@ -190,6 +201,13 @@ static struct Row rows[] = {
{ .key="Input.PadLookInvertY", .label="Invert look (controller)", .kind=ROW_TOGGLE, .step=1, .names=kOnOff },
{ .key="Input.PadDeadzone", .label="Stick deadzone", .kind=ROW_SLIDER, .step=500 },
{ .key="Input.PadTriggerPct", .label="Trigger threshold", .kind=ROW_SLIDER, .step=1 },
/* v0.4.0 M3 (modern options wave, D371): the GEPD mouse-injector
* key-layout preset (docs/dev/notes/GEPORT-REFERENCE-DEEPDIVE.md
* section 7.3) + the crouch bind's fire mode. Turok standard: these are
* control bindings, so they live in INPUT -- no provenance bucket.
* Defaults are the N64-original FPS layout / hold. */
{ .key="Input.Layout", .label="Key layout", .kind=ROW_ENUM, .step=1, .names=kKeyLayout },
{ .key="Input.CrouchMode", .label="Crouch mode", .kind=ROW_ENUM, .step=1, .names=kHold },
{ .key="__ResetInput", .label="Reset to defaults", .kind=ROW_ACTION },
{ .key="__HdrGameplay", .label="GAMEPLAY", .kind=ROW_HEADER },
@@ -726,6 +744,15 @@ static void rowSetCommit(struct Row *r, double v, int commit)
videoRequestLiveConfig();
}
/* v0.4.0 M3: key-layout preset / crouch mode -- re-derive the binds
* (inputRebuildBinds via inputLayoutApply; also drops the crouch latch).
* Safe at scheduler-thread commit time; a mid-poll rebuild can only
* drop/add one scancode for one frame (D214 thread-pairing). */
if (strcmp(r->key, "Input.Layout") == 0 ||
strcmp(r->key, "Input.CrouchMode") == 0) {
inputLayoutApply();
}
/* Linked aim/turn sensitivity (Input.SensLink, default on): moving either
* knob scales the other to hold the stock default ratio -- AimModeSens 38
* : MouseTurnSpeed 50 (the D194/D238 calibrated defaults). */
@@ -871,6 +898,8 @@ static const struct { const char *key; double def; } kResetDefaults[] = {
{ "Input.PadLookInvertY", 0 }, /* = 0 */
{ "Input.PadDeadzone", 7000 },/* = STICK_DEADZONE (7000) */
{ "Input.PadTriggerPct", 23 }, /* = 23 */
{ "Input.Layout", 0 }, /* = 0 (FPS defaults) */
{ "Input.CrouchMode", 0 }, /* = 0 (hold) */
/* GRAPHICS (port/src/video.c initializers) */
{ "Video.MSAA", 2 }, /* = 2 */
{ "Video.TextureFilter", 1 }, /* = 1 (bilinear) */