diff --git a/docs/controls.md b/docs/controls.md index 5091bbb3..d938be3c 100644 --- a/docs/controls.md +++ b/docs/controls.md @@ -18,7 +18,25 @@ amplitude before the strength (0.2 to 1.5, default 0.6; lower makes weak pulses The log names the layout in effect (`input: gamepad layout ...`) and each gamepad's first press (`input: gamepad "...": first press`); the menu shows the layout under *Automatic*. The game's prompts show the Switch -letters. +letters unless *Button prompts* says otherwise (below). + +## Button prompts + +`NX2NAVI_PROMPTS` (`prompts`, the *Button prompts* row on the Controls tab) picks the controller whose buttons the game's prompts show: `nintendo` (default, the game's own), `xbox` or `playstation`. It changes three things the game draws: + +- the button font every prompt in text uses (dialogue, the button guides, the controls screen): A B X Y, L R ZL ZR, Plus and Minus, the d-pad and the sticks with their directions become that pad's buttons (LB RB LT RT, Menu and View; L1 R1 L2 R2, Options and Create; its d-pad and sticks). Power, Home and the console stay; +- the letter in the action prompt (top right); +- the ocarina's note buttons. + +A, B, X and Y show the button the *Button layout* presses: with `nintendo` (by position) Switch A is the right face button, so its prompts show B or circle, B shows A or cross, X shows Y or triangle and Y shows X or square; with `xbox` and `automatic` (by label) A shows A or cross, B shows B or circle, X shows X or square and Y shows Y or triangle. The game reads the font and the interface textures once at start, so a change shows after a restart (the row says so; the menu's Restart button restarts). A button remapped on this tab keeps its old prompt. + +The prompts live in four overlay folders next to the exe, `romfs-patches-xbox`, `romfs-patches-xbox-label`, `romfs-patches-playstation` and `romfs-patches-playstation-label` (by position and by label), which `runtime/romfs.c` loads in place of the game's files. `tools/pad_prompts.py` draws them on the player's machine from the player's own RomFS and a folder of button art, which the repository does not hold: + +``` +python tools\pad_prompts.py --art \Source --game +``` + +The art folder holds `XGamepad/Default`, `XGamepad/Light`, `P5Gamepad/Default` and `P5Gamepad/Light` with the file names of `STYLES` in the tool. Without the folders the row says *prompt files missing* and the Switch prompts stay; so they do when a RomFS mod replaces one of the same files (*off: a RomFS mod replaces their files*). The log has a `RomFS overlay: ... button prompts` line for the folder in use. The tool needs fontTools, Pillow and zstandard (`tools/requirements-py314.txt`). The actions are the game's own, as its *Basic Controls* screen (Plus menu, System) names them: diff --git a/nx2navi.ini.example b/nx2navi.ini.example index 1a7c7803..37404a5c 100644 --- a/nx2navi.ini.example +++ b/nx2navi.ini.example @@ -291,11 +291,10 @@ # PlayStation pad) or auto (nintendo for a pad with Nintendo labels, xbox for the others). The bind_ lines below apply # on top. Changes while the game runs take effect at once. 0.1.9's xbox_prompts is read as nintendo with prompts = xbox. # pad_layout = nintendo -# The buttons the game's prompts show: nintendo (the game's own), xbox (LB RB LT RT, Menu, View and a d-pad cross, -# and A B X Y as labelled on the button pad_layout presses: with nintendo, A shows as B and X as Y). Read at start -# (docs/controls.md, "Xbox button prompts"). pc: the key or mouse button the keyboard defaults press each button with -# (E, Shift, Space, left click, Q, R, right click, F, Tab, M, 1 to 4, W A S D, Ctrl, C) and the mouse for the right -# stick, whatever pad_layout (docs/controls.md, "PC button prompts"). +# The buttons the game's prompts show: nintendo (the game's own), xbox (LB RB LT RT, Menu, View) or playstation (L1 R1 +# L2 R2, Options, Create), with A B X Y as the button pad_layout presses is labelled: with nintendo, A shows as B or +# circle and X as Y or triangle. Read at start; their files come from tools/pad_prompts.py (docs/controls.md, "Button +# prompts"). pc, the keys and mouse buttons, has no files for this game. # prompts = nintendo # Gamepad stick dead zone in percent of full deflection (0 to 50). Raise it for a stick that drifts: inside the circle # the stick reads centred, and the rest of its travel is scaled to start there. diff --git a/runtime/cfg.c b/runtime/cfg.c index 3539201a..cb330feb 100644 --- a/runtime/cfg.c +++ b/runtime/cfg.c @@ -151,7 +151,7 @@ static const CfgMeta metas[] = { {"VOLUME", "100", "0..100", LIVE}, {"MENU_SOUND_VOLUME", "80", "0..100", LIVE}, {"AUDIO_CHANNELS", "stereo", "auto|stereo|5.1", RESTART}, {"MOTION", "1", "0|1", LIVE}, {"RUMBLE_STRENGTH", "130", "0..300", LIVE}, {"RUMBLE_CURVE", "0.6", "0.2..1.5", LIVE}, {"RUMBLE_TRIGGERS", "0", "0|1", LIVE}, {"PAD_LAYOUT", "nintendo", "auto|nintendo|xbox", LIVE}, - {"PROMPTS", "nintendo", "nintendo|xbox|pc", RESTART}, + {"PROMPTS", "nintendo", "nintendo|xbox|playstation|pc", RESTART}, {"STICK_DEADZONE", "8", "0..50", LIVE}, {"STICK_SENS", "1", "0.1..3", RESTART}, {"LSTICK_DEADZONE", "", "0..50", LIVE}, {"RSTICK_DEADZONE", "", "0..50", LIVE}, {"MOUSE_MODE", "direct", "stick|direct", LIVE}, diff --git a/runtime/cfg_probe.c b/runtime/cfg_probe.c index d5790ef4..ccd6dbd8 100644 --- a/runtime/cfg_probe.c +++ b/runtime/cfg_probe.c @@ -338,14 +338,26 @@ int probe_cfg(void) { EQ(input_prompts_folder(PROMPTS_XBOX, "auto"), "romfs-patches-xbox-label") && EQ(input_prompts_folder(PROMPTS_XBOX, NULL), "romfs-patches-xbox-label"), "prompts: the face buttons by position for the Nintendo layout, by label for xbox and auto"); - // romfs-patches-xbox shows A as B (tests/romfs_prompts_test.py checks the folders' contents). It is right exactly - // where the layout puts A on the east button, which an Xbox pad labels B. + CHECK(input_prompts_pick("PlayStation", "nintendo") == PROMPTS_PLAYSTATION && + input_prompts_pick("playstation", "xbox_prompts") == PROMPTS_PLAYSTATION && + input_prompts_pick("ps", "nintendo") == PROMPTS_NINTENDO, + "prompts: playstation in any case; an unknown value is the game's own"); + CHECK(EQ(input_prompts_folder(PROMPTS_PLAYSTATION, "nintendo"), "romfs-patches-playstation") && + EQ(input_prompts_folder(PROMPTS_PLAYSTATION, "xbox_prompts"), "romfs-patches-playstation") && + EQ(input_prompts_folder(PROMPTS_PLAYSTATION, "xbox"), "romfs-patches-playstation-label") && + EQ(input_prompts_folder(PROMPTS_PLAYSTATION, "auto"), "romfs-patches-playstation-label") && + EQ(input_prompts_folder(PROMPTS_PLAYSTATION, NULL), "romfs-patches-playstation-label"), + "prompts: the PlayStation face buttons by position for the Nintendo layout, by label for xbox and auto"); + // romfs-patches-xbox shows A as B and romfs-patches-playstation A as circle (tools/pad_prompts.py FACE). That is + // right exactly where the layout puts A on the east button, which an Xbox pad labels B and a PlayStation pad circle. input_map_reset(); static const char *const layouts[] = {"nintendo", "xbox", "auto", "xbox_prompts"}; for (int i = 0; i < 4; i++) { int a_east = input_map_pad_buttons((uint8_t[PAD_COUNT]){[PAD_EAST] = 1}, input_pad_layout_pick(layouts[i], 0)) == 1ull << B_A; CHECK(a_east == EQ(input_prompts_folder(PROMPTS_XBOX, layouts[i]), "romfs-patches-xbox"), "prompts: the folder names A as B exactly when the layout puts A on the button labelled B"); + CHECK(a_east == EQ(input_prompts_folder(PROMPTS_PLAYSTATION, layouts[i]), "romfs-patches-playstation"), + "prompts: the folder names A as circle exactly when the layout puts A on the circle button"); } // The default layout is the Switch's own buttons by position, whatever the pad's labels (an Xbox pad's east button // is labelled B and does Switch A): A right, B bottom, X top, Y left, L/R the bumpers, ZL/ZR the triggers. diff --git a/runtime/input_map.c b/runtime/input_map.c index 6a60765c..984b4eff 100644 --- a/runtime/input_map.c +++ b/runtime/input_map.c @@ -297,16 +297,21 @@ int input_pad_layout_pick(const char *setting, int east_labelled_a) { int input_prompts_pick(const char *prompts, const char *pad_layout) { if (prompts && *prompts) - return !strcasecmp(prompts, "xbox") ? PROMPTS_XBOX : !strcasecmp(prompts, "pc") ? PROMPTS_PC : PROMPTS_NINTENDO; + return !strcasecmp(prompts, "xbox") ? PROMPTS_XBOX + : !strcasecmp(prompts, "playstation") ? PROMPTS_PLAYSTATION + : !strcasecmp(prompts, "pc") ? PROMPTS_PC + : PROMPTS_NINTENDO; return pad_layout && !strcasecmp(pad_layout, "xbox_prompts") ? PROMPTS_XBOX : PROMPTS_NINTENDO; } const char *input_prompts_folder(int prompts, const char *pad_layout) { if (prompts == PROMPTS_PC) return "romfs-patches-pc"; - if (prompts != PROMPTS_XBOX) return NULL; + if (prompts != PROMPTS_XBOX && prompts != PROMPTS_PLAYSTATION) return NULL; // The folders are read at start, before a pad is open: auto counts as Xbox, as for a pad without Nintendo labels. // A Nintendo-labelled pad under auto plays by label too (its A is on the button labelled A), so it matches as well. - return input_pad_layout_pick(pad_layout, 0) == PAD_LAYOUT_NINTENDO ? "romfs-patches-xbox" : "romfs-patches-xbox-label"; + int by_position = input_pad_layout_pick(pad_layout, 0) == PAD_LAYOUT_NINTENDO; + if (prompts == PROMPTS_PLAYSTATION) return by_position ? "romfs-patches-playstation" : "romfs-patches-playstation-label"; + return by_position ? "romfs-patches-xbox" : "romfs-patches-xbox-label"; } uint64_t input_map_pad_buttons(const uint8_t down[PAD_COUNT], int layout) { diff --git a/runtime/input_map.h b/runtime/input_map.h index 2b297e4f..40c128c2 100644 --- a/runtime/input_map.h +++ b/runtime/input_map.h @@ -87,20 +87,21 @@ void input_map_configure(void); // pad's east button is labelled A (a Switch Pro Controller, a GuliKit) and Xbox otherwise. int input_pad_layout_pick(const char *setting, int east_labelled_a); -// Button prompts (NX2NAVI_PROMPTS): the controller whose buttons the game's prompts show. Nintendo: the game's own. Xbox: -// LB RB LT RT, Menu, View and a d-pad cross for the other buttons, and the face buttons named by the label of the -// button the pad layout presses (runtime/romfs.c overlay_init, tools/make_romfs_patches.py --prompts). PC: the key or -// mouse button the keyboard defaults press each button with, the mouse for the right stick. -enum { PROMPTS_NINTENDO, PROMPTS_XBOX, PROMPTS_PC }; -// The prompts for a NX2NAVI_PROMPTS value ("nintendo", "xbox" or "pc", any case). Unset or empty: Xbox while the pad -// layout setting is "xbox_prompts", else Nintendo. +// Button prompts (NX2NAVI_PROMPTS): the controller whose buttons the game's prompts show. Nintendo: the game's own. Xbox +// and PlayStation: that pad's buttons (LB RB LT RT, Menu and View; L1 R1 L2 R2, Options and Create), and the face buttons +// named by the label of the button the pad layout presses (runtime/romfs.c overlay_init, tools/pad_prompts.py). PC: +// the key or mouse button the keyboard defaults press each button with, the mouse for the right stick. +enum { PROMPTS_NINTENDO, PROMPTS_XBOX, PROMPTS_PC, PROMPTS_PLAYSTATION }; +// The prompts for a NX2NAVI_PROMPTS value ("nintendo", "xbox", "playstation" or "pc", any case). Unset or empty: Xbox +// while the pad layout setting is "xbox_prompts", else Nintendo. int input_prompts_pick(const char *prompts, const char *pad_layout); // The folder next to romfs-index.bin that holds those prompts for a NX2NAVI_PAD_LAYOUT value, NULL for the game's own. The -// Nintendo layout plays an Xbox pad by position, so its folder names the face buttons the other way round -// (romfs-patches-xbox: the right button does A and is labelled B); xbox and auto put every Switch button on the button -// labelled with its letter (auto picks the layout per pad by its labels), so they share romfs-patches-xbox-label. The PC -// prompts follow no pad: romfs-patches-pc whatever the layout (romfs.c overlay_init serves romfs-patches-pc-x -// under that name at a game resolution of another shape). +// Nintendo layout plays a pad by position, so its folder names the face buttons the other way round +// (romfs-patches-xbox: the right button does A and is labelled B; romfs-patches-playstation: it is circle); xbox and +// auto put every Switch button on the button labelled with its letter, or on cross, circle, square and triangle in +// that order (auto picks the layout per pad by its labels), so they share romfs-patches-