Skip to content

Commit 0d74d5c

Browse files
committed
Activate and Improve controller support across UI menus, bindings, and loading flows
This series of changes improves the controller experience in Q3Rally by making menu navigation, key rebinding, and save/readback behavior work reliably with modern gamepad keys (K_PAD0_*). It also removes legacy key-range assumptions and introduces optional joystick auto-enable on device detection for better out-of-the-box behavior. 1) Better controller navigation in menus and scroll lists Added/expanded gamepad navigation support in UI menu handling: A/Start as confirm/select B as back/cancel DPAD up/down for menu cursor movement Extended scroll-list input to support controller directional controls directly (including map lists), with support for: K_PAD0_DPAD_* K_PAD0_LEFTSTICK_* This allows selecting list entries using the left stick, not only keyboard arrows. 2) Controller-friendly back/cancel semantics in controls UI Added K_PAD0_B handling in Rally Controls for: exit-confirm cancel action backing out from controls menu canceling active “waiting for key” bind mode so controller users can consistently back out without keyboard/mouse. 3) Graphics loading/update dialog can be controlled with gamepad Updated loading/update acknowledgement key handling to accept controller confirm/cancel: confirm: PAD0_A, JOY1, Enter cancel/skip: PAD0_B, Escape, mouse2 Added fallback behavior to default to “Skip” when confirming with no hovered button, so flow is always operable via controller only. 4) Controller binds now persist/read back correctly Replaced fixed 0..255 key scans with 0..MAX_KEYS in controls key-assignment lookup paths. This fixes cases where controller binds looked unsaved because K_PAD0_* key indices were outside the old range. 5) Extended bind-start handling for controller keys Added Controls_IsMenuSelectKey in shared UI binding flow to accept gamepad buttons as valid bind-start keys, improving controller-first rebinding experience. 6) Optional joystick auto-enable when device is detected Added in_joystickAutoEnable cvar (default 1) and logic to auto-enable joystick input if a device is detected and joystick input is currently off. Documented the new cvar in engine docs. 7) Cleanup and warning fixes Fixed Controls_SearchFieldHasFocus check to avoid a meaningless array-null comparison and use proper bounds checking. Fixed misleading indentation in Controls_MenuKey to remove compiler warning and keep control flow clear.
1 parent bb1f58d commit 0d74d5c

File tree

9 files changed

+109
-42
lines changed

9 files changed

+109
-42
lines changed

