shithub: wipeout

Download patch

ref: b2e3ae80dd3e5f152cdf35bf58473b8d6cef0ef4
parent: 0ded93de55bac828276e8a16877d6edf1f924d95
author: Dominic Szablewski <dominic@phoboslab.org>
date: Tue Aug 15 19:40:12 EDT 2023

Add button bindings to save data, add controls menu; close #6

--- a/src/input.c
+++ b/src/input.c
@@ -4,9 +4,9 @@
 #include "utils.h"
 
 static const char *button_names[] = {
-	NULL, 
-	NULL, 
-	NULL, 
+	NULL,
+	NULL,
+	NULL,
 	NULL,
 	[INPUT_KEY_A] = "A",
 	[INPUT_KEY_B] = "B",
--- a/src/input.h
+++ b/src/input.h
@@ -51,7 +51,7 @@
 	INPUT_KEY_LEFTBRACKET = 47,
 	INPUT_KEY_RIGHTBRACKET = 48,
 	INPUT_KEY_BACKSLASH = 49,
-	INPUT_KEY_HASH = 50, 
+	INPUT_KEY_HASH = 50,
 	INPUT_KEY_SEMICOLON = 51,
 	INPUT_KEY_APOSTROPHE = 52,
 	INPUT_KEY_TILDE = 53,
@@ -108,7 +108,7 @@
 	INPUT_KEY_LGUI = 103,
 	INPUT_KEY_RCTRL = 104,
 	INPUT_KEY_RSHIFT = 105,
-	INPUT_KEY_RALT = 106, 
+	INPUT_KEY_RALT = 106,
 
 	INPUT_KEY_MAX = 107,
 
--- a/src/wipeout/game.c
+++ b/src/wipeout/game.c
@@ -404,8 +404,20 @@
 
 	.has_rapier_class = true,  // for testing; should be false in prod
 	.has_bonus_circuts = true, // for testing; should be false in prod
-	.highscores_name = {0,0,0,0},
 
+	.buttons = {
+		[A_UP] = {INPUT_KEY_UP, INPUT_GAMEPAD_DPAD_UP},
+		[A_DOWN] = {INPUT_KEY_DOWN, INPUT_GAMEPAD_DPAD_DOWN},
+		[A_LEFT] = {INPUT_KEY_LEFT, INPUT_GAMEPAD_DPAD_LEFT},
+		[A_RIGHT] = {INPUT_KEY_RIGHT, INPUT_GAMEPAD_DPAD_RIGHT},
+		[A_BRAKE_LEFT] = {INPUT_KEY_C, INPUT_GAMEPAD_L_SHOULDER},
+		[A_BRAKE_RIGHT] = {INPUT_KEY_V, INPUT_GAMEPAD_R_SHOULDER},
+		[A_THRUST] = {INPUT_KEY_X, INPUT_GAMEPAD_A},
+		[A_FIRE] = {INPUT_KEY_Z, INPUT_GAMEPAD_X},
+		[A_CHANGE_VIEW] = {INPUT_KEY_A, INPUT_GAMEPAD_Y},
+	},
+
+	.highscores_name = {0,0,0,0},
 	.highscores = {
 		[RACE_CLASS_VENOM] = {
 			{
@@ -534,7 +546,7 @@
 
 	input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_X, A_MENU_SELECT);
 	input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_RETURN, A_MENU_START);
-	input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_ESCAPE, A_MENU_START);
+	input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_ESCAPE, A_MENU_QUIT);
 
 	// Gamepad
 	input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_UP, A_MENU_UP);
@@ -552,41 +564,17 @@
 
 	input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_A, A_MENU_SELECT);
 	input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_START, A_MENU_START);
+	
 
