--- /dev/null
+/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
+#ifndef VG_INPUT_H
+#define VG_INPUT_H
+
+#include "common.h"
+#include "vg/vg_loader.h"
+
+VG_STATIC inline float vg_get_axis( const char *axis );
+VG_STATIC inline int vg_get_button( const char *button );
+
+/*
+ * Cannot be used in fixed update
+ */
+VG_STATIC inline int vg_get_button_down( const char *button );
+VG_STATIC inline int vg_get_button_up( const char *button );
+
+enum vg_button_state
+{
+ k_button_state_down = 1,
+ k_button_state_up = 3,
+ k_button_state_pressed = 2,
+ k_button_state_none = 0
+};
+
+struct input_binding
+{
+ const char *name;
+
+ enum input_type
+ {
+ k_input_type_button,
+ k_input_type_axis,
+ k_input_type_axis_norm,
+
+ k_input_type_unknown,
+ k_input_type_keyboard_key,
+ k_input_type_mouse_button, /* ? TODO */
+ k_input_type_gamepad_axis,
+ k_input_type_gamepad_button
+ }
+ type;
+
+ union
+ {
+ struct input_axis
+ {
+ SDL_GameControllerAxis gamepad_axis;
+ SDL_Keycode keyboard_positive,
+ keyboard_negative;
+
+ int gamepad_inverted;
+ float value;
+ }
+ axis;
+
+ struct
+ {
+ SDL_GameControllerButton gamepad_id;
+ SDL_Keycode keyboard_id;
+ int value, prev;
+ }
+ button;
+ };
+
+ int save_this;
+};
+
+struct
+{
+ const u8 *sdl_keys;
+ struct input_binding named_inputs[ 32 ];
+ u32 named_input_count;
+
+ const char *controller_name;
+ SDL_GameController *controller_handle; /* null if unplugged */
+ SDL_JoystickID controller_joystick_id;
+ int controller_should_use_trackpad_look;
+
+ float controller_axises[ SDL_CONTROLLER_AXIS_MAX ];
+ int controller_buttons[ SDL_CONTROLLER_BUTTON_MAX ];
+}
+VG_STATIC vg_input;
+
+VG_STATIC void vg_create_unnamed_input( struct input_binding *bind,
+ enum input_type type )
+{
+ memset( bind, 0, sizeof(struct input_binding) );
+
+ bind->name = "API DEFINED";
+ bind->save_this = 0;
+ bind->type = type;
+
+ bind->axis.gamepad_axis = -1;
+ bind->axis.keyboard_positive = -1;
+ bind->axis.keyboard_negative = -1;
+ bind->button.gamepad_id = -1;
+ bind->button.keyboard_id = -1;
+}
+
+VG_STATIC struct input_binding *vg_create_named_input( const char *name,
+ enum input_type type )
+{
+ struct input_binding *bind =
+ &vg_input.named_inputs[ vg_input.named_input_count ++ ];
+ memset( bind, 0, sizeof(struct input_binding) );
+
+ bind->name = name;
+ bind->save_this = 0;
+ bind->type = type;
+
+ bind->axis.gamepad_axis = -1;
+ bind->axis.keyboard_positive = -1;
+ bind->axis.keyboard_negative = -1;
+ bind->button.gamepad_id = -1;
+ bind->button.keyboard_id = -1;
+
+ return bind;
+}
+
+VG_STATIC struct input_binding *vg_get_named_input( const char *name )
+{
+ if( name[0] == '+' || name[0] == '-' )
+ name ++;
+
+ for( u32 i=0; i<vg_input.named_input_count; i++ )
+ {
+ struct input_binding *bind = &vg_input.named_inputs[i];
+ if( !strcmp( bind->name, name ) )
+ return bind;
+ }
+
+ return NULL;
+}
+
+struct input_en
+{
+ enum input_type type;
+
+ const char *alias;
+ int id;
+}
+vg_all_bindable_inputs[] =
+{
+ {k_input_type_keyboard_key, "space", SDLK_SPACE},
+ {k_input_type_keyboard_key, ";", SDLK_SEMICOLON},
+ {k_input_type_keyboard_key, "-", SDLK_MINUS},
+ {k_input_type_keyboard_key, ".", SDLK_PERIOD},
+ {k_input_type_keyboard_key, ",", SDLK_COMMA},
+ {k_input_type_keyboard_key, "=", SDLK_EQUALS},
+ {k_input_type_keyboard_key, "[", SDLK_LEFTBRACKET},
+ {k_input_type_keyboard_key, "]", SDLK_RIGHTBRACKET},
+ {k_input_type_keyboard_key, "left", SDLK_LEFT},
+ {k_input_type_keyboard_key, "right", SDLK_RIGHT},
+ {k_input_type_keyboard_key, "up", SDLK_UP},
+ {k_input_type_keyboard_key, "down", SDLK_DOWN},
+ {k_input_type_keyboard_key, "shift", SDLK_LSHIFT},
+ {k_input_type_keyboard_key, "control", SDLK_LCTRL},
+ {k_input_type_keyboard_key, "\2enter", SDLK_RETURN},
+ {k_input_type_keyboard_key, "\2escape", SDLK_ESCAPE },
+
+ {k_input_type_gamepad_axis, "gp-lt", SDL_CONTROLLER_AXIS_TRIGGERLEFT},
+ {k_input_type_gamepad_axis, "gp-rt", SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
+ {k_input_type_gamepad_axis, "gp-ls-h", SDL_CONTROLLER_AXIS_LEFTX},
+ {k_input_type_gamepad_axis, "gp-ls-v", SDL_CONTROLLER_AXIS_LEFTY},
+ {k_input_type_gamepad_axis, "gp-rs-h", SDL_CONTROLLER_AXIS_RIGHTX},
+ {k_input_type_gamepad_axis, "gp-rs-v", SDL_CONTROLLER_AXIS_RIGHTY},
+
+ {k_input_type_gamepad_button, "gp-a", SDL_CONTROLLER_BUTTON_A},
+ {k_input_type_gamepad_button, "gp-b", SDL_CONTROLLER_BUTTON_B},
+ {k_input_type_gamepad_button, "gp-x", SDL_CONTROLLER_BUTTON_X},
+ {k_input_type_gamepad_button, "gp-y", SDL_CONTROLLER_BUTTON_Y},
+ {k_input_type_gamepad_button, "gp-rb", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {k_input_type_gamepad_button, "gp-lb", SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {k_input_type_gamepad_button, "gp-rs", SDL_CONTROLLER_BUTTON_RIGHTSTICK},
+ {k_input_type_gamepad_button, "gp-ls", SDL_CONTROLLER_BUTTON_LEFTSTICK},
+ {k_input_type_gamepad_button, "gp-dpad-down", SDL_CONTROLLER_BUTTON_DPAD_DOWN},
+ {k_input_type_gamepad_button, "gp-dpad-left", SDL_CONTROLLER_BUTTON_DPAD_LEFT},
+ {k_input_type_gamepad_button,"gp-dpad-right",SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
+ {k_input_type_gamepad_button, "gp-dpad-up", SDL_CONTROLLER_BUTTON_DPAD_UP},
+ {k_input_type_gamepad_button, "\2gp-menu", SDL_CONTROLLER_BUTTON_BACK}
+};
+
+VG_STATIC const char *vg_input_to_str( u32 input, enum input_type input_type )
+{
+ if( input == -1 )
+ return NULL;
+
+ if( input_type == k_input_type_keyboard_key )
+ {
+ if( (input >= SDLK_a) && (input <= SDLK_z) )
+ {
+ return &"a\0b\0c\0d\0e\0f\0g\0h\0i\0j\0k\0l\0m\0n\0o\0p\0"
+ "q\0r\0s\0t\0u\0v\0w\0x\0y\0z\0"[(input-SDLK_a)*2];
+ }
+
+ if( (input >= SDLK_0) && (input <= SDLK_9) )
+ {
+ return &"0\0" "1\0" "2\0" "3\0" "4\0"
+ "5\0" "6\0" "7\0" "8\0" "9\0"[(input-SDLK_0)*2];
+ }
+ }
+
+ for( int i=0; i<vg_list_size(vg_all_bindable_inputs); i++ )
+ {
+ struct input_en *desc = &vg_all_bindable_inputs[i];
+
+ if( (desc->type == input_type) && (desc->id == input) )
+ return desc->alias;
+ }
+
+ return NULL;
+}
+
+VG_STATIC enum input_type vg_str_to_input( const char *str, u32 *input )
+{
+ if( !str )
+ {
+ *input = -1;
+ return k_input_type_unknown;
+ }
+
+ u32 len = strlen(str);
+
+ if( len == 0 )
+ {
+ *input = -1;
+ return k_input_type_unknown;
+ }
+
+ if( len == 1 )
+ {
+ u8 uch = str[0];
+
+ if( (uch >= (u8)'a') && (uch <= (u8)'z') )
+ {
+ *input = SDLK_a + (uch-(u8)'a');
+ return k_input_type_keyboard_key;
+ }
+
+ if( (uch >= (u8)'0') && (uch <= (u8)'9') )
+ {
+ *input = SDLK_0 + (uch-(u8)'0');
+ return k_input_type_keyboard_key;
+ }
+ }
+
+ for( int i=0; i<vg_list_size(vg_all_bindable_inputs); i++ )
+ {
+ struct input_en *desc = &vg_all_bindable_inputs[i];
+
+ if( !strcmp( desc->alias, str ) )
+ {
+ *input = desc->id;
+ return desc->type;
+ }
+ }
+
+ *input = -1;
+ return k_input_type_unknown;
+}
+
+VG_STATIC void vg_print_binding_info( struct input_binding *bind )
+{
+ vg_info( " name: %s\n", bind->name );
+ vg_info( " type: %s\n", (const char *[]){"button","axis","axis[0-1]"}
+ [ bind->type ] );
+ vg_info( " save this? %d\n", bind->save_this );
+
+ if( (bind->type == k_input_type_axis) ||
+ (bind->type == k_input_type_axis_norm) )
+ {
+ vg_info( " gamepad_axis: %s\n",
+ vg_input_to_str(bind->axis.gamepad_axis, k_input_type_gamepad_axis));
+
+ vg_info( " keyboard_positive: %s\n",
+ vg_input_to_str(bind->axis.keyboard_positive,
+ k_input_type_keyboard_key ));
+
+ vg_info( " keyboard_negative: %s\n",
+ vg_input_to_str(bind->axis.keyboard_negative,
+ k_input_type_keyboard_key ));
+ }
+ else
+ {
+ vg_info( " gamepad_id: %s\n",
+ vg_input_to_str(bind->button.gamepad_id, k_input_type_gamepad_button));
+ vg_info( " keyboard_id: %s\n",
+ vg_input_to_str(bind->button.keyboard_id,
+ k_input_type_keyboard_key));
+ }
+}
+
+VG_STATIC void vg_apply_bind_str( struct input_binding *bind,
+ const char *mod,
+ const char *str )
+{
+ int axis_mod = 0;
+ char modch = ' ';
+ if( (mod[0] == '-') || (mod[0] == '+') )
+ {
+ axis_mod = 1;
+ modch = mod[0];
+ mod ++;
+ }
+
+ int invert = 0;
+ if( (str[0] == '-' ) )
+ {
+ invert = 1;
+ str ++;
+ }
+
+ u32 id;
+ enum input_type type = vg_str_to_input( str, &id );
+
+ if( bind->type == k_input_type_button )
+ {
+ if( axis_mod )
+ {
+ vg_error( "Cannot use axis modifiers on button input!\n" );
+ return;
+ }
+
+ if( invert )
+ {
+ vg_error( "Cannot invert button input!\n" );
+ return;
+ }
+
+ if( type == k_input_type_keyboard_key )
+ bind->button.keyboard_id = id;
+ else if( type == k_input_type_gamepad_button )
+ bind->button.gamepad_id = id;
+ else
+ {
+ vg_error( "Unknown button or key '%s'\n", str );
+ return;
+ }
+ }
+ else if( (bind->type == k_input_type_axis ) ||
+ (bind->type == k_input_type_axis_norm))
+ {
+ if( axis_mod )
+ {
+ if( type == k_input_type_keyboard_key )
+ {
+ if( invert )
+ {
+ vg_error( "Cannot invert a keyboard key!\n" );
+ return;
+ }
+
+ if( modch == '+' )
+ bind->axis.keyboard_positive = id;
+ else
+ bind->axis.keyboard_negative = id;
+ }
+ else
+ {
+ vg_error( "You can only bind keyboard keys to +- axises\n" );
+ return;
+ }
+ }
+ else
+ {
+ if( type == k_input_type_gamepad_axis )
+ {
+ bind->axis.gamepad_inverted = invert;
+ bind->axis.gamepad_axis = id;
+ }
+ else
+ {
+ vg_error( "You can only bind gamepad axises to this\n" );
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * bind x jump
+ * bind a -horizontal
+ * bind d +horizontal
+ * bind -gp-ls-h horizontal
+ */
+
+VG_STATIC int vg_rebind_input_cmd( int argc, const char *argv[] )
+{
+ if( argc == 0 )
+ {
+ vg_info( "Usage: bind jump x\n" );
+ vg_info( " bind -steerh j\n" );
+ vg_info( " bind steerh gp-ls-h\n" );
+ return 0;
+ }
+
+ const char *str_bind_name = argv[0];
+ struct input_binding *bind = vg_get_named_input( str_bind_name );
+
+ if( !bind )
+ {
+ vg_error( "There is no bind with that name '%s'\n", str_bind_name );
+ return 0;
+ }
+
+ if( argc == 1 )
+ {
+ vg_print_binding_info( bind );
+ return 0;
+ }
+
+ if( argc == 2 )
+ {
+ const char *str_input_id = argv[1];
+
+ vg_apply_bind_str( bind, str_bind_name, str_input_id );
+ return 0;
+ }
+
+ return 0;
+}
+
+VG_STATIC u8 vg_getkey( SDL_Keycode kc )
+{
+ SDL_Scancode sc = SDL_GetScancodeFromKey( kc );
+ return vg_input.sdl_keys[sc];
+}
+
+VG_STATIC void vg_input_update( u32 num, struct input_binding *binds )
+{
+ if( vg_console.enabled )
+ {
+ for( i32 i=0; i<num; i++ )
+ {
+ struct input_binding *bind = &binds[i];
+
+ if( bind->type == k_input_type_button )
+ {
+ bind->button.prev = bind->button.value;
+ bind->button.value = 0;
+ }
+ }
+
+ return;
+ }
+
+ for( i32 i=0; i<num; i++ )
+ {
+ struct input_binding *bind = &binds[i];
+
+ if( bind->type == k_input_type_button )
+ {
+ bind->button.prev = bind->button.value;
+ bind->button.value = 0;
+
+ if( bind->button.gamepad_id != -1 )
+ bind->button.value |=
+ vg_input.controller_buttons[ bind->button.gamepad_id ];
+
+ if( bind->button.keyboard_id != -1 )
+ bind->button.value |= vg_getkey( bind->button.keyboard_id );
+ }
+ else if( bind->type == k_input_type_axis )
+ {
+ float keyboard_value = 0.0f,
+ gamepad_value = 0.0f;
+
+ if( bind->axis.keyboard_positive != -1 )
+ if( vg_getkey( bind->axis.keyboard_positive ) )
+ keyboard_value += 1.0f;
+
+ if( bind->axis.keyboard_negative != -1 )
+ if( vg_getkey( bind->axis.keyboard_negative ) )
+ keyboard_value -= 1.0f;
+
+ if( bind->axis.gamepad_axis != -1 )
+ {
+ gamepad_value =
+ vg_input.controller_axises[ bind->axis.gamepad_axis ];
+
+ if( bind->axis.gamepad_inverted )
+ gamepad_value *= -1.0f;
+ }
+
+ if( fabsf(gamepad_value) <= 0.01f )
+ gamepad_value = 0.0f;
+
+ if( fabsf(keyboard_value) > fabsf(gamepad_value) )
+ bind->axis.value = keyboard_value;
+ else
+ bind->axis.value = gamepad_value;
+ }
+ else if( bind->type == k_input_type_axis_norm )
+ {
+ float value = 0.0f;
+ if( bind->axis.keyboard_positive != -1 )
+ if( vg_getkey( bind->axis.keyboard_positive ))
+ value = 1.0f;
+
+ if( bind->axis.gamepad_axis != -1 )
+ value = vg_maxf( value,
+ vg_input.controller_axises[bind->axis.gamepad_axis] );
+
+ bind->axis.value = value;
+ }
+ }
+}
+
+VG_STATIC void vg_input_controller_event( SDL_Event *ev )
+{
+ if( ev->type == SDL_CONTROLLERAXISMOTION )
+ {
+ if( ev->caxis.which == vg_input.controller_joystick_id )
+ {
+ vg_input.controller_axises[ ev->caxis.axis ] =
+ (float)ev->caxis.value / 32767.0f;
+ }
+ }
+ else if( ev->type == SDL_CONTROLLERBUTTONDOWN )
+ {
+ if( ev->cbutton.which == vg_input.controller_joystick_id )
+ vg_input.controller_buttons[ ev->cbutton.button ] = 1;
+ }
+ else if( ev->type == SDL_CONTROLLERBUTTONUP )
+ {
+ if( ev->cbutton.which == vg_input.controller_joystick_id )
+ vg_input.controller_buttons[ ev->cbutton.button ] = 0;
+ }
+}
+
+VG_STATIC void vg_try_attach_controller(void)
+{
+ int joy_count = SDL_NumJoysticks();
+ for( int i=0; i<joy_count; i++ )
+ {
+ if( SDL_IsGameController(i) )
+ {
+ vg_input.controller_handle = SDL_GameControllerOpen(i);
+ vg_input.controller_joystick_id = i;
+ vg_success( "Attached game controller with joystick ID %d\n", i );
+ return;
+ }
+ }
+}
+
+
+VG_STATIC void vg_update_inputs(void)
+{
+ vg_input.sdl_keys = SDL_GetKeyboardState(NULL);
+
+ if( vg_input.controller_handle )
+ {
+ if( !SDL_GameControllerGetAttached( vg_input.controller_handle ) )
+ {
+ SDL_GameControllerClose( vg_input.controller_handle );
+ vg_input.controller_handle = NULL;
+ }
+ }
+
+ if( !vg_input.controller_handle )
+ {
+ vg_input.controller_axises[ SDL_CONTROLLER_AXIS_TRIGGERLEFT ] = -1.0f;
+ vg_input.controller_axises[ SDL_CONTROLLER_AXIS_TRIGGERRIGHT ] = -1.0f;
+ vg_try_attach_controller();
+ }
+
+ /* update all inputs */
+ vg_input_update( vg_input.named_input_count, vg_input.named_inputs );
+}
+
+VG_STATIC int vg_console_enabled(void);
+VG_STATIC int vg_input_button_down( struct input_binding *bind )
+{
+ if( bind->button.value && !bind->button.prev )
+ return 1;
+ return 0;
+}
+
+VG_STATIC void vg_input_init(void)
+{
+ vg_acquire_thread_sync();
+
+ vg_function_push( (struct vg_cmd)
+ {
+ .name = "bind",
+ .function = vg_rebind_input_cmd
+ });
+
+ vg_info( "Checking for controller\n" );
+ SDL_GameControllerAddMappingsFromFile( "gamecontrollerdb.txt" );
+
+ int joy_count = SDL_NumJoysticks();
+ for( int i=0; i<joy_count; i++ )
+ {
+ vg_info( "joystick %d: %s [gamecontroller: %d]\n",
+ i, SDL_JoystickNameForIndex( i ),
+ SDL_IsGameController(i) );
+ }
+
+ vg_try_attach_controller();
+
+ vg_input.controller_axises[ SDL_CONTROLLER_AXIS_TRIGGERLEFT ] = -1.0f;
+ vg_input.controller_axises[ SDL_CONTROLLER_AXIS_TRIGGERRIGHT ] = -1.0f;
+
+ vg_release_thread_sync();
+}
+
+VG_STATIC void vg_input_free(void *_)
+{
+ if( vg_input.controller_handle )
+ {
+ SDL_GameControllerClose( vg_input.controller_handle );
+ vg_input.controller_handle = NULL;
+ }
+}
+
+#endif