dhewm3/neo/framework/UsercmdGen.cpp
Daniel Gibson ce4e6f076f Don't ignore "ridiculous mouse deltas", allow sensitivity < 1.0
Modern mice support ridiculously high DPI values, >20'000.
Not sure what that's actually good for, but if people use that, they
ran into the "idUsercmdGenLocal::MouseMove: Ignoring ridiculous
 mouse delta" case which just threw away the mouse input values so the
game didn't respond to mouse input anymore or at least felt choppy.

I'm not sure what that code was originally good for, under which
(undesired) circumstances that happened, but for now it's disabled,
only the warning is still logged, but only once.

For these high DPI values to still be usable (camera not moving way
too fast), it probably makes sense if the mouse sensitivity can be set
to values < 1.0. The CVar always supported that, but I adjusted the
Dhewm3SettingsMenu so it sensitivity can also be set to values between
0.01 and 1 there (still going up to 30, like before).

fixes #616
2024-12-03 19:27:58 +01:00

1413 lines
37 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 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 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 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 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 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 "sys/platform.h"
#include "idlib/math/Vector.h"
#include "idlib/Lib.h"
#include "framework/CVarSystem.h"
#include "framework/KeyInput.h"
#include "framework/async/AsyncNetwork.h"
#include "framework/UsercmdGen.h"
/*
================
usercmd_t::ByteSwap
================
*/
void usercmd_t::ByteSwap( void ) {
angles[0] = LittleShort( angles[0] );
angles[1] = LittleShort( angles[1] );
angles[2] = LittleShort( angles[2] );
sequence = LittleInt( sequence );
}
/*
================
usercmd_t::operator==
================
*/
bool usercmd_t::operator==( const usercmd_t &rhs ) const {
return ( buttons == rhs.buttons &&
forwardmove == rhs.forwardmove &&
rightmove == rhs.rightmove &&
upmove == rhs.upmove &&
angles[0] == rhs.angles[0] &&
angles[1] == rhs.angles[1] &&
angles[2] == rhs.angles[2] &&
impulse == rhs.impulse &&
flags == rhs.flags &&
mx == rhs.mx &&
my == rhs.my );
}
const int KEY_MOVESPEED = 127;
typedef enum {
UB_NONE,
UB_UP,
UB_DOWN,
UB_LEFT,
UB_RIGHT,
UB_FORWARD,
UB_BACK,
UB_LOOKUP,
UB_LOOKDOWN,
UB_STRAFE,
UB_MOVELEFT,
UB_MOVERIGHT,
UB_BUTTON0,
UB_BUTTON1,
UB_BUTTON2,
UB_BUTTON3,
UB_BUTTON4,
UB_BUTTON5,
UB_BUTTON6,
UB_BUTTON7,
UB_ATTACK, // NOTE: this value (20) is hardcoded in idUserInterfaceLocal::HandleEvent() !
UB_SPEED,
UB_ZOOM,
UB_SHOWSCORES,
UB_MLOOK,
UB_IMPULSE0,
UB_IMPULSE1,
UB_IMPULSE2,
UB_IMPULSE3,
UB_IMPULSE4,
UB_IMPULSE5,
UB_IMPULSE6,
UB_IMPULSE7,
UB_IMPULSE8,
UB_IMPULSE9,
UB_IMPULSE10,
UB_IMPULSE11,
UB_IMPULSE12,
UB_IMPULSE13,
UB_IMPULSE14,
UB_IMPULSE15,
UB_IMPULSE16,
UB_IMPULSE17,
UB_IMPULSE18,
UB_IMPULSE19,
UB_IMPULSE20,
UB_IMPULSE21,
UB_IMPULSE22,
UB_IMPULSE23,
UB_IMPULSE24,
UB_IMPULSE25,
UB_IMPULSE26,
UB_IMPULSE27,
UB_IMPULSE28,
UB_IMPULSE29,
UB_IMPULSE30,
UB_IMPULSE31,
UB_IMPULSE32,
UB_IMPULSE33,
UB_IMPULSE34,
UB_IMPULSE35,
UB_IMPULSE36,
UB_IMPULSE37,
UB_IMPULSE38,
UB_IMPULSE39,
UB_IMPULSE40,
UB_IMPULSE41,
UB_IMPULSE42,
UB_IMPULSE43,
UB_IMPULSE44,
UB_IMPULSE45,
UB_IMPULSE46,
UB_IMPULSE47,
UB_IMPULSE48,
UB_IMPULSE49,
UB_IMPULSE50,
UB_IMPULSE51,
UB_IMPULSE52,
UB_IMPULSE53,
UB_IMPULSE54,
UB_IMPULSE55,
UB_IMPULSE56,
UB_IMPULSE57,
UB_IMPULSE58,
UB_IMPULSE59,
UB_IMPULSE60,
UB_IMPULSE61,
UB_IMPULSE62,
UB_IMPULSE63,
UB_MAX_BUTTONS
} usercmdButton_t;
typedef struct {
const char *string;
usercmdButton_t button;
} userCmdString_t;
userCmdString_t userCmdStrings[] = {
{ "_moveUp", UB_UP },
{ "_moveDown", UB_DOWN },
{ "_left", UB_LEFT },
{ "_right", UB_RIGHT },
{ "_forward", UB_FORWARD },
{ "_back", UB_BACK },
{ "_lookUp", UB_LOOKUP },
{ "_lookDown", UB_LOOKDOWN },
{ "_strafe", UB_STRAFE },
{ "_moveLeft", UB_MOVELEFT },
{ "_moveRight", UB_MOVERIGHT },
{ "_attack", UB_ATTACK },
{ "_speed", UB_SPEED },
{ "_zoom", UB_ZOOM },
{ "_showScores", UB_SHOWSCORES },
{ "_mlook", UB_MLOOK },
{ "_button0", UB_BUTTON0 },
{ "_button1", UB_BUTTON1 },
{ "_button2", UB_BUTTON2 },
{ "_button3", UB_BUTTON3 },
{ "_button4", UB_BUTTON4 },
{ "_button5", UB_BUTTON5 },
{ "_button6", UB_BUTTON6 },
{ "_button7", UB_BUTTON7 },
{ "_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 },
{ "_impulse32", UB_IMPULSE32 },
{ "_impulse33", UB_IMPULSE33 },
{ "_impulse34", UB_IMPULSE34 },
{ "_impulse35", UB_IMPULSE35 },
{ "_impulse36", UB_IMPULSE36 },
{ "_impulse37", UB_IMPULSE37 },
{ "_impulse38", UB_IMPULSE38 },
{ "_impulse39", UB_IMPULSE39 },
{ "_impulse40", UB_IMPULSE40 },
{ "_impulse41", UB_IMPULSE41 },
{ "_impulse42", UB_IMPULSE42 },
{ "_impulse43", UB_IMPULSE43 },
{ "_impulse44", UB_IMPULSE44 },
{ "_impulse45", UB_IMPULSE45 },
{ "_impulse46", UB_IMPULSE46 },
{ "_impulse47", UB_IMPULSE47 },
{ "_impulse48", UB_IMPULSE48 },
{ "_impulse49", UB_IMPULSE49 },
{ "_impulse50", UB_IMPULSE50 },
{ "_impulse51", UB_IMPULSE51 },
{ "_impulse52", UB_IMPULSE52 },
{ "_impulse53", UB_IMPULSE53 },
{ "_impulse54", UB_IMPULSE54 },
{ "_impulse55", UB_IMPULSE55 },
{ "_impulse56", UB_IMPULSE56 },
{ "_impulse57", UB_IMPULSE57 },
{ "_impulse58", UB_IMPULSE58 },
{ "_impulse59", UB_IMPULSE59 },
{ "_impulse60", UB_IMPULSE60 },
{ "_impulse61", UB_IMPULSE61 },
{ "_impulse62", UB_IMPULSE62 },
{ "_impulse63", UB_IMPULSE63 },
{ NULL, UB_NONE },
};
class buttonState_t {
public:
int on;
bool held;
buttonState_t() { Clear(); };
void Clear( void );
void SetKeyState( int keystate, bool toggle );
};
/*
================
buttonState_t::Clear
================
*/
void buttonState_t::Clear( void ) {
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 );
void Init( void );
void InitForNewMap( void );
void Shutdown( void );
void Clear( void );
void ClearAngles( void );
usercmd_t TicCmd( int ticNumber );
void InhibitUsercmd( inhibit_t subsystem, bool inhibit );
void UsercmdInterrupt( void );
int CommandStringUsercmdData( const char *cmdString );
int GetNumUserCommands( void );
const char * GetUserCommandName( int index );
void MouseState( int *x, int *y, int *button, bool *down );
int ButtonState( int key );
int KeyState( int key );
usercmd_t GetDirectUsercmd( void );
private:
void MakeCurrent( void );
void InitCurrent( void );
bool Inhibited( void );
void AdjustAngles( void );
void KeyMove( void );
void CircleToSquare( float & axis_x, float & axis_y ) const;
void HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive );
void JoystickMove( void );
void JoystickFakeMouse(float axis_x, float axis_y, float deadzone);
void MouseMove( void );
void CmdButtons( void );
void Mouse( void );
void Keyboard( void );
void Joystick( void );
void Key( int keyNum, bool down );
// DG: if in_allowAlwaysRunInSP is set, you can use always run or toggle run even in SP.
// Why not, we're all adults here, if you run out of stamina it's your problem
// (though I'll probably add another CVar to disable stamina in SP)
inline bool AlwaysRunAllowed() const
{
return in_allowAlwaysRunInSP.GetBool() || idAsyncNetwork::IsActive();
}
idVec3 viewangles;
int flags;
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
int lastCommandTime;
bool initialized;
usercmd_t cmd; // the current cmd being built
usercmd_t buffered[MAX_BUFFERED_USERCMD];
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;
bool heldJump; // TODO: ???
static idCVar in_yawSpeed;
static idCVar in_pitchSpeed;
static idCVar in_angleSpeedKey;
static idCVar in_freeLook;
static idCVar in_allowAlwaysRunInSP; // DG: I don't care, I'm not a cop
static idCVar in_alwaysRun;
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_strafeScale;
static idCVar m_smooth;
static idCVar m_strafeSmooth;
static idCVar m_showMouseRate;
static idCVar m_invertLook; // DG: added this
};
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_freeLook( "in_freeLook", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "look around with mouse (reverse _mlook button)" );
idCVar idUsercmdGenLocal::in_allowAlwaysRunInSP ( "in_allowAlwaysRunInSP", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "Allow always run and toggle run in Single Player as well - keep in mind you may run out of stamina!" );
idCVar idUsercmdGenLocal::in_alwaysRun( "in_alwaysRun", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "always run (reverse _speed button) - only in MP, unless in_allowAlwaysRunInSP is set" );
idCVar idUsercmdGenLocal::in_toggleRun( "in_toggleRun", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _speed button toggles run on/off - only in MP, unless in_allowAlwaysRunInSP is set" );
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_strafeScale( "m_strafeScale", "6.25", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse strafe movement 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_strafeSmooth( "m_strafeSmooth", "4", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "number of samples blended for mouse moving", 1, 8, idCmdSystem::ArgCompletion_Integer<1,8> );
idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | CVAR_BOOL, "shows mouse movement" );
idCVar idUsercmdGenLocal::m_invertLook( "m_invertLook", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "invert mouse look 0: don't invert, 1: invert up/down (flight controls), 2: invert left/right, 3: invert both", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> );
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.25", CVAR_FLOAT | CVAR_ARCHIVE, "specifies how large the dead-zone is on the joystick" );
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", "130", 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 );
idCVar joy_invertLook( "joy_invertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" );
// 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_useGamepad( "in_useGamepad", "1", CVAR_ARCHIVE | CVAR_BOOL, "enables/disables the gamepad for PC use" );
// TODO 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!" );
static idUsercmdGenLocal localUsercmdGen;
idUsercmdGen *usercmdGen = &localUsercmdGen;
/*
================
idUsercmdGenLocal::idUsercmdGenLocal
================
*/
idUsercmdGenLocal::idUsercmdGenLocal( void ) {
lastCommandTime = 0;
initialized = false;
flags = 0;
impulse = 0;
toggled_crouch.Clear();
toggled_run.Clear();
toggled_zoom.Clear();
toggled_run.on = in_alwaysRun.GetBool();
lastLookValuePitch = lastLookValueYaw = 0.0f;
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::GetNumUserCommands
================
*/
int idUsercmdGenLocal::GetNumUserCommands( void ) {
return NUM_USER_COMMANDS;
}
/*
================
idUsercmdGenLocal::GetNumUserCommands
================
*/
const char *idUsercmdGenLocal::GetUserCommandName( int index ) {
if (index >= 0 && index < NUM_USER_COMMANDS) {
return userCmdStrings[index].string;
}
return "";
}
/*
================
idUsercmdGenLocal::Inhibited
is user cmd generation inhibited
================
*/
bool idUsercmdGenLocal::Inhibited( void ) {
return ( inhibitCommands != 0);
}
/*
================
idUsercmdGenLocal::AdjustAngles
Moves the local angle positions
================
*/
void idUsercmdGenLocal::AdjustAngles( void ) {
float speed;
if ( toggled_run.on ^ ( in_alwaysRun.GetBool() && AlwaysRunAllowed() ) ) { // DG: always run in SP
speed = idMath::M_MS2SEC * USERCMD_MSEC * in_angleSpeedKey.GetFloat();
} else {
speed = idMath::M_MS2SEC * USERCMD_MSEC;
}
if ( !ButtonState( UB_STRAFE ) ) {
viewangles[YAW] -= speed * in_yawSpeed.GetFloat() * ButtonState( UB_RIGHT );
viewangles[YAW] += speed * in_yawSpeed.GetFloat() * ButtonState( UB_LEFT );
}
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( void ) {
int forward, side, up;
forward = 0;
side = 0;
up = 0;
if ( ButtonState( UB_STRAFE ) ) {
side += KEY_MOVESPEED * ButtonState( UB_RIGHT );
side -= KEY_MOVESPEED * ButtonState( UB_LEFT );
}
side += KEY_MOVESPEED * ButtonState( UB_MOVERIGHT );
side -= KEY_MOVESPEED * ButtonState( UB_MOVELEFT );
up -= KEY_MOVESPEED * toggled_crouch.on;
up += KEY_MOVESPEED * ButtonState( UB_UP );
forward += KEY_MOVESPEED * ButtonState( UB_FORWARD );
forward -= KEY_MOVESPEED * ButtonState( UB_BACK );
// only set each movement variable if its unset at this point.
// NOTE: joystick input happens before this.
if (cmd.forwardmove == 0) {
cmd.forwardmove = idMath::ClampChar( forward );
}
if (cmd.rightmove == 0) {
cmd.rightmove = idMath::ClampChar( side );
}
if (cmd.upmove == 0) {
cmd.upmove = idMath::ClampChar( up );
}
}
/*
=================
idUsercmdGenLocal::MouseMove
=================
*/
void idUsercmdGenLocal::MouseMove( void ) {
float mx, my, strafeMx, strafeMy;
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;
// use a larger smoothing for strafing
smooth = m_strafeSmooth.GetInteger();
if ( smooth < 1 ) {
smooth = 1;
}
if ( smooth > 8 ) {
smooth = 8;
}
strafeMx = 0;
strafeMy = 0;
for ( i = 0 ; i < smooth ; i++ ) {
strafeMx += history[ ( historyCounter - i + 8 ) & 7 ][0];
strafeMy += history[ ( historyCounter - i + 8 ) & 7 ][1];
}
strafeMx /= smooth;
strafeMy /= smooth;
historyCounter++;
if ( idMath::Fabs( mx ) > 1000 || idMath::Fabs( my ) > 1000 ) {
// DG: This caused problems with High-DPI mice - there those values can legitimately happen.
// If it turns out that spurious big values happen for other reasons, we'll
// need a smarter check. Leaving the Sys_DebugPrintf() here to make detecting
// those cases easier, but added a static bool so High DPI mice don't spam the log.
static bool warningShown = false;
if ( !warningShown ) {
warningShown = true;
Sys_DebugPrintf( "idUsercmdGenLocal::MouseMove: Detected ridiculous mouse delta (expected with High DPI mice, though!).\n" );
}
//mx = my = 0;
}
mx *= sensitivity.GetFloat();
my *= sensitivity.GetFloat();
if ( m_showMouseRate.GetBool() ) {
Sys_DebugPrintf( "[%3i %3i = %5.1f %5.1f = %5.1f %5.1f] ", mouseDx, mouseDy, mx, my, strafeMx, strafeMy );
}
mouseDx = 0;
mouseDy = 0;
if ( !strafeMx && !strafeMy ) {
return;
}
if ( ButtonState( UB_STRAFE ) || !( cmd.buttons & BUTTON_MLOOK ) ) {
// add mouse X/Y movement to cmd
strafeMx *= m_strafeScale.GetFloat();
strafeMy *= m_strafeScale.GetFloat();
// clamp as a vector, instead of separate floats
float len = sqrt( strafeMx * strafeMx + strafeMy * strafeMy );
if ( len > 127 ) {
strafeMx = strafeMx * 127 / len;
strafeMy = strafeMy * 127 / len;
}
}
if ( !ButtonState( UB_STRAFE ) ) {
// m_invertLook 2 and 3 invert looking left/right
float invYaw = ( m_invertLook.GetInteger() & 2 ) ? -1.0f : 1.0f;
viewangles[YAW] -= m_yaw.GetFloat() * mx * invYaw;
} else {
cmd.rightmove = idMath::ClampChar( (int)(cmd.rightmove + strafeMx) );
}
if ( !ButtonState( UB_STRAFE ) && ( cmd.buttons & BUTTON_MLOOK ) ) {
// m_invertLook 1 and 3 invert looking up/down
float invPitch = ( m_invertLook.GetInteger() & 1 ) ? -1.0f : 1.0f;
viewangles[PITCH] += m_pitch.GetFloat() * my * invPitch;
} else {
cmd.forwardmove = idMath::ClampChar( (int)(cmd.forwardmove - strafeMy) );
}
}
/*
========================
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() );
}
#if 0 // TODO: aim assist maybe.
idGame * game = common->Game();
if ( game != NULL ) {
lookValue *= game->GetAimAssistSensitivity();
}
#endif
switch ( action ) {
case UB_FORWARD: {
float move = (float)cmd.forwardmove + ( KEY_MOVESPEED * value );
cmd.forwardmove = idMath::ClampChar( idMath::Ftoi( move ) );
break;
}
case UB_BACK: {
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 = joy_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 = joy_invertLook.GetBool() ? -1.0f : 1.0f;
viewangles[PITCH] += MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch;
break;
}
case UB_LEFT: {
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_RIGHT: {
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;
}
}
}
static float joyAxisToMouseDelta(float axis, float deadzone)
{
float ret = 0.0f;
float val = fabsf(axis); // calculations below require a positive value
if(val > deadzone) {
// from deadzone .. 1 to 0 .. 1-deadzone
val -= deadzone;
// and then to 0..1
val = val * (1.0f / (1.0f - deadzone));
// make it exponential curve - exp(val*3) should return sth between 1 and 20;
// then turning that into 0.5 .. 10
ret = expf( val * 3.0f ) * 0.5f;
if(axis < 0.0f) // restore sign
ret = -ret;
}
return ret;
}
extern bool D3_IN_interactiveIngameGuiActive; // from sys/events.cpp
void idUsercmdGenLocal::JoystickFakeMouse(float axis_x, float axis_y, float deadzone)
{
if ( D3_IN_interactiveIngameGuiActive ) {
float x = joyAxisToMouseDelta(axis_x, deadzone);
float y = joyAxisToMouseDelta(axis_y, deadzone);
continuousMouseX += x;
continuousMouseY += y;
}
}
/*
=================
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 );
JoystickFakeMouse( axis_x, axis_y, threshold );
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 );
JoystickFakeMouse( axis_x, axis_y, threshold );
HandleJoystickAxis( K_JOY_TRIGGER1, joystickAxis[ AXIS_LEFT_TRIG ], triggerThreshold, true );
HandleJoystickAxis( K_JOY_TRIGGER2, joystickAxis[ AXIS_RIGHT_TRIG ], triggerThreshold, true );
}
/*
==============
idUsercmdGenLocal::CmdButtons
==============
*/
void idUsercmdGenLocal::CmdButtons( void ) {
int i;
cmd.buttons = 0;
// figure button bits
for (i = 0 ; i <= 7 ; i++) {
if ( ButtonState( (usercmdButton_t)( UB_BUTTON0 + i ) ) ) {
cmd.buttons |= 1 << i;
}
}
// check the attack button
if ( ButtonState( UB_ATTACK ) ) {
cmd.buttons |= BUTTON_ATTACK;
}
// check the run button
if ( toggled_run.on ^ ( in_alwaysRun.GetBool() && AlwaysRunAllowed() ) ) { // DG: always run in SP
cmd.buttons |= BUTTON_RUN;
}
// check the zoom button
if ( toggled_zoom.on ) {
cmd.buttons |= BUTTON_ZOOM;
}
// check the scoreboard button
if ( ButtonState( UB_SHOWSCORES ) || ButtonState( UB_IMPULSE19 ) ) {
// the button is toggled in SP mode as well but without effect
cmd.buttons |= BUTTON_SCORES;
}
// check the mouse look button
if ( ButtonState( UB_MLOOK ) ^ in_freeLook.GetInteger() ) {
cmd.buttons |= BUTTON_MLOOK;
}
}
/*
================
idUsercmdGenLocal::InitCurrent
inits the current command for this frame
================
*/
void idUsercmdGenLocal::InitCurrent( void ) {
memset( &cmd, 0, sizeof( cmd ) );
cmd.flags = flags;
cmd.impulse = impulse;
cmd.buttons |= ( in_alwaysRun.GetBool() && AlwaysRunAllowed() ) ? BUTTON_RUN : 0; // DG: always run in SP
cmd.buttons |= in_freeLook.GetBool() ? BUTTON_MLOOK : 0;
}
/*
================
idUsercmdGenLocal::MakeCurrent
creates the current command for this frame
================
*/
void idUsercmdGenLocal::MakeCurrent( void ) {
idVec3 oldAngles;
int i;
oldAngles = viewangles;
if ( !Inhibited() ) {
// update toggled key states
toggled_crouch.SetKeyState( ButtonState( UB_DOWN ), in_toggleCrouch.GetBool() );
// DG: allow toggle run in SP (of in_allowAlwaysRun is set)
toggled_run.SetKeyState( ButtonState( UB_SPEED ), in_toggleRun.GetBool() && AlwaysRunAllowed() );
toggled_zoom.SetKeyState( ButtonState( UB_ZOOM ), in_toggleZoom.GetBool() );
// keyboard angle adjustment
AdjustAngles();
// get basic movement from joystick
JoystickMove();
// set button bits
CmdButtons();
// get basic movement from keyboard
KeyMove();
// get basic movement from mouse
MouseMove();
// 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 ( i = 0; i < 3; i++ ) {
cmd.angles[i] = ANGLE2SHORT( viewangles[i] );
}
cmd.mx = continuousMouseX;
cmd.my = continuousMouseY;
flags = cmd.flags;
impulse = cmd.impulse;
}
//=====================================================================
/*
================
idUsercmdGenLocal::CommandStringUsercmdData
Returns the button if the command string is used by the async 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( void ) {
initialized = true;
}
/*
================
idUsercmdGenLocal::InitForNewMap
================
*/
void idUsercmdGenLocal::InitForNewMap( void ) {
flags = 0;
impulse = 0;
toggled_crouch.Clear();
toggled_run.Clear();
toggled_zoom.Clear();
toggled_run.on = in_alwaysRun.GetBool();
Clear();
ClearAngles();
}
/*
================
idUsercmdGenLocal::Shutdown
================
*/
void idUsercmdGenLocal::Shutdown( void ) {
initialized = false;
}
/*
================
idUsercmdGenLocal::Clear
================
*/
void idUsercmdGenLocal::Clear( void ) {
// 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( void ) {
viewangles.Zero();
}
/*
================
idUsercmdGenLocal::TicCmd
Returns a buffered usercmd
================
*/
usercmd_t idUsercmdGenLocal::TicCmd( int ticNumber ) {
// the packetClient code can legally ask for com_ticNumber+1, because
// it is in the async code and com_ticNumber hasn't been updated yet,
// but all other code should never ask for anything > com_ticNumber
if ( ticNumber > com_ticNumber+1 ) {
common->Error( "idUsercmdGenLocal::TicCmd ticNumber > com_ticNumber" );
}
if ( ticNumber <= com_ticNumber - MAX_BUFFERED_USERCMD ) {
// this can happen when something in the game code hitches badly, allowing the
// async code to overflow the buffers
//common->Printf( "warning: idUsercmdGenLocal::TicCmd ticNumber <= com_ticNumber - MAX_BUFFERED_USERCMD\n" );
}
return buffered[ ticNumber & (MAX_BUFFERED_USERCMD-1) ];
}
//======================================================================
/*
===================
idUsercmdGenLocal::Key
Handles async 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 );
// TODO: if action == 0 return ?
if ( down ) {
buttonState[ action ]++;
if ( !Inhibited() ) {
if ( action >= UB_IMPULSE0 && action <= UB_IMPULSE61 ) {
cmd.impulse = action - UB_IMPULSE0;
cmd.flags ^= UCF_IMPULSE_SEQUENCE;
}
}
} 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( void ) {
int i, numEvents;
numEvents = Sys_PollMouseInputEvents();
if ( numEvents ) {
//
// Study each of the buffer elements and process them.
//
for( i = 0; i < numEvents; i++ ) {
int action, value;
if ( Sys_ReturnMouseInputEvent( i, action, value ) ) {
if ( action >= M_ACTION1 && action <= M_ACTION8 ) {
mouseButton = K_MOUSE1 + ( action - M_ACTION1 );
mouseDown = ( value != 0 );
Key( mouseButton, mouseDown );
} else {
switch ( action ) {
case M_DELTAX:
mouseDx += value;
continuousMouseX += value;
break;
case M_DELTAY:
mouseDy += value;
continuousMouseY += value;
break;
case M_DELTAZ:
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;
}
}
}
}
}
Sys_EndMouseInputEvents();
}
/*
===============
idUsercmdGenLocal::Keyboard
===============
*/
void idUsercmdGenLocal::Keyboard( void ) {
int numEvents = Sys_PollKeyboardInputEvents();
if ( numEvents ) {
//
// Study each of the buffer elements and process them.
//
int key;
bool state;
for( int i = 0; i < numEvents; i++ ) {
if (Sys_ReturnKeyboardInputEvent( i, key, state )) {
Key ( key, state );
}
}
}
Sys_EndKeyboardInputEvents();
}
/*
===============
idUsercmdGenLocal::Joystick
===============
*/
void idUsercmdGenLocal::Joystick( void ) {
int numEvents = Sys_PollJoystickInputEvents( 0 );
// 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_ACTION_FIRST && action <= J_ACTION_MAX ) {
int joyButton = K_FIRST_JOY + ( action - J_ACTION_FIRST );
Key( joyButton, ( value != 0 ) );
} else if ( ( action >= J_AXIS_MIN ) && ( action <= J_AXIS_MAX ) ) {
joystickAxis[ action - J_AXIS_MIN ] = static_cast<float>( value ) / 32767.0f;
} else {
//assert( !"Unknown joystick event" );
}
}
}
Sys_EndJoystickInputEvents();
}
/*
================
idUsercmdGenLocal::UsercmdInterrupt
Called asyncronously
================
*/
void idUsercmdGenLocal::UsercmdInterrupt( void ) {
// dedicated servers won't create usercmds
if ( !initialized ) {
return;
}
// init the usercmd for com_ticNumber+1
InitCurrent();
// process the system mouse events
Mouse();
// process the system keyboard events
Keyboard();
// process the system joystick events
if ( in_useGamepad.GetBool() ) {
Joystick();
}
// create the usercmd for com_ticNumber+1
MakeCurrent();
// save a number for debugging cmdDemos and networking
cmd.sequence = com_ticNumber+1;
buffered[(com_ticNumber+1) & (MAX_BUFFERED_USERCMD-1)] = cmd;
}
/*
================
idUsercmdGenLocal::MouseState
================
*/
void idUsercmdGenLocal::MouseState( int *x, int *y, int *button, bool *down ) {
*x = continuousMouseX;
*y = continuousMouseY;
*button = mouseButton;
*down = mouseDown;
}
/*
================
idUsercmdGenLocal::GetDirectUsercmd
================
*/
usercmd_t idUsercmdGenLocal::GetDirectUsercmd( void ) {
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 ( in_useGamepad.GetBool() ) {
Joystick();
}
// create the usercmd
MakeCurrent();
cmd.duplicateCount = 0;
lastPollTime = pollTime;
return cmd;
}