//========= Copyright (c) 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= // in_win.c -- windows 95 mouse and joystick code // 02/21/97 JCB Added extended DirectInput code to support external controllers. #include "input_mouse.h" #if SUPPORT_GOLDSOURCE_INPUT #include "hud.h" #include "cl_util.h" #include "camera.h" #include "kbutton.h" #include "cvardef.h" #include "const.h" #include "camera.h" #include "in_defs.h" #include "keydefs.h" #include "view.h" #if !_WIN32 #define USE_SDL2 1 #endif #if USE_SDL2 #define ARRAYSIZE(p) ( sizeof(p) /sizeof(p[0]) ) #include #include #include int (*pfnSDL_SetRelativeMouseMode)(SDL_bool); Uint32 (*pfnSDL_GetRelativeMouseState)(int* x, int* y); int (*pfnSDL_NumJoysticks)(void); SDL_bool (*pfnSDL_IsGameController)(int); SDL_GameController* (*pfnSDL_GameControllerOpen)(int); Sint16 (*pfnSDL_GameControllerGetAxis)(SDL_GameController*, SDL_GameControllerAxis); Uint8 (*pfnSDL_GameControllerGetButton)(SDL_GameController*, SDL_GameControllerButton); void (*pfnSDL_JoystickUpdate)(void); const char* (*pfnSDL_GameControllerName)(SDL_GameController*); int safe_pfnSDL_SetRelativeMouseMode(SDL_bool mode) { if (pfnSDL_SetRelativeMouseMode) return pfnSDL_SetRelativeMouseMode(mode); return -1; } Uint32 safe_pfnSDL_GetRelativeMouseState(int* x, int* y) { if (pfnSDL_GetRelativeMouseState) return pfnSDL_GetRelativeMouseState(x, y); return 0; } int safe_pfnSDL_NumJoysticks() { if (pfnSDL_NumJoysticks) return pfnSDL_NumJoysticks(); return -1; } SDL_bool safe_pfnSDL_IsGameController(int joystick_index) { if (pfnSDL_IsGameController) return pfnSDL_IsGameController(joystick_index); return SDL_FALSE; } SDL_GameController* safe_pfnSDL_GameControllerOpen(int joystick_index) { if (pfnSDL_GameControllerOpen) return pfnSDL_GameControllerOpen(joystick_index); return NULL; } Sint16 safe_pfnSDL_GameControllerGetAxis(SDL_GameController* gamecontroller, SDL_GameControllerAxis axis) { if (pfnSDL_GameControllerGetAxis) return pfnSDL_GameControllerGetAxis(gamecontroller, axis); return 0; } Uint8 safe_pfnSDL_GameControllerGetButton(SDL_GameController* gamecontroller, SDL_GameControllerButton button) { if (pfnSDL_GameControllerGetButton) return pfnSDL_GameControllerGetButton(gamecontroller, button); return 0; } void safe_pfnSDL_JoystickUpdate() { if (pfnSDL_JoystickUpdate) pfnSDL_JoystickUpdate(); } const char* safe_pfnSDL_GameControllerName(SDL_GameController* gamecontroller) { if (pfnSDL_GameControllerName) return pfnSDL_GameControllerName(gamecontroller); return NULL; } struct SDLFunction { void** ppfnFunc; const char* name; }; static SDLFunction sdlFunctions[] = { {(void**)&pfnSDL_SetRelativeMouseMode, "SDL_SetRelativeMouseMode"}, {(void**)&pfnSDL_GetRelativeMouseState, "SDL_GetRelativeMouseState"}, {(void**)&pfnSDL_NumJoysticks, "SDL_NumJoysticks"}, {(void**)&pfnSDL_IsGameController, "SDL_IsGameController"}, {(void**)&pfnSDL_GameControllerOpen, "SDL_GameControllerOpen"}, {(void**)&pfnSDL_GameControllerGetAxis, "SDL_GameControllerGetAxis"}, {(void**)&pfnSDL_GameControllerGetButton, "SDL_GameControllerGetButton"}, {(void**)&pfnSDL_JoystickUpdate, "SDL_JoystickUpdate"}, {(void**)&pfnSDL_GameControllerName, "SDL_GameControllerName"} }; #endif #if _WIN32 #include #else typedef unsigned int DWORD; #endif #define MOUSE_BUTTON_COUNT 5 extern float mouse_pos_extern[2]; // use IN_SetVisibleMouse to set: extern int iVisibleMouse; extern cl_enginefunc_t gEngfuncs; extern int iMouseInUse; extern kbutton_t in_strafe; extern kbutton_t in_mlook; extern kbutton_t in_speed; extern kbutton_t in_jlook; extern cvar_t *m_pitch; extern cvar_t *m_yaw; extern cvar_t *m_forward; extern cvar_t *m_side; extern cvar_t *lookstrafe; extern cvar_t *lookspring; extern cvar_t *cl_pitchdown; extern cvar_t *cl_pitchup; extern cvar_t *cl_yawspeed; extern cvar_t *cl_sidespeed; extern cvar_t *cl_forwardspeed; extern cvar_t *cl_pitchspeed; extern cvar_t *cl_movespeedkey; #if _WIN32 static cvar_t* m_rawinput = NULL; static double s_flRawInputUpdateTime = 0.0f; static bool m_bRawInput = false; static bool m_bMouseThread = false; bool isMouseRelative = false; #endif #if _WIN32 #include "progdefs.h" #endif int CL_IsDead( void ); extern Vector dead_viewangles; void V_StopPitchDrift( void ) { } // mouse variables cvar_t *m_filter; extern cvar_t *sensitivity; // Custom mouse acceleration (0 disable, 1 to enable, 2 enable with separate yaw/pitch rescale) static cvar_t *m_customaccel; //Formula: mousesensitivity = ( rawmousedelta^m_customaccel_exponent ) * m_customaccel_scale + sensitivity // If mode is 2, then x and y sensitivity are scaled by m_pitch and m_yaw respectively. // Custom mouse acceleration value. static cvar_t *m_customaccel_scale; //Max mouse move scale factor, 0 for no limit static cvar_t *m_customaccel_max; //Mouse move is raised to this power before being scaled by scale factor static cvar_t *m_customaccel_exponent; #if _WIN32 // if threaded mouse is enabled then the time to sleep between polls static cvar_t *m_mousethread_sleep; #endif float mouse_x, mouse_y; static int restore_spi; static int originalmouseparms[3], newmouseparms[3] = {0, 0, 1}; static int mouseactive = 0; static int mouseparmsvalid; static int mouseshowtoggle = 1; // joystick defines and variables // where should defines be moved? #define JOY_ABSOLUTE_AXIS 0x00000000 // control like a joystick #define JOY_RELATIVE_AXIS 0x00000010 // control like a mouse, spinner, trackball #define JOY_MAX_AXES 6 // X, Y, Z, R, U, V #define JOY_AXIS_X 0 #define JOY_AXIS_Y 1 #define JOY_AXIS_Z 2 #define JOY_AXIS_R 3 #define JOY_AXIS_U 4 #define JOY_AXIS_V 5 enum _ControlList { AxisNada = 0, AxisForward, AxisLook, AxisSide, AxisTurn }; #if !USE_SDL2 && _WIN32 DWORD dwAxisFlags[JOY_MAX_AXES] = { JOY_RETURNX, JOY_RETURNY, JOY_RETURNZ, JOY_RETURNR, JOY_RETURNU, JOY_RETURNV }; #endif DWORD dwAxisMap[ JOY_MAX_AXES ]; DWORD dwControlMap[ JOY_MAX_AXES ]; #if USE_SDL2 int pdwRawValue[ JOY_MAX_AXES ]; #elif _WIN32 PDWORD pdwRawValue[ JOY_MAX_AXES ]; #endif DWORD joy_oldbuttonstate, joy_oldpovstate; int joy_id; DWORD joy_numbuttons; #if USE_SDL2 SDL_GameController *s_pJoystick = NULL; #elif _WIN32 DWORD joy_flags; static JOYINFOEX ji; #endif // none of these cvars are saved over a session // this means that advanced controller configuration needs to be executed // each time. this avoids any problems with getting back to a default usage // or when changing from one controller to another. this way at least something // works. extern cvar_t *in_joystick; cvar_t *joy_name; cvar_t *joy_advanced; cvar_t *joy_advaxisx; cvar_t *joy_advaxisy; cvar_t *joy_advaxisz; cvar_t *joy_advaxisr; cvar_t *joy_advaxisu; cvar_t *joy_advaxisv; cvar_t *joy_forwardthreshold; cvar_t *joy_sidethreshold; cvar_t *joy_pitchthreshold; cvar_t *joy_yawthreshold; cvar_t *joy_forwardsensitivity; cvar_t *joy_sidesensitivity; cvar_t *joy_pitchsensitivity; cvar_t *joy_yawsensitivity; cvar_t *joy_wwhack1; cvar_t *joy_wwhack2; int joy_avail, joy_advancedinit, joy_haspov; #if _WIN32 unsigned int s_hMouseThreadId = 0; HANDLE s_hMouseThread = 0; HANDLE s_hMouseQuitEvent = 0; HANDLE s_hMouseThreadActiveLock = 0; #endif /* =========== Force_CenterView_f =========== */ void Force_CenterView_f (void) { vec3_t viewangles; if (!iMouseInUse) { gEngfuncs.GetViewAngles( (float *)viewangles ); viewangles[PITCH] = 0; gEngfuncs.SetViewAngles( (float *)viewangles ); } } #if _WIN32 LONG mouseThreadActive = 0; LONG mouseThreadCenterX = 0; LONG mouseThreadCenterY = 0; LONG mouseThreadDeltaX = 0; LONG mouseThreadDeltaY = 0; LONG mouseThreadSleep = 0; bool MouseThread_ActiveLock_Enter( void ) { if(!m_bMouseThread) return true; return WAIT_OBJECT_0 == WaitForSingleObject( s_hMouseThreadActiveLock, INFINITE); } void MouseThread_ActiveLock_Exit( void ) { if(!m_bMouseThread) return; SetEvent( s_hMouseThreadActiveLock ); } unsigned __stdcall MouseThread_Function( void * pArg ) { while ( true ) { DWORD sleepVal = (DWORD)InterlockedExchangeAdd(&mouseThreadSleep, 0); if(0 > sleepVal) sleepVal = 0; else if(1000 < sleepVal) sleepVal = 1000; if(WAIT_OBJECT_0 == WaitForSingleObject( s_hMouseQuitEvent, sleepVal)) { break; } if( MouseThread_ActiveLock_Enter() ) { if ( InterlockedExchangeAdd(&mouseThreadActive, 0) ) { POINT mouse_pos; POINT center_pos; center_pos.x = InterlockedExchangeAdd(&mouseThreadCenterX, 0); center_pos.y = InterlockedExchangeAdd(&mouseThreadCenterY, 0); GetCursorPos(&mouse_pos); mouse_pos.x -= center_pos.x; mouse_pos.y -= center_pos.y; if(mouse_pos.x || mouse_pos.y) SetCursorPos( center_pos.x, center_pos.y ); InterlockedExchangeAdd(&mouseThreadDeltaX, mouse_pos.x); InterlockedExchangeAdd(&mouseThreadDeltaY, mouse_pos.y); } MouseThread_ActiveLock_Exit(); } } return 0; } /// Updates mouseThreadActive using the global variables mouseactive, iVisibleMouse and m_bRawInput. Should be called after any of these is changed. /// Has to be interlocked manually by programmer! Use MouseThread_ActiveLock_Enter and MouseThread_ActiveLock_Exit. void UpdateMouseThreadActive(void) { InterlockedExchange(&mouseThreadActive, mouseactive && !iVisibleMouse && !m_bRawInput); } #endif void IN_SetMouseMode(bool enable) { static bool currentMouseMode = false; if(enable == currentMouseMode) return; if(enable) { #if _WIN32 if (mouseparmsvalid) restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0); m_bRawInput = m_rawinput && m_rawinput->value != 0; if(m_bRawInput) { #if USE_SDL2 safe_pfnSDL_SetRelativeMouseMode(SDL_TRUE); #endif isMouseRelative = true; } #else safe_pfnSDL_SetRelativeMouseMode(SDL_TRUE); #endif currentMouseMode = true; } else { #if _WIN32 if(isMouseRelative) { #if USE_SDL2 safe_pfnSDL_SetRelativeMouseMode(SDL_FALSE); #endif isMouseRelative = false; } if (restore_spi) SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); #else safe_pfnSDL_SetRelativeMouseMode(SDL_FALSE); #endif currentMouseMode = false; } } void IN_SetVisibleMouse(bool visible) { #if _WIN32 bool lockEntered = MouseThread_ActiveLock_Enter(); #endif iVisibleMouse = visible; IN_SetMouseMode(!visible); #if _WIN32 UpdateMouseThreadActive(); if(lockEntered) MouseThread_ActiveLock_Exit(); #endif } void IN_ResetMouse( void ); /* =========== IN_ActivateMouse =========== */ void GoldSourceInput::IN_ActivateMouse (void) { if (mouseinitialized) { #if _WIN32 bool lockEntered = MouseThread_ActiveLock_Enter(); #endif IN_SetMouseMode(true); mouseactive = 1; #if _WIN32 UpdateMouseThreadActive(); if(lockEntered) MouseThread_ActiveLock_Exit(); #endif // now is a good time to reset mouse positon: IN_ResetMouse(); } } /* =========== IN_DeactivateMouse =========== */ void GoldSourceInput::IN_DeactivateMouse (void) { if (mouseinitialized) { #if _WIN32 bool lockEntered = MouseThread_ActiveLock_Enter(); #endif IN_SetMouseMode(false); mouseactive = 0; #if _WIN32 UpdateMouseThreadActive(); if(lockEntered) MouseThread_ActiveLock_Exit(); #endif } } /* =========== IN_StartupMouse =========== */ void GoldSourceInput::IN_StartupMouse (void) { if ( gEngfuncs.CheckParm ("-nomouse", NULL ) ) return; mouseinitialized = 1; #if _WIN32 mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0); if (mouseparmsvalid) { if ( gEngfuncs.CheckParm ("-noforcemspd", NULL ) ) newmouseparms[2] = originalmouseparms[2]; if ( gEngfuncs.CheckParm ("-noforcemaccel", NULL ) ) { newmouseparms[0] = originalmouseparms[0]; newmouseparms[1] = originalmouseparms[1]; } if ( gEngfuncs.CheckParm ("-noforcemparms", NULL ) ) { newmouseparms[0] = originalmouseparms[0]; newmouseparms[1] = originalmouseparms[1]; newmouseparms[2] = originalmouseparms[2]; } } #endif mouse_buttons = MOUSE_BUTTON_COUNT; } /* =========== IN_Shutdown =========== */ void GoldSourceInput::IN_Shutdown (void) { IN_DeactivateMouse (); #if _WIN32 if ( s_hMouseQuitEvent ) { SetEvent( s_hMouseQuitEvent ); } if ( s_hMouseThread ) { if(WAIT_OBJECT_0 != WaitForSingleObject( s_hMouseThread, 5000 )) { TerminateThread( s_hMouseThread, 0 ); } CloseHandle( s_hMouseThread ); s_hMouseThread = (HANDLE)0; } if ( s_hMouseQuitEvent ) { CloseHandle( s_hMouseQuitEvent ); s_hMouseQuitEvent = (HANDLE)0; } if( s_hMouseThreadActiveLock ) { CloseHandle( s_hMouseThreadActiveLock ); s_hMouseThreadActiveLock = (HANDLE)0; } #endif #if USE_SDL2 for (int j=0; jvalue; // Using special accleration values if ( m_customaccel->value != 0 ) { float raw_mouse_movement_distance = sqrt( mx * mx + my * my ); float acceleration_scale = m_customaccel_scale->value; float accelerated_sensitivity_max = m_customaccel_max->value; float accelerated_sensitivity_exponent = m_customaccel_exponent->value; float accelerated_sensitivity = ( (float)pow( raw_mouse_movement_distance, accelerated_sensitivity_exponent ) * acceleration_scale + mouse_senstivity ); if ( accelerated_sensitivity_max > 0.0001f && accelerated_sensitivity > accelerated_sensitivity_max ) { accelerated_sensitivity = accelerated_sensitivity_max; } *x *= accelerated_sensitivity; *y *= accelerated_sensitivity; // Further re-scale by yaw and pitch magnitude if user requests alternate mode 2 // This means that they will need to up their value for m_customaccel_scale greatly (>40x) since m_pitch/yaw default // to 0.022 if ( m_customaccel->value == 2 ) { *x *= m_yaw->value; *y *= m_pitch->value; } } else { // Just apply the default *x *= mouse_senstivity; *y *= mouse_senstivity; } } void GoldSourceInput::IN_GetMouseDelta( int *pOutX, int *pOutY) { bool active = mouseactive && !iVisibleMouse; int mx, my; if(active) { int deltaX, deltaY; #if _WIN32 if ( !m_bRawInput ) { if ( m_bMouseThread ) { // update mouseThreadSleep: InterlockedExchange(&mouseThreadSleep, (LONG)m_mousethread_sleep->value); bool lockEntered = MouseThread_ActiveLock_Enter(); current_pos.x = InterlockedExchange( &mouseThreadDeltaX, 0 ); current_pos.y = InterlockedExchange( &mouseThreadDeltaY, 0 ); if(lockEntered) MouseThread_ActiveLock_Exit(); } else { GetCursorPos (¤t_pos); } } else #endif { #if USE_SDL2 safe_pfnSDL_GetRelativeMouseState( &deltaX, &deltaY ); current_pos.x = deltaX; current_pos.y = deltaY; #else GetCursorPos (¤t_pos); deltaX = current_pos.x - gEngfuncs.GetWindowCenterX(); deltaY = current_pos.y - gEngfuncs.GetWindowCenterY(); #endif } #if _WIN32 if ( !m_bRawInput ) { if ( m_bMouseThread ) { mx = current_pos.x; my = current_pos.y; } else { mx = current_pos.x - gEngfuncs.GetWindowCenterX() + mx_accum; my = current_pos.y - gEngfuncs.GetWindowCenterY() + my_accum; } } else #endif { mx = deltaX + mx_accum; my = deltaY + my_accum; } mx_accum = 0; my_accum = 0; // reset mouse position if required, so there is room to move: #if _WIN32 // do not reset if mousethread would do it: if ( m_bRawInput || !m_bMouseThread ) #else if(true) #endif IN_ResetMouse(); #if _WIN32 // update m_bRawInput occasionally: const float currentTime = gEngfuncs.GetClientTime(); if ( currentTime - s_flRawInputUpdateTime > 1.0f || s_flRawInputUpdateTime == 0.0f ) { s_flRawInputUpdateTime = currentTime; bool lockEntered = MouseThread_ActiveLock_Enter(); m_bRawInput = m_rawinput && m_rawinput->value != 0; if(m_bRawInput && !isMouseRelative) { #if USE_SDL2 safe_pfnSDL_SetRelativeMouseMode(SDL_TRUE); #endif isMouseRelative = true; } else if(!m_bRawInput && isMouseRelative) { #if USE_SDL2 safe_pfnSDL_SetRelativeMouseMode(SDL_FALSE); #endif isMouseRelative = false; } UpdateMouseThreadActive(); if(lockEntered) MouseThread_ActiveLock_Exit(); } #endif } else { mx = my = 0; } if(pOutX) *pOutX = mx; if(pOutY) *pOutY = my; } /* =========== IN_MouseMove =========== */ void GoldSourceInput::IN_MouseMove ( float frametime, usercmd_t *cmd) { int mx, my; vec3_t viewangles; if( gHUD.m_iIntermission ) return; // we can't move during intermission if( CL_IsDead() ) { viewangles = dead_viewangles; // HACKHACK: see below } else { gEngfuncs.GetViewAngles( viewangles ); } if ( in_mlook.state & 1) { V_StopPitchDrift (); } //jjb - this disbles normal mouse control if the user is trying to // move the camera, or if the mouse cursor is visible or if we're in intermission if ( !iMouseInUse && !gHUD.m_iIntermission ) { IN_GetMouseDelta( &mx, &my ); if (m_filter && m_filter->value) { mouse_x = (mx + old_mouse_x) * 0.5; mouse_y = (my + old_mouse_y) * 0.5; } else { mouse_x = mx; mouse_y = my; } old_mouse_x = mx; old_mouse_y = my; // Apply custom mouse scaling/acceleration IN_ScaleMouse( &mouse_x, &mouse_y ); // add mouse X/Y movement to cmd /* if ( (in_strafe.state & 1) || (lookstrafe->value && (in_mlook.state & 1) )) cmd->sidemove += m_side->value * mouse_x; else viewangles[YAW] -= m_yaw->value * mouse_x; */ // Get mouse pos and normalize it to be sent to view.cpp POINT mouse_pos; // GetCursorPos( &mouse_pos ); IN_GetMousePos( (int*)&mouse_pos.x, (int*)&mouse_pos.y ); mouse_pos_extern[0] = mouse_pos.x; mouse_pos_extern[1] = mouse_pos.y; mouse_pos_extern[0] /= ScreenWidth / 2; mouse_pos_extern[1] /= ScreenHeight / 2; mouse_pos_extern[0] -= 1; mouse_pos_extern[1] -= 1; viewangles[YAW] = -atan2( double( mouse_pos_extern[1] * ( M_PI / 180 ) ), double( mouse_pos_extern[0] * ( M_PI / 180 ) ) ) * ( 180 / M_PI ) - 90; // I don't know why I have to negate the value and subtract 90, it just works that way. mouse_pos_extern[0] *= 27; mouse_pos_extern[1] *= 27; if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) { /* viewangles[PITCH] += m_pitch->value * mouse_y; if (viewangles[PITCH] > cl_pitchdown->value) viewangles[PITCH] = cl_pitchdown->value; if (viewangles[PITCH] < -cl_pitchup->value) viewangles[PITCH] = -cl_pitchup->value; */ } else { if ((in_strafe.state & 1) && gEngfuncs.IsNoClipping() ) { cmd->upmove -= m_forward->value * mouse_y; } else { cmd->forwardmove -= m_forward->value * mouse_y; } } } // HACKHACK: change viewangles directly in viewcode, // so viewangles when player is dead will not be changed on server if( !CL_IsDead() ) { gEngfuncs.SetViewAngles( viewangles ); } dead_viewangles = viewangles; // keep them actual /* //#define TRACE_TEST 1 #if TRACE_TEST { int mx, my; void V_Move( int mx, int my ); IN_GetMousePos( &mx, &my ); V_Move( mx, my ); } #endif */ } /* =========== IN_Accumulate =========== */ void GoldSourceInput::IN_Accumulate (void) { //only accumulate mouse if we are not moving the camera with the mouse if ( !iMouseInUse && !iVisibleMouse) { if (mouseactive) { #if _WIN32 if ( !m_bRawInput ) { if ( !m_bMouseThread ) { GetCursorPos (¤t_pos); mx_accum += current_pos.x - gEngfuncs.GetWindowCenterX(); my_accum += current_pos.y - gEngfuncs.GetWindowCenterY(); } } else #endif { #if USE_SDL2 int deltaX, deltaY; safe_pfnSDL_GetRelativeMouseState( &deltaX, &deltaY ); mx_accum += deltaX; my_accum += deltaY; #else GetCursorPos (¤t_pos); mx_accum += current_pos.x - gEngfuncs.GetWindowCenterX(); my_accum += current_pos.y - gEngfuncs.GetWindowCenterY(); #endif } // force the mouse to the center, so there's room to move #if _WIN32 // do not reset if mousethread would do it: if ( m_bRawInput || !m_bMouseThread ) #else if(true) #endif IN_ResetMouse(); } } } /* =================== IN_ClearStates =================== */ void GoldSourceInput::IN_ClearStates (void) { if ( !mouseactive ) return; mx_accum = 0; my_accum = 0; mouse_oldbuttonstate = 0; } /* =============== IN_StartupJoystick =============== */ void IN_StartupJoystick (void) { // abort startup if user requests no joystick if ( gEngfuncs.CheckParm ("-nojoy", NULL ) ) return; // assume no joystick joy_avail = 0; #if USE_SDL2 int nJoysticks = safe_pfnSDL_NumJoysticks(); if ( nJoysticks > 0 ) { for ( int i = 0; i < nJoysticks; i++ ) { if ( safe_pfnSDL_IsGameController( i ) ) { s_pJoystick = safe_pfnSDL_GameControllerOpen( i ); if ( s_pJoystick ) { //save the joystick's number of buttons and POV status joy_numbuttons = SDL_CONTROLLER_BUTTON_MAX; joy_haspov = 0; // old button and POV states default to no buttons pressed joy_oldbuttonstate = joy_oldpovstate = 0; // mark the joystick as available and advanced initialization not completed // this is needed as cvars are not available during initialization gEngfuncs.Con_Printf ("joystick found\n\n", safe_pfnSDL_GameControllerName(s_pJoystick)); joy_avail = 1; joy_advancedinit = 0; break; } } } } else { gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); } #elif _WIN32 int numdevs; JOYCAPS jc; MMRESULT mmr; // verify joystick driver is present if ((numdevs = joyGetNumDevs ()) == 0) { gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); return; } // cycle through the joystick ids for the first valid one for (joy_id=0 ; joy_idvalue == 0.0) { // default joystick initialization // 2 axes only with joystick control dwAxisMap[JOY_AXIS_X] = AxisTurn; // dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS; dwAxisMap[JOY_AXIS_Y] = AxisForward; // dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS; } else { if ( strcmp ( joy_name->string, "joystick") != 0 ) { // notify user of advanced controller gEngfuncs.Con_Printf ("\n%s configured\n\n", joy_name->string); } // advanced initialization here // data supplied by user via joy_axisn cvars dwTemp = (DWORD) joy_advaxisx->value; dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f; dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS; dwTemp = (DWORD) joy_advaxisy->value; dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f; dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS; dwTemp = (DWORD) joy_advaxisz->value; dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f; dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS; dwTemp = (DWORD) joy_advaxisr->value; dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f; dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS; dwTemp = (DWORD) joy_advaxisu->value; dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f; dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS; dwTemp = (DWORD) joy_advaxisv->value; dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f; dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS; } #if !USE_SDL2 && _WIN32 // compute the axes to collect from DirectInput joy_flags = JOY_RETURNCENTERED | JOY_RETURNBUTTONS | JOY_RETURNPOV; for (i = 0; i < JOY_MAX_AXES; i++) { if (dwAxisMap[i] != AxisNada) { joy_flags |= dwAxisFlags[i]; } } #endif } /* =========== IN_Commands =========== */ void GoldSourceInput::IN_Commands (void) { int i, key_index; if (!joy_avail) { return; } DWORD buttonstate, povstate; // loop through the joystick buttons // key a joystick event or auxillary event for higher number buttons for each state change #if USE_SDL2 buttonstate = 0; for ( i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++ ) { if ( safe_pfnSDL_GameControllerGetButton( s_pJoystick, (SDL_GameControllerButton)i ) ) { buttonstate |= 1<value != 0.0) { ji.dwUpos += 100; } return 1; } else { // read error occurred // turning off the joystick seems too harsh for 1 read error,\ // but what should be done? // Con_Printf ("IN_ReadJoystick: no response\n"); // joy_avail = 0; return 0; } #else return 0; #endif } /* =========== IN_JoyMove =========== */ void IN_JoyMove ( float frametime, usercmd_t *cmd ) { float speed, aspeed; float fAxisValue, fTemp; int i; vec3_t viewangles; gEngfuncs.GetViewAngles( (float *)viewangles ); // complete initialization if first time in // this is needed as cvars are not available at initialization time if( joy_advancedinit != 1 ) { Joy_AdvancedUpdate_f(); joy_advancedinit = 1; } // verify joystick is available and that the user wants to use it if (!joy_avail || !in_joystick->value) { return; } // collect the joystick data, if possible if (IN_ReadJoystick () != 1) { return; } if (in_speed.state & 1) speed = cl_movespeedkey->value; else speed = 1; aspeed = speed * frametime; // loop through the axes for (i = 0; i < JOY_MAX_AXES; i++) { // get the floating point zero-centered, potentially-inverted data for the current axis #if USE_SDL2 fAxisValue = (float)pdwRawValue[i]; #elif _WIN32 fAxisValue = (float) *pdwRawValue[i]; fAxisValue -= 32768.0; #endif if (joy_wwhack2->value != 0.0) { if (dwAxisMap[i] == AxisTurn) { // this is a special formula for the Logitech WingMan Warrior // y=ax^b; where a = 300 and b = 1.3 // also x values are in increments of 800 (so this is factored out) // then bounds check result to level out excessively high spin rates fTemp = 300.0 * pow(fabs(fAxisValue) / 800.0, 1.3); if (fTemp > 14000.0) fTemp = 14000.0; // restore direction information fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp; } } // convert range from -32768..32767 to -1..1 fAxisValue /= 32768.0; switch (dwAxisMap[i]) { case AxisForward: if ((joy_advanced->value == 0.0) && (in_jlook.state & 1)) { // user wants forward control to become look control if (fabs(fAxisValue) > joy_pitchthreshold->value) { // if mouse invert is on, invert the joystick pitch value // only absolute control support here (joy_advanced is 0) if (m_pitch->value < 0.0) { viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; } else { viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; } V_StopPitchDrift(); } else { // no pitch movement // disable pitch return-to-center unless requested by user // *** this code can be removed when the lookspring bug is fixed // *** the bug always has the lookspring feature on if(lookspring->value == 0.0) { V_StopPitchDrift(); } } } else { // user wants forward control to be forward control if (fabs(fAxisValue) > joy_forwardthreshold->value) { cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value; } } break; case AxisSide: if (fabs(fAxisValue) > joy_sidethreshold->value) { cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; } break; case AxisTurn: if ((in_strafe.state & 1) || (lookstrafe->value && (in_jlook.state & 1))) { // user wants turn control to become side control if (fabs(fAxisValue) > joy_sidethreshold->value) { cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; } } else { // user wants turn control to be turn control if (fabs(fAxisValue) > joy_yawthreshold->value) { if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) { viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value; } else { viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0; } } } break; case AxisLook: if (in_jlook.state & 1) { if (fabs(fAxisValue) > joy_pitchthreshold->value) { // pitch movement detected and pitch movement desired by user if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) { viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; } else { viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0; } V_StopPitchDrift(); } else { // no pitch movement // disable pitch return-to-center unless requested by user // *** this code can be removed when the lookspring bug is fixed // *** the bug always has the lookspring feature on if( lookspring->value == 0.0 ) { V_StopPitchDrift(); } } } break; default: break; } } // bounds check pitch if (viewangles[PITCH] > cl_pitchdown->value) viewangles[PITCH] = cl_pitchdown->value; if (viewangles[PITCH] < -cl_pitchup->value) viewangles[PITCH] = -cl_pitchup->value; gEngfuncs.SetViewAngles( (float *)viewangles ); } /* =========== IN_Move =========== */ void GoldSourceInput::IN_Move ( float frametime, usercmd_t *cmd) { if ( !iMouseInUse && mouseactive ) { IN_MouseMove ( frametime, cmd); } IN_JoyMove ( frametime, cmd); } /* =========== IN_Init =========== */ void GoldSourceInput::IN_Init (void) { m_filter = gEngfuncs.pfnRegisterVariable ( "m_filter","0", FCVAR_ARCHIVE ); sensitivity = gEngfuncs.pfnRegisterVariable ( "sensitivity","3", FCVAR_ARCHIVE ); // user mouse sensitivity setting. in_joystick = gEngfuncs.pfnRegisterVariable ( "joystick","0", FCVAR_ARCHIVE ); joy_name = gEngfuncs.pfnRegisterVariable ( "joyname", "joystick", 0 ); joy_advanced = gEngfuncs.pfnRegisterVariable ( "joyadvanced", "0", 0 ); joy_advaxisx = gEngfuncs.pfnRegisterVariable ( "joyadvaxisx", "0", 0 ); joy_advaxisy = gEngfuncs.pfnRegisterVariable ( "joyadvaxisy", "0", 0 ); joy_advaxisz = gEngfuncs.pfnRegisterVariable ( "joyadvaxisz", "0", 0 ); joy_advaxisr = gEngfuncs.pfnRegisterVariable ( "joyadvaxisr", "0", 0 ); joy_advaxisu = gEngfuncs.pfnRegisterVariable ( "joyadvaxisu", "0", 0 ); joy_advaxisv = gEngfuncs.pfnRegisterVariable ( "joyadvaxisv", "0", 0 ); joy_forwardthreshold = gEngfuncs.pfnRegisterVariable ( "joyforwardthreshold", "0.15", 0 ); joy_sidethreshold = gEngfuncs.pfnRegisterVariable ( "joysidethreshold", "0.15", 0 ); joy_pitchthreshold = gEngfuncs.pfnRegisterVariable ( "joypitchthreshold", "0.15", 0 ); joy_yawthreshold = gEngfuncs.pfnRegisterVariable ( "joyyawthreshold", "0.15", 0 ); joy_forwardsensitivity = gEngfuncs.pfnRegisterVariable ( "joyforwardsensitivity", "-1.0", 0 ); joy_sidesensitivity = gEngfuncs.pfnRegisterVariable ( "joysidesensitivity", "-1.0", 0 ); joy_pitchsensitivity = gEngfuncs.pfnRegisterVariable ( "joypitchsensitivity", "1.0", 0 ); joy_yawsensitivity = gEngfuncs.pfnRegisterVariable ( "joyyawsensitivity", "-1.0", 0 ); joy_wwhack1 = gEngfuncs.pfnRegisterVariable ( "joywwhack1", "0.0", 0 ); joy_wwhack2 = gEngfuncs.pfnRegisterVariable ( "joywwhack2", "0.0", 0 ); m_customaccel = gEngfuncs.pfnRegisterVariable ( "m_customaccel", "0", FCVAR_ARCHIVE ); m_customaccel_scale = gEngfuncs.pfnRegisterVariable ( "m_customaccel_scale", "0.04", FCVAR_ARCHIVE ); m_customaccel_max = gEngfuncs.pfnRegisterVariable ( "m_customaccel_max", "0", FCVAR_ARCHIVE ); m_customaccel_exponent = gEngfuncs.pfnRegisterVariable ( "m_customaccel_exponent", "1", FCVAR_ARCHIVE ); #if _WIN32 m_rawinput = gEngfuncs.pfnGetCvarPointer("m_rawinput"); m_bRawInput = m_rawinput && m_rawinput->value != 0; m_bMouseThread = gEngfuncs.CheckParm ("-mousethread", NULL ) != NULL; m_mousethread_sleep = gEngfuncs.pfnRegisterVariable ( "m_mousethread_sleep", "1", FCVAR_ARCHIVE ); // default to less than 1000 Hz m_bMouseThread = m_bMouseThread && NULL != m_mousethread_sleep; if (m_bMouseThread) { // init mouseThreadSleep: #if 0 // _beginthreadex is not defined on VS 6? InterlockedExchange(&mouseThreadSleep, (LONG)m_mousethread_sleep->value); s_hMouseQuitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); s_hMouseThreadActiveLock = CreateEvent( NULL, FALSE, TRUE, NULL ); if ( s_hMouseQuitEvent && s_hMouseThreadActiveLock) { s_hMouseThread = (HANDLE)_beginthreadex( NULL, 0, MouseThread_Function, NULL, 0, &s_hMouseThreadId ); } m_bMouseThread = NULL != s_hMouseThread; #else m_bMouseThread = 0; #endif // at this early stage this won't print anything: // gEngfuncs.Con_DPrintf ("Mouse thread %s.\n", m_bMouseThread ? "initalized" : "failed to initalize"); } #endif #if USE_SDL2 #if __APPLE__ #define SDL2_FULL_LIBNAME "libsdl2-2.0.0.dylib" #else #define SDL2_FULL_LIBNAME "libSDL2-2.0.so.0" #endif sdl2Lib = dlopen(SDL2_FULL_LIBNAME, RTLD_NOW|RTLD_LOCAL); if (sdl2Lib) { for (int j=0; j