mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 18:01:03 +00:00
While a game menu has the pointer the left mouse button is A, and it was Y's only default key, so keyboard and mouse players could not sort the inventory or press Y anywhere in the menus. T is now Y's second default key. A bind line in totk.ini now also removes its keys from the defaults of buttons without a line, as the remapping rows already do: with bind_X = key:t, T presses X only, not X and Y. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
414 lines
19 KiB
C
414 lines
19 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_T, 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 = 28, 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();
|
|
uint8_t lined[ACTION_COUNT] = {0};
|
|
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)) lined[a] = 1, rt_log("input: %s = %s", var + 5, v);
|
|
}
|
|
// A key a line binds leaves the defaults of the actions without a line, as the remapping rows move a key: with
|
|
// bind_X = key:t, T presses X only, not also Y, whose defaults hold it.
|
|
uint8_t bound[KEY_COUNT] = {0}, list[INPUT_BINDING_MAX];
|
|
for (int a = 0; a < ACTION_COUNT; a++) {
|
|
if (!lined[a]) continue;
|
|
int n = input_map_keys_of(&input_map, a, list, INPUT_BINDING_MAX);
|
|
for (int i = 0; i < n; i++) bound[list[i]] = 1;
|
|
}
|
|
for (int a = 0; a < ACTION_COUNT; a++) {
|
|
if (lined[a]) continue;
|
|
int n = input_map_keys_of(&input_map, a, list, INPUT_BINDING_MAX), w = 0;
|
|
for (int i = 0; i < n; i++)
|
|
if (!bound[list[i]]) list[w++] = list[i];
|
|
if (w < n) keys_put(&input_map, a, list, w);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|