mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-24 00:02:45 +02:00
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:
1 parent
e9cf24ce7d
commit
546d803870
5 files changed
+355
-78
No files matched your search
+44
-7
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user