-
-
-	// User defined
-	// TODO: these should be configurable and stored in the save struct
-	// Keyboard
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_UP, A_UP);
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_DOWN, A_DOWN);
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_LEFT, A_LEFT);
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_RIGHT, A_RIGHT);
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_C, A_BRAKE_LEFT);
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_V, A_BRAKE_RIGHT);
-
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_X, A_THRUST);
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_Z, A_FIRE);
-	input_bind(INPUT_LAYER_USER, INPUT_KEY_A, A_CHANGE_VIEW);
-
-	// Gamepad
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_DPAD_UP, A_UP);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_DPAD_DOWN, A_DOWN);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_DPAD_LEFT, A_LEFT);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_DPAD_RIGHT, A_RIGHT);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_L_STICK_UP, A_UP);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_L_STICK_DOWN, A_DOWN);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_L_STICK_LEFT, A_LEFT);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_L_STICK_RIGHT, A_RIGHT);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_L_TRIGGER, A_BRAKE_LEFT);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_R_TRIGGER, A_BRAKE_RIGHT);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_L_SHOULDER, A_BRAKE_LEFT);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_R_SHOULDER, A_BRAKE_RIGHT);
-
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_A, A_THRUST);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_X, A_FIRE);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_Y, A_CHANGE_VIEW);
-	input_bind(INPUT_LAYER_USER, INPUT_GAMEPAD_SELECT, A_CHANGE_VIEW);
+	// User defined, loaded from the save struct
+	for (int action = 0; action < len(save.buttons); action++) {
+		if (save.buttons[action][0] != INPUT_INVALID) {
+			input_bind(INPUT_LAYER_USER, save.buttons[action][0], action);
+		}
+		if (save.buttons[action][1] != INPUT_INVALID) {
+			input_bind(INPUT_LAYER_USER, save.buttons[action][1], action);
+		}
+	}
 
 
 	game_set_scene(GAME_SCENE_INTRO);
--- a/src/wipeout/game.h
+++ b/src/wipeout/game.h
@@ -20,15 +20,6 @@
 #define SAVE_DATA_MAGIC 0x64736f77
 
 typedef enum {
-	A_MENU_UP,
-	A_MENU_DOWN,
-	A_MENU_LEFT,
-	A_MENU_RIGHT,
-	A_MENU_BACK,
-	A_MENU_SELECT,
-	A_MENU_START,
-	A_MENU_QUIT,
-
 	A_UP,
 	A_DOWN,
 	A_LEFT,
@@ -38,6 +29,16 @@
 	A_THRUST,
 	A_FIRE,
 	A_CHANGE_VIEW,
+	NUM_GAME_ACTIONS,
+
+	A_MENU_UP,
+	A_MENU_DOWN,
+	A_MENU_LEFT,
+	A_MENU_RIGHT,
+	A_MENU_BACK,
+	A_MENU_SELECT,
+	A_MENU_START,
+	A_MENU_QUIT,
 } action_t;
 
 
@@ -247,6 +248,9 @@
 
 	uint32_t has_rapier_class;
 	uint32_t has_bonus_circuts;
+	
+	uint8_t buttons[NUM_GAME_ACTIONS][2];
+
 	char highscores_name[4];
 	highscores_t highscores[NUM_RACE_CLASSES][NUM_CIRCUTS][NUM_HIGHSCORE_TABS];
 } save_t;
--- a/src/wipeout/main_menu.c
+++ b/src/wipeout/main_menu.c
@@ -2,6 +2,7 @@
 #include "../system.h"
 #include "../mem.h"
 #include "../platform.h"
+#include "../input.h"
 
 #include "menu.h"
 #include "main_menu.h"
@@ -133,8 +134,121 @@
 // -----------------------------------------------------------------------------
 // Options Controls
 
