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) 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;