#include "twn_input_c.h" #include "twn_util.h" #include "twn_control.h" #include "twn_engine_context_c.h" #include #include #include #include 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_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; action->position.y = (float)input->mouse_window_position.y; /* 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 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 == NULL) { log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name); return; } Action *action = &action_item->value; /* check every binding to make sure this code isn't already bound */ for (size_t i = 0; i < (uint64_t)ctx.keybind_slots; ++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_KEYBOARD_CHARACTER: is_already_bound = binding->code.keycode == code.keycode; 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; } if (is_already_bound) { log_warn("(%s) Code already bound to action \"%s\".", __func__, action_name); 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) - (sizeof action->bindings[0]); SDL_memmove(action->bindings, action->bindings + 1, shifted_size); } action->bindings[action->num_bindings++] = (Button) { .source = source, .code = code, }; } 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 == NULL) { log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name); return; } 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 < (uint64_t)ctx.keybind_slots; ++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_KEYBOARD_CHARACTER: is_bound = binding->code.keycode == code.keycode; 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; } /* stop early, this won't change */ if (is_bound) break; } if (!is_bound) { log_warn("(%s) Code is not bound to action \"%s\".", __func__, action_name); 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); } void input_state_deinit(InputState *input) { input_reset_state(input); } void input_state_update(InputState *input) { input->keyboard_state = SDL_GetKeyboardState(NULL); input->mouse_state = SDL_GetMouseState(&input->mouse_window_position.x, &input->mouse_window_position.y); SDL_GetRelativeMouseState(&input->mouse_relative_position.x, &input->mouse_relative_position.y); ctx.game.mouse_position = input->mouse_window_position; ctx.game.mouse_movement = input->mouse_relative_position; for (size_t i = 0; i < shlenu(input->action_hash); ++i) { Action *action = &input->action_hash[i].value; update_action_pressed_state(input, action); } } void input_bind_action_control(char const *action_name, Control control) { SDL_assert_always(action_name); if (CONTROL_SCANCODE_START <= control && control < CONTROL_SCANCODE_LIMIT) input_bind_code_to_action(&ctx.input, action_name, BUTTON_SOURCE_KEYBOARD_PHYSICAL, (union ButtonCode) { .scancode = (SDL_Scancode)control }); else if (CONTROL_MOUSECODE_START <= control && control < CONTROL_MOUSECODE_LIMIT) { uint8_t const mouse_button = (uint8_t)(control - CONTROL_MOUSECODE_START); input_bind_code_to_action(&ctx.input, action_name, BUTTON_SOURCE_MOUSE, (union ButtonCode) { .mouse_button = SDL_BUTTON(mouse_button)}); } else log_warn("(%s) Invalid control value given: %i.", __func__, control); } void input_unbind_action_control(char const *action_name, Control control) { SDL_assert_always(action_name); if (CONTROL_SCANCODE_START <= control && control < CONTROL_SCANCODE_LIMIT) input_unbind_code_from_action(&ctx.input, action_name, BUTTON_SOURCE_KEYBOARD_PHYSICAL, (union ButtonCode) { .scancode = (SDL_Scancode)control }); else if (CONTROL_MOUSECODE_START <= control && control < CONTROL_MOUSECODE_LIMIT) { uint8_t const mouse_button = (uint8_t)(control - CONTROL_MOUSECODE_START); input_unbind_code_from_action(&ctx.input, action_name, BUTTON_SOURCE_MOUSE, (union ButtonCode) { .mouse_button = SDL_BUTTON(mouse_button)}); } else log_warn("(%s) Invalid control value given: %i.", __func__, control); } void input_add_action(char const *action_name) { SDL_assert_always(action_name); if (shgeti(ctx.input.action_hash, action_name) >= 0) { log_warn("(%s) Action \"%s\" is already registered.", __func__, action_name); return; } Action new_action = { 0 }; new_action.bindings = ccalloc(ctx.keybind_slots, sizeof *new_action.bindings); shput(ctx.input.action_hash, action_name, new_action); } void input_delete_action(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\" is not registered.", __func__, action_name); return; } SDL_free(action->value.bindings); shdel(ctx.input.action_hash, action_name); } bool input_is_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_is_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_is_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_get_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_set_mouse_captured(bool enabled) { if (SDL_SetRelativeMouseMode(enabled) != 0) log_warn("(%s) Mouse capture isn't supported.", __func__); } bool input_is_mouse_captured(void) { return SDL_GetRelativeMouseMode(); } 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); }