diff options
Diffstat (limited to 'game/client/in_steamcontroller.cpp')
| -rw-r--r-- | game/client/in_steamcontroller.cpp | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/game/client/in_steamcontroller.cpp b/game/client/in_steamcontroller.cpp new file mode 100644 index 0000000..b0c062a --- /dev/null +++ b/game/client/in_steamcontroller.cpp @@ -0,0 +1,462 @@ +//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Mouse input routines +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// + + +#include "cbase.h" +#include "basehandle.h" +#include "utlvector.h" +#include "cdll_client_int.h" +#include "cdll_util.h" +#include "kbutton.h" +#include "usercmd.h" +#include "input.h" +#include "iviewrender.h" +#include "convar.h" +#include "hud.h" +#include "vgui/ISurface.h" +#include "vgui_controls/Controls.h" +#include "vgui/Cursor.h" +#include "tier0/icommandline.h" +#include "inputsystem/iinputsystem.h" +#include "inputsystem/ButtonCode.h" +#include "math.h" +#include "tier1/convar_serverbounded.h" +#include "cam_thirdperson.h" +#include "ienginevgui.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef UINT64_MAX +const uint64 UINT64_MAX = 0xffffffffffffffff; +#endif + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 + +extern const ConVar *sv_cheats; + +extern ConVar cam_idealyaw; +extern ConVar cam_idealpitch; +extern ConVar thirdperson_platformer; + +extern ConVar cl_forwardspeed; +extern ConVar cl_backspeed; +extern ConVar cl_sidespeed; + +static ConVar sc_yaw_sensitivity( "sc_yaw_sensitivity","1.0", FCVAR_ARCHIVE , "SteamController yaw factor." ); +static ConVar sc_yaw_sensitivity_default( "sc_yaw_sensitivity_default","1.0", FCVAR_NONE ); + +static ConVar sc_pitch_sensitivity( "sc_pitch_sensitivity","0.75", FCVAR_ARCHIVE , "SteamController pitch factor." ); +static ConVar sc_pitch_sensitivity_default( "sc_pitch_sensitivity_default","0.75", FCVAR_NONE ); + +ConVar sc_look_sensitivity_scale( "sc_look_sensitivity_scale", "0.125", FCVAR_NONE, "Steam Controller look sensitivity global scale factor." ); + +void CInput::ApplySteamControllerCameraMove( QAngle& viewangles, CUserCmd *cmd, Vector2D vecPosition ) +{ + //roll the view angles so roll is 0 (the HL2 assumed state) and mouse adjustments are relative to the screen. + //Assuming roll is unchanging, we want mouse left to translate to screen left at all times (same for right, up, and down) + + ConVarRef cl_pitchdown ( "cl_pitchdown" ); + ConVarRef cl_pitchup ( "cl_pitchup" ); + + // 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). + float yaw = CAM_CapYaw( sc_yaw_sensitivity.GetFloat() * vecPosition.x ); + float pitch = CAM_CapPitch( sc_pitch_sensitivity.GetFloat() * vecPosition.y ); + + if ( CAM_IsThirdPerson() ) + { + if ( vecPosition.x ) + { + auto vecCameraOffset = g_ThirdPersonManager.GetCameraOffsetAngles(); + + // use the mouse to orbit the camera around the player, and update the idealAngle + vecCameraOffset[YAW] -= yaw; + cam_idealyaw.SetValue( vecCameraOffset[ YAW ] - viewangles[ YAW ] ); + viewangles[YAW] -= yaw; + } + } + else + { + // Otherwize, use mouse to spin around vertical axis + viewangles[YAW] -= yaw; + } + + if ( CAM_IsThirdPerson() && thirdperson_platformer.GetInt() ) + { + if ( vecPosition.y ) + { + // use the mouse to orbit the camera around the player, and update the idealAngle + auto vecCameraOffset = g_ThirdPersonManager.GetCameraOffsetAngles(); + vecCameraOffset[PITCH] += pitch; + cam_idealpitch.SetValue( vecCameraOffset[ PITCH ] - viewangles[ PITCH ] ); + } + } + else + { + viewangles[PITCH] -= pitch; + + // Check pitch bounds + viewangles[PITCH] = clamp ( viewangles[PITCH], -cl_pitchdown.GetFloat(), cl_pitchup.GetFloat() ); + } + + // Finally, add mouse state to usercmd. + // NOTE: Does rounding to int cause any issues? ywb 1/17/04 + cmd->mousedx = (int)vecPosition.x; + cmd->mousedy = (int)vecPosition.y; +} + +#define CONTROLLER_ACTION_FLAGS_NONE 0 +#define CONTROLLER_ACTION_FLAGS_STOPS_TAUNT ( 1 << 0 ) +#define CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE ( 1 << 1 ) + +struct ControllerDigitalActionToCommand +{ + const char* action; + const char* cmd; + int flags; +}; + +// Map action names to commands. Mostly these are identity mappings, but we need to handle the case of mapping to a command +// string that contains spaces (e.g. for "vote option1"), which are not allowed in action names. +static ControllerDigitalActionToCommand g_ControllerDigitalGameActions[] = +{ + { "duck", "+duck", CONTROLLER_ACTION_FLAGS_NONE }, + { "attack", "+attack", CONTROLLER_ACTION_FLAGS_NONE }, + { "attack2", "+attack2", CONTROLLER_ACTION_FLAGS_NONE }, + { "attack3", "+attack3", CONTROLLER_ACTION_FLAGS_NONE }, + { "jump", "+jump", CONTROLLER_ACTION_FLAGS_STOPS_TAUNT }, + { "use_action_slot_item", "+use_action_slot_item", CONTROLLER_ACTION_FLAGS_NONE }, + { "invprev", "invprev", CONTROLLER_ACTION_FLAGS_STOPS_TAUNT|CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "invnext", "invnext", CONTROLLER_ACTION_FLAGS_STOPS_TAUNT|CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "reload", "+reload", CONTROLLER_ACTION_FLAGS_NONE }, + { "dropitem", "dropitem", CONTROLLER_ACTION_FLAGS_STOPS_TAUNT|CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "changeclass", "changeclass", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "changeteam", "changeteam", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "open_charinfo_direct", "open_charinfo_direct", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "open_charinfo_backpack", "open_charinfo_backpack", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "inspect", "+inspect", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "taunt", "+taunt", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "voicerecord", "+voicerecord", CONTROLLER_ACTION_FLAGS_NONE }, + { "show_quest_log", "show_quest_log", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "showscores", "+showscores", CONTROLLER_ACTION_FLAGS_NONE }, + { "callvote", "callvote", CONTROLLER_ACTION_FLAGS_NONE }, + { "cl_trigger_first_notification", "cl_trigger_first_notification", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "cl_decline_first_notification", "cl_decline_first_notification", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "cl_trigger_first_notification", "vote option1", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, // In here twice, because we overload this action to issue both commands + { "cl_decline_first_notification", "vote option2", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, // In here twice, because we overload this action to issue both commands + { "vote_option3", "vote option3", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "vote_option4", "vote option4", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "vote_option5", "vote option5", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, + { "next_target", "spec_next", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, // In the spectator action set only + { "prev_target", "spec_prev", CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE }, // In the spectator action set only +}; + +struct ControllerDigitalActionState { + const char* cmd; + ControllerDigitalActionHandle_t handle; + bool bState; + bool bAwaitingDebounce; +}; + +static ControllerDigitalActionState g_ControllerDigitalActionState[ARRAYSIZE(g_ControllerDigitalGameActions)]; + +static ControllerAnalogActionHandle_t g_ControllerMoveHandle; +static ControllerAnalogActionHandle_t g_ControllerCameraHandle; + +bool CInput::InitializeSteamControllerGameActionSets() +{ + auto steamcontroller = g_pInputSystem->SteamControllerInterface(); + if ( !steamcontroller ) + { + return false; + } + + bool bGotHandle = true; + + for ( int i = 0; i < ARRAYSIZE( g_ControllerDigitalGameActions ); ++i ) + { + const char* action = g_ControllerDigitalGameActions[i].action; + const char* cmd = g_ControllerDigitalGameActions[i].cmd; + + ControllerDigitalActionState& state = g_ControllerDigitalActionState[i]; + state.handle = steamcontroller->GetDigitalActionHandle( action ); + bGotHandle = bGotHandle && ( state.handle != 0 ); // We're only successful if we get *all* the handles. + state.cmd = cmd; + state.bState = false; + state.bAwaitingDebounce = false; + } + + g_ControllerMoveHandle = steamcontroller->GetAnalogActionHandle( "Move" ); + g_ControllerCameraHandle = steamcontroller->GetAnalogActionHandle( "Camera" ); + + if ( bGotHandle ) + { + m_PreferredGameActionSet = GAME_ACTION_SET_MENUCONTROLS; + } + + return bGotHandle; +} + +//----------------------------------------------------------------------------- +// Purpose: SteamControllerMove -- main entry point for applying Steam Controller Movements +// Input : *cmd - +//----------------------------------------------------------------------------- +void CInput::SteamControllerMove( float flFrametime, CUserCmd *cmd ) +{ + // Make sure we have an interface + auto steamcontroller = g_pInputSystem->SteamControllerInterface(); + if ( !steamcontroller ) + { + return; + } + + // Check there is a controller connected. Do this check before we ask about handles to avoid thrashing the + // handle query interface in the case where no controller is connected. + uint64 nControllerHandles[STEAM_CONTROLLER_MAX_COUNT]; + int nControllerCount = steamcontroller->GetConnectedControllers( nControllerHandles ); + if ( nControllerCount <= 0 ) + { + return; + } + + // Initialize action handles if we haven't successfully done so already. + if ( !m_bSteamControllerGameActionsInitialized ) + { + m_bSteamControllerGameActionsInitialized = InitializeSteamControllerGameActionSets(); + if ( !m_bSteamControllerGameActionsInitialized ) + { + return; + } + } + + g_pInputSystem->ActivateSteamControllerActionSet( m_PreferredGameActionSet ); + + QAngle viewangles; + engine->GetViewAngles( viewangles ); + + view->StopPitchDrift(); + + bool bTaunting = m_GameActionSetFlags & GAME_ACTION_SET_FLAGS_TAUNTING; + + uint64 controller = nControllerHandles[0]; + bool bReceivedInput = false; + for ( int i = 0; i < ARRAYSIZE( g_ControllerDigitalActionState ); ++i ) + { + ControllerDigitalActionToCommand& cmdmap = g_ControllerDigitalGameActions[ i ]; + ControllerDigitalActionState& state = g_ControllerDigitalActionState[ i ]; + ControllerDigitalActionData_t data = steamcontroller->GetDigitalActionData( controller, state.handle ); + + if ( data.bActive ) + { + state.bAwaitingDebounce = state.bAwaitingDebounce && data.bState; + + if ( data.bState != state.bState ) + { + bReceivedInput = true; + if ( ( data.bState && !state.bAwaitingDebounce ) || state.cmd[0] == '+' ) + { + char cmdbuf[128]; + Q_snprintf( cmdbuf, sizeof( cmdbuf ), "%s", state.cmd ); + if ( !data.bState ) + { + cmdbuf[0] = '-'; + } + + engine->ClientCmd_Unrestricted( cmdbuf ); + + // Hack - if we're taunting, we manufacture a stop taunt command for certain inputs (keyboard equivalent of this goes through another codepath). + if ( bTaunting && data.bState && ( cmdmap.flags & CONTROLLER_ACTION_FLAGS_STOPS_TAUNT ) ) + { + engine->ClientCmd_Unrestricted( "stop_taunt" ); + } + } + + state.bState = data.bState; + } + } + } + + ControllerAnalogActionData_t moveData = steamcontroller->GetAnalogActionData( controller, g_ControllerMoveHandle ); + + // 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 + // to produce longer vectors in the corners (for example), we catch that here. + Vector2D moveDir( moveData.x, moveData.y ); + + if ( moveDir.LengthSqr() > 1.0 ) + { + moveDir.NormalizeInPlace(); + } + + // Apply forward/back movement + if ( moveDir.y > 0.0 ) + { + cmd->forwardmove += cl_forwardspeed.GetFloat() * moveDir.y; + } + else + { + cmd->forwardmove += cl_backspeed.GetFloat() * moveDir.y; + } + + // Apply sidestep movement + cmd->sidemove += cl_sidespeed.GetFloat() * moveDir.x; + + ControllerAnalogActionData_t action = steamcontroller->GetAnalogActionData( controller, g_ControllerCameraHandle ); + + const float fSensitivityFactor = sc_look_sensitivity_scale.GetFloat(); + + Vector2D vecMouseDelta = Vector2D( action.x, -action.y ) * fSensitivityFactor; + + if ( vecMouseDelta.Length() > 0 ) + { + if ( !m_fCameraInterceptingMouse ) + { + ApplySteamControllerCameraMove( viewangles, cmd, vecMouseDelta ); + } + } + + engine->SetViewAngles( viewangles ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the preferred game action set, and "debounces" controls where +// appropriate if the set has changed. +//----------------------------------------------------------------------------- +void CInput::SetPreferredGameActionSet( GameActionSet_t action_set ) +{ + if ( m_PreferredGameActionSet != action_set ) + { + // Debounce. Flag some actions as needing debounce (i.e. must see a "released" state before we'll register another "pressed" input). + for ( int i = 0; i < ARRAYSIZE( g_ControllerDigitalActionState ); i++ ) + { + if ( g_ControllerDigitalGameActions[i].flags & CONTROLLER_ACTION_FLAGS_NEEDS_DEBOUNCE ) + { + g_ControllerDigitalActionState[i].bAwaitingDebounce = true; + } + } + + m_PreferredGameActionSet = action_set; + } +} + +GameActionSet_t CInput::GetPreferredGameActionSet() +{ + return m_PreferredGameActionSet; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets flags for special-case action handling +//----------------------------------------------------------------------------- +void CInput::SetGameActionSetFlags( GameActionSetFlags_t action_set_flags ) +{ + m_GameActionSetFlags = action_set_flags; +} + +//----------------------------------------------------------------------------- +// Purpose: Client should call this to determine if Steam Controllers are "active" +//----------------------------------------------------------------------------- +bool CInput::IsSteamControllerActive() +{ + // We're only active if the input system thinks we are AND game action sets were also initialized completely + return m_bSteamControllerGameActionsInitialized && g_pInputSystem->IsSteamControllerActive(); +} + +//----------------------------------------------------------------------------- +// Purpose: Console command for launching the Steam Controller binding panel +//----------------------------------------------------------------------------- +CON_COMMAND( sc_show_binding_panel, "Launches the Steam Controller binding panel UI" ) +{ + if ( g_pInputSystem ) + { + auto steamcontroller = g_pInputSystem->SteamControllerInterface(); + if ( steamcontroller ) + { + ControllerHandle_t controllers[STEAM_CONTROLLER_MAX_COUNT]; + int nConnected = steamcontroller->GetConnectedControllers( controllers ); + if ( nConnected > 0 ) + { + Msg( "%d controller(s) connected. Launching binding panel for first connected controller.\n", nConnected ); + if ( !steamcontroller->ShowBindingPanel( controllers[0] ) ) + { + Warning( "Unable to show binding panel. Steam overlay disabled, or Steam not in Big Picture mode.\n" ); + } + } + else + { + Warning( "No Steam Controllers connected.\n" ); + } + } + else + { + Warning( "Steam Controller interface not initialized.\n" ); + } + } + else + { + Warning( "Input system not initialized.\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Console command for dumping out some Steam Controller status information +//----------------------------------------------------------------------------- +CON_COMMAND( sc_status, "Show Steam Controller status information" ) +{ + if ( g_pInputSystem ) + { + auto steamcontroller = g_pInputSystem->SteamControllerInterface(); + if ( steamcontroller ) + { + ControllerHandle_t controllers[STEAM_CONTROLLER_MAX_COUNT]; + int nConnected = steamcontroller->GetConnectedControllers( controllers ); + if ( nConnected ) + { + Msg( "%d Steam Controller(s) connected.\n", nConnected ); + } + else + { + Warning( "No Steam Controllers(s) connected.\n" ); + } + + if ( ::input->IsSteamControllerActive() ) + { + Msg( "Steam Controller considered active (controller connected and all action handles initialized).\n" ); + } + else + { + Warning( "Steam Controller considered inactive (no controller connected, or not all action handles initialized).\n" ); + } + + auto action_set = ::input->GetPreferredGameActionSet(); + Msg( "Current action set = %d\n", action_set ); + + for ( int i = 0; i < ARRAYSIZE( g_ControllerDigitalActionState ); ++i ) + { + ControllerDigitalActionToCommand& cmdmap = g_ControllerDigitalGameActions[i]; + ControllerDigitalActionState& state = g_ControllerDigitalActionState[i]; + + Msg( "Action: '%s' handle = %d\n", cmdmap.action, (int)state.handle ); + } + } + else + { + Warning( "Steam Controller interface not initialized.\n" ); + } + } + else + { + Warning( "Input system not initialized.\n" ); + } +} |