From 951834fb57e65a1430f88c52566b731cda27872c Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Mon, 11 Apr 2022 17:45:21 -0700 Subject: [PATCH] Improve joypad input mapping Implement buttons 1-4 as left analog stick directions (with cooldown) to match SACT2. Other buttons are also remapped to match the SACT2 mapping. --- src/input.c | 206 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 162 insertions(+), 44 deletions(-) diff --git a/src/input.c b/src/input.c index 72ce11b..2d01605 100644 --- a/src/input.c +++ b/src/input.c @@ -15,6 +15,7 @@ */ #include +#include #include #include "system4.h" #include "gfx/gfx.h" @@ -24,7 +25,6 @@ #include "vm.h" bool key_state[VK_NR_KEYCODES]; -bool joybutton_state[SDL_CONTROLLER_BUTTON_MAX]; static enum sact_keycode sdl_keytable[] = { [SDL_SCANCODE_0] = VK_0, [SDL_SCANCODE_1] = VK_1, @@ -159,39 +159,6 @@ bool key_is_down(enum sact_keycode code) return key_state[code]; } -bool joy_key_is_down(uint8_t code) -{ - // Default SACT key mapping in comments below - switch (code) { - case 1: // Left Arrow - return joybutton_state[SDL_CONTROLLER_BUTTON_DPAD_LEFT]; - case 2: // Right Arrow - return joybutton_state[SDL_CONTROLLER_BUTTON_DPAD_RIGHT]; - case 3: // Up Arrow - return joybutton_state[SDL_CONTROLLER_BUTTON_DPAD_UP]; - case 4: // Down Arrow - return joybutton_state[SDL_CONTROLLER_BUTTON_DPAD_DOWN]; - case 5: // Esc - return joybutton_state[SDL_CONTROLLER_BUTTON_BACK] || joybutton_state[SDL_CONTROLLER_BUTTON_START]; - case 6: // Enter - return joybutton_state[SDL_CONTROLLER_BUTTON_A]; - case 7: // Space - return joybutton_state[SDL_CONTROLLER_BUTTON_Y]; - case 8: // Ctrl - return joybutton_state[SDL_CONTROLLER_BUTTON_B]; - case 9: // a - return joybutton_state[SDL_CONTROLLER_BUTTON_LEFTSTICK]; - case 10: // z - return joybutton_state[SDL_CONTROLLER_BUTTON_RIGHTSTICK]; - case 11: // Page Up - return joybutton_state[SDL_CONTROLLER_BUTTON_LEFTSHOULDER]; - case 12: // Page Down - return joybutton_state[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER]; - default: - return false; - } -} - void key_clear_flag(void) { for (int i = 0; i < VK_NR_KEYCODES; i++) { @@ -199,13 +166,6 @@ void key_clear_flag(void) } } -void joy_clear_flag(void) -{ - for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) { - joybutton_state[i] = false; - } -} - void mouse_get_pos(int *x, int *y) { int wx, wy; @@ -256,11 +216,166 @@ static void mouse_event(SDL_MouseButtonEvent *e) key_state[code] = e->state == SDL_PRESSED; } +#define JOYAXIS_DEADZONE 13500 + +enum joyaxis_axis { + JOYAXIS_X, + JOYAXIS_Y +}; + +enum joyaxis_direction { + JOYAXIS_DIR_LEFT_UP = -1, + JOYAXIS_DIR_NONE = 0, + JOYAXIS_DIR_RIGHT_DOWN = 1 +}; + +enum joyaxis_state { + // A: Initial state + // - when joy_clear_flag is called, goes on long cooldown + // and then enters JOYAXIS_STATE_B + JOYAXIS_STATE_A, + // B: Held state + // - when joy_clear_flag is called, goes on short cooldown + // state remains JOYAXIS_STATE_B until direction changes + JOYAXIS_STATE_B, +}; + +struct joyaxis { + enum joyaxis_state state; + unsigned long cooldown_expire_time; + enum joyaxis_direction real_direction; + enum joyaxis_direction logical_direction; +}; + +struct joyaxis joyaxis_state[2] = { 0 }; + +bool joybutton_state[SDL_CONTROLLER_BUTTON_MAX]; + +static unsigned long joy_time(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((unsigned long)ts.tv_sec * 1000) + ((unsigned long)ts.tv_nsec / 1000000); +} + +static bool joy_check_axis(struct joyaxis *axis, enum joyaxis_direction direction) +{ + if (axis->logical_direction == direction) + return true; + if (!axis->cooldown_expire_time) + return false; + // on cooldown: check if cooldown expired + if (joy_time() >= axis->cooldown_expire_time) { + axis->state = JOYAXIS_STATE_B; + axis->cooldown_expire_time = 0; + axis->logical_direction = axis->real_direction; + } + return axis->logical_direction == direction; +} + +bool joy_key_is_down(uint8_t code) +{ + // Default SACT key mapping in comments below + switch (code) { + case 1: // Left Arrow + return joy_check_axis(&joyaxis_state[JOYAXIS_X], JOYAXIS_DIR_LEFT_UP); + case 2: // Right Arrow + return joy_check_axis(&joyaxis_state[JOYAXIS_X], JOYAXIS_DIR_RIGHT_DOWN); + case 3: // Up Arrow + return joy_check_axis(&joyaxis_state[JOYAXIS_Y], JOYAXIS_DIR_LEFT_UP); + case 4: // Down Arrow + return joy_check_axis(&joyaxis_state[JOYAXIS_Y], JOYAXIS_DIR_RIGHT_DOWN); + case 5: // Esc + return joybutton_state[SDL_CONTROLLER_BUTTON_A]; + case 6: // Enter + return joybutton_state[SDL_CONTROLLER_BUTTON_B]; + case 7: // Space + return joybutton_state[SDL_CONTROLLER_BUTTON_X]; + case 8: // Ctrl + return joybutton_state[SDL_CONTROLLER_BUTTON_Y]; + case 9: // a + return joybutton_state[SDL_CONTROLLER_BUTTON_LEFTSHOULDER]; + case 10: // z + return joybutton_state[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER]; + case 11: // Page Up + return joybutton_state[SDL_CONTROLLER_BUTTON_BACK]; + case 12: // Page Down + return joybutton_state[SDL_CONTROLLER_BUTTON_START]; + case 13: + return joybutton_state[SDL_CONTROLLER_BUTTON_GUIDE]; + case 14: + return joybutton_state[SDL_CONTROLLER_BUTTON_LEFTSTICK]; + case 15: + return joybutton_state[SDL_CONTROLLER_BUTTON_RIGHTSTICK]; + case 16: + return joybutton_state[SDL_CONTROLLER_BUTTON_DPAD_LEFT]; + case 17: + return joybutton_state[SDL_CONTROLLER_BUTTON_DPAD_RIGHT]; + case 18: + return joybutton_state[SDL_CONTROLLER_BUTTON_DPAD_UP]; + case 19: + return joybutton_state[SDL_CONTROLLER_BUTTON_DPAD_DOWN]; + default: + return false; + } +} + +static void joyaxis_clear_flag(struct joyaxis *joy) +{ + // put axis on cooldown + if (joy->logical_direction) { + joy->logical_direction = 0; + if (joy->state == JOYAXIS_STATE_A) { + joy->cooldown_expire_time = joy_time() + 300; + } else { + joy->cooldown_expire_time = joy_time() + 50; + } + } +} + +void joy_clear_flag(void) +{ + for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) { + joybutton_state[i] = false; + } + joyaxis_clear_flag(&joyaxis_state[JOYAXIS_X]); + joyaxis_clear_flag(&joyaxis_state[JOYAXIS_Y]); +} + +static void joyaxis_update(enum joyaxis_axis axis, int value) +{ + // determine direction + int direction = 0; + if (value < -JOYAXIS_DEADZONE) + direction = -1; + else if (value > JOYAXIS_DEADZONE) + direction = 1; + + // reset axis state if direction changed + if (direction != joyaxis_state[axis].real_direction) { + joyaxis_state[axis].state = JOYAXIS_STATE_A; + joyaxis_state[axis].cooldown_expire_time = 0; + joyaxis_state[axis].real_direction = direction; + joyaxis_state[axis].logical_direction = direction; + } +} + +static void controller_axis_event(SDL_ControllerAxisEvent *e) +{ + switch (e->axis) { + case SDL_CONTROLLER_AXIS_LEFTX: + joyaxis_update(JOYAXIS_X, e->value); + break; + case SDL_CONTROLLER_AXIS_LEFTY: + joyaxis_update(JOYAXIS_Y, e->value); + break; + default: + break; + } +} + static void controller_button_event(SDL_ControllerButtonEvent *e) { - if (e->state != SDL_PRESSED) { - NOTICE("UNPRESSED"); - } joybutton_state[e->button] = e->state == SDL_PRESSED; } @@ -337,6 +452,9 @@ void handle_events(void) if (e.cdevice.which < MAX_CONTROLLERS) controllers[e.cdevice.which] = SDL_GameControllerOpen(e.cdevice.which); break; + case SDL_CONTROLLERAXISMOTION: + controller_axis_event(&e.caxis); + break; case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERBUTTONDOWN: controller_button_event(&e.cbutton);