townengine/src/twn_input.c

347 lines
12 KiB
C

#include "twn_input_c.h"
#include "twn_util.h"
#include "twn_control.h"
#include "twn_engine_context_c.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
#include <stdbool.h>
#include <stdlib.h>
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);
}