+static const char *button_names[NUM_GAME_ACTIONS][2] = {};
+static int control_current_action;
+static float await_input_deadline;
+
+void button_capture(void *user, button_t button, int32_t ascii_char) {
+	if (button == INPUT_INVALID) {
+		return;
+	}
+
+	menu_t *menu = (menu_t *)user;
+	if (button == INPUT_KEY_ESCAPE) {
+		input_capture(NULL, NULL);
+		menu_pop(menu);
+		return;
+	}
+
+	int index = button < INPUT_KEY_MAX ? 0 : 1; // joypad or keyboard
+
+	// unbind this button if it's bound anywhere
+	for (int i = 0; i < len(save.buttons); i++) {
+		if (save.buttons[i][index] == button) {
+			save.buttons[i][index] = INPUT_INVALID;
+		}
+	}
+	input_capture(NULL, NULL);
+	input_bind(INPUT_LAYER_USER, button, control_current_action);
+	save.buttons[control_current_action][index] = button;
+	save.is_dirty = true;
+	menu_pop(menu);
+}
+
+static void page_options_control_set_draw(menu_t *menu, int data) {
+	float remaining = await_input_deadline - platform_now();
+
+	menu_page_t *page = &menu->pages[menu->index];
+	char remaining_text[2] = { '0' + (uint8_t)clamp(remaining + 1, 0, 3), '\0'};
+	vec2i_t pos = vec2i(page->items_pos.x, page->items_pos.y + 24);
+	ui_draw_text_centered(remaining_text, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_16, UI_COLOR_DEFAULT);
+
+	if (remaining <= 0) {
+		input_capture(NULL, NULL);
+		menu_pop(menu);
+		return;
+	}
+}
+
+static void page_options_controls_set_init(menu_t *menu, int data) {
+	control_current_action = data;
+	await_input_deadline = platform_now() + 3;
+
+	menu_page_t *page = menu_push(menu, "AWAITING INPUT", page_options_control_set_draw);
+	input_capture(button_capture, menu);
+}
+
+
+static void page_options_control_draw(menu_t *menu, int data) {
+	menu_page_t *page = &menu->pages[menu->index];
+
+	int left = page->items_pos.x + page->block_width - 100;
+	int right = page->items_pos.x + page->block_width;
+	int line_y = page->items_pos.y - 20;
+
+	vec2i_t left_head_pos = vec2i(left - ui_text_width("KEYBOARD", UI_SIZE_8), line_y);
+	ui_draw_text("KEYBOARD", ui_scaled_pos(page->items_anchor, left_head_pos), UI_SIZE_8, UI_COLOR_DEFAULT);
+
+	vec2i_t right_head_pos = vec2i(right - ui_text_width("JOYSTICK", UI_SIZE_8), line_y);
+	ui_draw_text("JOYSTICK", ui_scaled_pos(page->items_anchor, right_head_pos), UI_SIZE_8, UI_COLOR_DEFAULT);
+	line_y += 20;
+
+	for (int action = 0; action < NUM_GAME_ACTIONS; action++) {
+		rgba_t text_color = UI_COLOR_DEFAULT;
+		if (action == data) {
+			text_color = UI_COLOR_ACCENT;
+		}
+
+		if (save.buttons[action][0] != INPUT_INVALID) {
+			const char *name = input_button_to_name(save.buttons[action][0]);
+			if (!name) {
+				name = "UNKNWN";
+			}
+			vec2i_t pos = vec2i(left - ui_text_width(name, UI_SIZE_8), line_y);
+			ui_draw_text(name, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color);
+		}
+		if (save.buttons[action][1] != INPUT_INVALID) {
+			const char *name = input_button_to_name(save.buttons[action][1]);
+			if (!name) {
+				name = "UNKNWN";
+			}
+			vec2i_t pos = vec2i(right - ui_text_width(name, UI_SIZE_8), line_y);
+			ui_draw_text(name, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color);
+		}
+		line_y += 12;
+	}
+}
+
 static void page_options_controls_init(menu_t *menu) {
-	menu_page_t *page = menu_push(menu, "TODO", page_options_draw);
+	menu_page_t *page = menu_push(menu, "CONTROLS", page_options_control_draw);
+	flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED);
+	page->title_pos = vec2i(-160, -100);
+	page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
+	page->items_pos = vec2i(-160, -50);
+	page->block_width = 320;
+	page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
+
+	// const char *thrust_name = button_name(A_THRUST);
+	// printf("thrust: %s\n", thrust_name);
+	menu_page_add_button(page, A_UP, "UP", page_options_controls_set_init);
+	menu_page_add_button(page, A_DOWN, "DOWN", page_options_controls_set_init);
+	menu_page_add_button(page, A_LEFT, "LEFT", page_options_controls_set_init);
+	menu_page_add_button(page, A_RIGHT, "RIGHT", page_options_controls_set_init);
+	menu_page_add_button(page, A_BRAKE_LEFT, "BRAKE L", page_options_controls_set_init);
+	menu_page_add_button(page, A_BRAKE_RIGHT, "BRAKE R", page_options_controls_set_init);
+	menu_page_add_button(page, A_THRUST, "THRUST", page_options_controls_set_init);
+	menu_page_add_button(page, A_FIRE, "FIRE", page_options_controls_set_init);
+	menu_page_add_button(page, A_CHANGE_VIEW, "VIEW", page_options_controls_set_init);
 }
 
 // -----------------------------------------------------------------------------
@@ -178,7 +292,7 @@
 	flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED);
 	page->title_pos = vec2i(-160, -100);
 	page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
-	page->items_pos = vec2i(-160, -80);
+	page->items_pos = vec2i(-160, -60);
 	page->block_width = 320;
 	page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER;