mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
The Button prompts row gets a third choice, PC: every button glyph of the game's button font and every image of the button guide is drawn as the key or mouse button the keyboard defaults press it with (E, Shift, Space, left click, Q, R, right click, F, Tab, M, 1 to 4, W A S D, Ctrl, C) and the right stick as the mouse, whatever the pad layout. The setup writes romfs-patches-pc next to the Xbox folders (make_romfs_patches.py --prompts pc, button_icons.py pc_icons); --binds <totk.ini> draws the keys of its bind_ lines instead. At a game resolution of another shape the game takes romfs-patches-pc-<w>x<h> (--prompts pc --aspect) in place of romfs-patches-<w>x<h> when it is there. (cherry picked from commit 3f81cf02be664100bd9c569c53588d620d90b527)
398 lines
18 KiB
C
398 lines
18 KiB
C
// Binding tables (keyboard/mouse and gamepad), their totk.ini overrides, the pure mapping functions and the Buttons
|
|
// tab's edits (input_map.h). No Win32 or SDL here.
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "cfg.h"
|
|
#include "host.h"
|
|
#include "input_map.h"
|
|
|
|
#define STICK_MAX 32767
|
|
#define DIAGONAL 23170 // STICK_MAX / sqrt(2)
|
|
#define MOUSE_FULL_CPS 3000.0
|
|
|
|
#define DEFAULT_KEYS \
|
|
{KEY_W, B_SL_UP}, {KEY_A, B_SL_LEFT}, {KEY_S, B_SL_DOWN}, {KEY_D, B_SL_RIGHT}, /* move */ \
|
|
{KEY_UP, B_SR_UP}, {KEY_LEFT, B_SR_LEFT}, {KEY_DOWN, B_SR_DOWN}, {KEY_RIGHT, B_SR_RIGHT}, /* camera, second source */ \
|
|
{KEY_MOUSE_L, B_Y}, {KEY_MOUSE_R, B_ZL}, {KEY_Z, B_ZL}, {KEY_F, B_ZR}, {KEY_X, B_ZR}, \
|
|
{KEY_SPACE, B_X}, {KEY_LSHIFT, B_B}, {KEY_E, B_A}, {KEY_Q, B_L}, {KEY_R, B_R}, \
|
|
{KEY_LCTRL, B_STICK_L}, {KEY_C, B_STICK_R}, \
|
|
{KEY_1, B_LEFT}, {KEY_2, B_UP}, {KEY_3, B_RIGHT}, {KEY_4, B_DOWN}, \
|
|
{KEY_TAB, B_PLUS}, {KEY_M, B_MINUS}, {KEY_G, B_SHAKE}
|
|
|
|
// By position: south is Switch B, east A, west Y, north X (Nintendo layout); the Xbox layout swaps A with B and X with Y
|
|
// (south A, as the Xbox labels; cross A on a PlayStation pad). The triggers are ZL and ZR.
|
|
#define DEFAULT_PADS(south, east, west, north) \
|
|
{PAD_SOUTH, south}, {PAD_EAST, east}, {PAD_WEST, west}, {PAD_NORTH, north}, {PAD_LSTICK, B_STICK_L}, \
|
|
{PAD_RSTICK, B_STICK_R}, {PAD_L, B_L}, {PAD_R, B_R}, {PAD_START, B_PLUS}, {PAD_BACK, B_MINUS}, \
|
|
{PAD_DPAD_LEFT, B_LEFT}, {PAD_DPAD_UP, B_UP}, {PAD_DPAD_RIGHT, B_RIGHT}, {PAD_DPAD_DOWN, B_DOWN}, {PAD_ZL, B_ZL}, \
|
|
{PAD_ZR, B_ZR}
|
|
#define NINTENDO_PADS DEFAULT_PADS(B_B, B_A, B_Y, B_X)
|
|
#define XBOX_PADS DEFAULT_PADS(B_A, B_B, B_X, B_Y)
|
|
enum { DEFAULT_KEY_COUNT = 27, DEFAULT_PAD_COUNT = 16 };
|
|
_Static_assert(sizeof (Binding[]){DEFAULT_KEYS} / sizeof (Binding) == DEFAULT_KEY_COUNT, "DEFAULT_KEY_COUNT");
|
|
|
|
static const InputMap defaults = {{DEFAULT_KEYS}, DEFAULT_KEY_COUNT, {{NINTENDO_PADS}, {XBOX_PADS}},
|
|
{DEFAULT_PAD_COUNT, DEFAULT_PAD_COUNT}};
|
|
InputMap input_map = {{DEFAULT_KEYS}, DEFAULT_KEY_COUNT, {{NINTENDO_PADS}, {XBOX_PADS}},
|
|
{DEFAULT_PAD_COUNT, DEFAULT_PAD_COUNT}};
|
|
|
|
static const char *const action_names[ACTION_COUNT] = {
|
|
"A", "B", "X", "Y", "LCLICK", "RCLICK", "L", "R", "ZL", "ZR", "PLUS", "MINUS", "LEFT", "UP", "RIGHT", "DOWN",
|
|
"LSTICK_LEFT", "LSTICK_UP", "LSTICK_RIGHT", "LSTICK_DOWN", "RSTICK_LEFT", "RSTICK_UP", "RSTICK_RIGHT", "RSTICK_DOWN", "SHAKE"};
|
|
static const char *const pad_names[PAD_COUNT] = {
|
|
NULL, "south", "east", "west", "north", "lstick", "rstick", "l", "r", "start", "back",
|
|
"dpad_left", "dpad_up", "dpad_right", "dpad_down", "zl", "zr", "lpaddle1", "rpaddle1", "lpaddle2", "rpaddle2"};
|
|
static const char *const key_names[KEY_COUNT] = { // each group starts at its id; the ids of a group are consecutive
|
|
[KEY_A] = "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
|
|
"v", "w", "x", "y", "z",
|
|
[KEY_0] = "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
|
[KEY_SPACE] = "space", "tab", "lshift", "lctrl", "up", "down", "left", "right",
|
|
[KEY_MOUSE_L] = "mouse_l", "mouse_r", "mouse_m", "mouse_x1", "mouse_x2",
|
|
[KEY_ENTER] = "enter", "backspace", "rshift", "rctrl", "lalt", "capslock", "insert", "delete", "home", "end",
|
|
"pageup", "pagedown",
|
|
[KEY_F2] = "f2", "f3", "f4", "f5", "f6", "f7", "f9", "f12",
|
|
[KEY_NUM0] = "num0", "num1", "num2", "num3", "num4", "num5", "num6", "num7", "num8", "num9",
|
|
[KEY_NUM_ADD] = "num_add", "num_sub", "num_mul", "num_div", "num_decimal",
|
|
[KEY_MINUS] = "minus", "equals", "lbracket", "rbracket", "semicolon", "apostrophe", "comma", "period", "slash",
|
|
"backslash", "grave"};
|
|
|
|
const char *input_action_name(int action) { return action >= 0 && action < ACTION_COUNT ? action_names[action] : NULL; }
|
|
const char *input_key_name(int key) { return key > KEY_NONE && key < KEY_COUNT ? key_names[key] : NULL; }
|
|
const char *input_pad_name(int pad) { return pad > PAD_NONE && pad < PAD_COUNT ? pad_names[pad] : NULL; }
|
|
|
|
void input_map_reset(void) { input_map = defaults; }
|
|
|
|
static int key_id(const char *name) {
|
|
for (int k = KEY_A; k < KEY_COUNT; k++)
|
|
if (key_names[k] && !strcasecmp(key_names[k], name)) return k;
|
|
return KEY_NONE;
|
|
}
|
|
|
|
static int pad_id(const char *name) {
|
|
for (int p = PAD_SOUTH; p < PAD_COUNT; p++)
|
|
if (!strcasecmp(pad_names[p], name)) return p;
|
|
return PAD_NONE;
|
|
}
|
|
|
|
static char *trim(char *s) {
|
|
while (*s == ' ' || *s == '\t') s++;
|
|
char *e = s + strlen(s);
|
|
while (e > s && (e[-1] == ' ' || e[-1] == '\t' || e[-1] == '\r')) *--e = 0;
|
|
return s;
|
|
}
|
|
|
|
int input_map_keys_of(const InputMap *m, int action, uint8_t *out, int max) {
|
|
int n = 0;
|
|
for (int i = 0; i < m->nkeys; i++)
|
|
if (m->keys[i].action == action && n < max) out[n++] = m->keys[i].key;
|
|
return n;
|
|
}
|
|
|
|
int input_map_pads_of(const InputMap *m, int action, int layout, uint8_t *out, int max) {
|
|
int n = 0;
|
|
for (int i = 0; i < m->npads[layout]; i++)
|
|
if (m->pads[layout][i].action == action && n < max) out[n++] = m->pads[layout][i].pad;
|
|
return n;
|
|
}
|
|
|
|
// action's keys become list[0..n): its old ones go, the new ones are appended (the table keeps its order otherwise).
|
|
static void keys_put(InputMap *m, int action, const uint8_t *list, int n) {
|
|
int w = 0;
|
|
for (int i = 0; i < m->nkeys; i++)
|
|
if (m->keys[i].action != action) m->keys[w++] = m->keys[i];
|
|
m->nkeys = w;
|
|
for (int i = 0; i < n && m->nkeys < INPUT_BINDING_MAX; i++) m->keys[m->nkeys++] = (Binding){list[i], (uint8_t)action};
|
|
}
|
|
|
|
static void pads_put(InputMap *m, int layout, int action, const uint8_t *list, int n) {
|
|
PadBinding *t = m->pads[layout];
|
|
int w = 0;
|
|
for (int i = 0; i < m->npads[layout]; i++)
|
|
if (t[i].action != action) t[w++] = t[i];
|
|
m->npads[layout] = w;
|
|
for (int i = 0; i < n && m->npads[layout] < INPUT_PAD_BINDING_MAX; i++)
|
|
t[m->npads[layout]++] = (PadBinding){list[i], (uint8_t)action};
|
|
}
|
|
|
|
int input_map_bind(const char *button, const char *list) {
|
|
int action = -1;
|
|
for (int a = 0; a < ACTION_COUNT; a++)
|
|
if (!strcasecmp(action_names[a], button)) action = a;
|
|
if (action < 0) { rt_log("input: bind: unknown button '%s'", button); return 0; }
|
|
uint8_t keys[INPUT_BINDING_MAX], pads[INPUT_PAD_BINDING_MAX];
|
|
int nk = 0, np = 0, none = 0, key_def = 0, pad_def = 0, bad = 0;
|
|
char buf[256];
|
|
snprintf(buf, sizeof buf, "%s", list);
|
|
for (char *save = NULL, *tok = strtok_r(buf, ",", &save); tok; tok = strtok_r(NULL, ",", &save)) {
|
|
tok = trim(tok);
|
|
if (!strcasecmp(tok, "none")) { none = 1; continue; }
|
|
int ok = 0;
|
|
if (!strncasecmp(tok, "pad:", 4) && action < B_SL_LEFT) {
|
|
const char *name = trim(tok + 4);
|
|
int p = pad_id(name);
|
|
if (!strcasecmp(name, "default")) pad_def = ok = 1;
|
|
else if (p && np < INPUT_PAD_BINDING_MAX) pads[np++] = (uint8_t)p, ok = 1;
|
|
} else if (!strncasecmp(tok, "key:", 4)) {
|
|
const char *name = trim(tok + 4);
|
|
int k = key_id(name);
|
|
if (!strcasecmp(name, "default")) key_def = ok = 1;
|
|
else if (k && nk < INPUT_BINDING_MAX) keys[nk++] = (uint8_t)k, ok = 1;
|
|
}
|
|
if (!ok) {
|
|
bad++;
|
|
rt_log("input: bind %s: ignoring '%s' (expected pad:<name> or key:<name>; stick directions and SHAKE take keys only)",
|
|
button, tok);
|
|
}
|
|
}
|
|
if (!nk && !np && !none && !key_def && !pad_def) {
|
|
if (bad) rt_log("input: bind %s: no valid entry, keeping the defaults", button);
|
|
return 0;
|
|
}
|
|
uint8_t list_k[INPUT_BINDING_MAX];
|
|
int n = key_def ? input_map_keys_of(&defaults, action, list_k, INPUT_BINDING_MAX) : 0;
|
|
for (int i = 0; i < nk && n < INPUT_BINDING_MAX; i++) list_k[n++] = keys[i];
|
|
keys_put(&input_map, action, list_k, n);
|
|
for (int l = 0; l < PAD_LAYOUT_COUNT; l++) { // a bind line names physical buttons: the same in every layout
|
|
uint8_t list_p[INPUT_PAD_BINDING_MAX];
|
|
int c = pad_def ? input_map_pads_of(&defaults, action, l, list_p, INPUT_PAD_BINDING_MAX) : 0;
|
|
for (int i = 0; i < np && c < INPUT_PAD_BINDING_MAX; i++) list_p[c++] = pads[i];
|
|
pads_put(&input_map, l, action, list_p, c);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void input_map_configure(void) {
|
|
input_map_reset();
|
|
for (int a = 0; a < ACTION_COUNT; a++) {
|
|
char var[32];
|
|
snprintf(var, sizeof var, "TOTK_BIND_%s", action_names[a]);
|
|
const char *v = cfg_get(var);
|
|
if (v && *v && input_map_bind(action_names[a], v)) rt_log("input: %s = %s", var + 5, v);
|
|
}
|
|
}
|
|
|
|
int input_map_set_key(InputMap *m, int action, int slot, int key) {
|
|
uint8_t list[INPUT_BINDING_MAX];
|
|
int n = input_map_keys_of(m, action, list, INPUT_BINDING_MAX), lost = -1;
|
|
if (key != KEY_NONE) {
|
|
for (int i = 0; i < m->nkeys; i++)
|
|
if (m->keys[i].key == key && m->keys[i].action != action) lost = m->keys[i].action;
|
|
if (lost >= 0) {
|
|
uint8_t other[INPUT_BINDING_MAX];
|
|
int c = 0;
|
|
for (int i = 0; i < m->nkeys; i++)
|
|
if (m->keys[i].action == lost && m->keys[i].key != key) other[c++] = m->keys[i].key;
|
|
keys_put(m, lost, other, c);
|
|
}
|
|
int w = 0; // the key once in this action's list too
|
|
for (int i = 0; i < n; i++)
|
|
if (list[i] != key) list[w++] = list[i];
|
|
n = w;
|
|
if (slot < n) list[slot] = (uint8_t)key;
|
|
else if (n < INPUT_BINDING_MAX) list[n++] = (uint8_t)key;
|
|
} else if (slot < n) {
|
|
memmove(list + slot, list + slot + 1, (size_t)(n - slot - 1));
|
|
n--;
|
|
}
|
|
keys_put(m, action, list, n);
|
|
return lost;
|
|
}
|
|
|
|
static int same_set(const uint8_t *a, int na, const uint8_t *b, int nb);
|
|
|
|
// Every action has its default gamepad buttons in layout.
|
|
static int pads_at_defaults(const InputMap *m, int layout) {
|
|
for (int a = 0; a < B_SL_LEFT; a++) {
|
|
uint8_t p[INPUT_PAD_BINDING_MAX], dp[INPUT_PAD_BINDING_MAX];
|
|
int np = input_map_pads_of(m, a, layout, p, INPUT_PAD_BINDING_MAX);
|
|
if (!same_set(p, np, dp, input_map_pads_of(&defaults, a, layout, dp, INPUT_PAD_BINDING_MAX))) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int input_map_set_pad(InputMap *m, int action, int layout, int pad) {
|
|
uint8_t mine[INPUT_PAD_BINDING_MAX];
|
|
int n = input_map_pads_of(m, action, layout, mine, INPUT_PAD_BINDING_MAX), other = -1;
|
|
if (pad != PAD_NONE)
|
|
for (int i = 0; i < m->npads[layout]; i++)
|
|
if (m->pads[layout][i].pad == pad && m->pads[layout][i].action != action) other = m->pads[layout][i].action;
|
|
if (other >= 0) { // the other action gives up pad and takes this action's old button
|
|
uint8_t list[INPUT_PAD_BINDING_MAX];
|
|
int c = 0;
|
|
for (int i = 0; i < m->npads[layout]; i++)
|
|
if (m->pads[layout][i].action == other && m->pads[layout][i].pad != pad) list[c++] = m->pads[layout][i].pad;
|
|
if (n && mine[0] != pad && c < INPUT_PAD_BINDING_MAX) list[c++] = mine[0];
|
|
pads_put(m, layout, other, list, c);
|
|
}
|
|
uint8_t p = (uint8_t)pad;
|
|
pads_put(m, layout, action, &p, pad != PAD_NONE);
|
|
if (pads_at_defaults(m, layout)) { // put back as it was: the layout setting moves the face buttons again
|
|
memcpy(m->pads, defaults.pads, sizeof m->pads);
|
|
memcpy(m->npads, defaults.npads, sizeof m->npads);
|
|
return other;
|
|
}
|
|
int pin[6] = {B_A, B_B, B_X, B_Y, action, other}, npin = other >= 0 ? 6 : 5;
|
|
for (int i = 0; i < npin; i++) {
|
|
uint8_t list[INPUT_PAD_BINDING_MAX];
|
|
int c = input_map_pads_of(m, pin[i], layout, list, INPUT_PAD_BINDING_MAX);
|
|
for (int l = 0; l < PAD_LAYOUT_COUNT; l++)
|
|
if (l != layout) pads_put(m, l, pin[i], list, c);
|
|
}
|
|
return other;
|
|
}
|
|
|
|
// The same bindings in any order (the menu appends; a line lists them its own way).
|
|
static int same_set(const uint8_t *a, int na, const uint8_t *b, int nb) {
|
|
if (na != nb) return 0;
|
|
for (int i = 0; i < na; i++) {
|
|
int ca = 0, cb = 0;
|
|
for (int j = 0; j < na; j++) ca += a[j] == a[i], cb += b[j] == a[i];
|
|
if (ca != cb) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void input_map_line(const InputMap *m, int action, char *out, size_t n) {
|
|
uint8_t k[INPUT_BINDING_MAX], dk[INPUT_BINDING_MAX];
|
|
int nk = input_map_keys_of(m, action, k, INPUT_BINDING_MAX);
|
|
int key_def = same_set(k, nk, dk, input_map_keys_of(&defaults, action, dk, INPUT_BINDING_MAX));
|
|
int pad_def = 1;
|
|
for (int l = 0; l < PAD_LAYOUT_COUNT; l++) {
|
|
uint8_t p[INPUT_PAD_BINDING_MAX], dp[INPUT_PAD_BINDING_MAX];
|
|
int np = input_map_pads_of(m, action, l, p, INPUT_PAD_BINDING_MAX);
|
|
pad_def &= same_set(p, np, dp, input_map_pads_of(&defaults, action, l, dp, INPUT_PAD_BINDING_MAX));
|
|
}
|
|
out[0] = 0;
|
|
if (key_def && pad_def) return;
|
|
size_t len = 0;
|
|
#define PUT(...) (len += (size_t)snprintf(out + len, len < n ? n - len : 0, __VA_ARGS__))
|
|
if (key_def) PUT("key:default");
|
|
else
|
|
for (int i = 0; i < nk; i++) PUT("%skey:%s", len ? ", " : "", key_names[k[i]]);
|
|
if (pad_def && action < B_SL_LEFT) PUT("%spad:default", len ? ", " : "");
|
|
else if (!pad_def) {
|
|
uint8_t p[INPUT_PAD_BINDING_MAX];
|
|
int np = input_map_pads_of(m, action, PAD_LAYOUT_NINTENDO, p, INPUT_PAD_BINDING_MAX);
|
|
for (int i = 0; i < np; i++) PUT("%spad:%s", len ? ", " : "", pad_names[p[i]]);
|
|
}
|
|
if (!len) PUT("none");
|
|
#undef PUT
|
|
}
|
|
|
|
int input_pad_layout_pick(const char *setting, int east_labelled_a) {
|
|
if (setting && (!strcasecmp(setting, "nintendo") || !strcasecmp(setting, "xbox_prompts"))) return PAD_LAYOUT_NINTENDO;
|
|
if (setting && !strcasecmp(setting, "xbox")) return PAD_LAYOUT_XBOX;
|
|
return east_labelled_a ? PAD_LAYOUT_NINTENDO : PAD_LAYOUT_XBOX;
|
|
}
|
|
|
|
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 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;
|
|
// 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";
|
|
}
|
|
|
|
uint64_t input_map_pad_buttons(const uint8_t down[PAD_COUNT], int layout) {
|
|
const PadBinding *t = input_map.pads[layout];
|
|
uint64_t b = 0;
|
|
for (int i = 0; i < input_map.npads[layout]; i++)
|
|
if (down[t[i].pad]) b |= 1ull << t[i].action;
|
|
return b;
|
|
}
|
|
|
|
uint64_t input_map_buttons(const uint8_t down[KEY_COUNT]) {
|
|
uint64_t b = 0;
|
|
for (int i = 0; i < input_map.nkeys; i++)
|
|
if (input_map.keys[i].action < B_SL_LEFT && down[input_map.keys[i].key]) b |= 1ull << input_map.keys[i].action;
|
|
return b;
|
|
}
|
|
|
|
int input_map_held(const uint8_t down[KEY_COUNT], int action) {
|
|
for (int i = 0; i < input_map.nkeys; i++)
|
|
if (input_map.keys[i].action == action && down[input_map.keys[i].key]) return 1;
|
|
return 0;
|
|
}
|
|
|
|
void input_map_stick(const uint8_t down[KEY_COUNT], int stick, int32_t out[2]) {
|
|
int first = stick ? B_SR_LEFT : B_SL_LEFT, dir[4] = {0, 0, 0, 0}; // left, up, right, down
|
|
for (int i = 0; i < input_map.nkeys; i++) {
|
|
int a = input_map.keys[i].action - first;
|
|
if (a >= 0 && a < 4 && down[input_map.keys[i].key]) dir[a] = 1;
|
|
}
|
|
int dx = dir[2] - dir[0], dy = dir[1] - dir[3];
|
|
int32_t m = dx && dy ? DIAGONAL : STICK_MAX;
|
|
out[0] = dx * m;
|
|
out[1] = dy * m;
|
|
}
|
|
|
|
void input_mouse_stick(int32_t dx, int32_t dy, double dt_s, float sens, int32_t out[2]) {
|
|
out[0] = out[1] = 0;
|
|
if (!(dt_s > 0) || !(sens > 0)) return;
|
|
double k = STICK_MAX * (double)sens / (MOUSE_FULL_CPS * dt_s);
|
|
double x = dx * k, y = -dy * k, len = sqrt(x * x + y * y);
|
|
if (len > STICK_MAX) { x *= STICK_MAX / len; y *= STICK_MAX / len; }
|
|
out[0] = (int32_t)lrint(x);
|
|
out[1] = (int32_t)lrint(y);
|
|
}
|
|
|
|
void input_pad_stick(int32_t x, int32_t y, float dead, int32_t out[2]) {
|
|
double fx = x / (double)STICK_MAX, fy = y / (double)STICK_MAX, m = sqrt(fx * fx + fy * fy);
|
|
out[0] = out[1] = 0;
|
|
if (m <= dead) return;
|
|
double k = ((m > 1 ? 1 : m) - dead) / (1 - dead) / m; // past the edge (a square gate's corner) the length is 1
|
|
out[0] = (int32_t)lrint(fmax(-1, fmin(1, fx * k)) * STICK_MAX);
|
|
out[1] = (int32_t)lrint(fmax(-1, fmin(1, fy * k)) * STICK_MAX);
|
|
}
|
|
|
|
float input_pad_deadzone(int stick) {
|
|
const char *own = stick ? "TOTK_RSTICK_DEADZONE" : "TOTK_LSTICK_DEADZONE", *set = cfg_get(own);
|
|
double v = cfg_double(set && *set ? own : "TOTK_STICK_DEADZONE");
|
|
return (float)(v < 0 ? 0 : v > 50 ? 50 : v) / 100.f;
|
|
}
|
|
|
|
double input_stick_percent(int32_t x, int32_t y) {
|
|
return sqrt((double)x * x + (double)y * y) * 100.0 / STICK_MAX;
|
|
}
|
|
|
|
// Minus and Plus held together open or close the menu. *held: latched from the poll where both go down until neither is
|
|
// down (the caller keeps it). Every poll of the latch reports neither to the game, so releasing one button first does not
|
|
// leak the other; Minus alone before Plus reaches the game unchanged. With the menu open the game sees a neutral pad.
|
|
// Returns 1 in the poll where both go down: the caller toggles the menu once.
|
|
static int chord(int *held, uint64_t *buttons, uint64_t combo) {
|
|
uint64_t raw = *buttons & combo;
|
|
int edge = 0;
|
|
if (*held) {
|
|
*held = raw != 0;
|
|
} else if (raw == combo) {
|
|
*held = 1;
|
|
edge = 1;
|
|
}
|
|
if (*held) *buttons &= ~combo;
|
|
return edge;
|
|
}
|
|
|
|
int input_menu_gate(int *held, int menu_open, uint64_t *buttons, int32_t sticks[4]) {
|
|
int edge = chord(held, buttons, 1ull << B_MINUS | 1ull << B_PLUS);
|
|
if (menu_open) {
|
|
*buttons = 0;
|
|
sticks[0] = sticks[1] = sticks[2] = sticks[3] = 0;
|
|
}
|
|
return edge;
|
|
}
|
|
|
|
// Both stick clicks held together step the performance overlay as F10 does (a gamepad has no F10: the Steam Deck in
|
|
// Game Mode). The same latch as the menu's above; the game sees neither click while it holds.
|
|
int input_overlay_gate(int *held, uint64_t *buttons) {
|
|
return chord(held, buttons, 1ull << B_STICK_L | 1ull << B_STICK_R);
|
|
}
|