#include "twn_input_c.h" #include "twn_util.h" #include "twn_util_c.h" #include "twn_engine_context_c.h" #include "twn_input.h" #include #include #include struct ScancodeHashItem { char *const key; SDL_Scancode value; }; static struct ScancodeHashItem *control_to_scancode; struct MouseButtonHashItem { char *const key; uint8_t value; }; static struct MouseButtonHashItem *control_to_mouse_mask; /* prepares translation maps for controls */ static void init_control_maps(void) { if (control_to_scancode) return; /* these correspond to SDL_events.h definition, restricted to what deemed useful */ shput(control_to_scancode, "A", 4); shput(control_to_scancode, "B", 5); shput(control_to_scancode, "C", 6); shput(control_to_scancode, "D", 7); shput(control_to_scancode, "E", 8); shput(control_to_scancode, "F", 9); shput(control_to_scancode, "G", 10); shput(control_to_scancode, "H", 11); shput(control_to_scancode, "I", 12); shput(control_to_scancode, "J", 13); shput(control_to_scancode, "K", 14); shput(control_to_scancode, "L", 15); shput(control_to_scancode, "M", 16); shput(control_to_scancode, "N", 17); shput(control_to_scancode, "O", 18); shput(control_to_scancode, "P", 19); shput(control_to_scancode, "Q", 20); shput(control_to_scancode, "R", 21); shput(control_to_scancode, "S", 22); shput(control_to_scancode, "T", 23); shput(control_to_scancode, "U", 24); shput(control_to_scancode, "V", 25); shput(control_to_scancode, "W", 26); shput(control_to_scancode, "X", 27); shput(control_to_scancode, "Y", 28); shput(control_to_scancode, "Z", 29); shput(control_to_scancode, "1", 30); shput(control_to_scancode, "2", 31); shput(control_to_scancode, "3", 32); shput(control_to_scancode, "4", 33); shput(control_to_scancode, "5", 34); shput(control_to_scancode, "6", 35); shput(control_to_scancode, "7", 36); shput(control_to_scancode, "8", 37); shput(control_to_scancode, "9", 38); shput(control_to_scancode, "0", 39); shput(control_to_scancode, "RETURN", 40); shput(control_to_scancode, "ENTER", 40); /* an alias */ shput(control_to_scancode, "ESCAPE", 41); shput(control_to_scancode, "BACKSPACE", 42); shput(control_to_scancode, "TAB", 43); shput(control_to_scancode, "SPACE", 44); shput(control_to_scancode, "MINUS", 45); shput(control_to_scancode, "EQUALS", 46); shput(control_to_scancode, "LEFTBRACKET", 47); shput(control_to_scancode, "RIGHTBRACKET", 48); shput(control_to_scancode, "BACKSLASH", 49); shput(control_to_scancode, "NONUSHASH", 50); shput(control_to_scancode, "SEMICOLON", 51); shput(control_to_scancode, "APOSTROPHE", 52); shput(control_to_scancode, "GRAVE", 53); shput(control_to_scancode, "COMMA", 54); shput(control_to_scancode, "PERIOD", 55); shput(control_to_scancode, "SLASH", 56); shput(control_to_scancode, "CAPSLOCK", 57); shput(control_to_scancode, "F1", 58); shput(control_to_scancode, "F2", 59); shput(control_to_scancode, "F3", 60); shput(control_to_scancode, "F4", 61); shput(control_to_scancode, "F5", 62); shput(control_to_scancode, "F6", 63); shput(control_to_scancode, "F7", 64); shput(control_to_scancode, "F8", 65); shput(control_to_scancode, "F9", 66); shput(control_to_scancode, "F10", 67); shput(control_to_scancode, "F11", 68); shput(control_to_scancode, "F12", 69); shput(control_to_scancode, "PRINTSCREEN", 70); shput(control_to_scancode, "SCROLLLOCK", 71); shput(control_to_scancode, "PAUSE", 72); shput(control_to_scancode, "INSERT", 73); shput(control_to_scancode, "HOME", 74); shput(control_to_scancode, "PAGEUP", 75); shput(control_to_scancode, "DELETE", 76); shput(control_to_scancode, "END", 77); shput(control_to_scancode, "PAGEDOWN", 78); shput(control_to_scancode, "RIGHT", 79); shput(control_to_scancode, "LEFT", 80); shput(control_to_scancode, "DOWN", 81); shput(control_to_scancode, "UP", 82); shput(control_to_scancode, "KPDIVIDE", 84); shput(control_to_scancode, "KPMULTIPLY", 85); shput(control_to_scancode, "KPMINUS", 86); shput(control_to_scancode, "KPPLUS", 87); shput(control_to_scancode, "KPENTER", 88); shput(control_to_scancode, "KP1", 89); shput(control_to_scancode, "KP2", 90); shput(control_to_scancode, "KP3", 91); shput(control_to_scancode, "KP4", 92); shput(control_to_scancode, "KP5", 93); shput(control_to_scancode, "KP6", 94); shput(control_to_scancode, "KP7", 95); shput(control_to_scancode, "KP8", 96); shput(control_to_scancode, "KP9", 97); shput(control_to_scancode, "KP0", 98); shput(control_to_scancode, "LCTRL", 224); shput(control_to_scancode, "LSHIFT", 225); shput(control_to_scancode, "LALT", 226); shput(control_to_scancode, "RCTRL", 228); shput(control_to_scancode, "RSHIFT", 229); /* TODO: support for double clicks */ shput(control_to_mouse_mask, "LCLICK", SDL_BUTTON(SDL_BUTTON_LEFT)); shput(control_to_mouse_mask, "MCLICK", SDL_BUTTON(SDL_BUTTON_MIDDLE)); shput(control_to_mouse_mask, "RCLICK", SDL_BUTTON(SDL_BUTTON_RIGHT)); } static void update_action_pressed_state(InputState *input, Action *action) { for (size_t i = 0; i < (uint64_t)ctx.keybind_slots; ++i) { switch (action->bindings[i].source) { case BUTTON_SOURCE_NOT_SET: break; case BUTTON_SOURCE_GAMEPAD: CRY("Action pressed state updated failed", "BUTTON_SOURCE_GAMEPAD isn't handled"); break; case BUTTON_SOURCE_KEYBOARD_PHYSICAL: /* not pressed */ if (input->keyboard_state[action->bindings[i].code.scancode] == 0) { action->just_changed = action->is_pressed; action->is_pressed = false; } /* pressed */ else { action->just_changed = !action->is_pressed; action->is_pressed = true; return; } break; case BUTTON_SOURCE_MOUSE: /* not pressed */ if ((input->mouse_state & action->bindings[i].code.mouse_button) == 0) { action->just_changed = action->is_pressed; action->is_pressed = false; } /* pressed */ else { action->just_changed = !action->is_pressed; action->is_pressed = true; action->position.x = ((float)input->mouse_window_position.x - ctx.viewport_rect.x) / ctx.viewport_scale; action->position.y = ((float)input->mouse_window_position.y - ctx.viewport_rect.y) / ctx.viewport_scale; /* TODO: */ /* * SDL_RenderWindowToLogical will turn window mouse * coords into a position inside the logical render * area. this has to be done to get an accurate point * that can actually be used in game logic */ // SDL_RenderWindowToLogical(input->renderer, // input->mouse_window_position.x, // input->mouse_window_position.y, // &action->position.x, // &action->position.y); return; } break; default: break; } } } static ActionHashItem *input_add_action(char const *action_name) { SDL_assert(action_name); Action new_action = { 0 }; new_action.bindings = ccalloc(ctx.keybind_slots, sizeof *new_action.bindings); shput(ctx.input.action_hash, action_name, new_action); return shgetp(ctx.input.action_hash, action_name); } static void input_delete_action(char const *action_name) { SDL_assert(action_name); ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name); SDL_assert(action); SDL_free(action->value.bindings); shdel(ctx.input.action_hash, action_name); } static void input_bind_code_to_action(InputState *input, char const *action_name, ButtonSource source, union ButtonCode code) { ActionHashItem *action_item = shgetp_null(input->action_hash, action_name); if (!action_item) action_item = input_add_action(action_name); Action *action = &action_item->value; /* check every binding to make sure this code isn't already bound */ for (size_t i = 0; i < action->num_bindings; ++i) { Button *binding = &action->bindings[i]; if (binding->source != source) break; bool is_already_bound = false; switch (binding->source) { case BUTTON_SOURCE_NOT_SET: break; case BUTTON_SOURCE_KEYBOARD_PHYSICAL: is_already_bound = binding->code.scancode == code.scancode; break; case BUTTON_SOURCE_GAMEPAD: is_already_bound = binding->code.gamepad_button == code.gamepad_button; break; case BUTTON_SOURCE_MOUSE: is_already_bound = binding->code.mouse_button == code.mouse_button; break; default: SDL_assert(false); } if (is_already_bound) { /* keep it alive */ binding->in_use = true; return; } } /* if we're at max bindings, forget the first element and shift the rest */ if (action->num_bindings == (uint64_t)ctx.keybind_slots) { --action->num_bindings; size_t shifted_size = sizeof action->bindings[0] * (ctx.keybind_slots - 1); SDL_memmove(action->bindings, action->bindings + 1, shifted_size); } action->bindings[action->num_bindings++] = (Button) { .source = source, .code = code, .in_use = true, }; } static void input_unbind_code_from_action(InputState *input, char const *action_name, ButtonSource source, union ButtonCode code) { ActionHashItem *action_item = shgetp_null(input->action_hash, action_name); if (!action_item) action_item = input_add_action(action_name); Action *action = &action_item->value; /* check every binding to make sure this code is bound */ size_t index = 0; bool is_bound = false; for (index = 0; index < action->num_bindings; ++index) { Button *binding = &action->bindings[index]; if (binding->source != source) continue; switch (binding->source) { case BUTTON_SOURCE_NOT_SET: break; case BUTTON_SOURCE_KEYBOARD_PHYSICAL: is_bound = binding->code.scancode == code.scancode; break; case BUTTON_SOURCE_GAMEPAD: is_bound = binding->code.gamepad_button == code.gamepad_button; break; case BUTTON_SOURCE_MOUSE: is_bound = binding->code.mouse_button == code.mouse_button; break; default: SDL_assert(false); } /* stop early, this won't change */ if (is_bound) break; } if (!is_bound) return; /* remove the element to unbind and shift the rest so there isn't a gap */ size_t elements_after_index = action->num_bindings - index; size_t shifted_size = elements_after_index * sizeof action->bindings[0]; SDL_memmove(action->bindings + index, action->bindings + index + 1, shifted_size); --action->num_bindings; } void input_state_init(InputState *input) { sh_new_strdup(input->action_hash); init_control_maps(); } void input_state_deinit(InputState *input) { shfree(control_to_mouse_mask); shfree(control_to_scancode); input_reset_state(input); } void input_state_update_postframe(InputState *input) { (void)input; /* TODO: don't spam it if it happens */ if (SDL_SetRelativeMouseMode(ctx.game_copy.mouse_capture && ctx.window_mouse_resident) != 0) log_warn("(%s) Mouse capture isn't supported.", __func__); } void input_state_update(InputState *input) { int x, y; input->keyboard_state = SDL_GetKeyboardState(NULL); input->mouse_state = SDL_GetMouseState(&x, &y); input->mouse_window_position = (Vec2){ (float)x, (float)y }; SDL_GetRelativeMouseState(&x, &y); input->mouse_relative_position = (Vec2){ (float)x, (float)y }; ctx.game.mouse_position.x = ((float)input->mouse_window_position.x - ctx.viewport_rect.x) / ctx.viewport_scale; ctx.game.mouse_position.y = ((float)input->mouse_window_position.y - ctx.viewport_rect.y) / ctx.viewport_scale; if (ctx.window_mouse_resident) ctx.game.mouse_movement = input->mouse_relative_position; else ctx.game.mouse_movement = (Vec2){0}; for (size_t i = 0; i < shlenu(input->action_hash); ++i) { Action *action = &input->action_hash[i].value; /* collect unused */ for (size_t u = 0; u < action->num_bindings; ++u) { Button *button = &action->bindings[u]; if (!button->in_use) input_unbind_code_from_action(input, input->action_hash[i].key, button->source, button->code); else button->in_use = false; } update_action_pressed_state(input, action); } size_t removed = 0; for (size_t i = 0; i < shlenu(input->action_hash); ++i) { if (input->action_hash[i - removed].value.num_bindings == 0) input_delete_action(input->action_hash[i - removed].key); } } static Button *infer_control_desc(char const *control) { Button *result = NULL; char *copy = SDL_strdup(control); char *saveptr = NULL; char const *part = SDL_strtokr(copy, "+", &saveptr); do { struct ScancodeHashItem const *scancode = shgetp_null(control_to_scancode, part); if (scancode) { Button const button = { .source = BUTTON_SOURCE_KEYBOARD_PHYSICAL, .code.scancode = scancode->value, }; arrpush(result, button); continue; } struct MouseButtonHashItem const *mouse_button = shgetp_null(control_to_mouse_mask, part); if (mouse_button) { Button const button = { .source = BUTTON_SOURCE_MOUSE, .code.mouse_button = mouse_button->value, }; arrpush(result, button); continue; } log_warn("Unknown control part given (%s)", part); } while ((part = SDL_strtokr(NULL, "+", &saveptr))); SDL_free(copy); return result; } void input_action(char const *action_name, char const *control) { SDL_assert_always(action_name); Button *combo = infer_control_desc(control); if (!combo) { log_warn("Invalid control (%s) for action bind", control); return; } /* TODO: */ if (arrlenu(combo) > 1) { log_warn("TODO: Control combinations are not yet supported."); return; } if (combo[0].source == BUTTON_SOURCE_KEYBOARD_PHYSICAL) input_bind_code_to_action(&ctx.input, action_name, BUTTON_SOURCE_KEYBOARD_PHYSICAL, (union ButtonCode) { .scancode = combo[0].code.scancode }); else if (combo[0].source == BUTTON_SOURCE_MOUSE) input_bind_code_to_action(&ctx.input, action_name, BUTTON_SOURCE_MOUSE, (union ButtonCode) { .mouse_button = combo[0].code.mouse_button }); else log_warn("(%s) Unsupported control source value given: %i.", __func__, control); arrfree(combo); } bool input_action_pressed(char const *action_name) { SDL_assert_always(action_name); ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name); if (action == NULL) { log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name); return false; } return action->value.is_pressed; } bool input_action_just_pressed(char const *action_name) { SDL_assert_always(action_name); ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name); if (action == NULL) { log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name); return false; } return action->value.is_pressed && action->value.just_changed; } bool input_action_just_released(char const *action_name) { SDL_assert_always(action_name); ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name); if (action == NULL) { log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name); return false; } return !action->value.is_pressed && action->value.just_changed; } Vec2 input_action_position(char const *action_name) { SDL_assert_always(action_name); ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name); if (action == NULL) { log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name); return (Vec2) { 0 }; } return action->value.position; } void input_reset_state(InputState *input) { for (size_t i = 0; i < shlenu(input->action_hash); ++i) { Action *action = &input->action_hash[i].value; SDL_free(action->bindings); } stbds_shfree(input->action_hash); }