diff --git a/src/client/header/keyboard.h b/src/client/header/keyboard.h index d0ffe9f1..60ce807e 100644 --- a/src/client/header/keyboard.h +++ b/src/client/header/keyboard.h @@ -206,6 +206,7 @@ enum QKEYS { // Keyboard keys / codes end here. Any new ones should go before this. // From here on, only gamepad controls must be allowed. + // Otherwise, separate bindings (keyboard / controller) menu options will not work. K_BTN_A, K_JOY_FIRST_REGULAR = K_BTN_A, diff --git a/src/client/menu/header/qmenu.h b/src/client/menu/header/qmenu.h index 2cbf9eef..a8f3cde6 100644 --- a/src/client/menu/header/qmenu.h +++ b/src/client/menu/header/qmenu.h @@ -43,6 +43,12 @@ #define QMF_GRAYED 0x00000002 #define QMF_NUMBERSONLY 0x00000004 +enum { + KEYS_ALL = 0, + KEYS_KEYBOARD_MOUSE, + KEYS_CONTROLLER +}; + typedef struct _tag_menuframework { int x, y; diff --git a/src/client/menu/menu.c b/src/client/menu/menu.c index 8cdc07e3..bf996d4b 100644 --- a/src/client/menu/menu.c +++ b/src/client/menu/menu.c @@ -64,6 +64,7 @@ static void M_Menu_Video_f(void); static void M_Menu_Options_f(void); static void M_Menu_Keys_f(void); static void M_Menu_Joy_f(void); +static void M_Menu_ControllerButtons_f(void); static void M_Menu_Quit_f(void); void M_Menu_Credits(void); @@ -790,8 +791,7 @@ char *bindnames[][2] = {"invdrop", "drop item"}, {"invprev", "prev item"}, {"invnext", "next item"}, - {"cmd help", "help computer"}, - {"+joyaltselector", "enable alt joy keys"} + {"cmd help", "help computer"} }; #define NUM_BINDNAMES (sizeof bindnames / sizeof bindnames[0]) @@ -803,11 +803,20 @@ static menuframework_s s_joy_menu; static menuaction_s s_keys_actions[NUM_BINDNAMES]; static void -M_UnbindCommand(char *command) +M_UnbindCommand(char *command, int scope) { int j; + int begin = 0, end = K_LAST; + switch (scope) + { + case KEYS_KEYBOARD_MOUSE: + end = K_JOY_FIRST_REGULAR; + break; + case KEYS_CONTROLLER: + begin = K_JOY_FIRST_REGULAR; + } - for (j = 0; j < K_LAST; j++) + for (j = begin; j < end; j++) { char *b; b = keybindings[j]; @@ -825,15 +834,24 @@ M_UnbindCommand(char *command) } static void -M_FindKeysForCommand(char *command, int *twokeys) +M_FindKeysForCommand(char *command, int *twokeys, int scope) { int count; int j; + int begin = 0, end = K_LAST; + switch (scope) + { + case KEYS_KEYBOARD_MOUSE: + end = K_JOY_FIRST_REGULAR; + break; + case KEYS_CONTROLLER: + begin = K_JOY_FIRST_REGULAR; + } twokeys[0] = twokeys[1] = -1; count = 0; - for (j = 0; j < K_LAST; j++) + for (j = begin; j < end; j++) { char *b; b = keybindings[j]; @@ -879,7 +897,7 @@ DrawKeyBindingFunc(void *self) menuaction_s *a = (menuaction_s *)self; float scale = SCR_GetMenuScale(); - M_FindKeysForCommand(bindnames[a->generic.localdata[0]][0], keys); + M_FindKeysForCommand(bindnames[a->generic.localdata[0]][0], keys, KEYS_KEYBOARD_MOUSE); if (keys[0] == -1) { @@ -915,11 +933,11 @@ KeyBindingFunc(void *self) menuaction_s *a = (menuaction_s *)self; int keys[2]; - M_FindKeysForCommand(bindnames[a->generic.localdata[0]][0], keys); + M_FindKeysForCommand(bindnames[a->generic.localdata[0]][0], keys, KEYS_KEYBOARD_MOUSE); if (keys[1] != -1) { - M_UnbindCommand(bindnames[a->generic.localdata[0]][0]); + M_UnbindCommand(bindnames[a->generic.localdata[0]][0], KEYS_KEYBOARD_MOUSE); } menukeyitem_bind = true; @@ -967,7 +985,8 @@ Keys_MenuKey(int key) if (menukeyitem_bind) { - if ((key != K_ESCAPE) && (key != '`')) + // Any key/button except from the game controller and escape keys + if ((key != K_ESCAPE) && (key != '`') && (key < K_JOY_FIRST_REGULAR)) { char cmd[1024]; @@ -988,7 +1007,7 @@ Keys_MenuKey(int key) KeyBindingFunc(item); return menu_in_sound; case K_BACKSPACE: /* delete bindings */ - M_UnbindCommand(bindnames[item->generic.localdata[0]][0]); + M_UnbindCommand(bindnames[item->generic.localdata[0]][0], KEYS_KEYBOARD_MOUSE); return menu_out_sound; default: return Default_MenuKey(&s_keys_menu, key); @@ -1028,7 +1047,7 @@ MultiplayerDrawKeyBindingFunc(void *self) menuaction_s *a = (menuaction_s *)self; float scale = SCR_GetMenuScale(); - M_FindKeysForCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], keys); + M_FindKeysForCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], keys, KEYS_ALL); if (keys[0] == -1) { @@ -1064,11 +1083,11 @@ MultiplayerKeyBindingFunc(void *self) menuaction_s *a = (menuaction_s *)self; int keys[2]; - M_FindKeysForCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], keys); + M_FindKeysForCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], keys, KEYS_ALL); if (keys[1] != -1) { - M_UnbindCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0]); + M_UnbindCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], KEYS_ALL); } menukeyitem_bind = true; @@ -1116,7 +1135,8 @@ MultiplayerKeys_MenuKey(int key) if (menukeyitem_bind) { - if ((key != K_ESCAPE) && (key != '`')) + // Any key/button but the escape ones + if ((key != K_ESCAPE) && (key != '`') && (key != K_JOY_BACK)) { char cmd[1024]; @@ -1137,7 +1157,7 @@ MultiplayerKeys_MenuKey(int key) MultiplayerKeyBindingFunc(item); return menu_in_sound; case K_BACKSPACE: /* delete bindings */ - M_UnbindCommand(multiplayer_key_bindnames[item->generic.localdata[0]][0]); + M_UnbindCommand(multiplayer_key_bindnames[item->generic.localdata[0]][0], KEYS_ALL); return menu_out_sound; default: return Default_MenuKey(&s_multiplayer_keys_menu, key); @@ -1151,6 +1171,168 @@ M_Menu_Multiplayer_Keys_f(void) M_PushMenu(MultiplayerKeys_MenuDraw, MultiplayerKeys_MenuKey); } +/* + * GAME CONTROLLER ( GAMEPAD / JOYSTICK ) BUTTONS MENU + */ + +char *controller_bindnames[][2] = +{ + {"+attack", "attack"}, + {"+moveup", "up / jump"}, + {"+movedown", "down / crouch"}, + {"weapnext", "next weapon"}, + {"weapprev", "previous weapon"}, + {"cycleweap weapon_chaingun weapon_machinegun weapon_blaster", "long range: quickswitch 1"}, + {"cycleweap weapon_supershotgun weapon_shotgun", "close range: quickswitch 2"}, + {"cycleweap weapon_rocketlauncher weapon_grenadelauncher ammo_grenades", "explosives: quickswitch 3"}, + {"cycleweap weapon_bfg weapon_railgun weapon_hyperblaster", "special: quickswitch 4"}, + {"prefweap weapon_railgun weapon_hyperblaster weapon_chaingun weapon_supershotgun weapon_machinegun weapon_shotgun weapon_blaster", "best safe weapon"}, + {"prefweap weapon_bfg weapon_railgun weapon_rocketlauncher weapon_hyperblaster weapon_grenadelauncher weapon_chaingun ammo_grenades weapon_supershotgun", "best unsafe weapon"}, + {"centerview", "center view"}, + {"inven", "inventory"}, + {"invuse", "use item"}, + {"invdrop", "drop item"}, + {"invprev", "prev item"}, + {"invnext", "next item"}, + {"cmd help", "help computer"}, + {"+joyaltselector", "alt buttons modifier"} +}; +#define NUM_CONTROLLER_BINDNAMES (sizeof controller_bindnames / sizeof controller_bindnames[0]) + +static menuframework_s s_controller_buttons_menu; +static menuaction_s s_controller_buttons_actions[NUM_CONTROLLER_BINDNAMES]; + +static void +DrawControllerButtonBindingFunc(void *self) +{ + int keys[2]; + menuaction_s *a = (menuaction_s *)self; + float scale = SCR_GetMenuScale(); + + M_FindKeysForCommand(controller_bindnames[a->generic.localdata[0]][0], keys, KEYS_CONTROLLER); + + if (keys[0] == -1) + { + Menu_DrawString(a->generic.x + a->generic.parent->x + RCOLUMN_OFFSET * scale, + a->generic.y + a->generic.parent->y, "???"); + } + else + { + int x; + const char *name; + + name = Key_KeynumToString(keys[0]); + + Menu_DrawString(a->generic.x + a->generic.parent->x + RCOLUMN_OFFSET * scale, + a->generic.y + a->generic.parent->y, name); + + x = strlen(name) * 8; + + if (keys[1] != -1) + { + Menu_DrawString(a->generic.x + a->generic.parent->x + 24 * scale + (x * scale), + a->generic.y + a->generic.parent->y, "or"); + Menu_DrawString(a->generic.x + a->generic.parent->x + 48 * scale + (x * scale), + a->generic.y + a->generic.parent->y, + Key_KeynumToString(keys[1])); + } + } +} + +static void +ControllerButtonBindingFunc(void *self) +{ + menuaction_s *a = (menuaction_s *)self; + int keys[2]; + + M_FindKeysForCommand(controller_bindnames[a->generic.localdata[0]][0], keys, KEYS_CONTROLLER); + + if (keys[1] != -1) + { + M_UnbindCommand(controller_bindnames[a->generic.localdata[0]][0], KEYS_CONTROLLER); + } + + menukeyitem_bind = true; + + Menu_SetStatusBar(&s_controller_buttons_menu, "press a button for this action"); +} + +static void +ControllerButtons_MenuInit(void) +{ + int i; + + s_controller_buttons_menu.x = (int)(viddef.width * 0.50f); + s_controller_buttons_menu.nitems = 0; + s_controller_buttons_menu.cursordraw = KeyCursorDrawFunc; + + for (i = 0; i < NUM_CONTROLLER_BINDNAMES; i++) + { + s_controller_buttons_actions[i].generic.type = MTYPE_ACTION; + s_controller_buttons_actions[i].generic.flags = QMF_GRAYED; + s_controller_buttons_actions[i].generic.x = 0; + s_controller_buttons_actions[i].generic.y = (i * 9); + s_controller_buttons_actions[i].generic.ownerdraw = DrawControllerButtonBindingFunc; + s_controller_buttons_actions[i].generic.localdata[0] = i; + s_controller_buttons_actions[i].generic.name = controller_bindnames[s_controller_buttons_actions[i].generic.localdata[0]][1]; + + Menu_AddItem(&s_controller_buttons_menu, (void *)&s_controller_buttons_actions[i]); + } + + Menu_SetStatusBar(&s_controller_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + Menu_Center(&s_controller_buttons_menu); +} + +static void +ControllerButtons_MenuDraw(void) +{ + Menu_AdjustCursor(&s_controller_buttons_menu, 1); + Menu_Draw(&s_controller_buttons_menu); +} + +static const char * +ControllerButtons_MenuKey(int key) +{ + menuaction_s *item = (menuaction_s *)Menu_ItemAtCursor(&s_controller_buttons_menu); + + if (menukeyitem_bind) + { + // Only controller buttons allowed + if (key >= K_JOY_FIRST_REGULAR && key != K_JOY_BACK) + { + char cmd[1024]; + + Com_sprintf(cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", + Key_KeynumToString(key), controller_bindnames[item->generic.localdata[0]][0]); + Cbuf_InsertText(cmd); + } + + Menu_SetStatusBar(&s_controller_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + menukeyitem_bind = false; + return menu_out_sound; + } + + key = Key_GetMenuKey(key); + switch (key) + { + case K_ENTER: + ControllerButtonBindingFunc(item); + return menu_in_sound; + case K_BACKSPACE: + M_UnbindCommand(controller_bindnames[item->generic.localdata[0]][0], KEYS_CONTROLLER); + return menu_out_sound; + default: + return Default_MenuKey(&s_controller_buttons_menu, key); + } +} + +static void +M_Menu_ControllerButtons_f(void) +{ + ControllerButtons_MenuInit(); + M_PushMenu(ControllerButtons_MenuDraw, ControllerButtons_MenuKey); +} + /* * JOY MENU */ @@ -1161,6 +1343,13 @@ static menuslider_s s_joy_forwardsensitivity_slider; static menuslider_s s_joy_sidesensitivity_slider; static menuslider_s s_joy_upsensitivity_slider; static menuslider_s s_joy_haptic_slider; +static menuaction_s s_joy_customize_buttons_action; + +static void +CustomizeControllerButtonsFunc(void *unused) +{ + M_Menu_ControllerButtons_f(); +} static void HapticMagnitudeFunc(void *unused) @@ -1300,6 +1489,16 @@ Joy_MenuInit(void) Menu_AddItem(&s_joy_menu, (void *)&s_joy_haptic_slider); } + y += 10; + + s_joy_customize_buttons_action.generic.type = MTYPE_ACTION; + s_joy_customize_buttons_action.generic.x = 0; + s_joy_customize_buttons_action.generic.y = y; + y += 10; + s_joy_customize_buttons_action.generic.name = "customize buttons"; + s_joy_customize_buttons_action.generic.callback = CustomizeControllerButtonsFunc; + Menu_AddItem(&s_joy_menu, (void *)&s_joy_customize_buttons_action); + Menu_Center(&s_joy_menu); } @@ -1669,7 +1868,7 @@ Options_MenuInit(void) s_options_customize_joy_action.generic.type = MTYPE_ACTION; s_options_customize_joy_action.generic.x = 0; s_options_customize_joy_action.generic.y = 130; - s_options_customize_joy_action.generic.name = "customize joystick"; + s_options_customize_joy_action.generic.name = "customize gamepad"; s_options_customize_joy_action.generic.callback = CustomizeJoyFunc; s_options_customize_options_action.generic.type = MTYPE_ACTION; @@ -5041,6 +5240,7 @@ M_Init(void) Cmd_AddCommand("menu_options", M_Menu_Options_f); Cmd_AddCommand("menu_keys", M_Menu_Keys_f); Cmd_AddCommand("menu_joy", M_Menu_Joy_f); + Cmd_AddCommand("menu_buttons", M_Menu_ControllerButtons_f); Cmd_AddCommand("menu_quit", M_Menu_Quit_f); /* initialize the server address book cvars (adr0, adr1, ...)