/* =========================================================================== 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 . 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 ) { 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 = %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( 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; }