/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code. If not, see .
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
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", "1", 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.2", 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", "100", 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_mouseSpeed( "in_mouseSpeed", "1", CVAR_ARCHIVE | CVAR_FLOAT, "speed at which the mouse moves", 0.25f, 4.0f );
idCVar in_alwaysRun( "in_alwaysRun", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "always run (reverse _speed button) - only in MP" );
idCVar in_useJoystick( "in_useJoystick", "0", CVAR_ARCHIVE | CVAR_BOOL, "enables/disables the gamepad for PC use" );
idCVar in_joystickRumble( "in_joystickRumble", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "enable joystick rumble" );
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!" );
/*
================
usercmd_t::ByteSwap
================
*/
void usercmd_t::ByteSwap()
{
angles[0] = LittleShort( angles[0] );
angles[1] = LittleShort( angles[1] );
angles[2] = LittleShort( angles[2] );
}
/*
================
usercmd_t::Serialize
================
*/
void usercmd_t::Serialize( idSerializer& ser, const usercmd_t& base )
{
ser.SerializeDelta( buttons, base.buttons );
ser.SerializeDelta( forwardmove, base.forwardmove );
ser.SerializeDelta( rightmove, base.rightmove );
ser.SerializeDelta( angles[0], base.angles[0] );
ser.SerializeDelta( angles[1], base.angles[1] );
ser.SerializeDelta( angles[2], base.angles[2] );
ser.SerializeDelta( pos.x, base.pos.x );
ser.SerializeDelta( pos.y, base.pos.y );
ser.SerializeDelta( pos.z, base.pos.z );
ser.SerializeDelta( clientGameMilliseconds, base.clientGameMilliseconds );
ser.SerializeDelta( serverGameMilliseconds, base.serverGameMilliseconds );
ser.SerializeDelta( fireCount, base.fireCount );
ser.SerializeDelta( speedSquared, base.speedSquared );
ser.SerializeDelta( impulse, base.impulse );
ser.SerializeDelta( impulseSequence, base.impulseSequence );
}
/*
================
usercmd_t::operator==
================
*/
bool usercmd_t::operator==( const usercmd_t& rhs ) const
{
return ( buttons == rhs.buttons &&
forwardmove == rhs.forwardmove &&
rightmove == rhs.rightmove &&
angles[0] == rhs.angles[0] &&
angles[1] == rhs.angles[1] &&
angles[2] == rhs.angles[2] &&
impulse == rhs.impulse &&
impulseSequence == rhs.impulseSequence &&
mx == rhs.mx &&
my == rhs.my &&
fireCount == rhs.fireCount &&
speedSquared == speedSquared );
}
const int KEY_MOVESPEED = 127;
userCmdString_t userCmdStrings[] =
{
{ "_moveUp", UB_MOVEUP },
{ "_moveDown", UB_MOVEDOWN },
{ "_left", UB_LOOKLEFT },
{ "_right", UB_LOOKRIGHT },
{ "_forward", UB_MOVEFORWARD },
{ "_back", UB_MOVEBACK },
{ "_lookUp", UB_LOOKUP },
{ "_lookDown", UB_LOOKDOWN },
{ "_moveLeft", UB_MOVELEFT },
{ "_moveRight", UB_MOVERIGHT },
{ "_attack", UB_ATTACK },
{ "_speed", UB_SPEED },
{ "_zoom", UB_ZOOM },
{ "_showScores", UB_SHOWSCORES },
{ "_use", UB_USE },
{ "_impulse0", UB_IMPULSE0 },
{ "_impulse1", UB_IMPULSE1 },
{ "_impulse2", UB_IMPULSE2 },
{ "_impulse3", UB_IMPULSE3 },
{ "_impulse4", UB_IMPULSE4 },
{ "_impulse5", UB_IMPULSE5 },
{ "_impulse6", UB_IMPULSE6 },
{ "_impulse7", UB_IMPULSE7 },
{ "_impulse8", UB_IMPULSE8 },
{ "_impulse9", UB_IMPULSE9 },
{ "_impulse10", UB_IMPULSE10 },
{ "_impulse11", UB_IMPULSE11 },
{ "_impulse12", UB_IMPULSE12 },
{ "_impulse13", UB_IMPULSE13 },
{ "_impulse14", UB_IMPULSE14 },
{ "_impulse15", UB_IMPULSE15 },
{ "_impulse16", UB_IMPULSE16 },
{ "_impulse17", UB_IMPULSE17 },
{ "_impulse18", UB_IMPULSE18 },
{ "_impulse19", UB_IMPULSE19 },
{ "_impulse20", UB_IMPULSE20 },
{ "_impulse21", UB_IMPULSE21 },
{ "_impulse22", UB_IMPULSE22 },
{ "_impulse23", UB_IMPULSE23 },
{ "_impulse24", UB_IMPULSE24 },
{ "_impulse25", UB_IMPULSE25 },
{ "_impulse26", UB_IMPULSE26 },
{ "_impulse27", UB_IMPULSE27 },
{ "_impulse28", UB_IMPULSE28 },
{ "_impulse29", UB_IMPULSE29 },
{ "_impulse30", UB_IMPULSE30 },
{ "_impulse31", UB_IMPULSE31 },
{ NULL, UB_NONE },
};
class buttonState_t
{
public:
int on;
bool held;
buttonState_t()
{
Clear();
};
void Clear();
void SetKeyState( int keystate, bool toggle );
};
/*
================
buttonState_t::Clear
================
*/
void buttonState_t::Clear()
{
held = false;
on = 0;
}
/*
================
buttonState_t::SetKeyState
================
*/
void buttonState_t::SetKeyState( int keystate, bool toggle )
{
if( !toggle )
{
held = false;
on = keystate;
}
else if( !keystate )
{
held = false;
}
else if( !held )
{
held = true;
on ^= 1;
}
}
const int NUM_USER_COMMANDS = sizeof( userCmdStrings ) / sizeof( userCmdString_t );
const int MAX_CHAT_BUFFER = 127;
class idUsercmdGenLocal : public idUsercmdGen
{
public:
idUsercmdGenLocal();
void Init();
void InitForNewMap();
void Shutdown();
void Clear();
void ClearAngles();
void InhibitUsercmd( inhibit_t subsystem, bool inhibit );
int CommandStringUsercmdData( const char* cmdString );
void BuildCurrentUsercmd( int deviceNum );
usercmd_t GetCurrentUsercmd()
{
return cmd;
};
void MouseState( int* x, int* y, int* button, bool* down );
int ButtonState( int key );
int KeyState( int key );
private:
void MakeCurrent();
void InitCurrent();
bool Inhibited();
void AdjustAngles();
void KeyMove();
void CircleToSquare( float& axis_x, float& axis_y ) const;
void HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive );
void JoystickMove();
void JoystickMove2();
void MouseMove();
void CmdButtons();
void AimAssist();
void Mouse();
void Keyboard();
void Joystick( int deviceNum );
void Key( int keyNum, bool down );
idVec3 viewangles;
int impulseSequence;
int impulse;
buttonState_t toggled_crouch;
buttonState_t toggled_run;
buttonState_t toggled_zoom;
int buttonState[UB_MAX_BUTTONS];
bool keyState[K_LAST_KEY];
int inhibitCommands; // true when in console or menu locally
bool initialized;
usercmd_t cmd; // the current cmd being built
int continuousMouseX, continuousMouseY; // for gui event generatioin, never zerod
int mouseButton; // for gui event generatioin
bool mouseDown;
int mouseDx, mouseDy; // added to by mouse events
float joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events
int pollTime;
int lastPollTime;
float lastLookValuePitch;
float lastLookValueYaw;
static idCVar in_yawSpeed;
static idCVar in_pitchSpeed;
static idCVar in_angleSpeedKey;
static idCVar in_toggleRun;
static idCVar in_toggleCrouch;
static idCVar in_toggleZoom;
static idCVar sensitivity;
static idCVar m_pitch;
static idCVar m_yaw;
static idCVar m_smooth;
static idCVar m_showMouseRate;
};
idCVar idUsercmdGenLocal::in_yawSpeed( "in_yawspeed", "140", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "yaw change speed when holding down _left or _right button" );
idCVar idUsercmdGenLocal::in_pitchSpeed( "in_pitchspeed", "140", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "pitch change speed when holding down look _lookUp or _lookDown button" );
idCVar idUsercmdGenLocal::in_angleSpeedKey( "in_anglespeedkey", "1.5", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "angle change scale when holding down _speed button" );
idCVar idUsercmdGenLocal::in_toggleRun( "in_toggleRun", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _speed button toggles run on/off - only in MP" );
idCVar idUsercmdGenLocal::in_toggleCrouch( "in_toggleCrouch", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _movedown button toggles player crouching/standing" );
idCVar idUsercmdGenLocal::in_toggleZoom( "in_toggleZoom", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _zoom button toggles zoom on/off" );
idCVar idUsercmdGenLocal::sensitivity( "sensitivity", "5", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse view sensitivity" );
idCVar idUsercmdGenLocal::m_pitch( "m_pitch", "0.022", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse pitch scale" );
idCVar idUsercmdGenLocal::m_yaw( "m_yaw", "0.022", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse yaw scale" );
idCVar idUsercmdGenLocal::m_smooth( "m_smooth", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "number of samples blended for mouse viewing", 1, 8, idCmdSystem::ArgCompletion_Integer<1, 8> );
idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | CVAR_BOOL, "shows mouse movement" );
static idUsercmdGenLocal localUsercmdGen;
idUsercmdGen* usercmdGen = &localUsercmdGen;
/*
================
idUsercmdGenLocal::idUsercmdGenLocal
================
*/
idUsercmdGenLocal::idUsercmdGenLocal()
{
initialized = false;
pollTime = 0;
lastPollTime = 0;
lastLookValuePitch = 0.0f;
lastLookValueYaw = 0.0f;
impulseSequence = 0;
impulse = 0;
toggled_crouch.Clear();
toggled_run.Clear();
toggled_zoom.Clear();
toggled_run.on = false;
ClearAngles();
Clear();
}
/*
================
idUsercmdGenLocal::InhibitUsercmd
================
*/
void idUsercmdGenLocal::InhibitUsercmd( inhibit_t subsystem, bool inhibit )
{
if( inhibit )
{
inhibitCommands |= 1 << subsystem;
}
else
{
inhibitCommands &= ( 0xffffffff ^ ( 1 << subsystem ) );
}
}
/*
===============
idUsercmdGenLocal::ButtonState
Returns (the fraction of the frame) that the key was down
===============
*/
int idUsercmdGenLocal::ButtonState( int key )
{
if( key < 0 || key >= UB_MAX_BUTTONS )
{
return -1;
}
return ( buttonState[key] > 0 ) ? 1 : 0;
}
/*
===============
idUsercmdGenLocal::KeyState
Returns (the fraction of the frame) that the key was down
bk20060111
===============
*/
int idUsercmdGenLocal::KeyState( int key )
{
if( key < 0 || key >= K_LAST_KEY )
{
return -1;
}
return ( keyState[key] ) ? 1 : 0;
}
//=====================================================================
/*
================
idUsercmdGenLocal::Inhibited
is user cmd generation inhibited
================
*/
bool idUsercmdGenLocal::Inhibited()
{
return ( inhibitCommands != 0 );
}
/*
================
idUsercmdGenLocal::AdjustAngles
Moves the local angle positions
================
*/
void idUsercmdGenLocal::AdjustAngles()
{
float speed = MS2SEC( 16 );
if( toggled_run.on || ( in_alwaysRun.GetBool() && common->IsMultiplayer() ) )
{
speed *= in_angleSpeedKey.GetFloat();
}
viewangles[YAW] -= speed * in_yawSpeed.GetFloat() * ButtonState( UB_LOOKRIGHT );
viewangles[YAW] += speed * in_yawSpeed.GetFloat() * ButtonState( UB_LOOKLEFT );
viewangles[PITCH] -= speed * in_pitchSpeed.GetFloat() * ButtonState( UB_LOOKUP );
viewangles[PITCH] += speed * in_pitchSpeed.GetFloat() * ButtonState( UB_LOOKDOWN );
}
/*
================
idUsercmdGenLocal::KeyMove
Sets the usercmd_t based on key states
================
*/
void idUsercmdGenLocal::KeyMove()
{
int forward = 0;
int side = 0;
side += KEY_MOVESPEED * ButtonState( UB_MOVERIGHT );
side -= KEY_MOVESPEED * ButtonState( UB_MOVELEFT );
forward += KEY_MOVESPEED * ButtonState( UB_MOVEFORWARD );
forward -= KEY_MOVESPEED * ButtonState( UB_MOVEBACK );
cmd.forwardmove += idMath::ClampChar( forward );
cmd.rightmove += idMath::ClampChar( side );
}
/*
=================
idUsercmdGenLocal::MouseMove
=================
*/
void idUsercmdGenLocal::MouseMove()
{
float mx, my;
static int history[8][2];
static int historyCounter;
int i;
history[historyCounter & 7][0] = mouseDx;
history[historyCounter & 7][1] = mouseDy;
// allow mouse movement to be smoothed together
int smooth = m_smooth.GetInteger();
if( smooth < 1 )
{
smooth = 1;
}
if( smooth > 8 )
{
smooth = 8;
}
mx = 0;
my = 0;
for( i = 0 ; i < smooth ; i++ )
{
mx += history[( historyCounter - i + 8 ) & 7 ][0];
my += history[( historyCounter - i + 8 ) & 7 ][1];
}
mx /= smooth;
my /= smooth;
historyCounter++;
if( idMath::Fabs( mx ) > 1000 || idMath::Fabs( my ) > 1000 )
{
Sys_DebugPrintf( "idUsercmdGenLocal::MouseMove: Ignoring ridiculous mouse delta.\n" );
mx = my = 0;
}
mx *= sensitivity.GetFloat();
my *= sensitivity.GetFloat();
if( m_showMouseRate.GetBool() )
{
Sys_DebugPrintf( "[%3i %3i = %5.1f %5.1f] ", mouseDx, mouseDy, mx, my );
}
mouseDx = 0;
mouseDy = 0;
viewangles[YAW] -= m_yaw.GetFloat() * mx * in_mouseSpeed.GetFloat();
viewangles[PITCH] += m_pitch.GetFloat() * in_mouseSpeed.GetFloat() * ( in_mouseInvertLook.GetBool() ? -my : my );
}
/*
========================
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() );
}
idGame* game = common->Game();
if( game != NULL )
{
lookValue *= game->GetAimAssistSensitivity();
}
switch( action )
{
case UB_MOVEFORWARD:
{
float move = ( float )cmd.forwardmove + ( KEY_MOVESPEED * value );
cmd.forwardmove = idMath::ClampChar( idMath::Ftoi( move ) );
break;
}
case UB_MOVEBACK:
{
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_LOOKLEFT:
{
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_LOOKRIGHT:
{
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()
{
float threshold = joy_deadZone.GetFloat();
float triggerThreshold = joy_triggerThreshold.GetFloat();
float axis_y = joystickAxis[ AXIS_LEFT_Y ];
float axis_x = joystickAxis[ AXIS_LEFT_X ];
CircleToSquare( axis_x, axis_y );
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 );
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 );
}
enum transferFunction_t
{
FUNC_LINEAR,
FUNC_LOGARITHMIC,
FUNC_EXPONENTIAL
};
/*
=================
JoypadFunction
=================
*/
idVec2 JoypadFunction(
const idVec2 raw,
const float aimAssistScale,
const float threshold,
const float range,
const transferFunction_t shape,
const bool mergedThreshold )
{
if( range <= threshold )
{
return idVec2( 0.0f, 0.0f );
}
idVec2 threshed;
if( !mergedThreshold )
{
// if the thresholding is performed independently, you can more easily move
// or look in a pure axial direction without drifting
for( int i = 0 ; i < 2 ; i++ )
{
const float v = raw[i];
float t;
if( v > 0.0f )
{
t = Max( 0.0f, v - threshold );
}
else
{
t = Min( 0.0f, v + threshold );
}
threshed[i] = t;
}
}
else
{
// thresholding together is the most predictable in free-form movement,
// but you tend to slide off axis based on which side your thumb is
// on the pad
const float rawLength = raw.Length();
const float afterThreshold = Max( 0.0f, rawLength - threshold );
idVec2 rawDir = raw;
rawDir.Normalize();
threshed = rawDir * afterThreshold;
}
// threshold and range reduce the range of raw values, but we
// scale them back up to the full 0.0 - 1.0 range
const float rangeScale = 1.0f / ( range - threshold );
idVec2 reScaled = threshed * rangeScale;
const float rescaledLen = reScaled.Length();
// if inside the deadband area, return a solid 0,0
if( rescaledLen <= 0.0f )
{
return idVec2( 0.0f, 0.0f );
}
reScaled.Normalize();
// apply the acceleration
float accelerated;
if( shape == FUNC_EXPONENTIAL )
{
accelerated = idMath::Pow( 1.04712854805f, rescaledLen * 100.0f ) * 0.01f;
}
else if( shape == FUNC_LOGARITHMIC )
{
const float power = 2.0f;
accelerated = idMath::Pow( rescaledLen, power );
}
else // FUNC_LINEAR
{
accelerated = rescaledLen;
}
// optionally slow down for aim-assist
const float aimAssisted = accelerated * aimAssistScale;
const float clamped = ( aimAssisted > 1.0f ) ? 1.0f : aimAssisted;
return reScaled * clamped;
}
/*
=================
DrawJoypadTexture
Draws axis and threshold / range rings into an RGBA image
=================
*/
void DrawJoypadTexture(
const int size,
byte image[],
const idVec2 raw,
const float threshold,
const float range,
const transferFunction_t shape,
const bool mergedThreshold )
{
// assert( raw.x >= -1.0f && raw.x <= 1.0f && raw.y >= -1.0f && raw.y <= 1.0f );
idVec2 clamped;
for( int i = 0 ; i < 2 ; i++ )
{
clamped[i] = Max( -1.0f, Min( raw[i], 1.0f ) );
}
const int halfSize = size / 2;
// find the offsets that will give certain values for
// the rings
static const int NUM_RINGS = 5;
float ringSizes[NUM_RINGS] = {};
float ringValue[NUM_RINGS] = { 0.0f, 0.25f, 0.5f, 0.75f, 0.99f };
int ringNum = 0;
for( int i = 1 ; i < size ; i++ )
{
const float v = ( float )i / ( size - 1 );
const idVec2 mapped = JoypadFunction(
idVec2( v, 0.0f ), 1.0f, threshold, range, shape, mergedThreshold );
if( mapped.x > ringValue[ ringNum ] )
{
ringSizes[ ringNum ] = v * halfSize;
ringNum++;
if( ringNum == NUM_RINGS )
{
break;
}
}
}
memset( image, 0, size * size * 4 );
#define PLOT(x,y) ((int *)image)[(int)(y)*size+(int)(x)]=0xffffffff
#define CPLOT(x,y) ((int *)image)[(int)(halfSize+y)*size+(int)(halfSize+x)]=0xffffffff
int clampedX = halfSize + Min( halfSize - 1, ( int )( halfSize * clamped.x ) );
int clampedY = halfSize + Min( halfSize - 1, ( int )( halfSize * clamped.y ) );
// draw the box edge outline and center lines
for( int i = 0 ; i < size ; i++ )
{
PLOT( i, 0 );
PLOT( i, size - 1 );
PLOT( 0, i );
PLOT( size - 1, i );
PLOT( i, clampedY );
PLOT( clampedX, i );
}
const int iThresh = size * threshold * 0.5f;
if( !mergedThreshold )
{
const int open = size * 0.5f - iThresh;
for( int i = 0 ; i < open ; i++ )
{
PLOT( i, halfSize - iThresh );
PLOT( i, halfSize + iThresh );
PLOT( size - 1 - i, halfSize - iThresh );
PLOT( size - 1 - i, halfSize + iThresh );
PLOT( halfSize - iThresh, i );
PLOT( halfSize + iThresh, i );
PLOT( halfSize - iThresh, size - 1 - i );
PLOT( halfSize + iThresh, size - 1 - i );
}
}
// I'm not going to bother writing a proper circle drawing algorithm...
const int octantPoints = size * 2;
float rad = 0.0f;
float radStep = idMath::PI / ( 4 * octantPoints );
for( int point = 0 ; point < octantPoints ; point++, rad += radStep )
{
float s, c;
idMath::SinCos( rad, s, c );
for( int ringNum = 0 ; ringNum < NUM_RINGS ; ringNum++ )
{
const float ringSize = ringSizes[ ringNum ];
const int ix = idMath::Floor( ringSize * c );
const int iy = idMath::Floor( ringSize * s );
#if 0
if( !mergedThreshold && ( ix < iThresh || iy < iThresh ) )
{
continue;
}
#endif
CPLOT( ix, iy );
CPLOT( iy, ix );
CPLOT( -ix, iy );
CPLOT( -iy, ix );
CPLOT( ix, -iy );
CPLOT( iy, -ix );
CPLOT( -ix, -iy );
CPLOT( -iy, -ix );
}
}
#undef PLOT
}
static idVec2 lastLookJoypad;
/*
=================
DrawJoypadTexture
Can be called to fill in a scratch texture for visualization
=================
*/
void DrawJoypadTexture( const int size, byte image[] )
{
const float threshold = joy_deadZone.GetFloat();
const float range = joy_range.GetFloat();
const bool mergedThreshold = joy_mergedThreshold.GetBool();
const transferFunction_t shape = ( transferFunction_t )joy_gammaLook.GetInteger();
DrawJoypadTexture( size, image, lastLookJoypad, threshold, range, shape, mergedThreshold );
}
/*
=================
idUsercmdGenLocal::JoystickMove2
=================
*/
void idUsercmdGenLocal::JoystickMove2()
{
const bool invertLook = in_invertLook.GetBool();
const float threshold = joy_deadZone.GetFloat();
const float range = joy_range.GetFloat();
const transferFunction_t shape = ( transferFunction_t )joy_gammaLook.GetInteger();
const bool mergedThreshold = joy_mergedThreshold.GetBool();
const float pitchSpeed = joy_pitchSpeed.GetFloat();
const float yawSpeed = joy_yawSpeed.GetFloat();
idGame* game = common->Game();
const float aimAssist = game != NULL ? game->GetAimAssistSensitivity() : 1.0f;
idVec2 leftRaw( joystickAxis[ AXIS_LEFT_X ], joystickAxis[ AXIS_LEFT_Y ] );
idVec2 rightRaw( joystickAxis[ AXIS_RIGHT_X ], joystickAxis[ AXIS_RIGHT_Y ] );
// optional stick swap
if( idKeyInput::GetUsercmdAction( K_JOY_STICK1_LEFT ) == UB_LOOKLEFT )
{
const idVec2 temp = leftRaw;
leftRaw = rightRaw;
rightRaw = temp;
}
// optional invert look by inverting the right Y axis
if( invertLook )
{
rightRaw.y = -rightRaw.y;
}
// save for visualization
lastLookJoypad = rightRaw;
idVec2 leftMapped = JoypadFunction( leftRaw, 1.0f, threshold, range, shape, mergedThreshold );
idVec2 rightMapped = JoypadFunction( rightRaw, aimAssist, threshold, range, shape, mergedThreshold );
// because idPhysics_Player::CmdScale scales mvoement values down so that 1,1 = sqrt(2), sqrt(2),
// we need to expand our circular values out to a square
CircleToSquare( leftMapped.x, leftMapped.y );
// add on top of mouse / keyboard move values
cmd.forwardmove = idMath::ClampChar( cmd.forwardmove + KEY_MOVESPEED * -leftMapped.y );
cmd.rightmove = idMath::ClampChar( cmd.rightmove + KEY_MOVESPEED * leftMapped.x );
viewangles[PITCH] += MS2SEC( pollTime - lastPollTime ) * rightMapped.y * pitchSpeed;
viewangles[YAW] += MS2SEC( pollTime - lastPollTime ) * -rightMapped.x * yawSpeed;
const float triggerThreshold = joy_triggerThreshold.GetFloat();
HandleJoystickAxis( K_JOY_TRIGGER1, joystickAxis[ AXIS_LEFT_TRIG ], triggerThreshold, true );
HandleJoystickAxis( K_JOY_TRIGGER2, joystickAxis[ AXIS_RIGHT_TRIG ], triggerThreshold, true );
}
/*
==============
idUsercmdGenLocal::CmdButtons
==============
*/
void idUsercmdGenLocal::CmdButtons()
{
cmd.buttons = 0;
// check the attack button
if( ButtonState( UB_ATTACK ) )
{
cmd.buttons |= BUTTON_ATTACK;
}
// check the use button
if( ButtonState( UB_USE ) )
{
cmd.buttons |= BUTTON_USE;
}
// check the run button
if( toggled_run.on || ( in_alwaysRun.GetBool() && common->IsMultiplayer() ) )
{
cmd.buttons |= BUTTON_RUN;
}
// check the zoom button
if( toggled_zoom.on )
{
cmd.buttons |= BUTTON_ZOOM;
}
if( ButtonState( UB_MOVEUP ) )
{
cmd.buttons |= BUTTON_JUMP;
}
if( toggled_crouch.on )
{
cmd.buttons |= BUTTON_CROUCH;
}
}
/*
================
idUsercmdGenLocal::InitCurrent
inits the current command for this frame
================
*/
void idUsercmdGenLocal::InitCurrent()
{
memset( &cmd, 0, sizeof( cmd ) );
cmd.impulseSequence = impulseSequence;
cmd.impulse = impulse;
cmd.buttons |= ( in_alwaysRun.GetBool() && common->IsMultiplayer() ) ? BUTTON_RUN : 0;
}
/*
================
idUsercmdGenLocal::MakeCurrent
creates the current command for this frame
================
*/
void idUsercmdGenLocal::MakeCurrent()
{
idVec3 oldAngles = viewangles;
if( !Inhibited() )
{
// update toggled key states
toggled_crouch.SetKeyState( ButtonState( UB_MOVEDOWN ), in_toggleCrouch.GetBool() );
toggled_run.SetKeyState( ButtonState( UB_SPEED ), in_toggleRun.GetBool() && common->IsMultiplayer() );
toggled_zoom.SetKeyState( ButtonState( UB_ZOOM ), in_toggleZoom.GetBool() );
// get basic movement from mouse
MouseMove();
// get basic movement from joystick and set key bits
// must be done before CmdButtons!
if( joy_newCode.GetBool() )
{
JoystickMove2();
}
else
{
JoystickMove();
}
// keyboard angle adjustment
AdjustAngles();
// set button bits
CmdButtons();
// get basic movement from keyboard
KeyMove();
// aim assist
AimAssist();
// check to make sure the angles haven't wrapped
if( viewangles[PITCH] - oldAngles[PITCH] > 90 )
{
viewangles[PITCH] = oldAngles[PITCH] + 90;
}
else if( oldAngles[PITCH] - viewangles[PITCH] > 90 )
{
viewangles[PITCH] = oldAngles[PITCH] - 90;
}
}
else
{
mouseDx = 0;
mouseDy = 0;
}
for( int i = 0; i < 3; i++ )
{
cmd.angles[i] = ANGLE2SHORT( viewangles[i] );
}
cmd.mx = continuousMouseX;
cmd.my = continuousMouseY;
impulseSequence = cmd.impulseSequence;
impulse = cmd.impulse;
}
/*
================
idUsercmdGenLocal::AimAssist
================
*/
void idUsercmdGenLocal::AimAssist()
{
// callback to the game to update the aim assist for the current device
idAngles aimAssistAngles( 0.0f, 0.0f, 0.0f );
idGame* game = common->Game();
if( game != NULL )
{
game->GetAimAssistAngles( aimAssistAngles );
}
viewangles[YAW] += aimAssistAngles.yaw;
viewangles[PITCH] += aimAssistAngles.pitch;
viewangles[ROLL] += aimAssistAngles.roll;
}
//=====================================================================
/*
================
idUsercmdGenLocal::CommandStringUsercmdData
Returns the button if the command string is used by the usercmd generator.
================
*/
int idUsercmdGenLocal::CommandStringUsercmdData( const char* cmdString )
{
for( userCmdString_t* ucs = userCmdStrings ; ucs->string ; ucs++ )
{
if( idStr::Icmp( cmdString, ucs->string ) == 0 )
{
return ucs->button;
}
}
return UB_NONE;
}
/*
================
idUsercmdGenLocal::Init
================
*/
void idUsercmdGenLocal::Init()
{
initialized = true;
}
/*
================
idUsercmdGenLocal::InitForNewMap
================
*/
void idUsercmdGenLocal::InitForNewMap()
{
impulseSequence = 0;
impulse = 0;
toggled_crouch.Clear();
toggled_run.Clear();
toggled_zoom.Clear();
toggled_run.on = false;
Clear();
ClearAngles();
}
/*
================
idUsercmdGenLocal::Shutdown
================
*/
void idUsercmdGenLocal::Shutdown()
{
initialized = false;
}
/*
================
idUsercmdGenLocal::Clear
================
*/
void idUsercmdGenLocal::Clear()
{
// clears all key states
memset( buttonState, 0, sizeof( buttonState ) );
memset( keyState, false, sizeof( keyState ) );
memset( joystickAxis, 0, sizeof( joystickAxis ) );
inhibitCommands = false;
mouseDx = mouseDy = 0;
mouseButton = 0;
mouseDown = false;
}
/*
================
idUsercmdGenLocal::ClearAngles
================
*/
void idUsercmdGenLocal::ClearAngles()
{
viewangles.Zero();
}
//======================================================================
/*
===================
idUsercmdGenLocal::Key
Handles mouse/keyboard button actions
===================
*/
void idUsercmdGenLocal::Key( int keyNum, bool down )
{
// Sanity check, sometimes we get double message :(
if( keyState[ keyNum ] == down )
{
return;
}
keyState[ keyNum ] = down;
int action = idKeyInput::GetUsercmdAction( keyNum );
if( down )
{
buttonState[ action ]++;
if( !Inhibited() )
{
if( action >= UB_IMPULSE0 && action <= UB_IMPULSE31 )
{
cmd.impulse = action - UB_IMPULSE0;
cmd.impulseSequence++;
}
}
}
else
{
buttonState[ action ]--;
// we might have one held down across an app active transition
if( buttonState[ action ] < 0 )
{
buttonState[ action ] = 0;
}
}
}
/*
===================
idUsercmdGenLocal::Mouse
===================
*/
void idUsercmdGenLocal::Mouse()
{
int mouseEvents[MAX_MOUSE_EVENTS][2];
int numEvents = Sys_PollMouseInputEvents( mouseEvents );
// Study each of the buffer elements and process them.
for( int i = 0; i < numEvents; i++ )
{
int action = mouseEvents[i][0];
int value = mouseEvents[i][1];
switch( action )
{
case M_ACTION1:
case M_ACTION2:
case M_ACTION3:
case M_ACTION4:
case M_ACTION5:
case M_ACTION6:
case M_ACTION7:
case M_ACTION8:
mouseButton = K_MOUSE1 + ( action - M_ACTION1 );
mouseDown = ( value != 0 );
Key( mouseButton, mouseDown );
break;
case M_DELTAX:
mouseDx += value;
continuousMouseX += value;
break;
case M_DELTAY:
mouseDy += value;
continuousMouseY += value;
break;
case M_DELTAZ: // mouse wheel, may have multiple clicks
{
int key = value < 0 ? K_MWHEELDOWN : K_MWHEELUP;
value = abs( value );
while( value-- > 0 )
{
Key( key, true );
Key( key, false );
mouseButton = key;
mouseDown = true;
}
}
break;
default: // some other undefined button
break;
}
}
}
/*
===============
idUsercmdGenLocal::Keyboard
===============
*/
void idUsercmdGenLocal::Keyboard()
{
int numEvents = Sys_PollKeyboardInputEvents();
// Study each of the buffer elements and process them.
for( int i = 0; i < numEvents; i++ )
{
int key;
bool state;
if( Sys_ReturnKeyboardInputEvent( i, key, state ) )
{
Key( key, state );
}
}
Sys_EndKeyboardInputEvents();
}
/*
===============
idUsercmdGenLocal::Joystick
===============
*/
void idUsercmdGenLocal::Joystick( int deviceNum )
{
int numEvents = Sys_PollJoystickInputEvents( deviceNum );
// 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( 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();
}
/*
================
idUsercmdGenLocal::MouseState
================
*/
void idUsercmdGenLocal::MouseState( int* x, int* y, int* button, bool* down )
{
*x = continuousMouseX;
*y = continuousMouseY;
*button = mouseButton;
*down = mouseDown;
}
/*
================
idUsercmdGenLocal::BuildCurrentUsercmd
================
*/
void idUsercmdGenLocal::BuildCurrentUsercmd( int deviceNum )
{
pollTime = Sys_Milliseconds();
if( pollTime - lastPollTime > 100 )
{
lastPollTime = pollTime - 100;
}
// initialize current usercmd
InitCurrent();
// process the system mouse events
Mouse();
// process the system keyboard events
Keyboard();
// process the system joystick events
if( deviceNum >= 0 && in_useJoystick.GetBool() )
{
Joystick( deviceNum );
}
// create the usercmd
MakeCurrent();
lastPollTime = pollTime;
}