engine/code/cgame/cg_scoreboard.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ static void CG_DrawColumnData(sbColumn_t colType, int x, int y, int width,
632632
case SBCOL_DELTA:
633633
if (ci->team == TEAM_SPECTATOR) {
634634
CG_DrawModernText(x, y, "-", 1, width, textColor, qfalse);
635-
} else if (score->client == cg.snap->ps.clientNum && cg.ghostSplitDeltaValid) {
635+
} else if (cg.snap && score->client == cg.snap->ps.clientNum && cg.ghostSplitDeltaValid) {
636636
deltaMs = cg.ghostSplitDeltaMs;
637637
sign = deltaMs < 0 ? '-' : '+';
638638
absMs = deltaMs < 0 ? -deltaMs : deltaMs;
@@ -666,7 +666,7 @@ static void CG_DrawColumnData(sbColumn_t colType, int x, int y, int width,
666666
break;
667667

668668
case SBCOL_STATUS:
669-
if (cg.snap->ps.stats[STAT_CLIENTS_READY] & (1 << score->client)) {
669+
if (cg.snap && (cg.snap->ps.stats[STAT_CLIENTS_READY] & (1 << score->client))) {
670670
CG_DrawModernText(x, y, "READY", 1, width, readyColor, qfalse);
671671
} else if (ci->team != TEAM_SPECTATOR) {
672672
CG_DrawModernText(x, y, "WAIT", 1, width, textColor, qfalse);

engine/code/q3_ui/ui_controls2.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ static void Controls_GetKeyAssignment (char *command, int *twokeys)
790790
twokeys[0] = twokeys[1] = -1;
791791
count = 0;
792792

793-
for ( j = 0; j < 256; j++ )
793+
for ( j = 0; j < MAX_KEYS; j++ )
794794
{
795795
trap_Key_GetBindingBuf( j, b, 256 );
796796
if ( *b == 0 ) {

engine/code/q3_ui/ui_playersettings.c

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2948,7 +2948,7 @@ static qboolean PlayerSettings_TryProfileFavorite( const char *favorite, char *m
29482948

29492949
static qboolean PlayerSettings_GetFavoriteValues( const char *favorite, char *modelName, char *skinName, char *rimName, char *headName ) {
29502950
if ( PlayerSettings_TryProfileFavorite( favorite, modelName, skinName, rimName, headName ) ) {
2951-
return qfalse;
2951+
return qtrue; /* profile favorite found - success */
29522952
}
29532953

29542954
return GetValuesFromFavorite( favorite, modelName, skinName, rimName, headName );
@@ -3519,10 +3519,10 @@ static void PlayerSettings_MenuEvent( void* ptr, int event ) {
35193519
// STONELANCE
35203520
/*
35213521
=================
3522-
PlayerSettigns_ChangeMenu
3522+
PlayerSettings_ChangeMenu
35233523
=================
35243524
*/
3525-
void PlayerSettigns_ChangeMenu( int menuID ){
3525+
void PlayerSettings_ChangeMenu( int menuID ){
35263526

35273527
switch(menuID){
35283528
case ID_CUSTOMIZE:
@@ -3609,7 +3609,7 @@ static void PlayerSettings_MenuInit( void ) {
36093609
// STONELANCE
36103610
s_playersettings.menu.draw = PlayerSettings_DrawBackShaders;
36113611
s_playersettings.menu.transition = PlayerSettings_RunTransition;
3612-
s_playersettings.menu.changeMenu = PlayerSettigns_ChangeMenu;
3612+
s_playersettings.menu.changeMenu = PlayerSettings_ChangeMenu;
36133613
// END
36143614

36153615
s_playersettings.banner.generic.type = MTYPE_BTEXT;
@@ -3996,9 +3996,13 @@ static void PlayerSettings_MenuInit( void ) {
39963996
}
39973997

39983998
s_playersettings.plate.generic.type = MTYPE_PTEXT;
3999-
s_playersettings.plate.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS;
3999+
s_playersettings.plate.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS|QMF_NODEFAULTINIT;
40004000
s_playersettings.plate.generic.x = 640 - 140;
40014001
s_playersettings.plate.generic.y = 378;
4002+
s_playersettings.plate.generic.left = s_playersettings.plate.generic.x - 20;
4003+
s_playersettings.plate.generic.top = s_playersettings.plate.generic.y + 6;
4004+
s_playersettings.plate.generic.right = s_playersettings.plate.generic.x + 120;
4005+
s_playersettings.plate.generic.bottom = s_playersettings.plate.generic.y + PROP_HEIGHT + 10;
40024006
s_playersettings.plate.generic.id = ID_PLATE;
40034007
s_playersettings.plate.generic.callback = PlayerSettings_MenuEvent;
40044008
s_playersettings.plate.string = "CHANGE PLATE";
@@ -4316,13 +4320,12 @@ static void PlateSelection_SetMenuItems( void ) {
43164320
if (!Q_stricmp( s_plateSelection.plateSkin, s_plateSelection.plateList[i] )){
43174321
// found pic, set selection here
43184322
s_plateSelection.list.curvalue = i;
4323+
/* Set top so the selected item is visible, then clamp to valid range */
4324+
s_plateSelection.list.top = i;
43194325
if (s_plateSelection.list.top + s_plateSelection.list.height > s_plateSelection.numPlates)
43204326
s_plateSelection.list.top = s_plateSelection.numPlates - s_plateSelection.list.height;
4321-
43224327
if (s_plateSelection.list.top < 0)
43234328
s_plateSelection.list.top = 0;
4324-
else
4325-
s_plateSelection.list.top = i;
43264329

43274330
return;
43284331
}
@@ -4375,7 +4378,7 @@ static void PlateSelection_MenuInit( void ) {
43754378
s_plateSelection.list.height = 11;
43764379
s_plateSelection.list.itemnames = (const char **)s_plateSelection.items;
43774380
s_plateSelection.list.numitems = s_plateSelection.numPlates;
4378-
for( i = 0; i < MAX_PLATEMODELS; i++ ) {
4381+
for( i = 0; i < s_plateSelection.numPlates; i++ ) {
43794382
s_plateSelection.items[i] = s_plateSelection.plateList[i];
43804383
}
43814384

engine/code/q3_ui/ui_qmenu.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ static sfxHandle_t RadioButton_Key( menuradiobutton_s *rb, int key )
496496
case K_JOY2:
497497
case K_JOY3:
498498
case K_JOY4:
499+
case K_PAD0_A:
499500
case K_ENTER:
500501
case K_KP_ENTER:
501502
case K_KP_LEFTARROW:
@@ -1086,6 +1087,7 @@ sfxHandle_t ListBox_Key( menulist_s *l, int key )
10861087

10871088
case K_KP_UPARROW:
10881089
case K_UPARROW:
1090+
case K_PAD0_DPAD_UP:
10891091
if (l->generic.flags & QMF_HASMOUSEFOCUS)
10901092
{
10911093
// clicked up
@@ -1106,6 +1108,7 @@ sfxHandle_t ListBox_Key( menulist_s *l, int key )
11061108

11071109
case K_KP_DOWNARROW:
11081110
case K_DOWNARROW:
1111+
case K_PAD0_DPAD_DOWN:
11091112
if (l->generic.flags & QMF_HASMOUSEFOCUS)
11101113
{
11111114
// clicked down
@@ -1509,6 +1512,8 @@ sfxHandle_t ScrollList_Key( menulist_s *l, int key )
15091512

15101513
case K_KP_UPARROW:
15111514
case K_UPARROW:
1515+
case K_PAD0_DPAD_UP:
1516+
case K_PAD0_LEFTSTICK_UP:
15121517
if( l->curvalue == 0 ) {
15131518
return menu_buzz_sound;
15141519
}
@@ -1533,6 +1538,8 @@ sfxHandle_t ScrollList_Key( menulist_s *l, int key )
15331538

15341539
case K_KP_DOWNARROW:
15351540
case K_DOWNARROW:
1541+
case K_PAD0_DPAD_DOWN:
1542+
case K_PAD0_LEFTSTICK_DOWN:
15361543
if( l->curvalue == l->numitems - 1 ) {
15371544
return menu_buzz_sound;
15381545
}
@@ -1557,6 +1564,8 @@ sfxHandle_t ScrollList_Key( menulist_s *l, int key )
15571564

15581565
case K_KP_LEFTARROW:
15591566
case K_LEFTARROW:
1567+
case K_PAD0_DPAD_LEFT:
1568+
case K_PAD0_LEFTSTICK_LEFT:
15601569
if( l->columns == 1 ) {
15611570
return menu_null_sound;
15621571
}
@@ -1580,6 +1589,8 @@ sfxHandle_t ScrollList_Key( menulist_s *l, int key )
15801589

15811590
case K_KP_RIGHTARROW:
15821591
case K_RIGHTARROW:
1592+
case K_PAD0_DPAD_RIGHT:
1593+
case K_PAD0_LEFTSTICK_RIGHT:
15831594
if( l->columns == 1 ) {
15841595
return menu_null_sound;
15851596
}
@@ -2089,6 +2100,15 @@ sfxHandle_t Menu_ActivateItem( menuframework_s *s, menucommon_s* item ) {
20892100
return 0;
20902101
}
20912102

2103+
static qboolean Menu_IsGamepadSelectKey( int key )
2104+
{
2105+
if ( key == K_PAD0_A || key == K_PAD0_START ) {
2106+
return qtrue;
2107+
}
2108+
2109+
return qfalse;
2110+
}
2111+
20922112
/*
20932113
=================
20942114
Menu_DefaultKey
@@ -2105,7 +2125,8 @@ sfxHandle_t Menu_DefaultKey( menuframework_s *m, int key )
21052125
{
21062126
case K_MOUSE2:
21072127
case K_ESCAPE:
2108-
// STONELANCE - dont pop the menu if we are on the top menu
2128+
case K_PAD0_B:
2129+
// STONELANCE - dont pop the menu if we are on the top menu
21092130
if (uis.mainMenu)
21102131
return sound;
21112132

@@ -2162,6 +2183,10 @@ sfxHandle_t Menu_DefaultKey( menuframework_s *m, int key )
21622183
}
21632184
}
21642185

2186+
if ( item && !(item->flags & (QMF_MOUSEONLY|QMF_GRAYED|QMF_INACTIVE)) && Menu_IsGamepadSelectKey( key ) ) {
2187+
return Menu_ActivateItem( m, item );
2188+
}
2189+
21652190
// default handling
21662191
switch ( key )
21672192
{
@@ -2176,6 +2201,7 @@ sfxHandle_t Menu_DefaultKey( menuframework_s *m, int key )
21762201
#endif
21772202
case K_KP_UPARROW:
21782203
case K_UPARROW:
2204+
case K_PAD0_DPAD_UP:
21792205
cursor_prev = m->cursor;
21802206
m->cursor_prev = m->cursor;
21812207
m->cursor--;
@@ -2189,6 +2215,7 @@ sfxHandle_t Menu_DefaultKey( menuframework_s *m, int key )
21892215
case K_TAB:
21902216
case K_KP_DOWNARROW:
21912217
case K_DOWNARROW:
2218+
case K_PAD0_DPAD_DOWN:
21922219
cursor_prev = m->cursor;
21932220
m->cursor_prev = m->cursor;
21942221
m->cursor++;

engine/code/q3_ui/ui_rally_controls.c

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ static qboolean Controls_SearchActive( void )
498498

499499
static qboolean Controls_SearchFieldHasFocus( void )
500500
{
501-
if ( !s_controls.menu.items || s_controls.menu.cursor < 0 || s_controls.menu.cursor >= s_controls.menu.nitems ) {
501+
if ( s_controls.menu.nitems <= 0 || s_controls.menu.cursor < 0 || s_controls.menu.cursor >= s_controls.menu.nitems ) {
502502
return qfalse;
503503
}
504504

@@ -1168,7 +1168,7 @@ static void Controls_GetKeyAssignment (char *command, int *twokeys)
11681168
twokeys[0] = twokeys[1] = -1;
11691169
count = 0;
11701170

1171-
for ( j = 0; j < 256; j++ )
1171+
for ( j = 0; j < MAX_KEYS; j++ )
11721172
{
11731173
trap_Key_GetBindingBuf( j, b, 256 );
11741174
if ( *b == 0 ) {
@@ -1452,7 +1452,7 @@ static void Controls_ExitConfirm_MenuEvent( void* ptr, int event )
14521452

14531453
static sfxHandle_t Controls_ExitConfirm_MenuKey( int key )
14541454
{
1455-
if ( key == K_ESCAPE || key == K_MOUSE2 ) {
1455+
if ( key == K_ESCAPE || key == K_MOUSE2 || key == K_PAD0_B ) {
14561456
Controls_ExitConfirm_MenuEvent( &s_controlsExitConfirm.cancel, QM_ACTIVATED );
14571457
return menu_out_sound;
14581458
}
@@ -1555,42 +1555,44 @@ static sfxHandle_t Controls_MenuKey( int key )
15551555

15561556
if (!s_controls.waitingforkey)
15571557
{
1558-
switch (key)
1559-
{
1558+
switch (key)
1559+
{
15601560
case K_BACKSPACE:
15611561
case K_DEL:
15621562
case K_KP_DEL:
15631563
key = -1;
15641564
break;
15651565

1566-
case K_MOUSE2:
1567-
case K_ESCAPE:
1568-
if (s_controls.changesmade) {
1569-
Controls_ExitConfirmMenu();
1570-
return menu_move_sound;
1571-
}
1572-
goto ignorekey;
1566+
case K_MOUSE2:
1567+
case K_ESCAPE:
1568+
case K_PAD0_B:
1569+
if (s_controls.changesmade) {
1570+
Controls_ExitConfirmMenu();
1571+
return menu_move_sound;
1572+
}
1573+
goto ignorekey;
15731574

15741575
default:
15751576
goto ignorekey;
15761577
}
15771578
}
15781579
else
1579-
{
1580-
if (key & K_CHAR_FLAG)
1581-
return 0;
1582-
1583-
switch (key)
15841580
{
1585-
case K_ESCAPE:
1586-
s_controls.waitingforkey = qfalse;
1587-
Controls_Update();
1588-
return (menu_out_sound);
1589-
1590-
case '`':
1591-
goto ignorekey;
1581+
if (key & K_CHAR_FLAG)
1582+
return 0;
1583+
1584+
switch (key)
1585+
{
1586+
case K_ESCAPE:
1587+
case K_PAD0_B:
1588+
s_controls.waitingforkey = qfalse;
1589+
Controls_Update();
1590+
return (menu_out_sound);
1591+
1592+
case '`':
1593+
goto ignorekey;
1594+
}
15921595
}
1593-
}
15941596

15951597
id = ((menucommon_s*)(s_controls.menu.items[s_controls.menu.cursor]))->id;
15961598

engine/code/q3_ui/ui_rally_gfxloading.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,17 @@ static void UI_GFX_Loading_MenuDraw(void) {
510510

511511
static sfxHandle_t UI_GFX_Loading_Key(int key) {
512512
if (s_gfxloading.requireUpdateAck && !s_gfxloading.updateAcked) {
513-
if (key == K_MOUSE1) {
513+
if (key == K_ESCAPE || key == K_MOUSE2 || key == K_PAD0_B) {
514+
s_gfxloading.updateAcked = qtrue;
515+
trap_S_StartLocalSound(menu_out_sound, CHAN_LOCAL_SOUND);
516+
return menu_out_sound;
517+
}
518+
519+
if (key == K_MOUSE1 || key == K_ENTER || key == K_KP_ENTER || key == K_JOY1 || key == K_PAD0_A) {
520+
if (s_gfxloading.hoveredBtn == UPD_BTN_NONE) {
521+
s_gfxloading.hoveredBtn = UPD_BTN_SKIP;
522+
}
523+
514524
switch (s_gfxloading.hoveredBtn) {
515525
case UPD_BTN_NOW:
516526
trap_Cmd_ExecuteText(EXEC_APPEND,

engine/code/sdl/sdl_input.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ static cvar_t *in_mouse = NULL;
5353
static cvar_t *in_nograb;
5454

5555
static cvar_t *in_joystick = NULL;
56+
static cvar_t *in_joystickAutoEnable = NULL;
5657
static cvar_t *in_joystickThreshold = NULL;
5758
static cvar_t *in_joystickNo = NULL;
5859
static cvar_t *in_joystickUseAnalog = NULL;
@@ -615,6 +616,12 @@ static void IN_InitJoystick( void )
615616
// Update cvar on in_restart or controller add/remove.
616617
Cvar_Set( "in_availableJoysticks", buf );
617618

619+
if ( !in_joystick->integer && total > 0 && in_joystickAutoEnable->integer ) {
620+
Com_Printf( "Auto-enabling joystick input (detected %d device%s).\n", total, total == 1 ? "" : "s" );
621+
Cvar_Set( "in_joystick", "1" );
622+
in_joystick = Cvar_Get( "in_joystick", "1", CVAR_ARCHIVE|CVAR_LATCH );
623+
}
624+
618625
if( !in_joystick->integer ) {
619626
Com_DPrintf( "Joystick is not active.\n" );
620627
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
@@ -1395,6 +1402,7 @@ void IN_Init( void *windowData )
13951402
in_nograb = Cvar_Get( "in_nograb", "0", CVAR_ARCHIVE );
13961403

13971404
in_joystick = Cvar_Get( "in_joystick", "0", CVAR_ARCHIVE|CVAR_LATCH );
1405+
in_joystickAutoEnable = Cvar_Get( "in_joystickAutoEnable", "1", CVAR_ARCHIVE );
13981406
in_joystickThreshold = Cvar_Get( "joy_threshold", "0.15", CVAR_ARCHIVE );
13991407

14001408
#if defined(PROTOCOL_HANDLER) && defined(__APPLE__)

engine/code/ui/ui_shared.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3539,14 +3539,31 @@ qboolean Display_KeyBindPending(void) {
35393539
return g_waitingForKey;
35403540
}
35413541

3542+
static qboolean Controls_IsMenuSelectKey( int key ) {
3543+
if ( key == K_ENTER || key == K_KP_ENTER ||
3544+
key == K_JOY1 || key == K_JOY2 || key == K_JOY3 || key == K_JOY4 ) {
3545+
return qtrue;
3546+
}
3547+
3548+
if ( key >= K_PAD0_A && key <= K_PAD0_DPAD_RIGHT ) {
3549+
return qtrue;
3550+
}
3551+
3552+
if ( key >= K_PAD0_MISC1 && key <= K_PAD0_TOUCHPAD ) {
3553+
return qtrue;
3554+
}
3555+
3556+
return qfalse;
3557+
}
3558+
35423559
qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) {
35433560
int id;
35443561
int i;
35453562

35463563
if (!g_waitingForKey)
35473564
{
35483565
if (down && ((key == K_MOUSE1 && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory))
3549-
|| key == K_ENTER || key == K_KP_ENTER || key == K_JOY1 || key == K_JOY2 || key == K_JOY3 || key == K_JOY4)) {
3566+
|| Controls_IsMenuSelectKey( key ))) {
35503567
g_waitingForKey = qtrue;
35513568
g_bindItem = item;
35523569
}
@@ -6057,4 +6074,3 @@ static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y) {
60576074
}
60586075
return qfalse;
60596076
}
6060-

0 commit comments

Comments
 (0)