/* Copyright (C) 1997-2001 Id Software, Inc. This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cl.input.c -- builds an intended movement command to send to the server #include "client.h" cvar_t *cl_nodelta; extern unsigned sys_frame_time; unsigned frame_msec; unsigned old_sys_frame_time; static vec3_t oldAngles; /* =============================================================================== MOUSE =============================================================================== */ extern cvar_t *in_grabinconsole; extern cvar_t *m_filter; extern cvar_t *m_accel; extern cvar_t *in_minMsecs; static qboolean mlooking = qfalse; static int mouse_x, mouse_y, old_mouse_x, old_mouse_y; // wsw : pb : moved mousemove to cl_input (q3 like) void CL_MouseMove( usercmd_t *cmd, int mx, int my ) { float accelSensitivity; float rate; if( cls.key_dest == key_menu ) { CL_UIModule_MouseMove( mx, my ); return; } if( (cls.key_dest == key_console) && !in_grabinconsole->integer ) return; if( m_filter->integer ) { mouse_x = (mx + old_mouse_x) * 0.5; mouse_y = (my + old_mouse_y) * 0.5; } else { mouse_x = mx; mouse_y = my; } old_mouse_x = mx; old_mouse_y = my; rate = sqrt( mouse_x * mouse_x + mouse_y * mouse_y ) / (float)frame_msec; accelSensitivity = sensitivity->value + rate * m_accel->value; // wsw : pb : to be added later when +zoom support // scale by FOV //accelSensitivity *= cl.cgameSensitivity; /* if ( rate && cl_showMouseRate->integer ) { Com_Printf( "%f : %f\n", rate, accelSensitivity ); } */ /* OLD mouse_x *= sensitivity->value; mouse_y *= sensitivity->value; */ // add mouse X/Y movement to cmd if( (in_strafe.state & 1) || (lookstrafe->integer && mlooking ) ) cmd->sidemove += (accelSensitivity * m_side->value) * mouse_x; else cl.viewangles[YAW] -= (accelSensitivity * m_yaw->value) * mouse_x; if( (mlooking || cl_freelook->integer) && !(in_strafe.state & 1) ) cl.viewangles[PITCH] += (accelSensitivity * m_pitch->value) * mouse_y; else cmd->forwardmove -= (accelSensitivity * m_forward->value) * mouse_y; } void IN_MLookDown (void) { mlooking = qtrue; } void IN_MLookUp (void) { mlooking = qfalse; if( !cl_freelook->integer && lookspring->integer ) IN_CenterView (); } /* =============================================================================== KEY BUTTONS Continuous button event tracking is complicated by the fact that two different input sources (say, mouse button 1 and the control key) can both press the same button, but the button should only be released when both of the pressing key have been released. When a key event issues a button command (+forward, +attack, etc), it appends its key number as a parameter to the command so it can be matched up with the release. state bit 0 is the current state of the key state bit 1 is edge triggered on the up to down transition state bit 2 is edge triggered on the down to up transition Key_Event (int key, qboolean down, unsigned time); +mlook src time =============================================================================== */ kbutton_t in_klook; kbutton_t in_left, in_right, in_forward, in_back; kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; kbutton_t in_strafe, in_speed, in_use, in_attack; kbutton_t in_up, in_down; kbutton_t in_special; // wsw : MiK static void KeyDown( kbutton_t *b ) { int k; char *c; c = Cmd_Argv(1); if (c[0]) k = atoi(c); else k = -1; // typed manually at the console for continuous down if( k == b->down[0] || k == b->down[1] ) return; // repeating key if( !b->down[0] ) b->down[0] = k; else if( !b->down[1] ) b->down[1] = k; else { Com_Printf( "Three keys down for a button!\n" ); return; } if( b->state & 1 ) return; // still down // save timestamp c = Cmd_Argv(2); b->downtime = atoi(c); if( !b->downtime ) b->downtime = sys_frame_time - 100; b->state |= 1 + 2; // down + impulse down } static void KeyUp( kbutton_t *b ) { int k; char *c; unsigned uptime; c = Cmd_Argv(1); if( c[0] ) k = atoi(c); else { // typed manually at the console, assume for unsticking, so clear all b->down[0] = b->down[1] = 0; b->state = 4; // impulse up return; } if( b->down[0] == k ) b->down[0] = 0; else if( b->down[1] == k ) b->down[1] = 0; else return; // key up without coresponding down (menu pass through) if( b->down[0] || b->down[1] ) return; // some other key is still holding it down if( !(b->state & 1) ) return; // still up (this should not happen) // save timestamp c = Cmd_Argv(2); uptime = atoi(c); if( uptime ) b->msec += uptime - b->downtime; else b->msec += 10; b->state &= ~1; // now up b->state |= 4; // impulse up } void IN_KLookDown( void ) {KeyDown(&in_klook);} void IN_KLookUp( void ) {KeyUp(&in_klook);} void IN_UpDown( void ) {KeyDown(&in_up);} void IN_UpUp( void ) {KeyUp(&in_up);} void IN_DownDown( void ) {KeyDown(&in_down);} void IN_DownUp( void ) {KeyUp(&in_down);} void IN_LeftDown( void ) {KeyDown(&in_left);} void IN_LeftUp( void ) {KeyUp(&in_left);} void IN_RightDown( void ) {KeyDown(&in_right);} void IN_RightUp( void ) {KeyUp(&in_right);} void IN_ForwardDown( void ) {KeyDown(&in_forward);} void IN_ForwardUp( void ) {KeyUp(&in_forward);} void IN_BackDown( void ) {KeyDown(&in_back);} void IN_BackUp( void ) {KeyUp(&in_back);} void IN_LookupDown( void ) {KeyDown(&in_lookup);} void IN_LookupUp( void ) {KeyUp(&in_lookup);} void IN_LookdownDown( void ) {KeyDown(&in_lookdown);} void IN_LookdownUp( void ) {KeyUp(&in_lookdown);} void IN_MoveleftDown( void ) {KeyDown(&in_moveleft);} void IN_MoveleftUp( void ) {KeyUp(&in_moveleft);} void IN_MoverightDown( void ) {KeyDown(&in_moveright);} void IN_MoverightUp( void ) {KeyUp(&in_moveright);} void IN_SpeedDown( void ) {KeyDown(&in_speed);} void IN_SpeedUp( void ) {KeyUp(&in_speed);} void IN_StrafeDown( void ) {KeyDown(&in_strafe);} void IN_StrafeUp( void ) {KeyUp(&in_strafe);} void IN_AttackDown( void ) {KeyDown(&in_attack);} void IN_AttackUp( void ) {KeyUp(&in_attack);} void IN_UseDown( void ) {KeyDown(&in_use);} void IN_UseUp( void ) {KeyUp(&in_use);} //wsw void IN_SpecialDown( void ) {KeyDown(&in_special);} void IN_SpecialUp( void ) {KeyUp(&in_special);} /* =============== CL_KeyState Returns the fraction of the frame that the key was down =============== */ float CL_KeyState( kbutton_t *key ) { float val; int msec; key->state &= 1; // clear impulses msec = key->msec; key->msec = 0; if( key->state ) { // still down msec += sys_frame_time - key->downtime; key->downtime = sys_frame_time; } val = (float)msec / frame_msec; return bound( 0, val, 1 ); } //========================================================================== cvar_t *cl_upspeed; cvar_t *cl_forwardspeed; cvar_t *cl_sidespeed; cvar_t *cl_yawspeed; cvar_t *cl_pitchspeed; cvar_t *cl_run; cvar_t *cl_anglespeedkey; /* ================ CL_AdjustAngles Moves the local angle positions ================ */ void CL_AdjustAngles( void ) { float speed; float up, down; if( in_speed.state & 1 ) speed = (frame_msec * 0.001) * cl_anglespeedkey->value; else speed = frame_msec * 0.001; if( !(in_strafe.state & 1) ) { cl.viewangles[YAW] -= speed * cl_yawspeed->value * CL_KeyState( &in_right ); cl.viewangles[YAW] += speed * cl_yawspeed->value * CL_KeyState( &in_left ); } if( in_klook.state & 1 ) { cl.viewangles[PITCH] -= speed * cl_pitchspeed->value * CL_KeyState( &in_forward ); cl.viewangles[PITCH] += speed * cl_pitchspeed->value * CL_KeyState( &in_back ); } up = CL_KeyState( &in_lookup ); down = CL_KeyState( &in_lookdown ); cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * up; cl.viewangles[PITCH] += speed*cl_pitchspeed->value * down; } /* ================ CL_BaseMove Send the intended movement message to the server ================ */ void CL_BaseMove ( usercmd_t *cmd ) { VectorCopy( cl.viewangles, oldAngles ); CL_AdjustAngles(); memset( cmd, 0, sizeof(*cmd) ); VectorCopy( cl.viewangles, cmd->angles ); if( !frame_msec ) return; if( in_strafe.state & 1 ) { cmd->sidemove += cl_sidespeed->value * CL_KeyState( &in_right ); cmd->sidemove -= cl_sidespeed->value * CL_KeyState( &in_left ); } cmd->sidemove += cl_sidespeed->value * CL_KeyState( &in_moveright ); cmd->sidemove -= cl_sidespeed->value * CL_KeyState( &in_moveleft ); cmd->upmove += cl_upspeed->value * CL_KeyState( &in_up ); cmd->upmove -= cl_upspeed->value * CL_KeyState( &in_down ); if( !(in_klook.state & 1) ) { cmd->forwardmove += cl_forwardspeed->value * CL_KeyState( &in_forward ); cmd->forwardmove -= cl_forwardspeed->value * CL_KeyState( &in_back ); } } /* ============== CL_FinishMove ============== */ void CL_FinishMove( usercmd_t *cmd ) { static double extramsec = 0; int ms; int i; // figure button bits if( in_attack.state & 3 ) cmd->buttons |= BUTTON_ATTACK; in_attack.state &= ~2; if(in_special.state & 3) // wsw : ByMiK : useful to have 'special' working like attack... cmd->buttons |= BUTTON_SPECIAL; in_special.state &= ~2; if(in_use.state & 3) cmd->buttons |= BUTTON_USE; in_use.state &= ~2; if(anykeydown && cls.key_dest == key_game) cmd->buttons |= BUTTON_ANY; // wsw : jal : decide walk in server side if( (in_speed.state & 1) ^ !cl_run->integer ) cmd->buttons |= BUTTON_WALK; // wsw : jal : add chat/console/ui icon as a button if( cls.key_dest != key_game ) cmd->buttons |= BUTTON_BUSYICON; extramsec += frame_msec; ms = extramsec; extramsec -= ms; // fractional part is left for next frame if( ms > 250 ) ms = 100; // time was unreasonable cmd->msec = ms; // from Q3: allow at most 90 degree moves per frame // wsw :mdr: I'm not sure about this if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) { cl.viewangles[PITCH] = oldAngles[PITCH] + 90; } else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) { cl.viewangles[PITCH] = oldAngles[PITCH] - 90; } for( i = 0; i < 3; i++ ) cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); } /* ================= CL_CreateCmd ================= */ usercmd_t CL_CreateCmd( void ) { usercmd_t cmd; frame_msec = sys_frame_time - old_sys_frame_time; // wsw : jal : allow transparent calls. if( !frame_msec || ((frame_msec < (unsigned int)in_minMsecs->integer ) && !cls.demoplaying) ) { // [DEMOCAM] -- PLX cmd.msec = 0; return cmd; } clamp( frame_msec, 1, 200 ); CL_BaseMove( &cmd ); // get basic movement from keyboard IN_Frame(); // reaquire the mouse IN_Move( &cmd ); // allow mice or other external controllers to add to the move CL_FinishMove( &cmd ); old_sys_frame_time = sys_frame_time; return cmd; } void IN_CenterView( void ) { if( !cl.curFrame ) return; cl.viewangles[PITCH] = -SHORT2ANGLE(cl.curFrame->playerState.pmove.delta_angles[PITCH]); } #ifdef DEMOCAM void IN_CenterViewOnVec( void ) // wsw: [DEMOCAM] Used by democam to make player look at cam direction -- PLX { if (Cmd_Argc() < 3) return; cl.viewangles[0] = atof(Cmd_Argv(1)); cl.viewangles[1] = atof(Cmd_Argv(2)); cl.viewangles[2] = atof(Cmd_Argv(3)); } #endif //================== //CL_UpdateUserCommand //================== void CL_UpdateUserCommand( void ) { usercmd_t tCmd, *uCmd; /* // get new key events Sys_SendKeyEvents(); // allow mice or other external controllers to add commands IN_Commands(); // process console commands Cbuf_Execute(); */ tCmd = CL_CreateCmd(); uCmd = &cl.cmds[cls.netchan.outgoingSequence & CMD_MASK]; if( cls.demoplaying ) { // outgoingsequence is not bumped when not connected, so use pure command *uCmd = tCmd; return; } uCmd->angles[0] = ANGLE2SHORT(cl.viewangles[0]); uCmd->angles[1] = ANGLE2SHORT(cl.viewangles[1]); uCmd->angles[2] = ANGLE2SHORT(cl.viewangles[2]); if ( !tCmd.msec ) // not time to update yet return; uCmd->buttons |= tCmd.buttons; uCmd->forwardmove += tCmd.forwardmove; uCmd->msec += tCmd.msec; uCmd->sidemove += tCmd.sidemove; uCmd->upmove += tCmd.upmove; uCmd->serverTimeStamp = cl.serverTime; // return the time stamp to the server // if( uCmd->msec > 250 ) { // uCmd->msec = 100; // } } void CL_UserInputFrame( void ) { // let the mouse activate or deactivate //IN_Frame(); // get new key events Sys_SendKeyEvents(); // allow mice or other external controllers to add commands IN_Commands(); // process console commands Cbuf_Execute(); // update client command up to the current msec for prediction if( !cls.demoplaying ) { CL_UpdateUserCommand(); } } /* ============ CL_InitInput ============ */ void CL_InitInput( void ) { Cmd_AddCommand( "in_restart", IN_Restart ); IN_Init(); #ifdef DEMOCAM Cmd_AddCommand( "centerviewonvec", IN_CenterViewOnVec ); // wsw: [DEMOCAM] -- PLX #endif Cmd_AddCommand( "centerview", IN_CenterView ); Cmd_AddCommand( "+moveup", IN_UpDown ); Cmd_AddCommand( "-moveup", IN_UpUp ); Cmd_AddCommand( "+movedown", IN_DownDown ); Cmd_AddCommand( "-movedown", IN_DownUp ); Cmd_AddCommand( "+left", IN_LeftDown ); Cmd_AddCommand( "-left", IN_LeftUp ); Cmd_AddCommand( "+right", IN_RightDown ); Cmd_AddCommand( "-right", IN_RightUp ); Cmd_AddCommand( "+forward", IN_ForwardDown ); Cmd_AddCommand( "-forward", IN_ForwardUp ); Cmd_AddCommand( "+back", IN_BackDown ); Cmd_AddCommand( "-back", IN_BackUp ); Cmd_AddCommand( "+lookup", IN_LookupDown ); Cmd_AddCommand( "-lookup", IN_LookupUp ); Cmd_AddCommand( "+lookdown", IN_LookdownDown ); Cmd_AddCommand( "-lookdown", IN_LookdownUp ); Cmd_AddCommand( "+strafe", IN_StrafeDown ); Cmd_AddCommand( "-strafe", IN_StrafeUp ); Cmd_AddCommand( "+moveleft", IN_MoveleftDown ); Cmd_AddCommand( "-moveleft", IN_MoveleftUp ); Cmd_AddCommand( "+moveright", IN_MoverightDown ); Cmd_AddCommand( "-moveright", IN_MoverightUp ); Cmd_AddCommand( "+speed", IN_SpeedDown ); Cmd_AddCommand( "-speed", IN_SpeedUp ); Cmd_AddCommand( "+attack", IN_AttackDown ); Cmd_AddCommand( "-attack", IN_AttackUp ); Cmd_AddCommand( "+use", IN_UseDown ); Cmd_AddCommand( "-use", IN_UseUp ); Cmd_AddCommand( "+klook", IN_KLookDown ); Cmd_AddCommand( "-klook", IN_KLookUp ); // wsw Cmd_AddCommand( "+mlook", IN_MLookDown ); Cmd_AddCommand( "-mlook", IN_MLookUp ); Cmd_AddCommand( "+special", IN_SpecialDown ); Cmd_AddCommand( "-special", IN_SpecialUp ); cl_nodelta = Cvar_Get( "cl_nodelta", "0", 0 ); } /* ============ CL_ShutdownInput ============ */ void CL_ShutdownInput( void ) { Cmd_RemoveCommand( "in_restart" ); IN_Shutdown(); #ifdef DEMOCAM Cmd_RemoveCommand( "centerviewonvec" ); // wsw: [DEMOCAM] -- PLX #endif Cmd_RemoveCommand( "centerview" ); Cmd_RemoveCommand( "+moveup" ); Cmd_RemoveCommand( "-moveup" ); Cmd_RemoveCommand( "+movedown" ); Cmd_RemoveCommand( "-movedown" ); Cmd_RemoveCommand( "+left" ); Cmd_RemoveCommand( "-left" ); Cmd_RemoveCommand( "+right" ); Cmd_RemoveCommand( "-right" ); Cmd_RemoveCommand( "+forward" ); Cmd_RemoveCommand( "-forward" ); Cmd_RemoveCommand( "+back" ); Cmd_RemoveCommand( "-back" ); Cmd_RemoveCommand( "+lookup" ); Cmd_RemoveCommand( "-lookup" ); Cmd_RemoveCommand( "+lookdown" ); Cmd_RemoveCommand( "-lookdown" ); Cmd_RemoveCommand( "+strafe" ); Cmd_RemoveCommand( "-strafe" ); Cmd_RemoveCommand( "+moveleft" ); Cmd_RemoveCommand( "-moveleft" ); Cmd_RemoveCommand( "+moveright" ); Cmd_RemoveCommand( "-moveright" ); Cmd_RemoveCommand( "+speed" ); Cmd_RemoveCommand( "-speed" ); Cmd_RemoveCommand( "+attack" ); Cmd_RemoveCommand( "-attack" ); Cmd_RemoveCommand( "+use" ); Cmd_RemoveCommand( "-use" ); Cmd_RemoveCommand( "+klook" ); Cmd_RemoveCommand( "-klook" ); // wsw Cmd_RemoveCommand( "+mlook" ); Cmd_RemoveCommand( "-mlook" ); Cmd_RemoveCommand( "+special" ); Cmd_RemoveCommand( "-special" ); } //jalfixme: this isn't the right file for this function /* ================= CL_WritePacket Send a packet to the server ================= */ void CL_WritePacket( usercmd_t *cmd ) { msg_t message; qbyte messageData[MAX_MSGLEN]; if( cls.state == CA_DISCONNECTED || cls.state == CA_CONNECTING ) return; if( cls.demoplaying ) return; MSG_Init( &message, messageData, sizeof(messageData) ); MSG_Clear( &message ); // write the command ack MSG_WriteByte( &message, clc_svcack ); MSG_WriteLong( &message, (unsigned long)cls.lastExecutedServerCommand ); // send only reliable commands during conneting time if( cls.state == CA_CONNECTED ) { if( cls.reliableSent < cls.reliableSequence || cls.realtime - cls.lastPacketSentTime > 1000 ) { //write up the clc commands CL_UpdateClientCommandsToServer( &message ); CL_Netchan_Transmit( &message ); } return; } // send a userinfo update if needed if( userinfo_modified ) { userinfo_modified = qfalse; CL_AddReliableCommand( va( "usri \"%s\"", Cvar_Userinfo() ) ); } CL_UpdateClientCommandsToServer( &message ); // wsw : jal : don't write user move commands unless we were given one if( cmd != NULL ) { usercmd_t *oldcmd; usercmd_t nullcmd; int checksumIndex; // begin a client move command MSG_WriteByte( &message, clc_move ); // save the position for a checksum byte checksumIndex = message.cursize; MSG_WriteByte( &message, 0 ); // (acknowledge server frame snap) // let the server know what the last frame we // got was, so the next message can be delta compressed if( cl_nodelta->integer || !cl.curFrame || !cl.curFrame->valid || cls.demowaiting ) MSG_WriteLong( &message, -1 ); // no compression else MSG_WriteLong( &message, cl.curFrame->serverFrame ); // send this and the previous cmds in the message, so // if the last packet was dropped, it can be recovered cmd = &cl.cmds[(cls.netchan.outgoingSequence-2) & CMD_MASK]; memset( &nullcmd, 0, sizeof(nullcmd) ); MSG_WriteDeltaUsercmd( &message, &nullcmd, cmd ); oldcmd = cmd; cmd = &cl.cmds[(cls.netchan.outgoingSequence-1) & CMD_MASK]; MSG_WriteDeltaUsercmd( &message, oldcmd, cmd ); oldcmd = cmd; cmd = &cl.cmds[(cls.netchan.outgoingSequence) & CMD_MASK]; MSG_WriteDeltaUsercmd( &message, oldcmd, cmd ); // calculate a checksum over the move commands message.data[checksumIndex] = COM_BlockSequenceCRCByte( message.data + checksumIndex + 1, message.cursize - checksumIndex - 1, cls.netchan.outgoingSequence ); } CL_Netchan_Transmit( &message ); }