Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

462 lines
16 KiB

  1. //===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose: Mouse input routines
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. // $NoKeywords: $
  8. //===========================================================================//
  9. #include "cbase.h"
  10. #include "basehandle.h"
  11. #include "utlvector.h"
  12. #include "cdll_client_int.h"
  13. #include "cdll_util.h"
  14. #include "kbutton.h"
  15. #include "usercmd.h"
  16. #include "input.h"
  17. #include "iviewrender.h"
  18. #include "convar.h"
  19. #include "hud.h"
  20. #include "vgui/ISurface.h"
  21. #include "vgui_controls/Controls.h"
  22. #include "vgui/Cursor.h"
  23. #include "tier0/icommandline.h"
  24. #include "inputsystem/iinputsystem.h"
  25. #include "inputsystem/ButtonCode.h"
  26. #include "math.h"
  27. #include "tier1/convar_serverbounded.h"
  28. #include "cam_thirdperson.h"
  29. #include "ienginevgui.h"
  30. #if defined( _X360 )
  31. #include "xbox/xbox_win32stubs.h"
  32. #endif
  33. // memdbgon must be the last include file in a .cpp file!!!
  34. #include "tier0/memdbgon.h"
  35. #ifndef UINT64_MAX
  36. const uint64 UINT64_MAX = 0xffffffffffffffff;
  37. #endif
  38. // up / down
  39. #define PITCH 0
  40. // left / right
  41. #define YAW 1
  42. extern const ConVar *sv_cheats;
  43. extern ConVar cam_idealyaw;
  44. extern ConVar cam_idealpitch;
  45. extern ConVar thirdperson_platformer;
  46. extern ConVar cl_forwardspeed;
  47. extern ConVar cl_backspeed;
  48. extern ConVar cl_sidespeed;
  49. static ConVar sc_yaw_sensitivity( "sc_yaw_sensitivity","1.0", FCVAR_ARCHIVE , "SteamController yaw factor." );
  50. static ConVar sc_yaw_sensitivity_default( "sc_yaw_sensitivity_default","1.0", FCVAR_NONE );
  51. static ConVar sc_pitch_sensitivity( "sc_pitch_sensitivity","0.75", FCVAR_ARCHIVE , "SteamController pitch factor." );
  52. static ConVar sc_pitch_sensitivity_default( "sc_pitch_sensitivity_default","0.75", FCVAR_NONE );
  53. ConVar sc_look_sensitivity_scale( "sc_look_sensitivity_scale", "0.125", FCVAR_NONE, "Steam Controller look sensitivity global scale factor." );
  54. void CInput::ApplySteamControllerCameraMove( QAngle& viewangles, CUserCmd *cmd, Vector2D vecPosition )
  55. {
  56. //roll the view angles so roll is 0 (the HL2 assumed state) and mouse adjustments are relative to the screen.
  57. //Assuming roll is unchanging, we want mouse left to translate to screen left at all times (same for right, up, and down)
  58. ConVarRef cl_pitchdown ( "cl_pitchdown" );
  59. ConVarRef cl_pitchup ( "cl_pitchup" );
  60. // Scale yaw and pitch inputs by sensitivity, and make sure they are within acceptable limits (important to avoid exploits, e.g. during Demoman charge we must restrict allowed yaw).
  61. float yaw = CAM_CapYaw( sc_yaw_sensitivity.GetFloat() * vecPosition.x );
  62. float pitch = CAM_CapPitch( sc_pitch_sensitivity.GetFloat() * vecPosition.y );
  63. if ( CAM_IsThirdPerson() )
  64. {
  65. if ( vecPosition.x )
  66. {
  67. auto vecCameraOffset = g_ThirdPersonManager.GetCameraOffsetAngles();
  68. // use the mouse to orbit the camera around the player, and update the idealAngle
  69. vecCameraOffset[YAW] -= yaw;
  70. cam_idealyaw.SetValue( vecCameraOffset[ YAW ] - viewangles[ YAW ] );
  71. viewangles[YAW] -= yaw;
  72. }
  73. }
  74. else
  75. {
  76. // Otherwize, use mouse to spin around vertical axis
  77. viewangles[YAW] -= yaw;
  78. }
  79. if ( CAM_IsThirdPerson() && thirdperson_platformer.GetInt() )
  80. {
  81. if ( vecPosition.y )
  82. {
  83. // use the mouse to orbit the camera around the player, and update the idealAngle
  84. auto vecCameraOffset = g_ThirdPersonManager.GetCameraOffsetAngles();
  85. vecCameraOffset[PITCH] += pitch;
  86. cam_idealpitch.SetValue( vecCameraOffset[ PITCH ] - viewangles[ PITCH ] );
  87. }
  88. }
  89. else
  90. {
  91. viewangles[PITCH] -= pitch;
  92. // Check pitch bounds
  93. viewangles[PITCH] = clamp ( viewangles[PITCH], -cl_pitchdown.GetFloat(), cl_pitchup.GetFloat() );
  94. }
  95. // Finally, add mouse state to usercmd.
  96. // NOTE: Does rounding to int cause any issues? ywb 1/17/04
  97. cmd->mousedx = (int)vecPosition.x;
  98. cmd->mousedy = (int)vecPosition.y;
  99. }
  100. #define CONTROLLER_ACTION_FLAGS_NONE 0
  101. #define CONTROLLER_ACTION_FLAGS_STOPS_TAUNT ( 1 << 0 )
  102. #define CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE ( 1 << 1 )
  103. struct ControllerDigitalActionToCommand
  104. {
  105. const char* action;
  106. const char* cmd;
  107. int flags;
  108. };
  109. // Map action names to commands. Mostly these are identity mappings, but we need to handle the case of mapping to a command
  110. // string that contains spaces (e.g. for "vote option1"), which are not allowed in action names.
  111. static ControllerDigitalActionToCommand g_ControllerDigitalGameActions[] =
  112. {
  113. { "duck", "+duck", CONTROLLER_ACTION_FLAGS_NONE },
  114. { "attack", "+attack", CONTROLLER_ACTION_FLAGS_NONE },
  115. { "attack2", "+attack2", CONTROLLER_ACTION_FLAGS_NONE },
  116. { "attack3", "+attack3", CONTROLLER_ACTION_FLAGS_NONE },
  117. { "jump", "+jump", CONTROLLER_ACTION_FLAGS_STOPS_TAUNT },
  118. { "use_action_slot_item", "+use_action_slot_item", CONTROLLER_ACTION_FLAGS_NONE },
  119. { "invprev", "invprev", CONTROLLER_ACTION_FLAGS_STOPS_TAUNT|CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  120. { "invnext", "invnext", CONTROLLER_ACTION_FLAGS_STOPS_TAUNT|CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  121. { "reload", "+reload", CONTROLLER_ACTION_FLAGS_NONE },
  122. { "dropitem", "dropitem", CONTROLLER_ACTION_FLAGS_STOPS_TAUNT|CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  123. { "changeclass", "changeclass", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  124. { "changeteam", "changeteam", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  125. { "open_charinfo_direct", "open_charinfo_direct", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  126. { "open_charinfo_backpack", "open_charinfo_backpack", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  127. { "inspect", "+inspect", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  128. { "taunt", "+taunt", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  129. { "voicerecord", "+voicerecord", CONTROLLER_ACTION_FLAGS_NONE },
  130. { "show_quest_log", "show_quest_log", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  131. { "showscores", "+showscores", CONTROLLER_ACTION_FLAGS_NONE },
  132. { "callvote", "callvote", CONTROLLER_ACTION_FLAGS_NONE },
  133. { "cl_trigger_first_notification", "cl_trigger_first_notification", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  134. { "cl_decline_first_notification", "cl_decline_first_notification", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  135. { "cl_trigger_first_notification", "vote option1", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, // In here twice, because we overload this action to issue both commands
  136. { "cl_decline_first_notification", "vote option2", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, // In here twice, because we overload this action to issue both commands
  137. { "vote_option3", "vote option3", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  138. { "vote_option4", "vote option4", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  139. { "vote_option5", "vote option5", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE },
  140. { "next_target", "spec_next", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, // In the spectator action set only
  141. { "prev_target", "spec_prev", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, // In the spectator action set only
  142. };
  143. struct ControllerDigitalActionState {
  144. const char* cmd;
  145. ControllerDigitalActionHandle_t handle;
  146. bool bState;
  147. bool bAwaitingDebounce;
  148. };
  149. static ControllerDigitalActionState g_ControllerDigitalActionState[ARRAYSIZE(g_ControllerDigitalGameActions)];
  150. static ControllerAnalogActionHandle_t g_ControllerMoveHandle;
  151. static ControllerAnalogActionHandle_t g_ControllerCameraHandle;
  152. bool CInput::InitializeSteamControllerGameActionSets()
  153. {
  154. auto steamcontroller = g_pInputSystem->SteamControllerInterface();
  155. if ( !steamcontroller )
  156. {
  157. return false;
  158. }
  159. bool bGotHandle = true;
  160. for ( int i = 0; i < ARRAYSIZE( g_ControllerDigitalGameActions ); ++i )
  161. {
  162. const char* action = g_ControllerDigitalGameActions[i].action;
  163. const char* cmd = g_ControllerDigitalGameActions[i].cmd;
  164. ControllerDigitalActionState& state = g_ControllerDigitalActionState[i];
  165. state.handle = steamcontroller->GetDigitalActionHandle( action );
  166. bGotHandle = bGotHandle && ( state.handle != 0 ); // We're only successful if we get *all* the handles.
  167. state.cmd = cmd;
  168. state.bState = false;
  169. state.bAwaitingDebounce = false;
  170. }
  171. g_ControllerMoveHandle = steamcontroller->GetAnalogActionHandle( "Move" );
  172. g_ControllerCameraHandle = steamcontroller->GetAnalogActionHandle( "Camera" );
  173. if ( bGotHandle )
  174. {
  175. m_PreferredGameActionSet = GAME_ACTION_SET_MENUCONTROLS;
  176. }
  177. return bGotHandle;
  178. }
  179. //-----------------------------------------------------------------------------
  180. // Purpose: SteamControllerMove -- main entry point for applying Steam Controller Movements
  181. // Input : *cmd -
  182. //-----------------------------------------------------------------------------
  183. void CInput::SteamControllerMove( float flFrametime, CUserCmd *cmd )
  184. {
  185. // Make sure we have an interface
  186. auto steamcontroller = g_pInputSystem->SteamControllerInterface();
  187. if ( !steamcontroller )
  188. {
  189. return;
  190. }
  191. // Check there is a controller connected. Do this check before we ask about handles to avoid thrashing the
  192. // handle query interface in the case where no controller is connected.
  193. uint64 nControllerHandles[STEAM_CONTROLLER_MAX_COUNT];
  194. int nControllerCount = steamcontroller->GetConnectedControllers( nControllerHandles );
  195. if ( nControllerCount <= 0 )
  196. {
  197. return;
  198. }
  199. // Initialize action handles if we haven't successfully done so already.
  200. if ( !m_bSteamControllerGameActionsInitialized )
  201. {
  202. m_bSteamControllerGameActionsInitialized = InitializeSteamControllerGameActionSets();
  203. if ( !m_bSteamControllerGameActionsInitialized )
  204. {
  205. return;
  206. }
  207. }
  208. g_pInputSystem->ActivateSteamControllerActionSet( m_PreferredGameActionSet );
  209. QAngle viewangles;
  210. engine->GetViewAngles( viewangles );
  211. view->StopPitchDrift();
  212. bool bTaunting = m_GameActionSetFlags & GAME_ACTION_SET_FLAGS_TAUNTING;
  213. uint64 controller = nControllerHandles[0];
  214. bool bReceivedInput = false;
  215. for ( int i = 0; i < ARRAYSIZE( g_ControllerDigitalActionState ); ++i )
  216. {
  217. ControllerDigitalActionToCommand& cmdmap = g_ControllerDigitalGameActions[ i ];
  218. ControllerDigitalActionState& state = g_ControllerDigitalActionState[ i ];
  219. ControllerDigitalActionData_t data = steamcontroller->GetDigitalActionData( controller, state.handle );
  220. if ( data.bActive )
  221. {
  222. state.bAwaitingDebounce = state.bAwaitingDebounce && data.bState;
  223. if ( data.bState != state.bState )
  224. {
  225. bReceivedInput = true;
  226. if ( ( data.bState && !state.bAwaitingDebounce ) || state.cmd[0] == '+' )
  227. {
  228. char cmdbuf[128];
  229. Q_snprintf( cmdbuf, sizeof( cmdbuf ), "%s", state.cmd );
  230. if ( !data.bState )
  231. {
  232. cmdbuf[0] = '-';
  233. }
  234. engine->ClientCmd_Unrestricted( cmdbuf );
  235. // Hack - if we're taunting, we manufacture a stop taunt command for certain inputs (keyboard equivalent of this goes through another codepath).
  236. if ( bTaunting && data.bState && ( cmdmap.flags & CONTROLLER_ACTION_FLAGS_STOPS_TAUNT ) )
  237. {
  238. engine->ClientCmd_Unrestricted( "stop_taunt" );
  239. }
  240. }
  241. state.bState = data.bState;
  242. }
  243. }
  244. }
  245. ControllerAnalogActionData_t moveData = steamcontroller->GetAnalogActionData( controller, g_ControllerMoveHandle );
  246. // Clamp input to a vector no longer than 1 unit. This shouldn't happen with the physical circular constraint on the joystick, but if somehow somebody did hack their input
  247. // to produce longer vectors in the corners (for example), we catch that here.
  248. Vector2D moveDir( moveData.x, moveData.y );
  249. if ( moveDir.LengthSqr() > 1.0 )
  250. {
  251. moveDir.NormalizeInPlace();
  252. }
  253. // Apply forward/back movement
  254. if ( moveDir.y > 0.0 )
  255. {
  256. cmd->forwardmove += cl_forwardspeed.GetFloat() * moveDir.y;
  257. }
  258. else
  259. {
  260. cmd->forwardmove += cl_backspeed.GetFloat() * moveDir.y;
  261. }
  262. // Apply sidestep movement
  263. cmd->sidemove += cl_sidespeed.GetFloat() * moveDir.x;
  264. ControllerAnalogActionData_t action = steamcontroller->GetAnalogActionData( controller, g_ControllerCameraHandle );
  265. const float fSensitivityFactor = sc_look_sensitivity_scale.GetFloat();
  266. Vector2D vecMouseDelta = Vector2D( action.x, -action.y ) * fSensitivityFactor;
  267. if ( vecMouseDelta.Length() > 0 )
  268. {
  269. if ( !m_fCameraInterceptingMouse )
  270. {
  271. ApplySteamControllerCameraMove( viewangles, cmd, vecMouseDelta );
  272. }
  273. }
  274. engine->SetViewAngles( viewangles );
  275. }
  276. //-----------------------------------------------------------------------------
  277. // Purpose: Sets the preferred game action set, and "debounces" controls where
  278. // appropriate if the set has changed.
  279. //-----------------------------------------------------------------------------
  280. void CInput::SetPreferredGameActionSet( GameActionSet_t action_set )
  281. {
  282. if ( m_PreferredGameActionSet != action_set )
  283. {
  284. // Debounce. Flag some actions as needing debounce (i.e. must see a "released" state before we'll register another "pressed" input).
  285. for ( int i = 0; i < ARRAYSIZE( g_ControllerDigitalActionState ); i++ )
  286. {
  287. if ( g_ControllerDigitalGameActions[i].flags & CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE )
  288. {
  289. g_ControllerDigitalActionState[i].bAwaitingDebounce = true;
  290. }
  291. }
  292. m_PreferredGameActionSet = action_set;
  293. }
  294. }
  295. GameActionSet_t CInput::GetPreferredGameActionSet()
  296. {
  297. return m_PreferredGameActionSet;
  298. }
  299. //-----------------------------------------------------------------------------
  300. // Purpose: Sets flags for special-case action handling
  301. //-----------------------------------------------------------------------------
  302. void CInput::SetGameActionSetFlags( GameActionSetFlags_t action_set_flags )
  303. {
  304. m_GameActionSetFlags = action_set_flags;
  305. }
  306. //-----------------------------------------------------------------------------
  307. // Purpose: Client should call this to determine if Steam Controllers are "active"
  308. //-----------------------------------------------------------------------------
  309. bool CInput::IsSteamControllerActive()
  310. {
  311. // We're only active if the input system thinks we are AND game action sets were also initialized completely
  312. return m_bSteamControllerGameActionsInitialized && g_pInputSystem->IsSteamControllerActive();
  313. }
  314. //-----------------------------------------------------------------------------
  315. // Purpose: Console command for launching the Steam Controller binding panel
  316. //-----------------------------------------------------------------------------
  317. CON_COMMAND( sc_show_binding_panel, "Launches the Steam Controller binding panel UI" )
  318. {
  319. if ( g_pInputSystem )
  320. {
  321. auto steamcontroller = g_pInputSystem->SteamControllerInterface();
  322. if ( steamcontroller )
  323. {
  324. ControllerHandle_t controllers[STEAM_CONTROLLER_MAX_COUNT];
  325. int nConnected = steamcontroller->GetConnectedControllers( controllers );
  326. if ( nConnected > 0 )
  327. {
  328. Msg( "%d controller(s) connected. Launching binding panel for first connected controller.\n", nConnected );
  329. if ( !steamcontroller->ShowBindingPanel( controllers[0] ) )
  330. {
  331. Warning( "Unable to show binding panel. Steam overlay disabled, or Steam not in Big Picture mode.\n" );
  332. }
  333. }
  334. else
  335. {
  336. Warning( "No Steam Controllers connected.\n" );
  337. }
  338. }
  339. else
  340. {
  341. Warning( "Steam Controller interface not initialized.\n" );
  342. }
  343. }
  344. else
  345. {
  346. Warning( "Input system not initialized.\n" );
  347. }
  348. }
  349. //-----------------------------------------------------------------------------
  350. // Purpose: Console command for dumping out some Steam Controller status information
  351. //-----------------------------------------------------------------------------
  352. CON_COMMAND( sc_status, "Show Steam Controller status information" )
  353. {
  354. if ( g_pInputSystem )
  355. {
  356. auto steamcontroller = g_pInputSystem->SteamControllerInterface();
  357. if ( steamcontroller )
  358. {
  359. ControllerHandle_t controllers[STEAM_CONTROLLER_MAX_COUNT];
  360. int nConnected = steamcontroller->GetConnectedControllers( controllers );
  361. if ( nConnected )
  362. {
  363. Msg( "%d Steam Controller(s) connected.\n", nConnected );
  364. }
  365. else
  366. {
  367. Warning( "No Steam Controllers(s) connected.\n" );
  368. }
  369. if ( ::input->IsSteamControllerActive() )
  370. {
  371. Msg( "Steam Controller considered active (controller connected and all action handles initialized).\n" );
  372. }
  373. else
  374. {
  375. Warning( "Steam Controller considered inactive (no controller connected, or not all action handles initialized).\n" );
  376. }
  377. auto action_set = ::input->GetPreferredGameActionSet();
  378. Msg( "Current action set = %d\n", action_set );
  379. for ( int i = 0; i < ARRAYSIZE( g_ControllerDigitalActionState ); ++i )
  380. {
  381. ControllerDigitalActionToCommand& cmdmap = g_ControllerDigitalGameActions[i];
  382. ControllerDigitalActionState& state = g_ControllerDigitalActionState[i];
  383. Msg( "Action: '%s' handle = %d\n", cmdmap.action, (int)state.handle );
  384. }
  385. }
  386. else
  387. {
  388. Warning( "Steam Controller interface not initialized.\n" );
  389. }
  390. }
  391. else
  392. {
  393. Warning( "Input system not initialized.\n" );
  394. }
  395. }