From ab53e9aa1340465049850a96bd6d7f0776cc3a6f Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Sun, 7 Jan 2024 01:26:45 +0100 Subject: [PATCH 01/14] Merge GameController support from Quadrilateral Cowboy https://github.com/blendogames/quadrilateralcowboy pretty much as it is there, with only minimal changes required to work with dhewm3 --- neo/framework/Common.cpp | 2 +- neo/framework/KeyInput.cpp | 35 +++-- neo/framework/KeyInput.h | 41 +++-- neo/framework/UsercmdGen.cpp | 291 +++++++++++++++++++++++++++++++---- neo/sys/events.cpp | 228 +++++++++++++++++++++++++++ neo/sys/sys_public.h | 73 ++++++++- neo/ui/Window.cpp | 4 +- 7 files changed, 604 insertions(+), 70 deletions(-) diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index bfc69f93..9289a833 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -2919,7 +2919,7 @@ void idCommonLocal::Init( int argc, char **argv ) { #endif #endif - if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK)) // init joystick to work around SDL 2.0.9 bug #4391 + if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER)) // init joystick to work around SDL 2.0.9 bug #4391 Sys_Error("Error while initializing SDL: %s", SDL_GetError()); Sys_InitThreads(); diff --git a/neo/framework/KeyInput.cpp b/neo/framework/KeyInput.cpp index bc5c2e17..b239613e 100644 --- a/neo/framework/KeyInput.cpp +++ b/neo/framework/KeyInput.cpp @@ -124,23 +124,24 @@ static const keyname_t keynames[] = {"JOY13", K_JOY13, "#str_07074"}, {"JOY14", K_JOY14, "#str_07075"}, {"JOY15", K_JOY15, "#str_07076"}, - {"JOY16", K_JOY16, "#str_07077"}, - {"JOY17", K_JOY17, "#str_07078"}, - {"JOY18", K_JOY18, "#str_07079"}, - {"JOY19", K_JOY19, "#str_07080"}, - {"JOY20", K_JOY20, "#str_07081"}, - {"JOY21", K_JOY21, "#str_07082"}, - {"JOY22", K_JOY22, "#str_07083"}, - {"JOY23", K_JOY23, "#str_07084"}, - {"JOY24", K_JOY24, "#str_07085"}, - {"JOY25", K_JOY25, "#str_07086"}, - {"JOY26", K_JOY26, "#str_07087"}, - {"JOY27", K_JOY27, "#str_07088"}, - {"JOY28", K_JOY28, "#str_07089"}, - {"JOY29", K_JOY29, "#str_07090"}, - {"JOY30", K_JOY30, "#str_07091"}, - {"JOY31", K_JOY31, "#str_07092"}, - {"JOY32", K_JOY32, "#str_07093"}, + + {"JOY_STICK1_UP", K_JOY_STICK1_UP, "JOY_STICK1_UP"}, + {"JOY_STICK1_DOWN", K_JOY_STICK1_DOWN, "JOY_STICK1_DOWN"}, + {"JOY_STICK1_LEFT", K_JOY_STICK1_LEFT, "JOY_STICK1_LEFT"}, + {"JOY_STICK1_RIGHT", K_JOY_STICK1_RIGHT, "JOY_STICK1_RIGHT"}, + + {"JOY_STICK2_UP", K_JOY_STICK2_UP, "JOY_STICK2_UP"}, + {"JOY_STICK2_DOWN", K_JOY_STICK2_DOWN, "JOY_STICK2_DOWN"}, + {"JOY_STICK2_LEFT", K_JOY_STICK2_LEFT, "JOY_STICK2_LEFT"}, + {"JOY_STICK2_RIGHT", K_JOY_STICK2_RIGHT, "JOY_STICK2_RIGHT"}, + + {"JOY_TRIGGER1", K_JOY_TRIGGER1, "JOY_TRIGGER1"}, + {"JOY_TRIGGER2", K_JOY_TRIGGER2, "JOY_TRIGGER2"}, + + {"JOY_DPAD_UP", K_JOY_DPAD_UP, "JOY_DPAD_UP"}, + {"JOY_DPAD_DOWN", K_JOY_DPAD_DOWN, "JOY_DPAD_DOWN"}, + {"JOY_DPAD_LEFT", K_JOY_DPAD_LEFT, "JOY_DPAD_LEFT"}, + {"JOY_DPAD_RIGHT", K_JOY_DPAD_RIGHT, "JOY_DPAD_RIGHT"}, {"AUX1", K_AUX1, "#str_07094"}, {"AUX2", K_AUX2, "#str_07095"}, diff --git a/neo/framework/KeyInput.h b/neo/framework/KeyInput.h index 77179d35..e959a1f7 100644 --- a/neo/framework/KeyInput.h +++ b/neo/framework/KeyInput.h @@ -138,6 +138,10 @@ typedef enum { K_MWHEELDOWN = 195, K_MWHEELUP, + //------------------------ + // K_JOY codes must be contiguous, too + //------------------------ + K_JOY1 = 197, K_JOY2, K_JOY3, @@ -154,23 +158,26 @@ typedef enum { K_JOY14, K_JOY15, K_JOY16, - K_JOY17, - K_JOY18, - K_JOY19, - K_JOY20, - K_JOY21, - K_JOY22, - K_JOY23, - K_JOY24, - K_JOY25, - K_JOY26, - K_JOY27, - K_GRAVE_A = 224, // lowercase a with grave accent - K_JOY28, - K_JOY29, - K_JOY30, - K_JOY31, - K_JOY32, + + K_JOY_STICK1_UP, + K_JOY_STICK1_DOWN, + K_JOY_STICK1_LEFT, + K_JOY_STICK1_RIGHT, + + K_JOY_STICK2_UP, + K_JOY_STICK2_DOWN, + K_JOY_STICK2_LEFT, + K_JOY_STICK2_RIGHT, + + K_JOY_TRIGGER1, + K_JOY_TRIGGER2, + + K_JOY_DPAD_UP, + K_JOY_DPAD_DOWN, + K_JOY_DPAD_LEFT, + K_JOY_DPAD_RIGHT, + + K_GRAVE_A = 229, // lowercase a with grave accent FIXME: used to be 224; this probably isn't used anyway K_AUX1 = 230, K_CEDILLA_C = 231, // lowercase c with Cedilla diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index f1b213f7..6fd46169 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -350,6 +350,8 @@ private: bool Inhibited( void ); void AdjustAngles( void ); void KeyMove( void ); + void CircleToSquare( float & axis_x, float & axis_y ) const; + void HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive ); void JoystickMove( void ); void MouseMove( void ); void CmdButtons( void ); @@ -384,7 +386,14 @@ private: bool mouseDown; int mouseDx, mouseDy; // added to by mouse events - int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + float joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + + int pollTime; + int lastPollTime; + float lastLookValuePitch; + float lastLookValueYaw; + + bool heldJump; // TODO: ??? static idCVar in_yawSpeed; static idCVar in_pitchSpeed; @@ -419,6 +428,24 @@ idCVar idUsercmdGenLocal::m_smooth( "m_smooth", "1", CVAR_SYSTEM | CVAR_ARCHIVE idCVar idUsercmdGenLocal::m_strafeSmooth( "m_strafeSmooth", "4", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "number of samples blended for mouse moving", 1, 8, idCmdSystem::ArgCompletion_Integer<1,8> ); idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | CVAR_BOOL, "shows mouse movement" ); +idCVar joy_mergedThreshold( "joy_mergedThreshold", "1", CVAR_BOOL | CVAR_ARCHIVE, "If the thresholds aren't merged, you drift more off center" ); +idCVar joy_newCode( "joy_newCode", "0", CVAR_BOOL | CVAR_ARCHIVE, "Use the new codepath" ); +idCVar joy_triggerThreshold( "joy_triggerThreshold", "0.05", CVAR_FLOAT | CVAR_ARCHIVE, "how far the joystick triggers have to be pressed before they register as down" ); +idCVar joy_deadZone( "joy_deadZone", "0.4", CVAR_FLOAT | CVAR_ARCHIVE, "specifies how large the dead-zone is on the joystick" ); +idCVar joy_range( "joy_range", "1.0", CVAR_FLOAT | CVAR_ARCHIVE, "allow full range to be mapped to a smaller offset" ); +idCVar joy_gammaLook( "joy_gammaLook", "1", CVAR_INTEGER | CVAR_ARCHIVE, "use a log curve instead of a power curve for movement" ); +idCVar joy_powerScale( "joy_powerScale", "2", CVAR_FLOAT | CVAR_ARCHIVE, "Raise joystick values to this power" ); +idCVar joy_pitchSpeed( "joy_pitchSpeed", "130", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing up or down on the joystick", 60, 600 ); +idCVar joy_yawSpeed( "joy_yawSpeed", "240", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing left or right on the joystick", 60, 600 ); + +// these were a bad idea! +idCVar joy_dampenLook( "joy_dampenLook", "1", CVAR_BOOL | CVAR_ARCHIVE, "Do not allow full acceleration on look" ); +idCVar joy_deltaPerMSLook( "joy_deltaPerMSLook", "0.003", CVAR_FLOAT | CVAR_ARCHIVE, "Max amount to be added on look per MS" ); + +idCVar in_useJoystick( "in_useJoystick", "1", CVAR_ARCHIVE | CVAR_BOOL, "enables/disables the gamepad for PC use" ); +idCVar in_invertLook( "in_invertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" ); +idCVar in_mouseInvertLook( "in_mouseInvertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" ); + static idUsercmdGenLocal localUsercmdGen; idUsercmdGen *usercmdGen = &localUsercmdGen; @@ -573,9 +600,17 @@ void idUsercmdGenLocal::KeyMove( void ) { forward += KEY_MOVESPEED * ButtonState( UB_FORWARD ); forward -= KEY_MOVESPEED * ButtonState( UB_BACK ); - cmd.forwardmove = idMath::ClampChar( forward ); - cmd.rightmove = idMath::ClampChar( side ); - cmd.upmove = idMath::ClampChar( up ); + // only set each movement variable if its unset at this point. + // NOTE: joystick input happens before this. + if (cmd.forwardmove == 0) { + cmd.forwardmove = idMath::ClampChar( forward ); + } + if (cmd.rightmove == 0) { + cmd.rightmove = idMath::ClampChar( side ); + } + if (cmd.upmove == 0) { + cmd.upmove = idMath::ClampChar( up ); + } } /* @@ -672,29 +707,199 @@ void idUsercmdGenLocal::MouseMove( void ) { } } +/* +======================== +idUsercmdGenLocal::CircleToSquare +======================== +*/ +void idUsercmdGenLocal::CircleToSquare( float & axis_x, float & axis_y ) const { + // bring everything in the first quadrant + bool flip_x = false; + if ( axis_x < 0.0f ) { + flip_x = true; + axis_x *= -1.0f; + } + bool flip_y = false; + if ( axis_y < 0.0f ) { + flip_y = true; + axis_y *= -1.0f; + } + + // swap the two axes so we project against the vertical line X = 1 + bool swap = false; + if ( axis_y > axis_x ) { + float tmp = axis_x; + axis_x = axis_y; + axis_y = tmp; + swap = true; + } + + if ( axis_x < 0.001f ) { + // on one of the axes where no correction is needed + return; + } + + // length (max 1.0f at the unit circle) + float len = idMath::Sqrt( axis_x * axis_x + axis_y * axis_y ); + if ( len > 1.0f ) { + len = 1.0f; + } + // thales + float axis_y_us = axis_y / axis_x; + + // use a power curve to shift the correction to happen closer to the unit circle + float correctionRatio = Square( len ); + axis_x += correctionRatio * ( len - axis_x ); + axis_y += correctionRatio * ( axis_y_us - axis_y ); + + // go back through the symmetries + if ( swap ) { + float tmp = axis_x; + axis_x = axis_y; + axis_y = tmp; + } + if ( flip_x ) { + axis_x *= -1.0f; + } + if ( flip_y ) { + axis_y *= -1.0f; + } +} + +/* +======================== +idUsercmdGenLocal::HandleJoystickAxis +======================== +*/ +void idUsercmdGenLocal::HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive ) { + if ( ( unclampedValue > 0.0f ) && !positive ) { + return; + } + if ( ( unclampedValue < 0.0f ) && positive ) { + return; + } + float value = 0.0f; + bool pressed = false; + if ( unclampedValue > threshold ) { + value = idMath::Fabs( ( unclampedValue - threshold ) / ( 1.0f - threshold ) ); + pressed = true; + } else if ( unclampedValue < -threshold ) { + value = idMath::Fabs( ( unclampedValue + threshold ) / ( 1.0f - threshold ) ); + pressed = true; + } + + int action = idKeyInput::GetUsercmdAction( keyNum ); + if ( action >= UB_ATTACK ) { + Key( keyNum, pressed ); + return; + } + if ( !pressed ) { + return; + } + + float lookValue = 0.0f; + if ( joy_gammaLook.GetBool() ) { + lookValue = idMath::Pow( 1.04712854805f, value * 100.0f ) * 0.01f; + } else { + lookValue = idMath::Pow( value, joy_powerScale.GetFloat() ); + } + +#if 0 // TODO: aim assist maybe. + idGame * game = common->Game(); + if ( game != NULL ) { + lookValue *= game->GetAimAssistSensitivity(); + } +#endif + + switch ( action ) { + case UB_FORWARD: { + float move = (float)cmd.forwardmove + ( KEY_MOVESPEED * value ); + cmd.forwardmove = idMath::ClampChar( idMath::Ftoi( move ) ); + break; + } + case UB_BACK: { + float move = (float)cmd.forwardmove - ( KEY_MOVESPEED * value ); + cmd.forwardmove = idMath::ClampChar( idMath::Ftoi( move ) ); + break; + } + case UB_MOVELEFT: { + float move = (float)cmd.rightmove - ( KEY_MOVESPEED * value ); + cmd.rightmove = idMath::ClampChar( idMath::Ftoi( move ) ); + break; + } + case UB_MOVERIGHT: { + float move = (float)cmd.rightmove + ( KEY_MOVESPEED * value ); + cmd.rightmove = idMath::ClampChar( idMath::Ftoi( move ) ); + break; + } + case UB_LOOKUP: { + if ( joy_dampenLook.GetBool() ) { + lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValuePitch ); + lastLookValuePitch = lookValue; + } + + float invertPitch = in_invertLook.GetBool() ? -1.0f : 1.0f; + viewangles[PITCH] -= MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch; + break; + } + case UB_LOOKDOWN: { + if ( joy_dampenLook.GetBool() ) { + lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValuePitch ); + lastLookValuePitch = lookValue; + } + + float invertPitch = in_invertLook.GetBool() ? -1.0f : 1.0f; + viewangles[PITCH] += MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch; + break; + } + case UB_LEFT: { + if ( joy_dampenLook.GetBool() ) { + lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValueYaw ); + lastLookValueYaw = lookValue; + } + viewangles[YAW] += MS2SEC( pollTime - lastPollTime ) * lookValue * joy_yawSpeed.GetFloat(); + break; + } + case UB_RIGHT: { + if ( joy_dampenLook.GetBool() ) { + lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValueYaw ); + lastLookValueYaw = lookValue; + } + viewangles[YAW] -= MS2SEC( pollTime - lastPollTime ) * lookValue * joy_yawSpeed.GetFloat(); + break; + } + } +} + /* ================= idUsercmdGenLocal::JoystickMove ================= */ -void idUsercmdGenLocal::JoystickMove( void ) { - float anglespeed; +void idUsercmdGenLocal::JoystickMove() { + float threshold = joy_deadZone.GetFloat(); + float triggerThreshold = joy_triggerThreshold.GetFloat(); - if ( toggled_run.on ^ ( in_alwaysRun.GetBool() && idAsyncNetwork::IsActive() ) ) { - anglespeed = idMath::M_MS2SEC * USERCMD_MSEC * in_angleSpeedKey.GetFloat(); - } else { - anglespeed = idMath::M_MS2SEC * USERCMD_MSEC; - } + float axis_y = joystickAxis[ AXIS_LEFT_Y ]; + float axis_x = joystickAxis[ AXIS_LEFT_X ]; + CircleToSquare( axis_x, axis_y ); - if ( !ButtonState( UB_STRAFE ) ) { - viewangles[YAW] += anglespeed * in_yawSpeed.GetFloat() * joystickAxis[AXIS_SIDE]; - viewangles[PITCH] += anglespeed * in_pitchSpeed.GetFloat() * joystickAxis[AXIS_FORWARD]; - } else { - cmd.rightmove = idMath::ClampChar( cmd.rightmove + joystickAxis[AXIS_SIDE] ); - cmd.forwardmove = idMath::ClampChar( cmd.forwardmove + joystickAxis[AXIS_FORWARD] ); - } + HandleJoystickAxis( K_JOY_STICK1_UP, axis_y, threshold, false ); + HandleJoystickAxis( K_JOY_STICK1_DOWN, axis_y, threshold, true ); + HandleJoystickAxis( K_JOY_STICK1_LEFT, axis_x, threshold, false ); + HandleJoystickAxis( K_JOY_STICK1_RIGHT, axis_x, threshold, true ); - cmd.upmove = idMath::ClampChar( cmd.upmove + joystickAxis[AXIS_UP] ); + axis_y = joystickAxis[ AXIS_RIGHT_Y ]; + axis_x = joystickAxis[ AXIS_RIGHT_X ]; + CircleToSquare( axis_x, axis_y ); + + HandleJoystickAxis( K_JOY_STICK2_UP, axis_y, threshold, false ); + HandleJoystickAxis( K_JOY_STICK2_DOWN, axis_y, threshold, true ); + HandleJoystickAxis( K_JOY_STICK2_LEFT, axis_x, threshold, false ); + HandleJoystickAxis( K_JOY_STICK2_RIGHT, axis_x, threshold, true ); + + HandleJoystickAxis( K_JOY_TRIGGER1, joystickAxis[ AXIS_LEFT_TRIG ], triggerThreshold, true ); + HandleJoystickAxis( K_JOY_TRIGGER2, joystickAxis[ AXIS_RIGHT_TRIG ], triggerThreshold, true ); } /* @@ -778,6 +983,9 @@ void idUsercmdGenLocal::MakeCurrent( void ) { // keyboard angle adjustment AdjustAngles(); + // get basic movement from joystick + JoystickMove(); + // set button bits CmdButtons(); @@ -787,9 +995,6 @@ void idUsercmdGenLocal::MakeCurrent( void ) { // get basic movement from mouse MouseMove(); - // get basic movement from joystick - JoystickMove(); - // check to make sure the angles haven't wrapped if ( viewangles[PITCH] - oldAngles[PITCH] > 90 ) { viewangles[PITCH] = oldAngles[PITCH] + 90; @@ -877,6 +1082,7 @@ void idUsercmdGenLocal::Clear( void ) { // clears all key states memset( buttonState, 0, sizeof( buttonState ) ); memset( keyState, false, sizeof( keyState ) ); + memset( joystickAxis, 0, sizeof( joystickAxis ) ); inhibitCommands = false; @@ -939,6 +1145,8 @@ void idUsercmdGenLocal::Key( int keyNum, bool down ) { int action = idKeyInput::GetUsercmdAction( keyNum ); + // TODO: if action == 0 return ? + if ( down ) { buttonState[ action ]++; @@ -1039,7 +1247,28 @@ idUsercmdGenLocal::Joystick =============== */ void idUsercmdGenLocal::Joystick( void ) { - memset( joystickAxis, 0, sizeof( joystickAxis ) ); + int numEvents = Sys_PollJoystickInputEvents( 0 ); + + // Study each of the buffer elements and process them. + for ( int i = 0; i < numEvents; i++ ) { + int action; + int value; + if ( Sys_ReturnJoystickInputEvent( i, action, value ) ) { + if ( action >= J_ACTION1 && action <= J_ACTION_MAX ) { + int joyButton = K_JOY1 + ( action - J_ACTION1 ); + Key( joyButton, ( value != 0 ) ); + } else if ( ( action >= J_AXIS_MIN ) && ( action <= J_AXIS_MAX ) ) { + joystickAxis[ action - J_AXIS_MIN ] = static_cast<float>( value ) / 32767.0f; + } else if ( action >= J_DPAD_UP && action <= J_DPAD_RIGHT ) { + int joyButton = K_JOY_DPAD_UP + ( action - J_DPAD_UP ); + Key( joyButton, ( value != 0 ) ); + } else { + //assert( !"Unknown joystick event" ); + } + } + } + + Sys_EndJoystickInputEvents(); } /* @@ -1065,7 +1294,9 @@ void idUsercmdGenLocal::UsercmdInterrupt( void ) { Keyboard(); // process the system joystick events - Joystick(); + if ( in_useJoystick.GetBool() ) { + Joystick(); + } // create the usercmd for com_ticNumber+1 MakeCurrent(); @@ -1095,6 +1326,11 @@ idUsercmdGenLocal::GetDirectUsercmd */ usercmd_t idUsercmdGenLocal::GetDirectUsercmd( void ) { + pollTime = Sys_Milliseconds(); + if ( pollTime - lastPollTime > 100 ) { + lastPollTime = pollTime - 100; + } + // initialize current usercmd InitCurrent(); @@ -1105,12 +1341,15 @@ usercmd_t idUsercmdGenLocal::GetDirectUsercmd( void ) { Keyboard(); // process the system joystick events - Joystick(); - + if ( in_useJoystick.GetBool() ) { + Joystick(); + } // create the usercmd MakeCurrent(); cmd.duplicateCount = 0; + lastPollTime = pollTime; + return cmd; } diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 6cc04516..f310dd22 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -108,8 +108,26 @@ struct mouse_poll_t { } }; +struct joystick_poll_t { + int action; + int value; + + joystick_poll_t() : action(0), value(0) {} // TODO: or -1? + + joystick_poll_t(int a, int v) { + action = a; + value = v; + } +}; + static idList<kbd_poll_t> kbd_polls; static idList<mouse_poll_t> mouse_polls; +static idList<joystick_poll_t> joystick_polls; + +static bool buttonStates[K_LAST_KEY]; +static int joyAxis[MAX_JOYSTICK_AXIS]; + +static idList<sysEvent_t> event_overflow; #if SDL_VERSION_ATLEAST(2, 0, 0) // for utf8ToISO8859_1() - used for non-ascii text input and Sys_GetLocalizedScancodeName() @@ -477,6 +495,73 @@ static byte mapkey(SDL_Keycode key) { return 0; } +static sys_jEvents mapjoybutton(SDL_GameControllerButton button) { + + switch (button) + { + case SDL_CONTROLLER_BUTTON_A: + return J_ACTION1; + case SDL_CONTROLLER_BUTTON_B: + return J_ACTION2; + case SDL_CONTROLLER_BUTTON_X: + return J_ACTION3; + case SDL_CONTROLLER_BUTTON_Y: + return J_ACTION4; + case SDL_CONTROLLER_BUTTON_BACK: + return J_ACTION10; + case SDL_CONTROLLER_BUTTON_GUIDE: + // TODO: + break; + case SDL_CONTROLLER_BUTTON_START: + return J_ACTION9; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + return J_ACTION7; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + return J_ACTION8; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return J_ACTION5; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return J_ACTION6; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + return J_DPAD_UP; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + return J_DPAD_DOWN; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + return J_DPAD_LEFT; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + return J_DPAD_RIGHT; + default: + common->Warning("unknown game controller button %u", button); + break; + } + + return MAX_JOY_EVENT; +} + +static sys_jEvents mapjoyaxis(SDL_GameControllerAxis axis) { + + switch (axis) + { + case SDL_CONTROLLER_AXIS_LEFTX: + return J_AXIS_LEFT_X; + case SDL_CONTROLLER_AXIS_LEFTY: + return J_AXIS_LEFT_Y; + case SDL_CONTROLLER_AXIS_RIGHTX: + return J_AXIS_RIGHT_X; + case SDL_CONTROLLER_AXIS_RIGHTY: + return J_AXIS_RIGHT_Y; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + return J_AXIS_LEFT_TRIG; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + return J_AXIS_RIGHT_TRIG; + default: + common->Warning("unknown game controller axis %u", axis); + break; + } + + return J_AXIS_MAX; +} + static void PushConsoleEvent(const char *s) { char *b; size_t len; @@ -531,6 +616,19 @@ void Sys_InitInput() { #else // SDL1.2 doesn't support this in_grabKeyboard.ClearModified(); #endif + + joystick_polls.SetGranularity(64); + event_overflow.SetGranularity(64); + + memset( buttonStates, 0, sizeof( buttonStates ) ); + memset( joyAxis, 0, sizeof( joyAxis ) ); + + const int NumJoysticks = SDL_NumJoysticks(); + printf("XXX found %d joysticks\n", NumJoysticks); + for( int i = 0; i < NumJoysticks; ++i ) + { + SDL_GameController* gc = SDL_GameControllerOpen( i ); + } } /* @@ -541,6 +639,8 @@ Sys_ShutdownInput void Sys_ShutdownInput() { kbd_polls.Clear(); mouse_polls.Clear(); + joystick_polls.Clear(); + event_overflow.Clear(); #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_iconv_close( iconvDesc ); // used by utf8ToISO8859_1() iconvDesc = ( SDL_iconv_t ) -1; @@ -666,6 +766,18 @@ void Sys_GrabMouseCursor(bool grabIt) { GLimp_GrabInput(flags); } + +static void PushButton( int key, bool value ) { + // So we don't keep sending the same SE_KEY message over and over again + if ( buttonStates[key] != value ) { + buttonStates[key] = value; + sysEvent_t res = { SE_KEY, key, value ? 1 : 0, 0, NULL }; + // this is done to generate two events per controller axis event + // one SE_JOYSTICK and one SE_KEY + event_overflow.Append(res); + } +} + /* ================ Sys_GetEvent @@ -678,6 +790,14 @@ sysEvent_t Sys_GetEvent() { static const sysEvent_t res_none = { SE_NONE, 0, 0, 0, NULL }; + // process any overflow. + if (event_overflow.Num() > 0) + { + res = event_overflow[0]; + event_overflow.RemoveIndex(0); + return res; + } + #if SDL_VERSION_ATLEAST(2, 0, 0) static char s[SDL_TEXTINPUTEVENT_TEXT_SIZE] = {0}; static size_t s_pos = 0; @@ -958,6 +1078,76 @@ sysEvent_t Sys_GetEvent() { return res; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + { + sys_jEvents jEvent = mapjoybutton( (SDL_GameControllerButton)ev.cbutton.button); + joystick_polls.Append(joystick_poll_t(jEvent, ev.cbutton.state == SDL_PRESSED ? 1 : 0) ); + + res.evType = SE_KEY; + res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0; + if ( ( jEvent >= J_ACTION1 ) && ( jEvent <= J_ACTION_MAX ) ) { + res.evValue = K_JOY1 + ( jEvent - J_ACTION1 ); + return res; + } else if ( ( jEvent >= J_DPAD_UP ) && ( jEvent <= J_DPAD_RIGHT ) ) { + res.evValue = K_JOY_DPAD_UP + ( jEvent - J_DPAD_UP ); + return res; + } + + continue; // try to get a decent event. + } + + case SDL_CONTROLLERAXISMOTION: + { + const int range = 16384; + + sys_jEvents jEvent = mapjoyaxis( (SDL_GameControllerAxis)ev.caxis.axis); + joystick_polls.Append(joystick_poll_t( jEvent, ev.caxis.value) ); + + if ( jEvent == J_AXIS_LEFT_X ) { + PushButton( K_JOY_STICK1_LEFT, ( ev.caxis.value < -range ) ); + PushButton( K_JOY_STICK1_RIGHT, ( ev.caxis.value > range ) ); + } else if ( jEvent == J_AXIS_LEFT_Y ) { + PushButton( K_JOY_STICK1_UP, ( ev.caxis.value < -range ) ); + PushButton( K_JOY_STICK1_DOWN, ( ev.caxis.value > range ) ); + } else if ( jEvent == J_AXIS_RIGHT_X ) { + PushButton( K_JOY_STICK2_LEFT, ( ev.caxis.value < -range ) ); + PushButton( K_JOY_STICK2_RIGHT, ( ev.caxis.value > range ) ); + } else if ( jEvent == J_AXIS_RIGHT_Y ) { + PushButton( K_JOY_STICK2_UP, ( ev.caxis.value < -range ) ); + PushButton( K_JOY_STICK2_DOWN, ( ev.caxis.value > range ) ); + } else if ( jEvent == J_AXIS_LEFT_TRIG ) { + PushButton( K_JOY_TRIGGER1, ( ev.caxis.value > range ) ); + } else if ( jEvent == J_AXIS_RIGHT_TRIG ) { + PushButton( K_JOY_TRIGGER2, ( ev.caxis.value > range ) ); + } + if ( jEvent >= J_AXIS_MIN && jEvent <= J_AXIS_MAX ) { + int axis = jEvent - J_AXIS_MIN; + int percent = ( ev.caxis.value * 16 ) / range; + if ( joyAxis[axis] != percent ) { + joyAxis[axis] = percent; + res.evType = SE_JOYSTICK; + res.evValue = axis; + res.evValue2 = percent; + return res; + } + } + + continue; // try to get a decent event. + } + break; + + case SDL_JOYDEVICEADDED: + SDL_GameControllerOpen( ev.jdevice.which ); + // TODO: hot swapping maybe. + //lbOnControllerPlugIn(event.jdevice.which); + break; + + case SDL_JOYDEVICEREMOVED: + // TODO: hot swapping maybe. + //lbOnControllerUnPlug(event.jdevice.which); + break; + case SDL_QUIT: PushConsoleEvent("quit"); return res_none; @@ -996,6 +1186,12 @@ void Sys_ClearEvents() { kbd_polls.SetNum(0, false); mouse_polls.SetNum(0, false); + joystick_polls.SetNum(0, false); + + memset( buttonStates, 0, sizeof( buttonStates ) ); + memset( joyAxis, 0, sizeof( joyAxis ) ); + + event_overflow.SetNum(0, false); } static void handleMouseGrab() { @@ -1146,3 +1342,35 @@ Sys_EndMouseInputEvents void Sys_EndMouseInputEvents() { mouse_polls.SetNum(0, false); } + +/* +================ +Joystick Input Methods +================ +*/ +void Sys_SetRumble( int device, int low, int hi ) { + // TODO: support multiple controllers. + assert(device == 0); + // TODO: support rumble maybe. + assert(0); +} + +int Sys_PollJoystickInputEvents( int deviceNum ) { + // TODO: support multiple controllers. + assert(deviceNum == 0); + return joystick_polls.Num(); +} + +int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ) { + if (n >= joystick_polls.Num()) + return 0; + + action = joystick_polls[n].action; + value = joystick_polls[n].value; + return 1; +} + +void Sys_EndJoystickInputEvents() { + joystick_polls.SetNum(0, false); +} + diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index d84ae8a9..d409a638 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -44,12 +44,12 @@ typedef enum { } cpuidSimd_t; typedef enum { - AXIS_SIDE, - AXIS_FORWARD, - AXIS_UP, - AXIS_ROLL, - AXIS_YAW, - AXIS_PITCH, + AXIS_LEFT_X, + AXIS_LEFT_Y, + AXIS_RIGHT_X, + AXIS_RIGHT_Y, + AXIS_LEFT_TRIG, + AXIS_RIGHT_TRIG, MAX_JOYSTICK_AXIS } joystickAxis_t; @@ -59,7 +59,7 @@ typedef enum { SE_CHAR, // evValue is an ascii char SE_MOUSE, // evValue and evValue2 are relative signed x / y moves SE_MOUSE_ABS, // evValue and evValue2 are absolute x / y coordinates in the window - SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_JOYSTICK, // evValue is an axis number and evValue2 is the current state (-127 to 127) SE_CONSOLE // evPtr is a char*, from typing something at a non-game console } sysEventType_t; @@ -77,6 +77,59 @@ typedef enum { M_DELTAZ } sys_mEvents; +typedef enum { + J_ACTION1, + J_ACTION2, + J_ACTION3, + J_ACTION4, + J_ACTION5, + J_ACTION6, + J_ACTION7, + J_ACTION8, + J_ACTION9, + J_ACTION10, + J_ACTION11, + J_ACTION12, + J_ACTION13, + J_ACTION14, + J_ACTION15, + J_ACTION16, + J_ACTION17, + J_ACTION18, + J_ACTION19, + J_ACTION20, + J_ACTION21, + J_ACTION22, + J_ACTION23, + J_ACTION24, + J_ACTION25, + J_ACTION26, + J_ACTION27, + J_ACTION28, + J_ACTION29, + J_ACTION30, + J_ACTION31, + J_ACTION32, + J_ACTION_MAX = J_ACTION32, + + J_AXIS_MIN, + J_AXIS_LEFT_X = J_AXIS_MIN + AXIS_LEFT_X, + J_AXIS_LEFT_Y = J_AXIS_MIN + AXIS_LEFT_Y, + J_AXIS_RIGHT_X = J_AXIS_MIN + AXIS_RIGHT_X, + J_AXIS_RIGHT_Y = J_AXIS_MIN + AXIS_RIGHT_Y, + J_AXIS_LEFT_TRIG = J_AXIS_MIN + AXIS_LEFT_TRIG, + J_AXIS_RIGHT_TRIG = J_AXIS_MIN + AXIS_RIGHT_TRIG, + + J_AXIS_MAX = J_AXIS_MIN + MAX_JOYSTICK_AXIS - 1, + + J_DPAD_UP, + J_DPAD_DOWN, + J_DPAD_LEFT, + J_DPAD_RIGHT, + + MAX_JOY_EVENT +} sys_jEvents; + struct sysEvent_t { sysEventType_t evType; int evValue; @@ -189,6 +242,12 @@ int Sys_PollMouseInputEvents( void ); int Sys_ReturnMouseInputEvent( const int n, int &action, int &value ); void Sys_EndMouseInputEvents( void ); +// joystick input polling +void Sys_SetRumble( int device, int low, int hi ); +int Sys_PollJoystickInputEvents( int deviceNum ); +int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ); +void Sys_EndJoystickInputEvents(); + // when the console is down, or the game is about to perform a lengthy // operation like map loading, the system can release the mouse cursor // when in windowed mode diff --git a/neo/ui/Window.cpp b/neo/ui/Window.cpp index 3948cea3..8e82453f 100644 --- a/neo/ui/Window.cpp +++ b/neo/ui/Window.cpp @@ -736,7 +736,7 @@ const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) *updateVisuals = true; } - if (event->evValue == K_MOUSE1) { + if (event->evValue == K_MOUSE1 || event->evValue == K_JOY2) { if (!event->evValue2 && GetCaptureChild()) { GetCaptureChild()->LoseCapture(); @@ -785,7 +785,7 @@ const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) } else if (!actionUpRun) { actionUpRun = RunScript( ON_ACTIONRELEASE ); } - } else if (event->evValue == K_MOUSE2) { + } else if (event->evValue == K_MOUSE2 || event->evValue == K_JOY1) { if (!event->evValue2 && GetCaptureChild()) { GetCaptureChild()->LoseCapture(); From 700b3ee558d78727dee9379d6572a9792fde32ac Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Mon, 8 Jan 2024 05:26:27 +0100 Subject: [PATCH 02/14] Clean up gamepad code a bit, rename buttons - renamed gamepad/joystick actions and keys to have some meaning for buttons (instead of just JOY1, JOY2 etc) - compiles with SDL1.2 again (there gamepads aren't supported though) - shorter names for gamepad keys/axis in the key bindings menu --- neo/framework/Common.cpp | 6 ++ neo/framework/KeyInput.cpp | 67 ++++++++------ neo/framework/KeyInput.h | 37 ++++---- neo/framework/UsercmdGen.cpp | 4 +- neo/sys/events.cpp | 168 +++++++++++++++++++++++++++++++---- neo/sys/sys_public.h | 63 ++++++------- 6 files changed, 244 insertions(+), 101 deletions(-) diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index 9289a833..0e504481 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -2919,8 +2919,14 @@ void idCommonLocal::Init( int argc, char **argv ) { #endif #endif +#if SDL_VERSION_ATLEAST(2, 0, 0) if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER)) // init joystick to work around SDL 2.0.9 bug #4391 +#else + if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO)) // no gamecontroller support in SDL1 +#endif + { Sys_Error("Error while initializing SDL: %s", SDL_GetError()); + } Sys_InitThreads(); diff --git a/neo/framework/KeyInput.cpp b/neo/framework/KeyInput.cpp index b239613e..56584114 100644 --- a/neo/framework/KeyInput.cpp +++ b/neo/framework/KeyInput.cpp @@ -109,39 +109,42 @@ static const keyname_t keynames[] = {"MWHEELUP", K_MWHEELUP, "#str_07131"}, {"MWHEELDOWN", K_MWHEELDOWN, "#str_07132"}, - {"JOY1", K_JOY1, "#str_07062"}, - {"JOY2", K_JOY2, "#str_07063"}, - {"JOY3", K_JOY3, "#str_07064"}, - {"JOY4", K_JOY4, "#str_07065"}, - {"JOY5", K_JOY5, "#str_07066"}, - {"JOY6", K_JOY6, "#str_07067"}, - {"JOY7", K_JOY7, "#str_07068"}, - {"JOY8", K_JOY8, "#str_07069"}, - {"JOY9", K_JOY9, "#str_07070"}, - {"JOY10", K_JOY10, "#str_07071"}, - {"JOY11", K_JOY11, "#str_07072"}, - {"JOY12", K_JOY12, "#str_07073"}, - {"JOY13", K_JOY13, "#str_07074"}, - {"JOY14", K_JOY14, "#str_07075"}, - {"JOY15", K_JOY15, "#str_07076"}, + // Note: for localized gamepad key names, we use Sys_GetLocalizedJoyKeyName() + // so the last column is just NULL + {"JOY_BTN_SOUTH", K_JOY_BTN_SOUTH, NULL}, + {"JOY_BTN_EAST", K_JOY_BTN_EAST, NULL}, + {"JOY_BTN_WEST", K_JOY_BTN_WEST, NULL}, + {"JOY_BTN_NORTH", K_JOY_BTN_NORTH, NULL}, + {"JOY_BTN_BACK", K_JOY_BTN_BACK, NULL}, + // leaving out K_JOY_BTN_GUIDE, as I think it shouldn't be used (might open Steam or similar) + {"JOY_BTN_START", K_JOY_BTN_START, NULL}, + {"JOY_BTN_LSTICK", K_JOY_BTN_LSTICK, NULL}, + {"JOY_BTN_RSTICK", K_JOY_BTN_RSTICK, NULL}, + {"JOY_BTN_LSHOULDER", K_JOY_BTN_LSHOULDER, NULL}, + {"JOY_BTN_RSHOULDER", K_JOY_BTN_RSHOULDER, NULL}, + {"JOY_BTN_MISC1", K_JOY_BTN_MISC1, NULL}, + {"JOY_BTN_RPADDLE1", K_JOY_BTN_RPADDLE1, NULL}, + {"JOY_BTN_LPADDLE1", K_JOY_BTN_LPADDLE1, NULL}, + {"JOY_BTN_RPADDLE2", K_JOY_BTN_RPADDLE2, NULL}, + {"JOY_BTN_LPADDLE2", K_JOY_BTN_LPADDLE2, NULL}, - {"JOY_STICK1_UP", K_JOY_STICK1_UP, "JOY_STICK1_UP"}, - {"JOY_STICK1_DOWN", K_JOY_STICK1_DOWN, "JOY_STICK1_DOWN"}, - {"JOY_STICK1_LEFT", K_JOY_STICK1_LEFT, "JOY_STICK1_LEFT"}, - {"JOY_STICK1_RIGHT", K_JOY_STICK1_RIGHT, "JOY_STICK1_RIGHT"}, + {"JOY_STICK1_UP", K_JOY_STICK1_UP, NULL}, + {"JOY_STICK1_DOWN", K_JOY_STICK1_DOWN, NULL}, + {"JOY_STICK1_LEFT", K_JOY_STICK1_LEFT, NULL}, + {"JOY_STICK1_RIGHT", K_JOY_STICK1_RIGHT, NULL}, - {"JOY_STICK2_UP", K_JOY_STICK2_UP, "JOY_STICK2_UP"}, - {"JOY_STICK2_DOWN", K_JOY_STICK2_DOWN, "JOY_STICK2_DOWN"}, - {"JOY_STICK2_LEFT", K_JOY_STICK2_LEFT, "JOY_STICK2_LEFT"}, - {"JOY_STICK2_RIGHT", K_JOY_STICK2_RIGHT, "JOY_STICK2_RIGHT"}, + {"JOY_STICK2_UP", K_JOY_STICK2_UP, NULL}, + {"JOY_STICK2_DOWN", K_JOY_STICK2_DOWN, NULL}, + {"JOY_STICK2_LEFT", K_JOY_STICK2_LEFT, NULL}, + {"JOY_STICK2_RIGHT", K_JOY_STICK2_RIGHT, NULL}, - {"JOY_TRIGGER1", K_JOY_TRIGGER1, "JOY_TRIGGER1"}, - {"JOY_TRIGGER2", K_JOY_TRIGGER2, "JOY_TRIGGER2"}, + {"JOY_TRIGGER1", K_JOY_TRIGGER1, NULL}, + {"JOY_TRIGGER2", K_JOY_TRIGGER2, NULL}, - {"JOY_DPAD_UP", K_JOY_DPAD_UP, "JOY_DPAD_UP"}, - {"JOY_DPAD_DOWN", K_JOY_DPAD_DOWN, "JOY_DPAD_DOWN"}, - {"JOY_DPAD_LEFT", K_JOY_DPAD_LEFT, "JOY_DPAD_LEFT"}, - {"JOY_DPAD_RIGHT", K_JOY_DPAD_RIGHT, "JOY_DPAD_RIGHT"}, + {"JOY_DPAD_UP", K_JOY_DPAD_UP, NULL}, + {"JOY_DPAD_DOWN", K_JOY_DPAD_DOWN, NULL}, + {"JOY_DPAD_LEFT", K_JOY_DPAD_LEFT, NULL}, + {"JOY_DPAD_RIGHT", K_JOY_DPAD_RIGHT, NULL}, {"AUX1", K_AUX1, "#str_07094"}, {"AUX2", K_AUX2, "#str_07095"}, @@ -410,6 +413,12 @@ const char *idKeyInput::KeyNumToString( int keynum, bool localized ) { } } + if ( localized && keynum >= K_FIRST_JOY && keynum <= K_LAST_JOY ) { + const char* jname = Sys_GetLocalizedJoyKeyName(keynum); + if(jname != NULL) + return jname; + } + // check for a key string for ( kn = keynames; kn->name; kn++ ) { if ( keynum == kn->keynum ) { diff --git a/neo/framework/KeyInput.h b/neo/framework/KeyInput.h index e959a1f7..c394ba47 100644 --- a/neo/framework/KeyInput.h +++ b/neo/framework/KeyInput.h @@ -139,25 +139,27 @@ typedef enum { K_MWHEELUP, //------------------------ - // K_JOY codes must be contiguous, too + // K_JOY codes must be contiguous, too, and K_JOY_BTN_* should be kept in sync with J_BTN_* of sys_jEvents //------------------------ - K_JOY1 = 197, - K_JOY2, - K_JOY3, - K_JOY4, - K_JOY5, - K_JOY6, - K_JOY7, - K_JOY8, - K_JOY9, - K_JOY10, - K_JOY11, - K_JOY12, - K_JOY13, - K_JOY14, - K_JOY15, - K_JOY16, + K_FIRST_JOY = 197, + K_JOY_BTN_SOUTH = K_FIRST_JOY, // bottom face button, like Xbox A + K_JOY_BTN_EAST, // right face button, like Xbox B + K_JOY_BTN_WEST, // left face button, like Xbox X + K_JOY_BTN_NORTH, // top face button, like Xbox Y + K_JOY_BTN_BACK, + K_JOY_BTN_GUIDE, // Note: this one should probably not be used? + K_JOY_BTN_START, + K_JOY_BTN_LSTICK, // press left stick + K_JOY_BTN_RSTICK, // press right stick + K_JOY_BTN_LSHOULDER, + K_JOY_BTN_RSHOULDER, + // NOTE: in SDL3, the 4 DPAD buttons would be following, we have those later + K_JOY_BTN_MISC1, // Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button) + K_JOY_BTN_RPADDLE1, // Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) + K_JOY_BTN_LPADDLE1, // Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) + K_JOY_BTN_RPADDLE2, // Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) + K_JOY_BTN_LPADDLE2, // Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) K_JOY_STICK1_UP, K_JOY_STICK1_DOWN, @@ -176,6 +178,7 @@ typedef enum { K_JOY_DPAD_DOWN, K_JOY_DPAD_LEFT, K_JOY_DPAD_RIGHT, + K_LAST_JOY = K_JOY_DPAD_RIGHT, K_GRAVE_A = 229, // lowercase a with grave accent FIXME: used to be 224; this probably isn't used anyway diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index 6fd46169..2b70e8d7 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -1254,8 +1254,8 @@ void idUsercmdGenLocal::Joystick( void ) { int action; int value; if ( Sys_ReturnJoystickInputEvent( i, action, value ) ) { - if ( action >= J_ACTION1 && action <= J_ACTION_MAX ) { - int joyButton = K_JOY1 + ( action - J_ACTION1 ); + if ( action >= J_ACTION_FIRST && action <= J_ACTION_MAX ) { + int joyButton = K_FIRST_JOY + ( action - J_ACTION_FIRST ); Key( joyButton, ( value != 0 ) ); } else if ( ( action >= J_AXIS_MIN ) && ( action <= J_AXIS_MAX ) ) { joystickAxis[ action - J_AXIS_MIN ] = static_cast<float>( value ) / 32767.0f; diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index f310dd22..31a57f20 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -189,7 +189,7 @@ static scancodename_t scancodemappings[] = { D3_SC_MAPPING(COMMA), D3_SC_MAPPING(PERIOD), D3_SC_MAPPING(SLASH), - // leaving out lots of key incl. from keypad, we already handle them as normal keys + // leaving out lots of keys incl. from keypad, we already handle them as normal keys D3_SC_MAPPING(NONUSBACKSLASH), D3_SC_MAPPING(INTERNATIONAL1), /**< used on Asian keyboards, see footnotes in USB doc */ D3_SC_MAPPING(INTERNATIONAL2), @@ -264,6 +264,121 @@ static bool utf8ToISO8859_1(const char* inbuf, char* outbuf, size_t outsize) { } #endif // SDL2 +const char* Sys_GetLocalizedJoyKeyName( int key ) { + // Note: trying to keep the returned names short, because the Doom3 binding window doesn't have much space for names.. + +#if SDL_VERSION_ATLEAST(2, 0, 0) // gamecontroller/gamepad not supported in SDL1 + if (key >= K_FIRST_JOY && key <= K_LAST_JOY) { + + if (key <= K_JOY_BTN_NORTH) { +#if SDL_VERSION_ATLEAST(3, 0, 0) + + SDL_GamepadButton gpbtn = SDL_GAMEPAD_BUTTON_SOUTH + (key - K_JOY_BTN_NORTH); + SDL_GamepadButtonLabel label = SDL_GetGamepadButtonLabeForTypel(TODO, gpbtn); + switch(label) { + case SDL_GAMEPAD_BUTTON_LABEL_A: + return "Pad A"; + case SDL_GAMEPAD_BUTTON_LABEL_B: + return "Pad B"; + case SDL_GAMEPAD_BUTTON_LABEL_X: + return "Pad X"; + case SDL_GAMEPAD_BUTTON_LABEL_Y: + return "Pad Y"; + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: + return "Pad Cross"; + case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: + return "Pad Circle"; + case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: + return "Pad Square"; + case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: + return "Pad Triangle"; + } + +#else // SDL2 + // using xbox-style names, like SDL2 does (SDL can't tell us if this is a xbox or PS or nintendo or whatever-style gamepad) + switch(key) { + case K_JOY_BTN_SOUTH: + return "Pad A"; + case K_JOY_BTN_EAST: + return "Pad B"; + case K_JOY_BTN_WEST: + return "Pad X"; + case K_JOY_BTN_NORTH: + return "Pad Y"; + } +#endif // face button names for SDL2 + } + + // the labels for the remaining keys are the same for SDL2 and SDL3 (and all controllers) + // Note: Would be nicer with "Pad " at the beginning, but then it's too long for the keybinding window :-/ + switch(key) { + case K_JOY_BTN_BACK: + return "Pad Back"; + + case K_JOY_BTN_GUIDE: + return NULL; // ??? + + case K_JOY_BTN_START: + return "Pad Start"; + case K_JOY_BTN_LSTICK: + return "Pad LStick"; + case K_JOY_BTN_RSTICK: + return "Pad RStick"; + case K_JOY_BTN_LSHOULDER: + return "Pad LShoulder"; + case K_JOY_BTN_RSHOULDER: + return "Pad RShoulder"; + // NOTE: in SDL3, the 4 DPAD buttons would be following, we have those later + case K_JOY_BTN_MISC1: + return "Pad Misc"; + case K_JOY_BTN_RPADDLE1: + return "Pad P1"; + case K_JOY_BTN_LPADDLE1: + return "Pad P3"; + case K_JOY_BTN_RPADDLE2: + return "Pad P2"; + case K_JOY_BTN_LPADDLE2: + return "Pad P4"; + + case K_JOY_STICK1_UP: + return "Stick1 Up"; + case K_JOY_STICK1_DOWN: + return "Stick1 Down"; + case K_JOY_STICK1_LEFT: + return "Stick1 Left"; + case K_JOY_STICK1_RIGHT: + return "Stick1 Right"; + + case K_JOY_STICK2_UP: + return "Stick2 Up"; + case K_JOY_STICK2_DOWN: + return "Stick2 Down"; + case K_JOY_STICK2_LEFT: + return "Stick2 Left"; + case K_JOY_STICK2_RIGHT: + return "Stick2 Right"; + + case K_JOY_TRIGGER1: + return "Trigger 1"; + case K_JOY_TRIGGER2: + return "Trigger 2"; + + case K_JOY_DPAD_UP: + return "DPad Up"; + case K_JOY_DPAD_DOWN: + return "DPad Down"; + case K_JOY_DPAD_LEFT: + return "DPad Left"; + case K_JOY_DPAD_RIGHT: + return "DPad Right"; + default: + assert(0 && "missing a case in Sys_GetLocalizedJoyKeyName() for axes or dpad!"); + } + } +#endif // SDL2+ + return NULL; +} + // returns localized name of the key (between K_FIRST_SCANCODE and K_LAST_SCANCODE), // regarding the current keyboard layout - if that name is in ASCII or corresponds // to a "High-ASCII" char supported by Doom3. @@ -495,33 +610,35 @@ static byte mapkey(SDL_Keycode key) { return 0; } -static sys_jEvents mapjoybutton(SDL_GameControllerButton button) { +#if SDL_VERSION_ATLEAST(2, 0, 0) +static sys_jEvents mapjoybutton(SDL_GameControllerButton button) { switch (button) { case SDL_CONTROLLER_BUTTON_A: - return J_ACTION1; + return J_BTN_SOUTH; case SDL_CONTROLLER_BUTTON_B: - return J_ACTION2; + return J_BTN_EAST; case SDL_CONTROLLER_BUTTON_X: - return J_ACTION3; + return J_BTN_WEST; case SDL_CONTROLLER_BUTTON_Y: - return J_ACTION4; + return J_BTN_NORTH; case SDL_CONTROLLER_BUTTON_BACK: - return J_ACTION10; + return J_BTN_BACK; case SDL_CONTROLLER_BUTTON_GUIDE: - // TODO: + // TODO: this one should probably not be bindable? + //return J_BTN_GUIDE; break; case SDL_CONTROLLER_BUTTON_START: - return J_ACTION9; + return J_BTN_START; case SDL_CONTROLLER_BUTTON_LEFTSTICK: - return J_ACTION7; + return J_BTN_LSTICK; case SDL_CONTROLLER_BUTTON_RIGHTSTICK: - return J_ACTION8; + return J_BTN_RSTICK; case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - return J_ACTION5; + return J_BTN_LSHOULDER; case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - return J_ACTION6; + return J_BTN_RSHOULDER; case SDL_CONTROLLER_BUTTON_DPAD_UP: return J_DPAD_UP; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: @@ -530,16 +647,26 @@ static sys_jEvents mapjoybutton(SDL_GameControllerButton button) { return J_DPAD_LEFT; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return J_DPAD_RIGHT; + // TODO: have the following always been supported in SDL2? + case SDL_CONTROLLER_BUTTON_MISC1: + return J_BTN_MISC1; + case SDL_CONTROLLER_BUTTON_PADDLE1: + return J_BTN_RPADDLE1; + case SDL_CONTROLLER_BUTTON_PADDLE2: + return J_BTN_RPADDLE2; + case SDL_CONTROLLER_BUTTON_PADDLE3: + return J_BTN_LPADDLE1; + case SDL_CONTROLLER_BUTTON_PADDLE4: + return J_BTN_LPADDLE2; + default: common->Warning("unknown game controller button %u", button); break; } - return MAX_JOY_EVENT; } static sys_jEvents mapjoyaxis(SDL_GameControllerAxis axis) { - switch (axis) { case SDL_CONTROLLER_AXIS_LEFTX: @@ -558,9 +685,9 @@ static sys_jEvents mapjoyaxis(SDL_GameControllerAxis axis) { common->Warning("unknown game controller axis %u", axis); break; } - return J_AXIS_MAX; } +#endif // SDL2+ gamecontroller code static void PushConsoleEvent(const char *s) { char *b; @@ -623,12 +750,13 @@ void Sys_InitInput() { memset( buttonStates, 0, sizeof( buttonStates ) ); memset( joyAxis, 0, sizeof( joyAxis ) ); +#if SDL_VERSION_ATLEAST(2, 0, 0) // gamecontroller/gamepad not supported in SDL1 const int NumJoysticks = SDL_NumJoysticks(); - printf("XXX found %d joysticks\n", NumJoysticks); for( int i = 0; i < NumJoysticks; ++i ) { SDL_GameController* gc = SDL_GameControllerOpen( i ); } +#endif } /* @@ -1078,6 +1206,7 @@ sysEvent_t Sys_GetEvent() { return res; +#if SDL_VERSION_ATLEAST(2, 0, 0) // gamecontroller/gamepad not supported in SDL1 case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: { @@ -1086,8 +1215,8 @@ sysEvent_t Sys_GetEvent() { res.evType = SE_KEY; res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0; - if ( ( jEvent >= J_ACTION1 ) && ( jEvent <= J_ACTION_MAX ) ) { - res.evValue = K_JOY1 + ( jEvent - J_ACTION1 ); + if ( ( jEvent >= J_BTN_SOUTH ) && ( jEvent <= J_ACTION_MAX ) ) { + res.evValue = K_JOY_BTN_SOUTH + ( jEvent - J_BTN_SOUTH ); return res; } else if ( ( jEvent >= J_DPAD_UP ) && ( jEvent <= J_DPAD_RIGHT ) ) { res.evValue = K_JOY_DPAD_UP + ( jEvent - J_DPAD_UP ); @@ -1147,6 +1276,7 @@ sysEvent_t Sys_GetEvent() { // TODO: hot swapping maybe. //lbOnControllerUnPlug(event.jdevice.which); break; +#endif // SDL2+ case SDL_QUIT: PushConsoleEvent("quit"); diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index d409a638..03592d66 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -78,41 +78,30 @@ typedef enum { } sys_mEvents; typedef enum { - J_ACTION1, - J_ACTION2, - J_ACTION3, - J_ACTION4, - J_ACTION5, - J_ACTION6, - J_ACTION7, - J_ACTION8, - J_ACTION9, - J_ACTION10, - J_ACTION11, - J_ACTION12, - J_ACTION13, - J_ACTION14, - J_ACTION15, - J_ACTION16, - J_ACTION17, - J_ACTION18, - J_ACTION19, - J_ACTION20, - J_ACTION21, - J_ACTION22, - J_ACTION23, - J_ACTION24, - J_ACTION25, - J_ACTION26, - J_ACTION27, - J_ACTION28, - J_ACTION29, - J_ACTION30, - J_ACTION31, - J_ACTION32, - J_ACTION_MAX = J_ACTION32, + J_ACTION_FIRST, + // these names are similar to the SDL3 SDL_GamepadButton names + J_BTN_SOUTH = J_ACTION_FIRST, // bottom face button, like Xbox A + J_BTN_EAST, // right face button, like Xbox B + J_BTN_WEST, // left face button, like Xbox X + J_BTN_NORTH, // top face button, like Xbox Y + J_BTN_BACK, + J_BTN_GUIDE, // Note: this one should probably not be used? + J_BTN_START, + J_BTN_LSTICK, // press left stick + J_BTN_RSTICK, // press right stick + J_BTN_LSHOULDER, + J_BTN_RSHOULDER, + // NOTE: in SDL3, the 4 DPAD buttons would be following, we have those at the end + J_BTN_MISC1, // Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button) + J_BTN_RPADDLE1, // Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) + J_BTN_LPADDLE1, // Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) + J_BTN_RPADDLE2, // Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) + J_BTN_LPADDLE2, // Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) - J_AXIS_MIN, + J_ACTION_MAX = J_BTN_LPADDLE2, + // leaving some space here for about 16 additional J_ACTIONs, if needed + + J_AXIS_MIN = 32, J_AXIS_LEFT_X = J_AXIS_MIN + AXIS_LEFT_X, J_AXIS_LEFT_Y = J_AXIS_MIN + AXIS_LEFT_Y, J_AXIS_RIGHT_X = J_AXIS_MIN + AXIS_RIGHT_X, @@ -232,6 +221,12 @@ const char* Sys_GetLocalizedScancodeName( int key ); // returns keyNum_t (K_SC_* constant) for given scancode name (like "SC_A") int Sys_GetKeynumForScancodeName( const char* name ); +// returns display name of the key (between K_FIRST_JOY and K_LAST_JOY) +// With SDL2 it'll return the name in the SDL_GameController standard layout +// (which is based on XBox/XInput => on Nintendo gamepads, A/B and X/Y will be flipped), +// with SDL3 it will return the "real" button name +const char* Sys_GetLocalizedJoyKeyName( int key ); + // keyboard input polling int Sys_PollKeyboardInputEvents( void ); int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state ); From f8557f6bd58753800bb4f8ca326914b931eee5dd Mon Sep 17 00:00:00 2001 From: wof8317 <wof8317@gmail.com> Date: Mon, 15 Jan 2024 15:18:55 -0600 Subject: [PATCH 03/14] Modified some code to compile --- neo/ui/Window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neo/ui/Window.cpp b/neo/ui/Window.cpp index 8e82453f..3dda7118 100644 --- a/neo/ui/Window.cpp +++ b/neo/ui/Window.cpp @@ -736,7 +736,7 @@ const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) *updateVisuals = true; } - if (event->evValue == K_MOUSE1 || event->evValue == K_JOY2) { + if (event->evValue == K_MOUSE1 || event->evValue == K_JOY_BTN_EAST) { if (!event->evValue2 && GetCaptureChild()) { GetCaptureChild()->LoseCapture(); @@ -785,7 +785,7 @@ const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) } else if (!actionUpRun) { actionUpRun = RunScript( ON_ACTIONRELEASE ); } - } else if (event->evValue == K_MOUSE2 || event->evValue == K_JOY1) { + } else if (event->evValue == K_MOUSE2 || event->evValue == K_JOY_BTN_SOUTH) { if (!event->evValue2 && GetCaptureChild()) { GetCaptureChild()->LoseCapture(); From 6eac0540bf0f20160af528db679d09eb41b3bc47 Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Tue, 16 Jan 2024 17:26:45 +0100 Subject: [PATCH 04/14] Various gamepad improvements - treat DPad as 4 regular buttons (was already the case mostly, but now the code is simpler) - rename in_invertLook to joy_invertLook and in_useJoystick to in_useGamepad and remove unused CVars - make controller Start button generate K_ESCAPE events, so it can always be used to open/close the menu (similar to D3BFG) - move mousecursor with sticks, A button (south) for left-click, B button (east) for right-click (doesn't work in PDA yet) - removed special handling of K_JOY_BTN_* in idWindow::HandleEvent() by generating fake mouse button events for gamepad A/B in idUserInterfaceLocal::HandleEvent() --- neo/framework/KeyInput.cpp | 9 ++-- neo/framework/KeyInput.h | 14 +++--- neo/framework/UsercmdGen.cpp | 20 +++----- neo/sys/events.cpp | 95 ++++++++++++++++++++++++++---------- neo/sys/sys_public.h | 16 +++--- neo/ui/UserInterface.cpp | 31 ++++++++++++ neo/ui/Window.cpp | 4 +- 7 files changed, 131 insertions(+), 58 deletions(-) diff --git a/neo/framework/KeyInput.cpp b/neo/framework/KeyInput.cpp index 56584114..299519c9 100644 --- a/neo/framework/KeyInput.cpp +++ b/neo/framework/KeyInput.cpp @@ -122,6 +122,10 @@ static const keyname_t keynames[] = {"JOY_BTN_RSTICK", K_JOY_BTN_RSTICK, NULL}, {"JOY_BTN_LSHOULDER", K_JOY_BTN_LSHOULDER, NULL}, {"JOY_BTN_RSHOULDER", K_JOY_BTN_RSHOULDER, NULL}, + {"JOY_DPAD_UP", K_JOY_DPAD_UP, NULL}, + {"JOY_DPAD_DOWN", K_JOY_DPAD_DOWN, NULL}, + {"JOY_DPAD_LEFT", K_JOY_DPAD_LEFT, NULL}, + {"JOY_DPAD_RIGHT", K_JOY_DPAD_RIGHT, NULL}, {"JOY_BTN_MISC1", K_JOY_BTN_MISC1, NULL}, {"JOY_BTN_RPADDLE1", K_JOY_BTN_RPADDLE1, NULL}, {"JOY_BTN_LPADDLE1", K_JOY_BTN_LPADDLE1, NULL}, @@ -141,11 +145,6 @@ static const keyname_t keynames[] = {"JOY_TRIGGER1", K_JOY_TRIGGER1, NULL}, {"JOY_TRIGGER2", K_JOY_TRIGGER2, NULL}, - {"JOY_DPAD_UP", K_JOY_DPAD_UP, NULL}, - {"JOY_DPAD_DOWN", K_JOY_DPAD_DOWN, NULL}, - {"JOY_DPAD_LEFT", K_JOY_DPAD_LEFT, NULL}, - {"JOY_DPAD_RIGHT", K_JOY_DPAD_RIGHT, NULL}, - {"AUX1", K_AUX1, "#str_07094"}, {"AUX2", K_AUX2, "#str_07095"}, {"AUX3", K_AUX3, "#str_07096"}, diff --git a/neo/framework/KeyInput.h b/neo/framework/KeyInput.h index c394ba47..acf7527d 100644 --- a/neo/framework/KeyInput.h +++ b/neo/framework/KeyInput.h @@ -147,6 +147,7 @@ typedef enum { K_JOY_BTN_EAST, // right face button, like Xbox B K_JOY_BTN_WEST, // left face button, like Xbox X K_JOY_BTN_NORTH, // top face button, like Xbox Y + K_JOY_BTN_BACK, K_JOY_BTN_GUIDE, // Note: this one should probably not be used? K_JOY_BTN_START, @@ -154,7 +155,12 @@ typedef enum { K_JOY_BTN_RSTICK, // press right stick K_JOY_BTN_LSHOULDER, K_JOY_BTN_RSHOULDER, - // NOTE: in SDL3, the 4 DPAD buttons would be following, we have those later + + K_JOY_DPAD_UP, + K_JOY_DPAD_DOWN, + K_JOY_DPAD_LEFT, + K_JOY_DPAD_RIGHT, + K_JOY_BTN_MISC1, // Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button) K_JOY_BTN_RPADDLE1, // Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) K_JOY_BTN_LPADDLE1, // Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) @@ -174,11 +180,7 @@ typedef enum { K_JOY_TRIGGER1, K_JOY_TRIGGER2, - K_JOY_DPAD_UP, - K_JOY_DPAD_DOWN, - K_JOY_DPAD_LEFT, - K_JOY_DPAD_RIGHT, - K_LAST_JOY = K_JOY_DPAD_RIGHT, + K_LAST_JOY = K_JOY_TRIGGER2, K_GRAVE_A = 229, // lowercase a with grave accent FIXME: used to be 224; this probably isn't used anyway diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index 2b70e8d7..8c5cc388 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -428,8 +428,6 @@ idCVar idUsercmdGenLocal::m_smooth( "m_smooth", "1", CVAR_SYSTEM | CVAR_ARCHIVE idCVar idUsercmdGenLocal::m_strafeSmooth( "m_strafeSmooth", "4", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "number of samples blended for mouse moving", 1, 8, idCmdSystem::ArgCompletion_Integer<1,8> ); idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | CVAR_BOOL, "shows mouse movement" ); -idCVar joy_mergedThreshold( "joy_mergedThreshold", "1", CVAR_BOOL | CVAR_ARCHIVE, "If the thresholds aren't merged, you drift more off center" ); -idCVar joy_newCode( "joy_newCode", "0", CVAR_BOOL | CVAR_ARCHIVE, "Use the new codepath" ); idCVar joy_triggerThreshold( "joy_triggerThreshold", "0.05", CVAR_FLOAT | CVAR_ARCHIVE, "how far the joystick triggers have to be pressed before they register as down" ); idCVar joy_deadZone( "joy_deadZone", "0.4", CVAR_FLOAT | CVAR_ARCHIVE, "specifies how large the dead-zone is on the joystick" ); idCVar joy_range( "joy_range", "1.0", CVAR_FLOAT | CVAR_ARCHIVE, "allow full range to be mapped to a smaller offset" ); @@ -437,14 +435,15 @@ idCVar joy_gammaLook( "joy_gammaLook", "1", CVAR_INTEGER | CVAR_ARCHIVE, "use a idCVar joy_powerScale( "joy_powerScale", "2", CVAR_FLOAT | CVAR_ARCHIVE, "Raise joystick values to this power" ); idCVar joy_pitchSpeed( "joy_pitchSpeed", "130", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing up or down on the joystick", 60, 600 ); idCVar joy_yawSpeed( "joy_yawSpeed", "240", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing left or right on the joystick", 60, 600 ); +idCVar joy_invertLook( "joy_invertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" ); // these were a bad idea! idCVar joy_dampenLook( "joy_dampenLook", "1", CVAR_BOOL | CVAR_ARCHIVE, "Do not allow full acceleration on look" ); idCVar joy_deltaPerMSLook( "joy_deltaPerMSLook", "0.003", CVAR_FLOAT | CVAR_ARCHIVE, "Max amount to be added on look per MS" ); -idCVar in_useJoystick( "in_useJoystick", "1", CVAR_ARCHIVE | CVAR_BOOL, "enables/disables the gamepad for PC use" ); -idCVar in_invertLook( "in_invertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" ); -idCVar in_mouseInvertLook( "in_mouseInvertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" ); +idCVar in_useGamepad( "in_useGamepad", "1", CVAR_ARCHIVE | CVAR_BOOL, "enables/disables the gamepad for PC use" ); + +// TODO idCVar in_mouseInvertLook( "in_mouseInvertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" ); static idUsercmdGenLocal localUsercmdGen; idUsercmdGen *usercmdGen = &localUsercmdGen; @@ -838,7 +837,7 @@ void idUsercmdGenLocal::HandleJoystickAxis( int keyNum, float unclampedValue, fl lastLookValuePitch = lookValue; } - float invertPitch = in_invertLook.GetBool() ? -1.0f : 1.0f; + float invertPitch = joy_invertLook.GetBool() ? -1.0f : 1.0f; viewangles[PITCH] -= MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch; break; } @@ -848,7 +847,7 @@ void idUsercmdGenLocal::HandleJoystickAxis( int keyNum, float unclampedValue, fl lastLookValuePitch = lookValue; } - float invertPitch = in_invertLook.GetBool() ? -1.0f : 1.0f; + float invertPitch = joy_invertLook.GetBool() ? -1.0f : 1.0f; viewangles[PITCH] += MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch; break; } @@ -1259,9 +1258,6 @@ void idUsercmdGenLocal::Joystick( void ) { Key( joyButton, ( value != 0 ) ); } else if ( ( action >= J_AXIS_MIN ) && ( action <= J_AXIS_MAX ) ) { joystickAxis[ action - J_AXIS_MIN ] = static_cast<float>( value ) / 32767.0f; - } else if ( action >= J_DPAD_UP && action <= J_DPAD_RIGHT ) { - int joyButton = K_JOY_DPAD_UP + ( action - J_DPAD_UP ); - Key( joyButton, ( value != 0 ) ); } else { //assert( !"Unknown joystick event" ); } @@ -1294,7 +1290,7 @@ void idUsercmdGenLocal::UsercmdInterrupt( void ) { Keyboard(); // process the system joystick events - if ( in_useJoystick.GetBool() ) { + if ( in_useGamepad.GetBool() ) { Joystick(); } @@ -1341,7 +1337,7 @@ usercmd_t idUsercmdGenLocal::GetDirectUsercmd( void ) { Keyboard(); // process the system joystick events - if ( in_useJoystick.GetBool() ) { + if ( in_useGamepad.GetBool() ) { Joystick(); } // create the usercmd diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 31a57f20..91497e56 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -60,6 +60,8 @@ If you have questions concerning this license or the applicable additional terms #define SDLK_PRINTSCREEN SDLK_PRINT #endif +extern idCVar in_useGamepad; // from UsercmdGen.cpp + // NOTE: g++-4.7 doesn't like when this is static (for idCmdSystem::ArgCompletion_String<kbdNames>) const char *_in_kbdNames[] = { #if SDL_VERSION_ATLEAST(2, 0, 0) // auto-detection is only available for SDL2 @@ -125,7 +127,7 @@ static idList<mouse_poll_t> mouse_polls; static idList<joystick_poll_t> joystick_polls; static bool buttonStates[K_LAST_KEY]; -static int joyAxis[MAX_JOYSTICK_AXIS]; +static float joyAxis[MAX_JOYSTICK_AXIS]; static idList<sysEvent_t> event_overflow; @@ -273,7 +275,7 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { if (key <= K_JOY_BTN_NORTH) { #if SDL_VERSION_ATLEAST(3, 0, 0) - SDL_GamepadButton gpbtn = SDL_GAMEPAD_BUTTON_SOUTH + (key - K_JOY_BTN_NORTH); + SDL_GamepadButton gpbtn = SDL_GAMEPAD_BUTTON_SOUTH + (key - K_JOY_BTN_SOUTH); SDL_GamepadButtonLabel label = SDL_GetGamepadButtonLabeForTypel(TODO, gpbtn); switch(label) { case SDL_GAMEPAD_BUTTON_LABEL_A: @@ -310,7 +312,6 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { } // the labels for the remaining keys are the same for SDL2 and SDL3 (and all controllers) - // Note: Would be nicer with "Pad " at the beginning, but then it's too long for the keybinding window :-/ switch(key) { case K_JOY_BTN_BACK: return "Pad Back"; @@ -328,7 +329,16 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { return "Pad LShoulder"; case K_JOY_BTN_RSHOULDER: return "Pad RShoulder"; - // NOTE: in SDL3, the 4 DPAD buttons would be following, we have those later + + case K_JOY_DPAD_UP: + return "DPad Up"; + case K_JOY_DPAD_DOWN: + return "DPad Down"; + case K_JOY_DPAD_LEFT: + return "DPad Left"; + case K_JOY_DPAD_RIGHT: + return "DPad Right"; + case K_JOY_BTN_MISC1: return "Pad Misc"; case K_JOY_BTN_RPADDLE1: @@ -340,6 +350,8 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { case K_JOY_BTN_LPADDLE2: return "Pad P4"; + // Note: Would be nicer with "Pad " (or even "Gamepad ") at the beginning, + // but then it's too long for the keybinding window :-/ case K_JOY_STICK1_UP: return "Stick1 Up"; case K_JOY_STICK1_DOWN: @@ -363,16 +375,8 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { case K_JOY_TRIGGER2: return "Trigger 2"; - case K_JOY_DPAD_UP: - return "DPad Up"; - case K_JOY_DPAD_DOWN: - return "DPad Down"; - case K_JOY_DPAD_LEFT: - return "DPad Left"; - case K_JOY_DPAD_RIGHT: - return "DPad Right"; default: - assert(0 && "missing a case in Sys_GetLocalizedJoyKeyName() for axes or dpad!"); + assert(0 && "missing a case in Sys_GetLocalizedJoyKeyName()!"); } } #endif // SDL2+ @@ -1210,16 +1214,26 @@ sysEvent_t Sys_GetEvent() { case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: { + if ( !in_useGamepad.GetBool() ) { + common->Warning( "Gamepad support is disabled! Set the in_useGamepad CVar to 1 to enable it!\n" ); + continue; + } + // special case: always treat the start button as escape so it opens/closes the menu + // (also makes that button non-bindable) + if ( ev.cbutton.button == SDL_CONTROLLER_BUTTON_START ) { + res.evType = SE_KEY; + res.evValue = K_ESCAPE; + res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0; + return res; + } + sys_jEvents jEvent = mapjoybutton( (SDL_GameControllerButton)ev.cbutton.button); joystick_polls.Append(joystick_poll_t(jEvent, ev.cbutton.state == SDL_PRESSED ? 1 : 0) ); res.evType = SE_KEY; res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0; - if ( ( jEvent >= J_BTN_SOUTH ) && ( jEvent <= J_ACTION_MAX ) ) { - res.evValue = K_JOY_BTN_SOUTH + ( jEvent - J_BTN_SOUTH ); - return res; - } else if ( ( jEvent >= J_DPAD_UP ) && ( jEvent <= J_DPAD_RIGHT ) ) { - res.evValue = K_JOY_DPAD_UP + ( jEvent - J_DPAD_UP ); + if ( ( jEvent >= J_ACTION_FIRST ) && ( jEvent <= J_ACTION_MAX ) ) { + res.evValue = K_FIRST_JOY + ( jEvent - J_ACTION_FIRST ); return res; } @@ -1230,6 +1244,12 @@ sysEvent_t Sys_GetEvent() { { const int range = 16384; + if ( !in_useGamepad.GetBool() ) { + // not printing a message here, I guess we get lots of spurious axis events.. + // TODO: or print a message if value is big enough? + continue; + } + sys_jEvents jEvent = mapjoyaxis( (SDL_GameControllerAxis)ev.caxis.axis); joystick_polls.Append(joystick_poll_t( jEvent, ev.caxis.value) ); @@ -1251,15 +1271,18 @@ sysEvent_t Sys_GetEvent() { PushButton( K_JOY_TRIGGER2, ( ev.caxis.value > range ) ); } if ( jEvent >= J_AXIS_MIN && jEvent <= J_AXIS_MAX ) { + // NOTE: the stuff set here is only used to move the cursor in menus + // ingame movement is done via joystick_polls int axis = jEvent - J_AXIS_MIN; - int percent = ( ev.caxis.value * 16 ) / range; - if ( joyAxis[axis] != percent ) { - joyAxis[axis] = percent; - res.evType = SE_JOYSTICK; - res.evValue = axis; - res.evValue2 = percent; - return res; + float val = ev.caxis.value * (1.25f / 32767.0f); + // 25% deadzone + if( val < 0.0f ) { + val = fminf(val + 0.25f, 0.0f); + } else { + val = fmaxf(val - 0.25f, 0.0f); } + + joyAxis[axis] = val; } continue; // try to get a decent event. @@ -1300,6 +1323,28 @@ sysEvent_t Sys_GetEvent() { } } + // first return joyaxis events, if gamepad is enabled and, and 16ms are over + // (or we haven't returned the values for all axis yet) + if ( in_useGamepad.GetBool() ) { + static unsigned int lastMS = 0; + static int joyAxisToSend = 0; + unsigned int nowMS = Sys_Milliseconds(); + if ( nowMS - lastMS >= 16 ) { + int val = joyAxis[joyAxisToSend] * 100; // float to percent + res.evType = SE_JOYSTICK; + res.evValue = joyAxisToSend; + res.evValue2 = val; + ++joyAxisToSend; + if(joyAxisToSend == MAX_JOYSTICK_AXIS) { + // we're done for this frame, so update lastMS and reset joyAxisToSend + joyAxisToSend = 0; + lastMS = nowMS; + } + return res; + } + + } + return res_none; } diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index 03592d66..c7bd89be 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -91,7 +91,12 @@ typedef enum { J_BTN_RSTICK, // press right stick J_BTN_LSHOULDER, J_BTN_RSHOULDER, - // NOTE: in SDL3, the 4 DPAD buttons would be following, we have those at the end + + J_DPAD_UP, + J_DPAD_DOWN, + J_DPAD_LEFT, + J_DPAD_RIGHT, + J_BTN_MISC1, // Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button) J_BTN_RPADDLE1, // Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) J_BTN_LPADDLE1, // Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) @@ -111,18 +116,13 @@ typedef enum { J_AXIS_MAX = J_AXIS_MIN + MAX_JOYSTICK_AXIS - 1, - J_DPAD_UP, - J_DPAD_DOWN, - J_DPAD_LEFT, - J_DPAD_RIGHT, - MAX_JOY_EVENT } sys_jEvents; struct sysEvent_t { sysEventType_t evType; - int evValue; - int evValue2; + int evValue; // for keys: K_* or ASCII code; for joystick: axis; for mouse: mouseX + int evValue2; // for keys: 0/1 for up/down; for axis: value; for mouse: mouseY int evPtrLength; // bytes of data pointed to by evPtr, for journaling void * evPtr; // this must be manually freed if not NULL }; diff --git a/neo/ui/UserInterface.cpp b/neo/ui/UserInterface.cpp index 032b3c4b..0d101e02 100644 --- a/neo/ui/UserInterface.cpp +++ b/neo/ui/UserInterface.cpp @@ -344,6 +344,9 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim return ret; } + // DG: used to turn gamepad A into left mouse click + sysEvent_t fakedEvent = {}; + if ( event->evType == SE_MOUSE || event->evType == SE_MOUSE_ABS ) { if ( !desktop || (desktop->GetFlags() & WIN_MENUGUI) ) { // DG: this is a fullscreen GUI, scale the mousedelta added to cursorX/Y @@ -401,6 +404,34 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim cursorY = 0; } } + else if ( event->evType == SE_JOYSTICK && event->evValue2 != 0 ) + { + // evValue: axis = jEvent - J_AXIS_MIN; + // evValue2: percent (-100 to 100) + + // currently uses both sticks for cursor movement + // TODO could use one stick for scrolling (maybe by generating K_UPARROW/DOWNARROW events?) + float addVal = event->evValue2 * 0.1f; + if( event->evValue == 0 || event->evValue == 2 ) { + cursorX += addVal; + } else if( event->evValue == 1 || event->evValue == 3 ) { + cursorY += addVal; + } + + if (cursorX < 0) { + cursorX = 0; + } + if (cursorY < 0) { + cursorY = 0; + } + } + else if( event->evType == SE_KEY && (event->evValue == K_JOY_BTN_SOUTH || event->evValue == K_JOY_BTN_EAST) ) + { + // map gamepad buttons south/east (A/B on xbox controller) to mouse1/2 + fakedEvent = *event; + fakedEvent.evValue = (event->evValue == K_JOY_BTN_SOUTH) ? K_MOUSE1 : K_MOUSE2; + event = &fakedEvent; + } if ( desktop ) { return desktop->HandleEvent( event, updateVisuals ); diff --git a/neo/ui/Window.cpp b/neo/ui/Window.cpp index 3dda7118..3948cea3 100644 --- a/neo/ui/Window.cpp +++ b/neo/ui/Window.cpp @@ -736,7 +736,7 @@ const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) *updateVisuals = true; } - if (event->evValue == K_MOUSE1 || event->evValue == K_JOY_BTN_EAST) { + if (event->evValue == K_MOUSE1) { if (!event->evValue2 && GetCaptureChild()) { GetCaptureChild()->LoseCapture(); @@ -785,7 +785,7 @@ const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) } else if (!actionUpRun) { actionUpRun = RunScript( ON_ACTIONRELEASE ); } - } else if (event->evValue == K_MOUSE2 || event->evValue == K_JOY_BTN_SOUTH) { + } else if (event->evValue == K_MOUSE2) { if (!event->evValue2 && GetCaptureChild()) { GetCaptureChild()->LoseCapture(); From e0bb01ef52439030a289a94b5440ab888c493453 Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Tue, 16 Jan 2024 19:06:08 +0100 Subject: [PATCH 05/14] Gamepad cursor control improvements - make moving the cursor more precise by using an exponential curve for axis value => cursor speed - emulate cursor keys with DPad and Enter with left trigger - also use right trigger for leftclick, as it's usually used for firing a weapon and thus used for "clicking" ingame UIs - fix hovering/highlighting menu elements when moving cursor with gamepad --- neo/ui/UserInterface.cpp | 62 ++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/neo/ui/UserInterface.cpp b/neo/ui/UserInterface.cpp index 0d101e02..98e3dea5 100644 --- a/neo/ui/UserInterface.cpp +++ b/neo/ui/UserInterface.cpp @@ -344,7 +344,7 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim return ret; } - // DG: used to turn gamepad A into left mouse click + // DG: used to translate gamepad input into events the UI system is familiar with sysEvent_t fakedEvent = {}; if ( event->evType == SE_MOUSE || event->evType == SE_MOUSE_ABS ) { @@ -404,14 +404,17 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim cursorY = 0; } } - else if ( event->evType == SE_JOYSTICK && event->evValue2 != 0 ) + else if ( event->evType == SE_JOYSTICK && event->evValue2 != 0 && event->evValue < 4 ) { // evValue: axis = jEvent - J_AXIS_MIN; // evValue2: percent (-100 to 100) // currently uses both sticks for cursor movement // TODO could use one stick for scrolling (maybe by generating K_UPARROW/DOWNARROW events?) - float addVal = event->evValue2 * 0.1f; + float addVal = expf( fabsf(event->evValue2 * 0.03f) ) * 0.5f; + if(event->evValue2 < 0) + addVal = -addVal; + if( event->evValue == 0 || event->evValue == 2 ) { cursorX += addVal; } else if( event->evValue == 1 || event->evValue == 3 ) { @@ -424,14 +427,55 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim if (cursorY < 0) { cursorY = 0; } - } - else if( event->evType == SE_KEY && (event->evValue == K_JOY_BTN_SOUTH || event->evValue == K_JOY_BTN_EAST) ) - { - // map gamepad buttons south/east (A/B on xbox controller) to mouse1/2 - fakedEvent = *event; - fakedEvent.evValue = (event->evValue == K_JOY_BTN_SOUTH) ? K_MOUSE1 : K_MOUSE2; + + // some things like highlighting hovered UI elements need a mouse event, + // so create a fake mouse event + fakedEvent.evType = SE_MOUSE; + // the coordinates (evValue/evValue2) aren't used, but keeping them at 0 + // (as default-initialized above) shouldn't hurt either way event = &fakedEvent; } + else if ( event->evType == SE_KEY && event->evValue >= K_FIRST_JOY && event->evValue <= K_LAST_JOY ) + { + // map some gamepad buttons to SE_KEY events that the UI already knows how to use + int key = 0; + switch(event->evValue) { + // emulate mouse buttons + case K_JOY_TRIGGER2: + // the right trigger is often used for shooting, so for ingame UIs + // it'll behave like mouseclick - do the same in menus + // fall-through + case K_JOY_BTN_SOUTH: // A on xbox controller + key = K_MOUSE1; + break; + case K_JOY_BTN_EAST: // B on xbox controller + key = K_MOUSE2; + break; + // emulate cursor keys (sometimes used for scrolling or selecting in a list) + case K_JOY_DPAD_UP: + key = K_UPARROW; + break; + case K_JOY_DPAD_DOWN: + key = K_DOWNARROW; + break; + case K_JOY_DPAD_LEFT: + key = K_LEFTARROW; + break; + case K_JOY_DPAD_RIGHT: + key = K_RIGHTARROW; + break; + // enter is useful after selecting something with cursor keys (or dpad) + // in a list, like selecting a savegame - I guess left trigger is suitable for that? + case K_JOY_TRIGGER1: + key = K_ENTER; + break; + } + if (key != 0) { + fakedEvent = *event; + fakedEvent.evValue = key; + event = &fakedEvent; + } + } if ( desktop ) { return desktop->HandleEvent( event, updateVisuals ); From 03ec74fd6fdf632b8898c600dd9c227f911f7d77 Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Wed, 17 Jan 2024 06:33:06 +0100 Subject: [PATCH 06/14] Make PDA work with gamepad, incl. making Pad A emulate leftclick this is a bit hacky and ugly, and doesn't work properly in multiplayer mode yet, see FIXME in idUserInterfaceLocal::Activate() --- neo/framework/UsercmdGen.cpp | 34 ++++++++++++++++++++- neo/sys/events.cpp | 57 +++++++++++++++++++++++++++++------- neo/sys/sys_public.h | 5 ++++ neo/ui/UserInterface.cpp | 5 ++++ 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index 8c5cc388..b212223f 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -353,6 +353,7 @@ private: void CircleToSquare( float & axis_x, float & axis_y ) const; void HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive ); void JoystickMove( void ); + void JoystickFakeMouse(float axis_x, float axis_y, float deadzone); void MouseMove( void ); void CmdButtons( void ); @@ -429,7 +430,7 @@ idCVar idUsercmdGenLocal::m_strafeSmooth( "m_strafeSmooth", "4", CVAR_SYSTEM | C idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | CVAR_BOOL, "shows mouse movement" ); idCVar joy_triggerThreshold( "joy_triggerThreshold", "0.05", CVAR_FLOAT | CVAR_ARCHIVE, "how far the joystick triggers have to be pressed before they register as down" ); -idCVar joy_deadZone( "joy_deadZone", "0.4", CVAR_FLOAT | CVAR_ARCHIVE, "specifies how large the dead-zone is on the joystick" ); +idCVar joy_deadZone( "joy_deadZone", "0.25", CVAR_FLOAT | CVAR_ARCHIVE, "specifies how large the dead-zone is on the joystick" ); idCVar joy_range( "joy_range", "1.0", CVAR_FLOAT | CVAR_ARCHIVE, "allow full range to be mapped to a smaller offset" ); idCVar joy_gammaLook( "joy_gammaLook", "1", CVAR_INTEGER | CVAR_ARCHIVE, "use a log curve instead of a power curve for movement" ); idCVar joy_powerScale( "joy_powerScale", "2", CVAR_FLOAT | CVAR_ARCHIVE, "Raise joystick values to this power" ); @@ -870,6 +871,33 @@ void idUsercmdGenLocal::HandleJoystickAxis( int keyNum, float unclampedValue, fl } } +static float joyAxisToMouseDelta(float axis, float deadzone) +{ + float ret = 0.0f; + float val = fabsf(axis); // calculations below require a positive value + if(val > deadzone) { + // from deadzone .. 1 to 0 .. 1-deadzone + val -= deadzone; + // and then to 0..1 + val = val * (1.0f / (1.0f - deadzone)); + + // make it exponential curve - exp(val*3) should return sth between 1 and 20; + // then turning that into 0.5 .. 10 + ret = expf( val * 3.0f ) * 0.5f; + if(axis < 0.0f) // restore sign + ret = -ret; + } + return ret; +} + +void idUsercmdGenLocal::JoystickFakeMouse(float axis_x, float axis_y, float deadzone) +{ + float x = joyAxisToMouseDelta(axis_x, deadzone); + float y = joyAxisToMouseDelta(axis_y, deadzone); + continuousMouseX += x; + continuousMouseY += y; +} + /* ================= idUsercmdGenLocal::JoystickMove @@ -888,6 +916,8 @@ void idUsercmdGenLocal::JoystickMove() { HandleJoystickAxis( K_JOY_STICK1_LEFT, axis_x, threshold, false ); HandleJoystickAxis( K_JOY_STICK1_RIGHT, axis_x, threshold, true ); + JoystickFakeMouse( axis_x, axis_y, threshold ); + axis_y = joystickAxis[ AXIS_RIGHT_Y ]; axis_x = joystickAxis[ AXIS_RIGHT_X ]; CircleToSquare( axis_x, axis_y ); @@ -897,6 +927,8 @@ void idUsercmdGenLocal::JoystickMove() { HandleJoystickAxis( K_JOY_STICK2_LEFT, axis_x, threshold, false ); HandleJoystickAxis( K_JOY_STICK2_RIGHT, axis_x, threshold, true ); + JoystickFakeMouse( axis_x, axis_y, threshold ); + HandleJoystickAxis( K_JOY_TRIGGER1, joystickAxis[ AXIS_LEFT_TRIG ], triggerThreshold, true ); HandleJoystickAxis( K_JOY_TRIGGER2, joystickAxis[ AXIS_RIGHT_TRIG ], triggerThreshold, true ); } diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 91497e56..986e9400 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -61,6 +61,7 @@ If you have questions concerning this license or the applicable additional terms #endif extern idCVar in_useGamepad; // from UsercmdGen.cpp +extern idCVar joy_deadZone; // ditto // NOTE: g++-4.7 doesn't like when this is static (for idCmdSystem::ArgCompletion_String<kbdNames>) const char *_in_kbdNames[] = { @@ -898,6 +899,25 @@ void Sys_GrabMouseCursor(bool grabIt) { GLimp_GrabInput(flags); } +static bool interactiveGuiActive = false; +/* +=============== +Sys_SetInteractiveIngameGuiActive +Tell the input system that currently an interactive *ingame* UI has focus, +so there is an active cursor. +Used for an ungodly hack to make gamepad button south (A) behave like +left mouse button in that case, so "clicking" with gamepad in the PDA +(and ingame GUIs) works as expected. +Not set for proper menus like main menu etc - the gamepad hacks for that +are in idUserInterfaceLocal::HandleEvent(). +I hope this won't explode in my face :-p +=============== + */ +void Sys_SetInteractiveIngameGuiActive(bool active) +{ + interactiveGuiActive = active; +} + static void PushButton( int key, bool value ) { // So we don't keep sending the same SE_KEY message over and over again @@ -1218,20 +1238,27 @@ sysEvent_t Sys_GetEvent() { common->Warning( "Gamepad support is disabled! Set the in_useGamepad CVar to 1 to enable it!\n" ); continue; } + + res.evType = SE_KEY; + res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0; + // special case: always treat the start button as escape so it opens/closes the menu // (also makes that button non-bindable) if ( ev.cbutton.button == SDL_CONTROLLER_BUTTON_START ) { - res.evType = SE_KEY; res.evValue = K_ESCAPE; - res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0; + return res; + } else if( ev.cbutton.button == SDL_CONTROLLER_BUTTON_A && interactiveGuiActive && sessLocal.GetActiveMenu() == NULL ) { + // ugly hack: currently an interactive ingame GUI (with a cursor) is active/focused + // so pretend that the gamepads A (south) button is the left mouse button + // so it can be used for "clicking".. + mouse_polls.Append( mouse_poll_t(M_ACTION1, res.evValue2) ); + res.evValue = K_MOUSE1; return res; } sys_jEvents jEvent = mapjoybutton( (SDL_GameControllerButton)ev.cbutton.button); joystick_polls.Append(joystick_poll_t(jEvent, ev.cbutton.state == SDL_PRESSED ? 1 : 0) ); - res.evType = SE_KEY; - res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0; if ( ( jEvent >= J_ACTION_FIRST ) && ( jEvent <= J_ACTION_MAX ) ) { res.evValue = K_FIRST_JOY + ( jEvent - J_ACTION_FIRST ); return res; @@ -1274,18 +1301,28 @@ sysEvent_t Sys_GetEvent() { // NOTE: the stuff set here is only used to move the cursor in menus // ingame movement is done via joystick_polls int axis = jEvent - J_AXIS_MIN; - float val = ev.caxis.value * (1.25f / 32767.0f); - // 25% deadzone - if( val < 0.0f ) { - val = fminf(val + 0.25f, 0.0f); + float dz = joy_deadZone.GetFloat(); + + float val = fabsf(ev.caxis.value * (1.0f / 32767.0f)); + if(val < dz) { + val = 0.0f; } else { - val = fmaxf(val - 0.25f, 0.0f); + // from deadzone .. 1 to 0 .. 1-deadzone + val -= dz; + // and then to 0..1 + val = val * (1.0f / (1.0f - dz)); + + if( ev.caxis.value < 0 ) { + val = -val; + } } joyAxis[axis] = val; } - continue; // try to get a decent event. + // handle next event; joy axis events are generated below, + // when there are no further SDL events + continue; } break; diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index c7bd89be..e20ab923 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -248,6 +248,11 @@ void Sys_EndJoystickInputEvents(); // when in windowed mode void Sys_GrabMouseCursor( bool grabIt ); +// DG: added this for an ungodly hack for gamepad support +// active = true means "currently a GUI with a cursor is active/focused" +// active = false means "that GUI is not active anymore" +void Sys_SetInteractiveIngameGuiActive(bool active); + void Sys_ShowWindow( bool show ); bool Sys_IsWindowVisible( void ); void Sys_ShowConsole( int visLevel, bool quitOnClose ); diff --git a/neo/ui/UserInterface.cpp b/neo/ui/UserInterface.cpp index 98e3dea5..c8b3e167 100644 --- a/neo/ui/UserInterface.cpp +++ b/neo/ui/UserInterface.cpp @@ -582,6 +582,11 @@ const char *idUserInterfaceLocal::Activate(bool activate, int _time) { time = _time; active = activate; if ( desktop ) { + // FIXME: this works ok, mostly, except in multiplayer, where this function + // is called twice with activate=true, and the first time GetActiveMenu() returns NULL, the second time not + if(interactive && sessLocal.GetActiveMenu() == NULL) { + Sys_SetInteractiveIngameGuiActive(activate); + } activateStr = ""; desktop->Activate( activate, activateStr ); return activateStr; From cf5d10f4e6fe80f2e266a0419bb10f5f6a349d0e Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Wed, 17 Jan 2024 16:47:21 +0100 Subject: [PATCH 07/14] Fix gamepad pseudo-mouse input for UIs in multiplayer mode also, only generate pseudo-mouse-move events for gamecode (by modifying idUsercmdGenLocal::continuousMouseX/Y) when an interactive ingame UI is active --- neo/framework/Session.cpp | 3 +++ neo/framework/UsercmdGen.cpp | 11 ++++++---- neo/framework/UsercmdGen.h | 4 ++-- neo/sys/events.cpp | 40 +++++++++++++++++++++++++++++++----- neo/sys/sys_public.h | 3 ++- neo/ui/UserInterface.cpp | 12 ++++++----- 6 files changed, 56 insertions(+), 17 deletions(-) diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp index 609c178e..837f7f52 100644 --- a/neo/framework/Session.cpp +++ b/neo/framework/Session.cpp @@ -1447,6 +1447,9 @@ void idSessionLocal::UnloadMap() { } mapSpawned = false; + + // DG: that state needs to be reset now + Sys_SetInteractiveIngameGuiActive( false, NULL ); } /* diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index b212223f..a64a4984 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -890,12 +890,15 @@ static float joyAxisToMouseDelta(float axis, float deadzone) return ret; } +extern bool D3_IN_interactiveIngameGuiActive; // from sys/events.cpp void idUsercmdGenLocal::JoystickFakeMouse(float axis_x, float axis_y, float deadzone) { - float x = joyAxisToMouseDelta(axis_x, deadzone); - float y = joyAxisToMouseDelta(axis_y, deadzone); - continuousMouseX += x; - continuousMouseY += y; + if ( D3_IN_interactiveIngameGuiActive ) { + float x = joyAxisToMouseDelta(axis_x, deadzone); + float y = joyAxisToMouseDelta(axis_y, deadzone); + continuousMouseX += x; + continuousMouseY += y; + } } /* diff --git a/neo/framework/UsercmdGen.h b/neo/framework/UsercmdGen.h index 17e88fe8..c9e9ecae 100644 --- a/neo/framework/UsercmdGen.h +++ b/neo/framework/UsercmdGen.h @@ -96,8 +96,8 @@ public: signed char rightmove; // left/right movement signed char upmove; // up/down movement short angles[3]; // view angles - short mx; // mouse delta x - short my; // mouse delta y + short mx; // mouse delta x - DG: not really delta, but from continuousMouseX which accumulates + short my; // mouse delta y - DG: same but from continuousMouseY signed char impulse; // impulse command byte flags; // additional flags int sequence; // just for debugging diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 986e9400..8d7fffca 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -899,7 +899,7 @@ void Sys_GrabMouseCursor(bool grabIt) { GLimp_GrabInput(flags); } -static bool interactiveGuiActive = false; + /* =============== Sys_SetInteractiveIngameGuiActive @@ -910,12 +910,42 @@ left mouse button in that case, so "clicking" with gamepad in the PDA (and ingame GUIs) works as expected. Not set for proper menus like main menu etc - the gamepad hacks for that are in idUserInterfaceLocal::HandleEvent(). +Call with ui = NULL to clear the state. I hope this won't explode in my face :-p =============== - */ -void Sys_SetInteractiveIngameGuiActive(bool active) +*/ +bool D3_IN_interactiveIngameGuiActive = false; +void Sys_SetInteractiveIngameGuiActive( bool active, idUserInterface* ui ) { - interactiveGuiActive = active; + static idList<idUserInterface*> lastuis; + if ( ui == NULL ) { + // special case for clearing + D3_IN_interactiveIngameGuiActive = false; + lastuis.Clear(); + return; + } + int idx = lastuis.FindIndex( ui ); + + if ( sessLocal.GetActiveMenu() == NULL && active ) { + // add ui to lastuis, if it has been activated and no proper menu + // (like main menu) is currently open + lastuis.Append( ui ); + } else if ( idx != -1 ) { + // if the UI is in lastuis and has been deactivated, or there + // is a proper menu opened, remove it from the list. + // this both handles the regular deactivate case and also works around + // main-menu-in-multiplayer weirdness: that menu calls idUserInterface::Activate() + // with activate = true twice, but on first call sessLocal.GetActiveMenu() is NULL + // so we want to remove it once we realize that it really is a "proper" menu after all. + // And because it's possible that we have an ingame UI focussed while opening + // the multiplayer-main-menu, we keep a list of lastuis, instead of just one, + // so D3_IN_interactiveIngameGuiActive remains true in that case + // (the ingame UI is still in the list) + + lastuis.RemoveIndex( idx ); + } + + D3_IN_interactiveIngameGuiActive = lastuis.Num() != 0; } @@ -1247,7 +1277,7 @@ sysEvent_t Sys_GetEvent() { if ( ev.cbutton.button == SDL_CONTROLLER_BUTTON_START ) { res.evValue = K_ESCAPE; return res; - } else if( ev.cbutton.button == SDL_CONTROLLER_BUTTON_A && interactiveGuiActive && sessLocal.GetActiveMenu() == NULL ) { + } else if( ev.cbutton.button == SDL_CONTROLLER_BUTTON_A && D3_IN_interactiveIngameGuiActive && sessLocal.GetActiveMenu() == NULL ) { // ugly hack: currently an interactive ingame GUI (with a cursor) is active/focused // so pretend that the gamepads A (south) button is the left mouse button // so it can be used for "clicking".. diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index e20ab923..9c32f9f9 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -251,7 +251,8 @@ void Sys_GrabMouseCursor( bool grabIt ); // DG: added this for an ungodly hack for gamepad support // active = true means "currently a GUI with a cursor is active/focused" // active = false means "that GUI is not active anymore" -void Sys_SetInteractiveIngameGuiActive(bool active); +class idUserInterface; +void Sys_SetInteractiveIngameGuiActive( bool active, idUserInterface* ui ); void Sys_ShowWindow( bool show ); bool Sys_IsWindowVisible( void ); diff --git a/neo/ui/UserInterface.cpp b/neo/ui/UserInterface.cpp index c8b3e167..58379b87 100644 --- a/neo/ui/UserInterface.cpp +++ b/neo/ui/UserInterface.cpp @@ -118,6 +118,9 @@ void idUserInterfaceManagerLocal::EndLevelLoad() { } } } + + // DG: this should probably be reset at this point + Sys_SetInteractiveIngameGuiActive( false, NULL ); } void idUserInterfaceManagerLocal::Reload( bool all ) { @@ -582,11 +585,10 @@ const char *idUserInterfaceLocal::Activate(bool activate, int _time) { time = _time; active = activate; if ( desktop ) { - // FIXME: this works ok, mostly, except in multiplayer, where this function - // is called twice with activate=true, and the first time GetActiveMenu() returns NULL, the second time not - if(interactive && sessLocal.GetActiveMenu() == NULL) { - Sys_SetInteractiveIngameGuiActive(activate); - } + // DG: added this hack for gamepad input + if ( interactive ) { + Sys_SetInteractiveIngameGuiActive( activate, this ); + } // DG end activateStr = ""; desktop->Activate( activate, activateStr ); return activateStr; From 86690df24e822187bf3a9b461775757524c0290e Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Wed, 17 Jan 2024 18:54:09 +0100 Subject: [PATCH 08/14] Add joy_gamepadLayout CVar to better support nintendo/PS-style gamepads The button names shown in the controls menu now depend on this CVar. So if you set it to 1 (Nintendo), the "A" button (which, based on its position, would be "B" on XBox/XInput gamepads) is actually shown as "Pad A", and if it's set to 2 (Playstation), it's shown as "Pad Cross". The "real" names, used in the config, remain the same and are based on position: JOY_BTN_SOUTH, JOY_BTN_EAST, JOY_BTN_WEST, JOY_BTN_NORTH --- neo/framework/Session_menu.cpp | 10 ++++++++++ neo/sys/events.cpp | 33 +++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/neo/framework/Session_menu.cpp b/neo/framework/Session_menu.cpp index b979b825..c69859a7 100644 --- a/neo/framework/Session_menu.cpp +++ b/neo/framework/Session_menu.cpp @@ -39,6 +39,8 @@ If you have questions concerning this license or the applicable additional terms idCVar idSessionLocal::gui_configServerRate( "gui_configServerRate", "0", CVAR_GUI | CVAR_ARCHIVE | CVAR_ROM | CVAR_INTEGER, "" ); +extern idCVar joy_gamepadLayout; // DG: used here to update bindings window when cvar is changed + // implements the setup for, and commands from, the main menu /* @@ -1209,6 +1211,14 @@ void idSessionLocal::GuiFrameEvents() { sysEvent_t ev; idUserInterface *gui; + // DG: if joy_gamepadLayout changes, the binding names in the main/controls menu must be updated + if ( joy_gamepadLayout.IsModified() ) { + if ( guiMainMenu != NULL ) { + guiMainMenu->SetKeyBindingNames(); + } + joy_gamepadLayout.ClearModified(); + } + // stop generating move and button commands when a local console or menu is active // running here so SP, async networking and no game all go through it if ( console->Active() || guiActive ) { diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 8d7fffca..a7ccac4c 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -80,6 +80,9 @@ static idCVar in_nograb("in_nograb", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "prevents static idCVar in_grabKeyboard("in_grabKeyboard", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_BOOL, "if enabled, grabs all keyboard input if mouse is grabbed (so keyboard shortcuts from the OS like Alt-Tab or Windows Key won't work)"); +idCVar joy_gamepadLayout("joy_gamepadLayout", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_INTEGER, + "Button layout of gamepad - 0: XBox-style, 1: Nintendo-style, 2: Playstation-style", idCmdSystem::ArgCompletion_Integer<0, 2> ); + // set in handleMouseGrab(), used in Sys_GetEvent() to decide what kind of internal mouse event to generate static bool in_relativeMouseMode = true; // set in Sys_GetEvent() on window focus gained/lost events @@ -275,9 +278,9 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { if (key <= K_JOY_BTN_NORTH) { #if SDL_VERSION_ATLEAST(3, 0, 0) - + // TODO: or use the SDL2 code and just set joy_gamepadLayout automatically based on SDL_GetGamepadType() ? SDL_GamepadButton gpbtn = SDL_GAMEPAD_BUTTON_SOUTH + (key - K_JOY_BTN_SOUTH); - SDL_GamepadButtonLabel label = SDL_GetGamepadButtonLabeForTypel(TODO, gpbtn); + SDL_GamepadButtonLabel label = SDL_GetGamepadButtonLabelForType(TODO, gpbtn); switch(label) { case SDL_GAMEPAD_BUTTON_LABEL_A: return "Pad A"; @@ -298,16 +301,22 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { } #else // SDL2 - // using xbox-style names, like SDL2 does (SDL can't tell us if this is a xbox or PS or nintendo or whatever-style gamepad) - switch(key) { - case K_JOY_BTN_SOUTH: - return "Pad A"; - case K_JOY_BTN_EAST: - return "Pad B"; - case K_JOY_BTN_WEST: - return "Pad X"; - case K_JOY_BTN_NORTH: - return "Pad Y"; + // South, East, West, North + static const char* xboxBtnNames[4] = { "Pad A", "Pad B", "Pad X", "Pad Y" }; + static const char* nintendoBtnNames[4] = { "Pad B", "Pad A", "Pad X", "Pad Y" }; + static const char* psBtnNames[4] = { "Pad Cross", "Pad Circle", "Pad Square", "Pad Triangle" }; + + unsigned btnIdx = key - K_JOY_BTN_SOUTH; + assert(btnIdx < 4); + switch( joy_gamepadLayout.GetInteger() ) { + default: + common->Warning( "joy_gamepadLayout has invalid value %d !\n", joy_gamepadLayout.GetInteger() ); + case 0: + return xboxBtnNames[btnIdx]; + case 1: + return nintendoBtnNames[btnIdx]; + case 2: + return psBtnNames[btnIdx]; } #endif // face button names for SDL2 } From bb568bc3dac4286f0b894b581094ac7a79dfce71 Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Wed, 17 Jan 2024 21:25:33 +0100 Subject: [PATCH 09/14] Gamepad layout autodetection (for SDL 2.0.12 and newer) turns out SDL 2.0.12 added SDL_GameControllerGetType() which tells you what kind of controller it is (xbox, playstation, nintendo, ..). Using this to implement an auto-mode for joy_gamepadLayout, when it's set to -1 (the new default). This should still build with older versions of SDL2 (but won't have that autodetection then). --- neo/sys/events.cpp | 102 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 11 deletions(-) diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index a7ccac4c..e37663dc 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -80,14 +80,20 @@ static idCVar in_nograb("in_nograb", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "prevents static idCVar in_grabKeyboard("in_grabKeyboard", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_BOOL, "if enabled, grabs all keyboard input if mouse is grabbed (so keyboard shortcuts from the OS like Alt-Tab or Windows Key won't work)"); -idCVar joy_gamepadLayout("joy_gamepadLayout", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_INTEGER, - "Button layout of gamepad - 0: XBox-style, 1: Nintendo-style, 2: Playstation-style", idCmdSystem::ArgCompletion_Integer<0, 2> ); +idCVar joy_gamepadLayout("joy_gamepadLayout", "-1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_INTEGER, + "Button layout of gamepad - -1: auto (needs SDL 2.0.12 or newer), 0: XBox-style, 1: Nintendo-style, 2: Playstation-style", idCmdSystem::ArgCompletion_Integer<-1, 2> ); // set in handleMouseGrab(), used in Sys_GetEvent() to decide what kind of internal mouse event to generate static bool in_relativeMouseMode = true; // set in Sys_GetEvent() on window focus gained/lost events static bool in_hasFocus = true; +static enum D3_Gamepad_Type { + D3_GAMEPAD_XINPUT, // XBox/XInput standard, the default + D3_GAMEPAD_NINTENDO, // nintendo-like (A/B and X/Y are switched) + D3_GAMEPAD_PLAYSTATION // PS-like (geometric symbols instead of A/B/X/Y) +} gamepadType = D3_GAMEPAD_XINPUT; + struct kbd_poll_t { int key; bool state; @@ -306,16 +312,23 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { static const char* nintendoBtnNames[4] = { "Pad B", "Pad A", "Pad X", "Pad Y" }; static const char* psBtnNames[4] = { "Pad Cross", "Pad Circle", "Pad Square", "Pad Triangle" }; + int layout = joy_gamepadLayout.GetInteger(); + if ( layout == -1 ) { + layout = gamepadType; + } + unsigned btnIdx = key - K_JOY_BTN_SOUTH; assert(btnIdx < 4); - switch( joy_gamepadLayout.GetInteger() ) { + + switch( layout ) { default: common->Warning( "joy_gamepadLayout has invalid value %d !\n", joy_gamepadLayout.GetInteger() ); - case 0: + // fall-through + case D3_GAMEPAD_XINPUT: return xboxBtnNames[btnIdx]; - case 1: + case D3_GAMEPAD_NINTENDO: return nintendoBtnNames[btnIdx]; - case 2: + case D3_GAMEPAD_PLAYSTATION: return psBtnNames[btnIdx]; } #endif // face button names for SDL2 @@ -626,6 +639,19 @@ static byte mapkey(SDL_Keycode key) { #if SDL_VERSION_ATLEAST(2, 0, 0) +#if ! SDL_VERSION_ATLEAST(2, 0, 14) +// Hack: to support newer SDL2 runtime versions than the one built against, +// define these controller buttons if needed +enum { + SDL_CONTROLLER_BUTTON_MISC1 = 15, /* Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button */ + SDL_CONTROLLER_BUTTON_PADDLE1, /* Xbox Elite paddle P1 */ + SDL_CONTROLLER_BUTTON_PADDLE2, /* Xbox Elite paddle P3 */ + SDL_CONTROLLER_BUTTON_PADDLE3, /* Xbox Elite paddle P2 */ + SDL_CONTROLLER_BUTTON_PADDLE4, /* Xbox Elite paddle P4 */ + SDL_CONTROLLER_BUTTON_TOUCHPAD, /* PS4/PS5 touchpad button */ +}; +#endif // ! SDL_VERSION_ATLEAST(2, 0, 14) + static sys_jEvents mapjoybutton(SDL_GameControllerButton button) { switch (button) { @@ -661,7 +687,7 @@ static sys_jEvents mapjoybutton(SDL_GameControllerButton button) { return J_DPAD_LEFT; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return J_DPAD_RIGHT; - // TODO: have the following always been supported in SDL2? + case SDL_CONTROLLER_BUTTON_MISC1: return J_BTN_MISC1; case SDL_CONTROLLER_BUTTON_PADDLE1: @@ -672,7 +698,6 @@ static sys_jEvents mapjoybutton(SDL_GameControllerButton button) { return J_BTN_LPADDLE1; case SDL_CONTROLLER_BUTTON_PADDLE4: return J_BTN_LPADDLE2; - default: common->Warning("unknown game controller button %u", button); break; @@ -701,6 +726,52 @@ static sys_jEvents mapjoyaxis(SDL_GameControllerAxis axis) { } return J_AXIS_MAX; } + +#if ! SDL_VERSION_ATLEAST(2, 24, 0) +// Hack: to support newer SDL2 runtime versions than the one compiled against, +// define some controller types that were added after 2.0.12 +enum { +#if ! SDL_VERSION_ATLEAST(2, 0, 14) + SDL_CONTROLLER_TYPE_PS5 = 7, +#endif + + // leaving out luna and stadia (from 2.0.16) + // and nvidia shield (from 2.24), they're similar enough to XBox/XInput + + // the following were added in 2.24 + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT = 11, + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT, + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR +}; +#endif // ! SDL_VERSION_ATLEAST(2, 24, 0) + +static void setGamepadType( SDL_GameController* gc ) +{ +#if SDL_VERSION_ATLEAST(2, 0, 12) + switch( SDL_GameControllerGetType( gc ) ) { + default: // the other controller like luna, stadia, whatever, have a very similar layout + case SDL_CONTROLLER_TYPE_UNKNOWN: + case SDL_CONTROLLER_TYPE_XBOX360: + case SDL_CONTROLLER_TYPE_XBOXONE: + gamepadType = D3_GAMEPAD_XINPUT; + break; + + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + gamepadType = D3_GAMEPAD_NINTENDO; + break; + + case SDL_CONTROLLER_TYPE_PS3: + case SDL_CONTROLLER_TYPE_PS4: + case SDL_CONTROLLER_TYPE_PS5: + gamepadType = D3_GAMEPAD_PLAYSTATION; + break; + } +#endif // SDL_VERSION_ATLEAST(2, 0, 12) +} + #endif // SDL2+ gamecontroller code static void PushConsoleEvent(const char *s) { @@ -769,6 +840,9 @@ void Sys_InitInput() { for( int i = 0; i < NumJoysticks; ++i ) { SDL_GameController* gc = SDL_GameControllerOpen( i ); + if ( gc != NULL ) { + setGamepadType( gc ); + } } #endif } @@ -1286,7 +1360,9 @@ sysEvent_t Sys_GetEvent() { if ( ev.cbutton.button == SDL_CONTROLLER_BUTTON_START ) { res.evValue = K_ESCAPE; return res; - } else if( ev.cbutton.button == SDL_CONTROLLER_BUTTON_A && D3_IN_interactiveIngameGuiActive && sessLocal.GetActiveMenu() == NULL ) { + } else if( ev.cbutton.button == SDL_CONTROLLER_BUTTON_A + && D3_IN_interactiveIngameGuiActive && sessLocal.GetActiveMenu() == NULL ) + { // ugly hack: currently an interactive ingame GUI (with a cursor) is active/focused // so pretend that the gamepads A (south) button is the left mouse button // so it can be used for "clicking".. @@ -1366,11 +1442,15 @@ sysEvent_t Sys_GetEvent() { break; case SDL_JOYDEVICEADDED: - SDL_GameControllerOpen( ev.jdevice.which ); + { + SDL_GameController* gc = SDL_GameControllerOpen( ev.jdevice.which ); + if ( gc != NULL ) { + setGamepadType( gc ); + } // TODO: hot swapping maybe. //lbOnControllerPlugIn(event.jdevice.which); break; - + } case SDL_JOYDEVICEREMOVED: // TODO: hot swapping maybe. //lbOnControllerUnPlug(event.jdevice.which); From 09c89206a462409983026dbed1d138200f4b6a47 Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Thu, 18 Jan 2024 03:11:23 +0100 Subject: [PATCH 10/14] Fix gamepad button names for nintendo gamepads oops, forgot to switch X and Y --- neo/sys/events.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index e37663dc..7a0eafde 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -309,7 +309,7 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { #else // SDL2 // South, East, West, North static const char* xboxBtnNames[4] = { "Pad A", "Pad B", "Pad X", "Pad Y" }; - static const char* nintendoBtnNames[4] = { "Pad B", "Pad A", "Pad X", "Pad Y" }; + static const char* nintendoBtnNames[4] = { "Pad B", "Pad A", "Pad Y", "Pad X" }; static const char* psBtnNames[4] = { "Pad Cross", "Pad Circle", "Pad Square", "Pad Triangle" }; int layout = joy_gamepadLayout.GetInteger(); From e7eb7d17fed697d963cb5d8d78b37e2bd44bc714 Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Thu, 18 Jan 2024 06:09:33 +0100 Subject: [PATCH 11/14] Improve Nintendo Pro Controller support and some general gamepad support improvements, like logging the detected gamepad name and type --- neo/framework/KeyInput.h | 2 +- neo/sys/events.cpp | 42 +++++++++++++++++++++++++--------------- neo/sys/sys_public.h | 2 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/neo/framework/KeyInput.h b/neo/framework/KeyInput.h index acf7527d..402649bd 100644 --- a/neo/framework/KeyInput.h +++ b/neo/framework/KeyInput.h @@ -150,7 +150,7 @@ typedef enum { K_JOY_BTN_BACK, K_JOY_BTN_GUIDE, // Note: this one should probably not be used? - K_JOY_BTN_START, + K_JOY_BTN_START, // hardcoded to generate Esc to open/close menu K_JOY_BTN_LSTICK, // press left stick K_JOY_BTN_RSTICK, // press right stick K_JOY_BTN_LSHOULDER, diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 7a0eafde..d1143a14 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -282,7 +282,7 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { #if SDL_VERSION_ATLEAST(2, 0, 0) // gamecontroller/gamepad not supported in SDL1 if (key >= K_FIRST_JOY && key <= K_LAST_JOY) { - if (key <= K_JOY_BTN_NORTH) { + if (key <= K_JOY_BTN_BACK) { #if SDL_VERSION_ATLEAST(3, 0, 0) // TODO: or use the SDL2 code and just set joy_gamepadLayout automatically based on SDL_GetGamepadType() ? SDL_GamepadButton gpbtn = SDL_GAMEPAD_BUTTON_SOUTH + (key - K_JOY_BTN_SOUTH); @@ -307,10 +307,11 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { } #else // SDL2 - // South, East, West, North - static const char* xboxBtnNames[4] = { "Pad A", "Pad B", "Pad X", "Pad Y" }; - static const char* nintendoBtnNames[4] = { "Pad B", "Pad A", "Pad Y", "Pad X" }; - static const char* psBtnNames[4] = { "Pad Cross", "Pad Circle", "Pad Square", "Pad Triangle" }; + // South, East, West, North Back + static const char* xboxBtnNames[5] = { "Pad A", "Pad B", "Pad X", "Pad Y", "Pad Back" }; + static const char* nintendoBtnNames[5] = { "Pad B", "Pad A", "Pad Y", "Pad X", "Pad -" }; + // TODO: on PS3 and older, back is "Select"; on PS4+ back it might be "share"? + static const char* psBtnNames[5] = { "Pad Cross", "Pad Circle", "Pad Square", "Pad Triangle", "Pad Select" }; int layout = joy_gamepadLayout.GetInteger(); if ( layout == -1 ) { @@ -318,7 +319,7 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { } unsigned btnIdx = key - K_JOY_BTN_SOUTH; - assert(btnIdx < 4); + assert(btnIdx < 5); switch( layout ) { default: @@ -336,14 +337,10 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { // the labels for the remaining keys are the same for SDL2 and SDL3 (and all controllers) switch(key) { - case K_JOY_BTN_BACK: - return "Pad Back"; + case K_JOY_BTN_GUIDE: // can't be used in dhewm3, because it opens steam on some systems + case K_JOY_BTN_START: // can't be used for bindings, because it's hardcoded to generate Esc + return NULL; - case K_JOY_BTN_GUIDE: - return NULL; // ??? - - case K_JOY_BTN_START: - return "Pad Start"; case K_JOY_BTN_LSTICK: return "Pad LStick"; case K_JOY_BTN_RSTICK: @@ -748,12 +745,14 @@ enum { static void setGamepadType( SDL_GameController* gc ) { #if SDL_VERSION_ATLEAST(2, 0, 12) + const char* typestr = NULL; switch( SDL_GameControllerGetType( gc ) ) { default: // the other controller like luna, stadia, whatever, have a very similar layout case SDL_CONTROLLER_TYPE_UNKNOWN: case SDL_CONTROLLER_TYPE_XBOX360: case SDL_CONTROLLER_TYPE_XBOXONE: gamepadType = D3_GAMEPAD_XINPUT; + typestr = "XBox-like"; break; case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: @@ -761,14 +760,19 @@ static void setGamepadType( SDL_GameController* gc ) case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: gamepadType = D3_GAMEPAD_NINTENDO; + typestr = "Nintendo-like"; break; case SDL_CONTROLLER_TYPE_PS3: case SDL_CONTROLLER_TYPE_PS4: case SDL_CONTROLLER_TYPE_PS5: gamepadType = D3_GAMEPAD_PLAYSTATION; + typestr = "Playstation-like"; break; } + + common->Printf( "Detected Gamepad %s as type %s\n", SDL_GameControllerName( gc ), typestr ); + #endif // SDL_VERSION_ATLEAST(2, 0, 12) } @@ -836,6 +840,11 @@ void Sys_InitInput() { memset( joyAxis, 0, sizeof( joyAxis ) ); #if SDL_VERSION_ATLEAST(2, 0, 0) // gamecontroller/gamepad not supported in SDL1 + // use button positions instead of button labels, + // Sys_GetLocalizedJoyKeyName() will do the translation + // (I think this also was the default before 2.0.12?) + SDL_SetHint("SDL_GAMECONTROLLER_USE_BUTTON_LABELS", "0"); + const int NumJoysticks = SDL_NumJoysticks(); for( int i = 0; i < NumJoysticks; ++i ) { @@ -1371,8 +1380,8 @@ sysEvent_t Sys_GetEvent() { return res; } - sys_jEvents jEvent = mapjoybutton( (SDL_GameControllerButton)ev.cbutton.button); - joystick_polls.Append(joystick_poll_t(jEvent, ev.cbutton.state == SDL_PRESSED ? 1 : 0) ); + sys_jEvents jEvent = mapjoybutton( (SDL_GameControllerButton)ev.cbutton.button ); + joystick_polls.Append( joystick_poll_t(jEvent, ev.cbutton.state == SDL_PRESSED ? 1 : 0) ); if ( ( jEvent >= J_ACTION_FIRST ) && ( jEvent <= J_ACTION_MAX ) ) { res.evValue = K_FIRST_JOY + ( jEvent - J_ACTION_FIRST ); @@ -1479,7 +1488,8 @@ sysEvent_t Sys_GetEvent() { } } - // first return joyaxis events, if gamepad is enabled and, and 16ms are over + // before returning res_none for "these were all events for now", + // first return joyaxis events, if gamepad is enabled and 16ms are over // (or we haven't returned the values for all axis yet) if ( in_useGamepad.GetBool() ) { static unsigned int lastMS = 0; diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index 9c32f9f9..b006a394 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -104,7 +104,7 @@ typedef enum { J_BTN_LPADDLE2, // Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) J_ACTION_MAX = J_BTN_LPADDLE2, - // leaving some space here for about 16 additional J_ACTIONs, if needed + // leaving some space here for about 12 additional J_ACTIONs, if needed J_AXIS_MIN = 32, J_AXIS_LEFT_X = J_AXIS_MIN + AXIS_LEFT_X, From 9e8d399257fe20c6b71fd4bc7000b724a75686b6 Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Thu, 18 Jan 2024 19:53:45 +0100 Subject: [PATCH 12/14] Further gamepad improvements - the gamepad button (or trigger) bound to attack (fire) now always acts like the left mouse button in menus - Display correct button name for "Back" button on Playstation-like gamepads, even depending on whether it's PS3-like ("Select") or PS4/5-like ("Share") - Log some more information about detected gamepads --- neo/framework/UsercmdGen.cpp | 2 +- neo/sys/events.cpp | 25 ++++++++++++--- neo/ui/UserInterface.cpp | 62 +++++++++++++++++++----------------- 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index a64a4984..2d33722e 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -93,7 +93,7 @@ typedef enum { UB_BUTTON6, UB_BUTTON7, - UB_ATTACK, + UB_ATTACK, // NOTE: this value (20) is hardcoded in idUserInterfaceLocal::HandleEvent() ! UB_SPEED, UB_ZOOM, UB_SHOWSCORES, diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index d1143a14..1d0b741b 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -81,7 +81,7 @@ static idCVar in_grabKeyboard("in_grabKeyboard", "0", CVAR_SYSTEM | CVAR_ARCHIVE "if enabled, grabs all keyboard input if mouse is grabbed (so keyboard shortcuts from the OS like Alt-Tab or Windows Key won't work)"); idCVar joy_gamepadLayout("joy_gamepadLayout", "-1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_INTEGER, - "Button layout of gamepad - -1: auto (needs SDL 2.0.12 or newer), 0: XBox-style, 1: Nintendo-style, 2: Playstation-style", idCmdSystem::ArgCompletion_Integer<-1, 2> ); + "Button layout of gamepad. -1: auto (needs SDL 2.0.12 or newer), 0: XBox-style, 1: Nintendo-style, 2: PS4/5-style, 3: PS2/3-style", idCmdSystem::ArgCompletion_Integer<-1, 3> ); // set in handleMouseGrab(), used in Sys_GetEvent() to decide what kind of internal mouse event to generate static bool in_relativeMouseMode = true; @@ -91,7 +91,8 @@ static bool in_hasFocus = true; static enum D3_Gamepad_Type { D3_GAMEPAD_XINPUT, // XBox/XInput standard, the default D3_GAMEPAD_NINTENDO, // nintendo-like (A/B and X/Y are switched) - D3_GAMEPAD_PLAYSTATION // PS-like (geometric symbols instead of A/B/X/Y) + D3_GAMEPAD_PLAYSTATION, // PS-like (geometric symbols instead of A/B/X/Y) + D3_GAMEPAD_PLAYSTATION_OLD // PS2/PS3-like: the back button is called "select" instead of "share" } gamepadType = D3_GAMEPAD_XINPUT; struct kbd_poll_t { @@ -310,8 +311,7 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { // South, East, West, North Back static const char* xboxBtnNames[5] = { "Pad A", "Pad B", "Pad X", "Pad Y", "Pad Back" }; static const char* nintendoBtnNames[5] = { "Pad B", "Pad A", "Pad Y", "Pad X", "Pad -" }; - // TODO: on PS3 and older, back is "Select"; on PS4+ back it might be "share"? - static const char* psBtnNames[5] = { "Pad Cross", "Pad Circle", "Pad Square", "Pad Triangle", "Pad Select" }; + static const char* psBtnNames[5] = { "Pad Cross", "Pad Circle", "Pad Square", "Pad Triangle", "Pad Share" }; int layout = joy_gamepadLayout.GetInteger(); if ( layout == -1 ) { @@ -329,6 +329,11 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { return xboxBtnNames[btnIdx]; case D3_GAMEPAD_NINTENDO: return nintendoBtnNames[btnIdx]; + case D3_GAMEPAD_PLAYSTATION_OLD: + if ( key == K_JOY_BTN_BACK ) + return "Pad Select"; + // the other button names are identical for PS2/3 and PS4/5 + // fall-through case D3_GAMEPAD_PLAYSTATION: return psBtnNames[btnIdx]; } @@ -764,6 +769,9 @@ static void setGamepadType( SDL_GameController* gc ) break; case SDL_CONTROLLER_TYPE_PS3: + gamepadType = D3_GAMEPAD_PLAYSTATION_OLD; + typestr = "Playstation2/3-like"; + break; case SDL_CONTROLLER_TYPE_PS4: case SDL_CONTROLLER_TYPE_PS5: gamepadType = D3_GAMEPAD_PLAYSTATION; @@ -772,6 +780,15 @@ static void setGamepadType( SDL_GameController* gc ) } common->Printf( "Detected Gamepad %s as type %s\n", SDL_GameControllerName( gc ), typestr ); + SDL_Joystick* joy = SDL_GameControllerGetJoystick( gc ); + SDL_JoystickGUID guid = SDL_JoystickGetGUID( joy ); + char guidstr[34] = {}; + SDL_JoystickGetGUIDString( guid, guidstr, sizeof(guidstr) ); + Uint16 vendor = SDL_GameControllerGetVendor( gc ); + Uint16 product = SDL_GameControllerGetProduct( gc ); + const char* joyname = SDL_JoystickName( joy ); + + common->Printf( " USB IDs: %.4hx:%.4hx Joystick Name: \"%s\" GUID: %s\n", vendor, product, joyname, guidstr ); #endif // SDL_VERSION_ATLEAST(2, 0, 12) } diff --git a/neo/ui/UserInterface.cpp b/neo/ui/UserInterface.cpp index 58379b87..bab78000 100644 --- a/neo/ui/UserInterface.cpp +++ b/neo/ui/UserInterface.cpp @@ -442,36 +442,38 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim { // map some gamepad buttons to SE_KEY events that the UI already knows how to use int key = 0; - switch(event->evValue) { - // emulate mouse buttons - case K_JOY_TRIGGER2: - // the right trigger is often used for shooting, so for ingame UIs - // it'll behave like mouseclick - do the same in menus - // fall-through - case K_JOY_BTN_SOUTH: // A on xbox controller - key = K_MOUSE1; - break; - case K_JOY_BTN_EAST: // B on xbox controller - key = K_MOUSE2; - break; - // emulate cursor keys (sometimes used for scrolling or selecting in a list) - case K_JOY_DPAD_UP: - key = K_UPARROW; - break; - case K_JOY_DPAD_DOWN: - key = K_DOWNARROW; - break; - case K_JOY_DPAD_LEFT: - key = K_LEFTARROW; - break; - case K_JOY_DPAD_RIGHT: - key = K_RIGHTARROW; - break; - // enter is useful after selecting something with cursor keys (or dpad) - // in a list, like selecting a savegame - I guess left trigger is suitable for that? - case K_JOY_TRIGGER1: - key = K_ENTER; - break; + if( idKeyInput::GetUsercmdAction( event->evValue ) == 20 /* UB_ATTACK*/ ) { + // if this button is bound to _attack (fire), treat it as left mouse button + key = K_MOUSE1; + } else { + switch(event->evValue) { + // emulate mouse buttons + case K_JOY_BTN_SOUTH: // A on xbox controller + key = K_MOUSE1; + break; + case K_JOY_BTN_EAST: // B on xbox controller + key = K_MOUSE2; + break; + // emulate cursor keys (sometimes used for scrolling or selecting in a list) + case K_JOY_DPAD_UP: + key = K_UPARROW; + break; + case K_JOY_DPAD_DOWN: + key = K_DOWNARROW; + break; + case K_JOY_DPAD_LEFT: + key = K_LEFTARROW; + break; + case K_JOY_DPAD_RIGHT: + key = K_RIGHTARROW; + break; + // enter is useful after selecting something with cursor keys (or dpad) + // in a list, like selecting a savegame - I guess left trigger is suitable for that? + // (right trigger is often used for shooting, which we use as K_MOUSE1 here) + case K_JOY_TRIGGER1: + key = K_ENTER; + break; + } } if (key != 0) { fakedEvent = *event; From 5b8e67762b85329730ac82003cf6911a7604c8b4 Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Mon, 22 Jan 2024 05:50:45 +0100 Subject: [PATCH 13/14] Also allow using "Pad Y" for leftclick in menus --- neo/framework/UsercmdGen.cpp | 3 ++- neo/sys/events.cpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index 2d33722e..f52fde16 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -431,7 +431,6 @@ idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | idCVar joy_triggerThreshold( "joy_triggerThreshold", "0.05", CVAR_FLOAT | CVAR_ARCHIVE, "how far the joystick triggers have to be pressed before they register as down" ); idCVar joy_deadZone( "joy_deadZone", "0.25", CVAR_FLOAT | CVAR_ARCHIVE, "specifies how large the dead-zone is on the joystick" ); -idCVar joy_range( "joy_range", "1.0", CVAR_FLOAT | CVAR_ARCHIVE, "allow full range to be mapped to a smaller offset" ); idCVar joy_gammaLook( "joy_gammaLook", "1", CVAR_INTEGER | CVAR_ARCHIVE, "use a log curve instead of a power curve for movement" ); idCVar joy_powerScale( "joy_powerScale", "2", CVAR_FLOAT | CVAR_ARCHIVE, "Raise joystick values to this power" ); idCVar joy_pitchSpeed( "joy_pitchSpeed", "130", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing up or down on the joystick", 60, 600 ); @@ -466,6 +465,8 @@ idUsercmdGenLocal::idUsercmdGenLocal( void ) { toggled_zoom.Clear(); toggled_run.on = in_alwaysRun.GetBool(); + lastLookValuePitch = lastLookValueYaw = 0.0f; + ClearAngles(); Clear(); } diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 1d0b741b..7da003bf 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -1386,12 +1386,12 @@ sysEvent_t Sys_GetEvent() { if ( ev.cbutton.button == SDL_CONTROLLER_BUTTON_START ) { res.evValue = K_ESCAPE; return res; - } else if( ev.cbutton.button == SDL_CONTROLLER_BUTTON_A + } else if( (ev.cbutton.button == SDL_CONTROLLER_BUTTON_A || ev.cbutton.button == SDL_CONTROLLER_BUTTON_Y) && D3_IN_interactiveIngameGuiActive && sessLocal.GetActiveMenu() == NULL ) { // ugly hack: currently an interactive ingame GUI (with a cursor) is active/focused - // so pretend that the gamepads A (south) button is the left mouse button - // so it can be used for "clicking".. + // so pretend that the gamepads A (south) or Y (north, used by D3BFG to click ingame GUIs) button + // is the left mouse button so it can be used for "clicking".. mouse_polls.Append( mouse_poll_t(M_ACTION1, res.evValue2) ); res.evValue = K_MOUSE1; return res; From d5f2dc4916069d83212c2daecd8bde25dac15e8b Mon Sep 17 00:00:00 2001 From: Daniel Gibson <metalcaedes@gmail.com> Date: Mon, 22 Jan 2024 05:52:16 +0100 Subject: [PATCH 14/14] Add and link Configuration.md and gamepad configs it documents dhewm3-specific configuration, esp. for gamepads (but also listing other CVars added in dhewm3) --- Changelog.md | 2 + Configuration.md | 143 ++++++++++++++++++++++++++++++++++++++++++ README.md | 6 ++ base/gamepad-d3xp.cfg | 21 +++++++ base/gamepad.cfg | 21 +++++++ 5 files changed, 193 insertions(+) create mode 100644 Configuration.md create mode 100755 base/gamepad-d3xp.cfg create mode 100755 base/gamepad.cfg diff --git a/Changelog.md b/Changelog.md index 09c6a259..a133bb0f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -23,6 +23,8 @@ Note: Numbers starting with a "#" like #330 refer to the bugreport with that num (0 = TGA, still the default, 1 = BMP, 2 = PNG, 3 = JPG). `r_screenshotJpgQuality` and `r_screenshotPngCompression` allow configuring how JPG/PNG are compressed. Thanks *eezstreet (Nick Whitlock)*! +* Support for gamepads (based on code from [Quadrilateral Cowboy](https://github.com/blendogames/quadrilateralcowboy), + but heavily expanded). See [Configuration.md](./Configuration.md#using-gamepads) for more information. 1.5.2 (2022-06-13) ------------------------------------------------------------------------ diff --git a/Configuration.md b/Configuration.md new file mode 100644 index 00000000..4747aa04 --- /dev/null +++ b/Configuration.md @@ -0,0 +1,143 @@ +# Configuration + +This document explains some dhewm3-specific configuration options. + +For general Doom3 configuration see for example [this list of CVars](https://modwiki.dhewm3.org/CVars_%28Doom_3%29) +and [this list of Console Commands](https://modwiki.dhewm3.org/Commands_%28Doom_3%29). + +**CVars** are set by entering `cvarName value` in the console, for example `com_showFPS 1`. +They can also be set as commandline arguments when starting dhewm3, for example `./dhewm3 +set r_fullscreen 0`. + +Just entering a CVar's name (without a value) will show its current value, its default value +and a short description of what it does. + +Starting dhewm3 with the commandline argument `-h` (for example `dhewm3.exe -h`) will show some +useful commandline arguments, for example how to tell dhewm3 where the game data can be found on your system. + +## The Console + +Like most id Software games from Quake 1 on, Doom3 has a console that allows entering commands +and setting Console Variables ("CVars"), often for advanced configuration or to aid development, +see also https://modwiki.dhewm3.org/Console. + +Unlike in original Doom3, in dhewm3 the console is always available (no need to set `com_allowconsole 1` +or similar), and **can be opened with the key combination `Shift + Esc`**. +The classic "console key" (the one between `Esc`, `Tab` and `1`) should also still work with +most keyboard layouts. However you can disable that, so you can bind that key like any other key +(for example to select the chainsaw), by setting `in_ignoreConsoleKey 1`. + +## Using Gamepads + +Starting with 1.5.3 (or the git commits preceding the one adding this document), dhewm3 supports +using gamepads, as long as they're supported by SDL2. +This includes XBox Controllers (and compatible ones), Playstation 3-5 controllers, +Nintendo Switch Pro Controllers, many thirdparty controllers for those consoles, and lots of other +gamepads for PC. + +Some notes: +* By default, no bindings for the gamepad exist, so you need to configure them once in the + Settings -> Controls menu. + - You need to bind *Turn Left*, *Turn Right*, *Look Up* and *Look Down* to the corresponding + directions of one stick to use it to look around or aim. + - Similarly, you need to bind *Forward*, *Backpedal*, *Move Left* and *Move Right* to the + corresponding directions of a stick to use it for player movement. +* The "Start" button ("+" on Nintendo gamepads, "Options" on Playstation 4/5 controllers) acts + like the Escape key, so it will **open/close the menu** and can not be bound. + The other buttons, axes and triggers can be bound to arbitrary actions in the Controls menu, + except for the Home button, which can't be used by dhewm3 at all (because it opens Steam when that is running). +* In **menus**, either stick will move the cursor, and the button you assign to *attack* (fire) acts + like the left mouse button, and so does the lower face button (A on XBox controllers, B on Nintendo + controllers, Cross on PS controllers) and the upper face button (Y on XBox, X on Nintendo, Triangle on PS). +* The layout of the controller (XBox-like, Nintendo-like, Playstation-like) should be automatically + detected and is used to display the button names according to the layout. If yours isn't detected + correctly, you can overwrite it with the `joy_gamepadLayout` CVar. +* Requires SDL2, layout detection requires SDL 2.0.12 or newer. +* Only one gamepad is supported or, more specifically, if multiple are connected, they all behave the same + and you can't bind their buttons/axes to different actions, and the auto-layout detection will use the + last gamepad it found to determine the layout. +* You can disable gamepads by setting the `in_useGamepad` CVar to `0`. +* There are several CVars to tweak the behavior: + - `joy_deadZone` Deadzone of the sticks, where `1.0` would be "stick moved fully in one direction". + This means that values below this register as 0. If you move or look around ingame even though + you're not moving a stick, try increasing the `joy_deadZone` value (default is `0.25`). + - `joy_triggerThreshold` Basically the deadzone for triggers. If your trigger triggers without + being touched, try increasing this value (default is `0.05`). + - `joy_gamepadLayout` overwrite automatically detected layout (XBox, Nintendo, PS), see above. + - `joy_pitchSpeed` How fast you look up/down (when the stick is at a maximum position) + - `joy_yawSpeed` Same for turning left/right + - `joy_invertLook` Inverts the controls for looking up/down (like in a flight simulator) + - `joy_gammaLook` If set to `1`, use a log curve instead of a power curve for looking around, + affects how fast you turn (or look up/down) when the stick is between center and maximum. + - `joy_powerScale` If `joy_gammaLook` is `0`, this is the exponent used for the power curve. + - `joy_dampenLook` if enabled (`1`), somehow reduced the speed of looking around, depending on + `joy_deltaPerMSLook`. + +I created gamepad configs for the base game and d3xp (Resurrection of Evil), based on the standard bindings +of Doom3 BFG, see gamepad.cfg and gamepad-d3xp.cfg in the [base/ directory](./base/). +Put them in your base/ folder, open the console and enter `exec gamepad.cfg` for the base game, +or `exec gamepad-d3xp.cfg` for Resurrection of Evil (probably also works for Doom3: Lost Mission). + +**_Note_** that in *configs* (or `bind` commands in the console), the following names are used for +gamepad buttons, sticks and triggers: + +<details><summary>Click to see the list of gamepad button/stick/trigger names</summary> + +* "JOY_BTN_SOUTH" - `A` button on XBox-style gamepads, `B` on Nintendo-style gamepads or `Cross` on Playstation-style gamepads +* "JOY_BTN_EAST" - `B` (XBox), `A` (Nintendo), `Circle` (Playstation) +* "JOY_BTN_WEST" - `X` (XBox), `Y` (Nintendo), `Square` (Playstation) +* "JOY_BTN_NORTH" - `Y` (XBox), `X` (Nintendo), `Triangle` (Playstation) +* "JOY_BTN_BACK" - The `Back` button, aka `-` (Nintendo) or `Select`/`Share` (Playstation) +* "JOY_BTN_LSTICK" - Pressing the Left Stick down +* "JOY_BTN_RSTICK" - Pressing the Right Stick down +* "JOY_BTN_LSHOULDER" - Left Shoulder Button +* "JOY_BTN_RSHOULDER" - Right Shoulder button +* "JOY_DPAD_UP" - DPad Up +* "JOY_DPAD_DOWN" - DPad Down +* "JOY_DPAD_LEFT" - DPad Left +* "JOY_DPAD_RIGHT" - DPad Right +* "JOY_BTN_MISC1" - misc. additional button, like Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button +* "JOY_BTN_RPADDLE1" - Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) +* "JOY_BTN_LPADDLE1" - Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) +* "JOY_BTN_RPADDLE2" - Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) +* "JOY_BTN_LPADDLE2" - Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4 +* "JOY_STICK1_UP" - Moving Left Stick up +* "JOY_STICK1_DOWN" - Moving Left Stick down +* "JOY_STICK1_LEFT" - Moving Left Stick to the left +* "JOY_STICK1_RIGHT" - Moving Left Stick to the right +* "JOY_STICK2_UP" - Moving Right Stick up +* "JOY_STICK2_DOWN" - Moving Right Stick down +* "JOY_STICK2_LEFT" - Moving Right Stick to the left +* "JOY_STICK2_RIGHT" - Moving Right Stick to the right +* "JOY_TRIGGER1" - Pressing the Left Trigger +* "JOY_TRIGGER2" - Pressing the Right Trigger + +</details> + +## Screenshot configuration + +Doom3 always supported taking screenshots, but dhewm3 (from 1.5.3 on) supports using different +formats than TGA. +This can be configured with the following CVars: + +- `r_screenshotFormat` What format screenshots should be in: + `0` = TGA (default), `1` = BMP, `2` = PNG, `3` = JPG +- `r_screenshotJpgQuality` Quality when using JPG screenshots (`0` - `100`) +- `r_screenshotPngCompression` Compression level when using PNG screenshots (`0` - `9`) + +## CVars added in dhewm3 that I'm currently too lazy to document more thoroughly + +- g_hitEffect + +- in_nograb +- in_grabKeyboard + +- in_tty +- in_kbd + +- r_fullscreenDesktop +- r_fillWindowAlphaChan + +- r_useCarmacksReverse +- r_useStencilOpSeparate + +- s_alReverbGain diff --git a/README.md b/README.md index ce79d2a8..1df3653f 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Compared to the original _DOOM 3_, the changes of _dhewm 3_ worth mentioning are - SDL for low-level OS support, OpenGL and input handling - OpenAL for audio output, all OS-specific audio backends are gone - OpenAL EFX for EAX reverb effects (read: EAX-like sound effects on all platforms/hardware) +- Gamepad support - Better support for widescreen (and arbitrary display resolutions) - A portable build system based on CMake - (Cross-)compilation with MinGW-w64 @@ -55,6 +56,11 @@ https://store.steampowered.com/app/208200/DOOM_3/ See https://dhewm3.org/#how-to-install for game data installation instructions. +## Configuration + +See [Configuration.md](./Configuration.md) for dhewm3-specific configuration, especially for +using gamepads. + ## Compiling The build system is based on CMake: http://cmake.org/ diff --git a/base/gamepad-d3xp.cfg b/base/gamepad-d3xp.cfg new file mode 100755 index 00000000..3ced364b --- /dev/null +++ b/base/gamepad-d3xp.cfg @@ -0,0 +1,21 @@ +bind "JOY_BTN_SOUTH" "_moveUp" +bind "JOY_BTN_WEST" "_impulse13" +bind "JOY_BTN_BACK" "_impulse19" +bind "JOY_BTN_LSTICK" "_speed" +bind "JOY_BTN_RSTICK" "_moveDown" +bind "JOY_BTN_LSHOULDER" "_impulse15" +bind "JOY_BTN_RSHOULDER" "_impulse14" +bind "JOY_DPAD_UP" "_impulse8" +bind "JOY_DPAD_DOWN" "_impulse11" +bind "JOY_DPAD_LEFT" "_impulse12" +bind "JOY_DPAD_RIGHT" "_impulse1" +bind "JOY_STICK1_UP" "_forward" +bind "JOY_STICK1_DOWN" "_back" +bind "JOY_STICK1_LEFT" "_moveLeft" +bind "JOY_STICK1_RIGHT" "_moveRight" +bind "JOY_STICK2_UP" "_lookUp" +bind "JOY_STICK2_DOWN" "_lookDown" +bind "JOY_STICK2_LEFT" "_left" +bind "JOY_STICK2_RIGHT" "_right" +bind "JOY_TRIGGER1" "_impulse0" +bind "JOY_TRIGGER2" "_attack" \ No newline at end of file diff --git a/base/gamepad.cfg b/base/gamepad.cfg new file mode 100755 index 00000000..99c470df --- /dev/null +++ b/base/gamepad.cfg @@ -0,0 +1,21 @@ +bind "JOY_BTN_SOUTH" "_moveUp" +bind "JOY_BTN_WEST" "_impulse13" +bind "JOY_BTN_BACK" "_impulse19" +bind "JOY_BTN_LSTICK" "_speed" +bind "JOY_BTN_RSTICK" "_moveDown" +bind "JOY_BTN_LSHOULDER" "_impulse15" +bind "JOY_BTN_RSHOULDER" "_impulse14" +bind "JOY_DPAD_UP" "_impulse5" +bind "JOY_DPAD_DOWN" "_impulse8" +bind "JOY_DPAD_LEFT" "_impulse9" +bind "JOY_DPAD_RIGHT" "_impulse0" +bind "JOY_STICK1_UP" "_forward" +bind "JOY_STICK1_DOWN" "_back" +bind "JOY_STICK1_LEFT" "_moveLeft" +bind "JOY_STICK1_RIGHT" "_moveRight" +bind "JOY_STICK2_UP" "_lookUp" +bind "JOY_STICK2_DOWN" "_lookDown" +bind "JOY_STICK2_LEFT" "_left" +bind "JOY_STICK2_RIGHT" "_right" +bind "JOY_TRIGGER1" "_impulse11" +bind "JOY_TRIGGER2" "_attack" \ No newline at end of file