2D Run-and-gun shooter inspired by One Man's Doomsday, Counter-Strike, and Metal Slug.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

301 lines
11 KiB

  1. #include "IZ_gamecontroller.h"
  2. static INI_ConfigItem game_controller_config_items[IZ_PLAYERS * (IZ_CONTROLS + IZ_GAME_CONTROLLER_DEFAULT_CONFIGS) + 1];
  3. bool IZ_GameControllerIsValidAxisThreshold(u16 value) {
  4. return (IZ_GAME_CONTROLLER_MIN_AXIS_THRESHOLD <= value && value <= IZ_GAME_CONTROLLER_MAX_AXIS_THRESHOLD);
  5. }
  6. void IZ_GameControllerHandleDeviceEvents(IZ_GameControllerState* state, SDL_Event e) {
  7. if (e.type == SDL_CONTROLLERDEVICEADDED) {
  8. u8 game_controllers_count = SDL_NumJoysticks();
  9. if (game_controllers_count <= IZ_PLAYERS && !state->device) {
  10. state->device = SDL_GameControllerOpen(e.cdevice.which);
  11. }
  12. return;
  13. }
  14. if (e.type == SDL_CONTROLLERDEVICEREMOVED) {
  15. if (
  16. state->device
  17. && SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->device)) == e.cdevice.which
  18. ) {
  19. state->device = NULL;
  20. }
  21. }
  22. }
  23. void IZ_GameControllerHandleAxisEvents(IZ_GameControllerState* state, IZ_Action* action, SDL_Event e) {
  24. if (e.type != SDL_CONTROLLERAXISMOTION) {
  25. return;
  26. }
  27. u8 control_index;
  28. for (control_index = 0; control_index < IZ_CONTROLS; control_index += 1) {
  29. const char* current_axis_name = SDL_GameControllerGetStringForAxis(e.caxis.axis);
  30. if (!current_axis_name) {
  31. continue;
  32. }
  33. char current_positive_axis_full_name[IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH];
  34. sprintf(
  35. current_positive_axis_full_name,
  36. "axis:+%s",
  37. current_axis_name
  38. );
  39. char current_negative_axis_full_name[IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH];
  40. sprintf(
  41. current_negative_axis_full_name,
  42. "axis:-%s",
  43. current_axis_name
  44. );
  45. bool is_capture = false;
  46. const u16 bitflag = (0x1 << control_index);
  47. if (strstr(state->config.control_mapping[control_index], current_positive_axis_full_name)) {
  48. if (*action & bitflag) {
  49. IZ_LogInfo(IZ_LOG_CATEGORY_INPUT, "input/gamepad", "up %s", IZ_ACTION_NAMES[control_index]);
  50. }
  51. // TODO: optimize unsetting of action
  52. *action &= ~bitflag;
  53. // should we implement actions that do not cancel out across input controllers?
  54. // add extra byte for source of action:
  55. // 0x1 - keyboard
  56. // 0x2 - game controller dpad
  57. // 0x4 - game controller left stick
  58. // 0x8 - game controller right stick
  59. // 0x10 - other device
  60. }
  61. if (strstr(state->config.control_mapping[control_index], current_negative_axis_full_name)) {
  62. if (*action & bitflag) {
  63. IZ_LogInfo(IZ_LOG_CATEGORY_INPUT, "input/gamepad", "up %s", IZ_ACTION_NAMES[control_index]);
  64. }
  65. *action &= ~bitflag;
  66. // should we implement actions that do not cancel out across input controllers?
  67. // add extra byte for source of action:
  68. // 0x1 - keyboard
  69. // 0x2 - game controller dpad
  70. // 0x4 - game controller left stick
  71. // 0x8 - game controller right stick
  72. // 0x10 - other device
  73. }
  74. char current_axis_full_name[IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH];
  75. IZ_memset(current_axis_full_name, 0, IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH);
  76. if (e.caxis.value > 0) {
  77. IZ_memcpy(
  78. current_axis_full_name,
  79. IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH,
  80. current_positive_axis_full_name,
  81. IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH
  82. );
  83. is_capture = e.caxis.value > state->config.axis_threshold;
  84. }
  85. if (e.caxis.value < 0) {
  86. IZ_memcpy(
  87. current_axis_full_name,
  88. IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH,
  89. current_negative_axis_full_name,
  90. IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH
  91. );
  92. is_capture = e.jaxis.value <= -state->config.axis_threshold;
  93. }
  94. if (!strstr(state->config.control_mapping[control_index], current_axis_full_name)) {
  95. continue;
  96. }
  97. if (!is_capture) {
  98. continue;
  99. }
  100. *action |= bitflag;
  101. IZ_LogInfo(IZ_LOG_CATEGORY_INPUT, "input/gamepad", "dn %s (%s)", IZ_ACTION_NAMES[control_index], current_axis_full_name);
  102. }
  103. }
  104. void IZ_GameControllerHandleButtonEvents(IZ_GameControllerState* state, IZ_Action* action, SDL_Event e) {
  105. u8 control_index;
  106. for (control_index = 0; control_index < IZ_CONTROLS; control_index += 1) {
  107. const char* current_button_name = SDL_GameControllerGetStringForButton(e.cbutton.button);
  108. if (!current_button_name) {
  109. continue;
  110. }
  111. char current_button_full_name[IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH];
  112. sprintf(
  113. current_button_full_name,
  114. "button:%s",
  115. current_button_name
  116. );
  117. if (!IZ_contains(state->config.control_mapping[control_index], IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH, current_button_full_name, IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH)) {
  118. continue;
  119. }
  120. const u16 bitflag = (0x1 << control_index);
  121. if (e.type == SDL_CONTROLLERBUTTONDOWN) {
  122. IZ_LogInfo(IZ_LOG_CATEGORY_INPUT, "input/gamepad", "dn %s (%s)", IZ_ACTION_NAMES[control_index], current_button_full_name);
  123. *action |= bitflag;
  124. return;
  125. }
  126. if (e.type == SDL_CONTROLLERBUTTONUP) {
  127. IZ_LogInfo(IZ_LOG_CATEGORY_INPUT, "input/gamepad", "up %s (%s)", IZ_ACTION_NAMES[control_index], current_button_full_name);
  128. *action &= ~bitflag;
  129. return;
  130. }
  131. }
  132. }
  133. void IZ_GameControllerHandleEvents(IZ_GameControllerState(* state)[IZ_PLAYERS], IZ_Action(* action)[IZ_PLAYERS], SDL_Event e) {
  134. u8 player_index;
  135. for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) {
  136. IZ_GameControllerHandleDeviceEvents(&(*state)[player_index], e);
  137. IZ_GameControllerHandleAxisEvents(&(*state)[player_index], &(*action)[player_index], e);
  138. IZ_GameControllerHandleButtonEvents(&(*state)[player_index], &(*action)[player_index], e);
  139. }
  140. }
  141. void IZ_GameControllerBindStateToConfig(IZ_GameControllerState(* state)[IZ_PLAYERS], INI_ConfigItem config_items[]) {
  142. u8 player_index;
  143. u8 control_index;
  144. for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) {
  145. u8 base_index = (player_index * (IZ_CONTROLS + IZ_GAME_CONTROLLER_DEFAULT_CONFIGS));
  146. config_items[base_index + 0].dest = &((*state)[player_index].config.axis_threshold);
  147. config_items[base_index + 1].dest = &((*state)[player_index].config.guid);
  148. for (control_index = 0; control_index < IZ_CONTROLS; control_index += 1) {
  149. config_items[base_index + IZ_GAME_CONTROLLER_DEFAULT_CONFIGS + control_index].dest = &((*state)[player_index].config.control_mapping[control_index]);
  150. }
  151. }
  152. }
  153. IZ_ProcedureResult IZ_GameControllerSaveConfig(IZ_GameControllerState(* state)[IZ_PLAYERS], const char* config_path) {
  154. IZ_GameControllerBindStateToConfig(state, game_controller_config_items);
  155. return INI_ConfigSave(game_controller_config_items, config_path);
  156. }
  157. void IZ_GameControllerInitializeConfigItems(INI_ConfigItem config_items[]) {
  158. u8 player_index;
  159. u8 control_index;
  160. char* main_section_name;
  161. char* control_mapping_section_name;
  162. for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) {
  163. main_section_name = IZ_calloc(64, sizeof(char));
  164. sprintf(main_section_name, "GameController.%d", player_index);
  165. u8 base_index = (player_index * (IZ_CONTROLS + IZ_GAME_CONTROLLER_DEFAULT_CONFIGS));
  166. config_items[base_index + 0] = (INI_ConfigItem) {
  167. INI_CONFIG_TYPE_U16,
  168. main_section_name,
  169. "AxisThreshold",
  170. NULL,
  171. &IZ_GAME_CONTROLLER_DEFAULT_STATE[player_index].config.axis_threshold,
  172. IZ_GameControllerIsValidAxisThreshold,
  173. INI_CONFIG_TRANSFORMER_NONE,
  174. NULL,
  175. };
  176. config_items[base_index + 1] = (INI_ConfigItem) {
  177. INI_CONFIG_TYPE_GUID,
  178. main_section_name,
  179. "GUID",
  180. NULL,
  181. &IZ_GAME_CONTROLLER_DEFAULT_STATE[player_index].config.guid,
  182. NULL,
  183. INI_CONFIG_TRANSFORMER_NONE,
  184. NULL,
  185. };
  186. control_mapping_section_name = IZ_calloc(64, sizeof(char));
  187. sprintf(control_mapping_section_name, "GameController.%d.ControlMapping", player_index);
  188. for (control_index = 0; control_index < IZ_CONTROLS; control_index += 1) {
  189. config_items[base_index + IZ_GAME_CONTROLLER_DEFAULT_CONFIGS + control_index] = (INI_ConfigItem) {
  190. INI_CONFIG_TYPE_STRING(IZ_GAME_CONTROLLER_MAX_CONTROL_NAME_LENGTH),
  191. control_mapping_section_name,
  192. IZ_ACTION_NAMES[control_index],
  193. NULL,
  194. &IZ_GAME_CONTROLLER_DEFAULT_STATE[player_index].config.control_mapping[control_index],
  195. NULL,
  196. INI_CONFIG_TRANSFORMER_NONE,
  197. NULL,
  198. };
  199. }
  200. }
  201. config_items[IZ_PLAYERS * (IZ_CONTROLS + IZ_GAME_CONTROLLER_DEFAULT_CONFIGS)] = INI_CONFIG_ITEM_NULL;
  202. }
  203. IZ_ProcedureResult IZ_GameControllerInitializeConfig(IZ_GameControllerState(* state)[IZ_PLAYERS], const char* config_path, u8 argc, const char* argv[]) {
  204. IZ_GameControllerInitializeConfigItems(game_controller_config_items);
  205. IZ_GameControllerBindStateToConfig(state, game_controller_config_items);
  206. if (INI_ConfigInitialize(game_controller_config_items, config_path, argc, argv) < 0) {
  207. return -1;
  208. }
  209. return 0;
  210. }
  211. IZ_ProcedureResult IZ_GameControllerInitialize(IZ_GameControllerState(* state)[IZ_PLAYERS], const char* config_path, u8 argc, const char* argv[]) {
  212. IZ_memcpy(state, sizeof(IZ_GameControllerState) * IZ_PLAYERS, &IZ_GAME_CONTROLLER_DEFAULT_STATE, sizeof(IZ_GameControllerState) * IZ_PLAYERS);
  213. // TODO query this file from the internet?
  214. // Download from https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt
  215. // Add the subproject
  216. IZ_LogInfo(IZ_LOG_CATEGORY_GENERIC, "input", "Loading game controller mappings file: %s", "assets/gamecontrollerdb.txt");
  217. const i16 loaded_mappings = SDL_GameControllerAddMappingsFromFile("assets/gamecontrollerdb.txt");
  218. if (loaded_mappings <= 0) {
  219. return -1;
  220. }
  221. IZ_LogInfo(IZ_LOG_CATEGORY_GENERIC, "input", "Mappings loaded: %d", loaded_mappings);
  222. u8 player_index;
  223. if (IZ_GameControllerInitializeConfig(state, config_path, argc, argv) < 0) {
  224. return -2;
  225. }
  226. u8 game_controllers_count = SDL_NumJoysticks();
  227. for (player_index = 0; player_index < game_controllers_count; player_index += 1) {
  228. if (player_index >= IZ_PLAYERS) {
  229. break;
  230. }
  231. (*state)[player_index].device = SDL_GameControllerOpen(state[player_index]->config.device_id);
  232. if (!(*state)[player_index].device) {
  233. break;
  234. }
  235. (*state)[player_index].config.device_id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick((*state)[player_index].device));
  236. SDL_GUID game_controller_guid = SDL_JoystickGetGUID(SDL_GameControllerGetJoystick((*state)[player_index].device));
  237. IZ_memcpy(&(*state)[player_index].config.guid, sizeof(SDL_GUID), &game_controller_guid, sizeof(SDL_GUID));
  238. IZ_LogInfo(
  239. IZ_LOG_CATEGORY_GENERIC,
  240. "input/gamepad",
  241. "Found device for player %u: %s",
  242. player_index,
  243. SDL_GameControllerName((*state)[player_index].device)
  244. );
  245. }
  246. // Post config (after game_controller GUIDs have been queried), this is unique to game_controllers since they can be plugged in any
  247. // time.
  248. INI_ConfigSaveResult post_config_save_result = IZ_GameControllerSaveConfig(state, config_path);
  249. if (post_config_save_result < 0) {
  250. return -3;
  251. }
  252. return 0;
  253. }
  254. void IZ_GameControllerTeardown(IZ_GameControllerState(* state)[IZ_PLAYERS]) {
  255. u8 player_index;
  256. for (player_index = 0; player_index < IZ_PLAYERS; player_index += 1) {
  257. if (!(*state)[player_index].device) {
  258. continue;
  259. }
  260. SDL_GameControllerClose((*state)[player_index].device);
  261. }
  262. }