M-24: mouse-look rework (D118b/c) + real ge007.ini parser

port/src/input.c: mode-aware mouse->pad map matching bondviewProcessInput.
Aim mode (RMB held) drives the analog stick past +/-60 for proportional
yaw+pitch and emits no C-buttons; hipfire keeps digital C-up/C-down pitch.
Fixes D118b (mouse-Y inverted) and D118c (aim + mouse-down -> crouch, from
C-down being overloaded to crouch/lean/zoom in insightaimmode). D118a
(hipfire digital pitch vs analog yaw) residual, minor. Port-layer only.

port/src/config.c: implement configLoad/configSave (were TODO stubs).
Parses $S/ge007.ini ([Section] blocks, Key = value, #/; comments), clamps
ints to registered bounds, writes defaults on first run. New knob
Input.MouseTurnSpeed (hipfire yaw %) split from MouseAimSpeed.

Verified: -level_09 crash-free 900+ frames @ 91.65% (unregressed),
-level_20 crash-free, ini round-trips with no unknown-key warnings.
This commit is contained in:
James Dansereau committed 2026-08-30 11:59:52 -04:00
1 parent e9cf24ce7d
commit 546d803870
5 files changed
+355 -78

No files matched your search

+44 -7
View File
@@ -35,13 +35,16 @@ Full workstream breakdown (WS1–WS6) in `docs/PLAN-linear-level-sweep.md`.
Committed through `f2beae4b` (M-12: D121 WS1 boot, D122 propDef fix).
- **Input layer (Phase 3) landed and playtested** (D118, M-9/M-10).
`port/src/input.c` is real: keyboard+mouse + SDL_GameController → N64
pad. Core aim/move works. **Open polish bugs (deferred):** D118a mouse
yaw (analog) vs pitch (digital) mismatch; D118b mouse-Y inverted;
**D118c — in manual-aim mode, mouse-down → crouch** (mouse Y emits
C-down, which game logic maps to crouch/zoom in `insightaimmode`;
clean fix = the deferred analog-aim `#ifdef PORT` hook, §F D118);
rebinding / gamepad hotplug / `ge007.ini` generation still TODO.
Weapon switch on kbd = A button (`Space`/`Z`/`E`) — no dedicated key.
pad. Core aim/move works. **M-24 mouse-look rework** (`port/src/input.c`,
port-only): mode-aware map — aim mode (RMB) drives the analog stick past
±60 and emits no C-buttons; hipfire keeps digital C-up/C-down pitch.
**D118b (mouse-Y inverted) and D118c (aim + mouse-down → crouch) FIXED**;
**D118a residual** — hipfire pitch still digital vs analog yaw (minor).
`config.c` INI load/save now implemented → `ge007.ini` is written on
first run and re-read; `[Input]` `MouseEnabled` / `MouseAimSpeed` (50) /
`MouseTurnSpeed` (100) / `MouseInvertY` (0) are live-tunable. Rebinding /
gamepad hotplug still TODO. Weapon switch on kbd = A button
(`Space`/`Z`/`E`) — no dedicated key.
- **Recent fixes (M-10, `d1e93b76`):** D119 guard-attack crash
(`weapons_held[]->chr` type-pun) fixed; D120 blood-stain hang guarded
(not fixed — `d43_emit.py` opcode-0x18 converter gap).
@@ -228,6 +231,40 @@ in the docs-restructure — fold those separately).
weapon-switch — GE overloads it), `X`/`R`/`F` = B (reload), `Q` = L,
`Enter`/`Tab` = Start, `Esc` quit. No dedicated weapon key.
## Done this session (M-24) — mouse-look rework + real INI parser (uncommitted)
Pre-playtest QoL, port-layer only, no `src/` / game-logic change.
- **`port/src/input.c` mouse-look rework.** Read `bondviewProcessInput` /
`MoveData`: GE aim is **mode-dependent** — hipfire yaw = analog stick-X,
pitch = digital C-up/C-down (stick-Y = move, no pitch); aim mode (R held)
yaw+pitch = analog stick past ±60, and C-up/C-down there = crouch/lean/
zoom, *not* aim. New map keyed on our own RMB/LShift (hold-to-aim proxy):
aim mode pushes `stick_x/y` into the 61..80 band and emits **no**
C-buttons; hipfire keeps digital C-pitch. GE's native pitch is inverted
(C-up→look down) — hidden so mouse-down looks down; `MouseInvertY` flips.
- **D118b FIXED** (mouse-Y inversion), **D118c FIXED** (aim+down→crouch —
no `src/` hook needed, aim look no longer emits C-down).
- **D118a residual**: hipfire pitch digital vs analog yaw. Minor (precise
vertical aim is an aim-mode activity). Full fix = the deferred analog
`#ifdef PORT` `bondview.c` hook.
- **`port/src/config.c` — INI load/save implemented** (was a stub that only
logged TODO). Parses `$S/ge007.ini` (`[Section]` blocks, `Key = value`,
`#`/`;` comments), clamps ints to registered bounds, writes defaults on
first run, rewrites on clean exit. Unblocks live tuning of the mouse
knobs + all future video options. Note: constructor-registered options
present at `configLoad()` time (main.c:52) are captured; anything
registered later would miss the first save.
- **New knob** `Input.MouseTurnSpeed` (hipfire yaw %, default 100), split
from `MouseAimSpeed` (aim-mode %, default 50) since they feed different
game curves.
- Verified: `-level_09` boots crash-free to 900+ frames @ 91.65% coverage
(unregressed); `-level_20` crash-free; `ge007.ini` written then re-read
with no unknown-key warnings. Build green (`ntsc-final`).
- **Still TODO (not started):** key rebinding, gamepad hotplug
(`SDL_CONTROLLERDEVICEADDED/REMOVED`), mouse-wheel weapon cycle,
screenshot/mouse-release hotkeys, on-screen FPS overlay.
## Next task (M-24 — Opus 5)
**Scope call (user, M-23):** D139 (stage unload) + D140 (pause menu / watch)
+39 -20
View File
@@ -814,7 +814,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D108–D112 | skeletal models: `d43_emit.py put_f32` byte-reversal bug | resolved |
| D113–D116 | portal BFS ok · matrix chain ok · player raw-offset audit + `gunfire.c` THROW* · HUD text X-mirror | D116 OPEN (parked — DO NOT re-static-trace) |
| D117 | frame nondeterminism root-caused; `GE_DETERM` deferred; `framediff.py` structural | resolved (mode deferred) |
| D118 | SDL input layer (`port/src/input.c`); D118a/b/c mouse polish open (D118c: aim-mode mouse-down → crouch, digital C-button overload — needs the deferred analog-aim hook) | resolved (polish open) |
| D118 | SDL input layer (`port/src/input.c`); M-24 mouse-look rework: mode-aware map (aim mode = analog stick, hipfire = digital C-pitch), `config.c` INI now real. D118b/c FIXED; D118a residual (hipfire pitch only) | resolved (D118a residual) |
| D137 | right-mouse (aim-sight) crash: `gunDrawSight` (`gunfire.c:6235`) `s32 sp54` holds a `Gfx*` the whole function (`sp54 = *gdl`; `texSelect(&sp54,…)`/`display_image_at_position(&sp54,…)` take `Gfx**`). As N64 `s32` it is 4 B, so `*(Gfx**)&sp54` reads 4 B of the adjacent stack float as the pointer high word → `_g = 0x03e4fca0_70081220` → wild DL write in `texSetRenderMode` (`othermodemicrocode.c:177`) the moment the crosshair raises. §A. | FIXED (`src/game/gunfire.c` `#ifdef PORT` — `sp54` is `Gfx *`) |
| D119 · D120 | guard-attack `weapons_held[]->chr` pun crash · blood-stain `PointUsage[]` chain hang (guarded, `d43_emit.py` opcode-0x18 gap) | D119 fixed · D120 guarded |
| D121 | WS1 frictionless per-level boot: bare `-level_XX` injects its `memallocstringtable[]` `-m*` row (`#ifdef PORT` in `boss.c`) | resolved |
@@ -3913,26 +3913,45 @@ UNTESTED (headless agent) — needs a human pass: see
`docs/HANDOFF.md`. Confidence: build/wiring HIGH; in-game feel of the
mouse→C-button bridge MEDIUM (aim speed tuning likely needed); gamepad
UNVERIFIED (none present in build env).
TODO: (1) human playtest + tune `MouseAimSpeed`; (2) key rebinding;
(3) gamepad hotplug (opened only at init today); (4) optional analog-aim
`#ifdef PORT` hook in bondview.c for true mouse-look.
TODO: (1) key rebinding; (2) gamepad hotplug (opened only at init today);
(3) optional analog-aim `#ifdef PORT` hook in bondview.c — only needed now
for D118a (fully-analog hipfire pitch) and toggle-aim-scheme correctness.
[M-24: mouse-look rework + real INI parser done — see "Mouse-look rework"
below.]
*Open polish bugs (from WS6 playtest, M-22):*
- **D118a** — mouse yaw (analog, `stick_x += aimDX`) vs pitch (digital
C-up/C-down pulse) feel very different; pitch is steppy/slow.
- **D118b** — mouse-Y may feel inverted (toggle: `ge007.ini [Input]
MouseInvertY`).
- **D118c** — **in manual-aim mode (right-mouse), moving the mouse down to
look down makes Bond crouch.** Root cause is the same digital bridge: mouse
Y emits `GE_CONT_D` (C-down), and `bondview2.c` `MoveData` maps
`C-down` in `insightaimmode` to `crouchDown` (`bondview2.c:5351`) / zoom
(`:5340`, DISABLE_CROUCH weapons) as well as free-aim pitch. The C-button
is genuinely overloaded by mode in game logic — the only clean fix is the
deferred analog-aim `#ifdef PORT` hook (TODO 4), which would drive pitch
without touching C-down. Low priority (aim-mode look-down is rare in
normal play). No cheap input-layer-only fix — suppressing C-down in aim
mode would break intentional crouch/zoom.
- **D137 (FIXED)** — right-mouse *crash* (not this crouch bug):
*Mouse-look rework — M-24 (`port/src/input.c`, port-layer only, no `src/`
change).* Root reading of `bondviewProcessInput`/`MoveData`: GE's aim is
**mode-dependent** — hipfire (`!insightaimmode`) yaw = analog stick-X,
pitch = digital C-up/C-down (stick-Y = move, does not pitch); aim mode
(R held) yaw+pitch = analog stick past ±60 → `(stick-60)/10`, and
C-up/C-down there = crouch/lean/zoom (`bondview2.c:5340,5351`), *not* aim.
Also GE's native pitch is inverted (`U_CBUTTONS → speedVertaDown`, i.e.
C-up looks down, `:5272-5279`). New mapping in `inputComputePad`, keyed on
our own RMB/LShift state as the hold-to-aim proxy:
- **aim mode** → push `stick_x`/`stick_y` into the 61..80 band
(proportional to per-poll mouse delta × `MouseAimSpeed`/100 × 4), **emit
no C-buttons**. Yaw and pitch now identical analog feel.
- **hipfire** → yaw `stick_x += dx × MouseTurnSpeed/100 × 6`; pitch =
digital C-up/C-down on `|dy| ≥ 1.5` px/poll.
- "mouse-down = look down" by default (accounts for GE's inversion);
`MouseInvertY` flips.
Per-poll deltas, no accumulator (rate device). `ge007.ini [Input]`:
`MouseEnabled`, `MouseAimSpeed` (50), `MouseTurnSpeed` (100), `MouseInvertY`
(0) — **now actually parsed** (`config.c` INI load/save implemented, M-24).
- **D118b (FIXED, M-24)** — mouse-Y inversion resolved by the "mouse-down
looks down" default above + `MouseInvertY` toggle.
- **D118c (FIXED, M-24)** — aim + mouse-down → crouch. Fixed *without* a
`src/` hook: in aim mode the mouse now drives the analog stick and emits
no C-button, so the crouch/lean/zoom mappings are never triggered by
look input. (Proxy caveat: a toggle-aim control scheme would need a read
of `g_CurrentPlayer->insightaimmode` since RMB-held ≠ aim state there.)
- **D118a (RESIDUAL, lower)** — in **hipfire only**, yaw (analog) vs pitch
(digital C-button) still feel different. Aim mode is now fully analog and
consistent. A fully-analog hipfire pitch would need the `#ifdef PORT`
`bondview.c` hook (TODO 4). Minor — precise vertical aim in GE happens in
aim mode, not hipfire.
- **D137 (FIXED)** — right-mouse *crash* (not the crouch bug):
`gunDrawSight` `s32 sp54` truncated a `Gfx*`; see §F D137.
- Weapon switch on kbd/mouse = the A button (`Space`/`Z`/`E`), same as
action/use (GE overloads it). No dedicated key; mouse-wheel cycle is a
+12 -5
View File
@@ -206,11 +206,18 @@ through a converter or a runtime bswap fixup reads scrambled.
macro. libultra.c wraps the include in `#pragma push_macro("errno")` /
`#undef errno`; cleaner for a new module is to duplicate the handful of
`CONT_*` bits it needs (D118 `input.c`).
- GE aims with **digital C-buttons**, no analog-aim hook outside `src/`.
Mouse-look is bridged by integrating the relative-mouse delta into a
clamped per-axis accumulator and emitting a C-button per poll while
|accum| ≥ 0.5, draining one unit → proportional press *dwell*. Non-linear
(GE's own accel curve) but playable (D118).
- GE's aim model is **mode-dependent** (`bondview2.c bondviewProcessInput`):
in **hipfire** (`!insightaimmode`) yaw = analog stick-X ("natural turn")
and pitch = **digital C-up/C-down only** (stick-Y is move fwd/back); in
**aim mode** (R held) yaw *and* pitch are analog — stick pushed past ±60
→ proportional `(stick-60)/10` — and **C-up/C-down mean crouch/lean/zoom,
not aim**. So the mouse→pad map must also be mode-aware: aim mode pushes
the stick into the 61..80 band and emits **no** C-buttons (emitting C-down
for "look down" while aiming = crouch, D118c); hipfire keeps the digital
C-button pitch. `input.c` reads its own RMB/LShift state as the mode proxy
(exact for hold-to-aim; a toggle scheme needs `g_CurrentPlayer->
insightaimmode`). GE's native pitch is **inverted** (C-up → look down) —
hide it so mouse-down looks down; `MouseInvertY` flips (D118, M-24).
- `osContGetReadData(pad)` must fill **one OSContPad per channel**
(`MAXCONTROLLERS`-long array), not just controller 0 — joy.c passes the
whole `samples[i].pads` array (D118).
+174 -6
View File
@@ -2,11 +2,20 @@
* INI configuration (ge007.ini).
*
* A tiny key=value store. Options register themselves (usually via a
* PD_CONSTRUCTOR) and are loaded from / saved to the ini in the data dir.
* PD_CONSTRUCTOR) and are loaded from / saved to "$S/ge007.ini".
*
* Modelled on the PD port's port/src/config.c (~300 lines).
* Modelled on the PD port's port/src/config.c (~300 lines) but trimmed:
* Int + String options only (GE's config surface is small so far).
*
* STATUS: scaffolding stub — implement during Phase 1.
* File format: keys are dotted ("Input.MouseAimSpeed"). On disk they are
* grouped into `[Section]` blocks by the part before the last '.', e.g.
*
* [Input]
* MouseAimSpeed = 50
* MouseInvertY = 0
*
* A bare `Key = value` with no preceding section, or a fully dotted
* `Section.Key = value` line, are both accepted on read.
*/
#include <stdio.h>
@@ -18,6 +27,7 @@
#include "config.h"
#define MAX_OPTIONS 256
#define INI_PATH "$S/ge007.ini"
struct IntOption { char key[64]; int *value; int min, max; };
struct StrOption { char key[64]; char *value; int bufSize; };
@@ -46,13 +56,171 @@ void configRegisterString(const char *key, char *value, int bufSize)
o->bufSize = bufSize;
}
static int parseInt(const char *s)
{
while (*s == ' ' || *s == '\t') s++;
int sign = 1;
if (*s == '-') { sign = -1; s++; }
else if (*s == '+') s++;
int v = 0;
while (*s >= '0' && *s <= '9') v = v * 10 + (*s++ - '0');
return v * sign;
}
static int lc(int c) { return (c >= 'A' && c <= 'Z') ? c + 32 : c; }
static int isws(int c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; }
static int ci_eq(const char *a, const char *b)
{
for (; *a && *b; a++, b++) {
if (lc((unsigned char)*a) != lc((unsigned char)*b))
return 0;
}
return *a == *b;
}
static char *trim(char *s)
{
while (*s && isws((unsigned char)*s)) s++;
char *end = s + strlen(s);
while (end > s && isws((unsigned char)end[-1])) *--end = 0;
return s;
}
static void applyKV(const char *dottedKey, const char *val)
{
for (int i = 0; i < numIntOpts; i++) {
if (ci_eq(intOpts[i].key, dottedKey)) {
int v = parseInt(val);
if (intOpts[i].min != intOpts[i].max) {
if (v < intOpts[i].min) v = intOpts[i].min;
if (v > intOpts[i].max) v = intOpts[i].max;
}
*intOpts[i].value = v;
return;
}
}
for (int i = 0; i < numStrOpts; i++) {
if (ci_eq(strOpts[i].key, dottedKey)) {
strncpy(strOpts[i].value, val, strOpts[i].bufSize - 1);
strOpts[i].value[strOpts[i].bufSize - 1] = 0;
return;
}
}
sysLogPrintf(LOG_NOTE, "config: unknown key '%s' (ignored)", dottedKey);
}
void configLoad(void)
{
/* TODO(Phase 1): parse "$S/ge007.ini", apply to registered options. */
sysLogPrintf(LOG_INFO, "configLoad: TODO (Phase 1)");
const char *path = sysResolvePath(INI_PATH);
FILE *f = fopen(path, "r");
if (!f) {
sysLogPrintf(LOG_INFO, "config: no %s yet; writing defaults", path);
configSave();
return;
}
char line[512];
char section[64] = "";
while (fgets(line, sizeof(line), f)) {
char *p = trim(line);
if (!*p || *p == '#' || *p == ';')
continue;
if (*p == '[') {
char *close = strchr(p, ']');
if (close) {
*close = 0;
strncpy(section, trim(p + 1), sizeof(section) - 1);
section[sizeof(section) - 1] = 0;
}
continue;
}
char *eq = strchr(p, '=');
if (!eq)
continue;
*eq = 0;
char *key = trim(p);
char *val = trim(eq + 1);
char dotted[128];
if (strchr(key, '.') || !section[0])
snprintf(dotted, sizeof(dotted), "%s", key);
else
snprintf(dotted, sizeof(dotted), "%s.%s", section, key);
applyKV(dotted, val);
}
fclose(f);
sysLogPrintf(LOG_INFO, "config: loaded %s", path);
}
/* Section name = everything before the last '.'; "" if the key isn't dotted. */
static void splitKey(const char *dotted, char *sec, int secSize, const char **leaf)
{
const char *dot = strrchr(dotted, '.');
if (dot) {
int n = (int)(dot - dotted);
if (n > secSize - 1) n = secSize - 1;
memcpy(sec, dotted, n);
sec[n] = 0;
*leaf = dot + 1;
} else {
sec[0] = 0;
*leaf = dotted;
}
}
void configSave(void)
{
/* TODO(Phase 1): write all registered options to "$S/ge007.ini". */
const char *path = sysResolvePath(INI_PATH);
FILE *f = fopen(path, "w");
if (!f) {
sysLogPrintf(LOG_WARNING, "config: cannot write %s", path);
return;
}
fputs("# GoldenEye 007 PC port config. Edit while the game is closed;\n"
"# rewritten (comments dropped) on a clean exit.\n", f);
/* Emit grouped by section, preserving registration order within each.
* O(n^2) over a handful of options -- fine. */
char done[MAX_OPTIONS] = {0};
for (int i = 0; i < numIntOpts; i++) {
if (done[i]) continue;
char sec[64];
const char *leaf;
splitKey(intOpts[i].key, sec, sizeof(sec), &leaf);
(void)leaf;
fprintf(f, "\n[%s]\n", sec[0] ? sec : "General");
for (int j = i; j < numIntOpts; j++) {
char sj[64];
const char *lj;
splitKey(intOpts[j].key, sj, sizeof(sj), &lj);
if (ci_eq(sj, sec)) {
fprintf(f, "%s = %d\n", lj, *intOpts[j].value);
done[j] = 1;
}
}
}
char sdone[MAX_OPTIONS] = {0};
for (int i = 0; i < numStrOpts; i++) {
if (sdone[i]) continue;
char sec[64];
const char *leaf;
splitKey(strOpts[i].key, sec, sizeof(sec), &leaf);
(void)leaf;
fprintf(f, "\n[%s]\n", sec[0] ? sec : "General");
for (int j = i; j < numStrOpts; j++) {
char sj[64];
const char *lj;
splitKey(strOpts[j].key, sj, sizeof(sj), &lj);
if (ci_eq(sj, sec)) {
fprintf(f, "%s = %s\n", lj, strOpts[j].value);
sdone[j] = 1;
}
}
}
fclose(f);
sysLogPrintf(LOG_INFO, "config: wrote %s", path);
}
+86 -40
View File
@@ -21,7 +21,7 @@
*
* Keyboard + mouse (controller 0):
* W/S/A/D or arrows .. analog stick (move / strafe)
* mouse motion ....... C-buttons (proportional pulse -- see below)
* mouse motion ....... aim (mode-aware -- see MOUSE-LOOK below)
* left mouse / LCtrl . Z trigger (fire)
* right mouse / LShift R trigger (aim mode)
* Space / Z / E ...... A button (action / use)
@@ -42,23 +42,30 @@
* D-pad ............ N64 D-pad
* Start ............ Start
*
* MOUSE-LOOK -> C-BUTTON BRIDGE
* GE has no analog-aim hook we can reach without touching src/ (game
* logic). The only lever is OSContPad, so mouse motion is converted to
* digital C-button presses. inputUpdate() integrates the relative mouse
* delta (scaled by Input.MouseAimSpeed/100) into a signed accumulator per
* axis, clamped to +/-AIM_ACCUM_CLAMP. Each poll inputComputePad() emits
* the matching C-button while |accum| >= 0.5 and drains one unit. A large
* flick therefore holds the C-button down for several frames
* (proportional dwell) rather than a single blip.
* MOUSE-LOOK (mode-aware, no src/ changes)
* GE's aim model (bondview2.c bondviewProcessInput / MoveData) is
* mode-dependent, so the mouse->pad mapping is too:
*
* Limitations: (a) it is still digital -- GE applies its own accel curve
* to a held C-button, so aim speed is not linear in mouse speed;
* (b) very fast flicks saturate at the clamp (~8 frames of turn);
* (c) diagonal precision is limited to the 8 C-button combinations.
* Tunable via ge007.ini [Input] MouseAimSpeed / MouseInvertY. A true
* analog aim path would need an #ifdef PORT hook in bondview.c and is
* left as a TODO.
* - Hipfire (!insightaimmode): yaw = analog stick-X ("natural turn");
* pitch = DIGITAL C-up/C-down (stick-Y here is move fwd/back and does
* not pitch). Mouse Y emits a C-button on polls where it moved.
* - Aim mode (R / RMB held): yaw AND pitch are analog -- the stick pushed
* past +/-60 gives proportional (stick-60)/10 aim speed. Mouse X/Y push
* the stick into the 61..80 band. We emit NO C-buttons in this mode
* because there they mean crouch / lean / zoom (this was D118c:
* aim + look-down -> crouch).
*
* "aim button held" is read from our own RMB/LShift state -- exact for
* the default hold-to-aim scheme; a toggle-aim scheme would need a read
* of g_CurrentPlayer->insightaimmode (still no logic change).
*
* GE's native pitch is inverted ("flight" style: C-up -> look down). We
* hide that: mouse-down looks down by default, MouseInvertY flips it.
*
* Tunable via ge007.ini [Input]: MouseAimSpeed (aim mode), MouseTurnSpeed
* (hipfire yaw), MouseInvertY, MouseEnabled. Residual: in hipfire, yaw
* (analog) and pitch (digital) still feel different (D118a) -- a fully
* analog hipfire pitch needs an #ifdef PORT hook in bondview.c (TODO).
* ------------------------------------------------------------------------
*/
@@ -94,26 +101,38 @@
#define STICK_MAX 80
#define TRIG_THRESHOLD (30 * 256)
#define RSTICK_THRESHOLD 0x4000
#define AIM_ACCUM_CLAMP 8.0
#define AIM_EMIT_THRESHOLD 0.5
#define MOUSE_TURN_GAIN 6.0 /* per-poll aimDX units -> stick-X counts */
/* Mouse-look tuning. GE's aim model is mode-dependent (bondview2.c
* bondviewProcessInput):
* - Hipfire (!insightaimmode): yaw = analog stick-X ("natural turn"),
* pitch = DIGITAL C-up/C-down only (stick-Y is move fwd/back here).
* - Aim mode (R held): yaw AND pitch = analog stick pushed past
* +/-60 -> proportional (stick-60)/10. C-up/C-down mean crouch/lean/
* zoom in this mode, NOT aim -- so we must NOT emit them while aiming
* (that was D118c: aim + mouse-down -> crouch).
* GE's native pitch is inverted ("flight" style): C-up -> look down. We
* hide that so mouse-down looks down by default; MouseInvertY flips it. */
#define MOUSE_TURN_GAIN 6.0 /* hipfire: raw px this poll -> stick-X counts */
#define MOUSE_PITCH_THRESH 1.5 /* hipfire: px/poll before a C-button fires */
#define AIM_BAND 20 /* aim mode: usable stick range above the 60 gate */
#define AIM_GAIN 4.0 /* aim mode: px/poll -> counts into the band */
#define AIM_MOVE_THRESH 0.5 /* aim mode: px/poll before the stick moves */
static int numControllers = 1;
static int connectedMask = 0x1; /* controller 0 always present */
static SDL_GameController *pads[MAX_PADS];
static int mouseEnabled = 1;
static int mouseAimSpeed = 50; /* /100 -> mouse-px to accumulator units */
static int mouseInvertY = 0;
static int mouseEnabled = 1;
static int mouseAimSpeed = 50; /* aim-mode sensitivity, percent */
static int mouseTurnSpeed = 100; /* hipfire yaw sensitivity, percent */
static int mouseInvertY = 0; /* 1 = mouse-down looks up */
/* Per-poll mouse-look deltas. NOT a persistent accumulator: mouse-look is a
* displacement device, GE's C-buttons are a rate device. Integrating and
* draining turned a flick into a pegged, slowly-recovering "stuck looking
* up/down". We now emit a C-button only on polls where the mouse actually
* moved that poll; stop moving -> button releases -> GE auto-centres. */
static double aimDX = 0.0;
static double aimDY = 0.0;
/* Raw relative-mouse delta accumulated since the last inputComputePad(0).
* NOT a persistent aim accumulator: mouse-look is a displacement device and
* GE's aim is a rate device, so we consume the whole delta each poll and
* reset -- stop moving and the stick/ C-button releases immediately. */
static double mouseDX = 0.0;
static double mouseDY = 0.0;
/* ------------------------------------------------------------------------ */
@@ -200,9 +219,10 @@ void inputUpdate(void)
int dx = 0, dy = 0;
SDL_GetRelativeMouseState(&dx, &dy);
const double scale = (double)mouseAimSpeed / 100.0;
aimDX = dx * scale;
aimDY = dy * scale * (mouseInvertY ? -1.0 : 1.0);
/* Raw px; per-mode sensitivity is applied in inputComputePad(). Accumulate
* in case inputUpdate() is polled more than once between pad reads. */
mouseDX += dx;
mouseDY += dy;
}
static int scaleAxis(int v)
@@ -251,7 +271,9 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y)
if ((mb & SDL_BUTTON(SDL_BUTTON_LEFT)) || keyDown(ks, SDL_SCANCODE_LCTRL))
button |= GE_CONT_G;
if ((mb & SDL_BUTTON(SDL_BUTTON_RIGHT)) || keyDown(ks, SDL_SCANCODE_LSHIFT))
int aimHeld = (mb & SDL_BUTTON(SDL_BUTTON_RIGHT)) ||
keyDown(ks, SDL_SCANCODE_LSHIFT);
if (aimHeld)
button |= GE_CONT_R;
if (keyDown(ks, SDL_SCANCODE_SPACE) || keyDown(ks, SDL_SCANCODE_Z) ||
keyDown(ks, SDL_SCANCODE_E))
@@ -264,14 +286,37 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y)
if (keyDown(ks, SDL_SCANCODE_RETURN) || keyDown(ks, SDL_SCANCODE_TAB))
button |= GE_CONT_START;
/* Mouse X -> analog turn (stick X, GE yaw is analog, not a C-button).
* Mouse Y -> C-up/C-down look (GE pitch has no analog hook). Per-poll,
* no carryover: motion this poll only, so releasing stops immediately. */
/* Mouse-look. Mode-dependent (see the tuning-constants comment):
* aim mode -> push the analog stick past +/-60 for proportional
* yaw + pitch; emit NO C-buttons (they mean crouch here).
* hipfire -> yaw on analog stick-X; pitch on digital C-up/C-down.
* "look down" convention: mouse-down looks down by default; GE's
* native pitch is inverted so hipfire down = C-up (GE_CONT_E) and
* aim-mode down = +stick_y. MouseInvertY flips both. */
if (mouseEnabled) {
sx += (int)(aimDX * MOUSE_TURN_GAIN);
if (aimDY >= AIM_EMIT_THRESHOLD) button |= GE_CONT_D;
else if (aimDY <= -AIM_EMIT_THRESHOLD) button |= GE_CONT_E;
double invert = mouseInvertY ? -1.0 : 1.0;
double dyLook = mouseDY * invert; /* >0 => look down */
if (aimHeld) {
double gx = fabs(mouseDX) * (mouseAimSpeed / 100.0) * AIM_GAIN;
double gy = fabs(dyLook) * (mouseAimSpeed / 100.0) * AIM_GAIN;
if (fabs(mouseDX) >= AIM_MOVE_THRESH) {
int m = 61 + (int)gx; if (m > 60 + AIM_BAND) m = 60 + AIM_BAND;
sx += (mouseDX > 0) ? m : -m;
}
if (fabs(dyLook) >= AIM_MOVE_THRESH) {
int m = 61 + (int)gy; if (m > 60 + AIM_BAND) m = 60 + AIM_BAND;
sy += (dyLook > 0) ? m : -m; /* +stick_y = look down */
}
} else {
sx += (int)(mouseDX * (mouseTurnSpeed / 100.0) * MOUSE_TURN_GAIN);
if (dyLook >= MOUSE_PITCH_THRESH) button |= GE_CONT_E; /* C-up = look down */
else if (dyLook <= -MOUSE_PITCH_THRESH) button |= GE_CONT_D; /* C-down = look up */
}
}
mouseDX = 0.0;
mouseDY = 0.0;
}
/* ---- gamepad ---- */
@@ -348,5 +393,6 @@ PD_CONSTRUCTOR static void inputConfigInit(void)
{
configRegisterInt("Input.MouseEnabled", &mouseEnabled, 0, 1);
configRegisterInt("Input.MouseAimSpeed", &mouseAimSpeed, 1, 500);
configRegisterInt("Input.MouseTurnSpeed", &mouseTurnSpeed, 1, 500);
configRegisterInt("Input.MouseInvertY", &mouseInvertY, 0, 1);
}