/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena 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 2 of the License,
or (at your option) any later version.

Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// cl.input.c  -- builds an intended movement command to send to the server

#include "client.h"

// Used for input registering/deletion
typedef struct {
	char		*name;
	xcommand_t	function;
} inputlist_t;

#define BUTTON_PRESS_NORMAL 0 // Default usage.
#define BUTTON_PRESS_SHORT	1 // Short usage.
#define BUTTON_PRESS_LONG	2 // Long usage.

unsigned	frame_msec;
int			old_com_frameTime;

bool bTEST;
/*
===============================================================================

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 argv(1) so it can be matched up with the release.

argv(2) will be set to the time the event happened, which allows exact
control even at low framerates when the down and up events may both get qued
at the same time.

===============================================================================
*/


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;
kbutton_t	in_up, in_down;
kbutton_t	in_buttons[16];

//--- ANDROID FEATURES
enum				{ GYRO_AXES_SWAP_X = 1, GYRO_AXES_SWAP_Y = 2, GYRO_AXES_SWAP_XY = 4 };
#define				TOUCHSCREEN_TAP_AREA (cls.glconfig.vidHeight / 6)
static const float	in_swipeSpeed = 0.2f;
static short		in_androidCameraYawSpeed, in_androidCameraPitchSpeed, in_androidCameraMultitouchYawSpeed, in_androidWeaponSelectionBarActive;
static short		in_swipeActivated, in_joystickJumpTriggerTime, in_attackButtonReleased, in_mouseSwipingActive, in_multitouchActive;
static int			in_mouseX, in_mouseY, in_multitouchX, in_multitouchY, in_tapMouseX, in_tapMouseY, in_swipeTime;
static float		in_swipeAngleRotate;
static bool			in_railgunZoomActive;
vec3_t				in_cameraAngles;


//--- Mouse Looking button (+mlook)
#ifdef _DEFINE_ADD_MOUSELOOK_CMD
bool		in_mlooking;

void IN_MLookDown( void ) 
{
	in_mlooking = true;
}

void IN_MLookUp( void ) 
{
	in_mlooking = false;

	/*if ( !cl_freelook->integer ) 
	{
		IN_CenterViewDown ();
		IN_CenterViewUp ();
	}*/
}
#endif

void IN_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 {
		return;
	}
	
	b->iPressMode = BUTTON_PRESS_NORMAL;
	if ( b->bPressed )
		return;		// still down

	// save timestamp for partial frame summing
	c = Cmd_Argv(2);
	b->downtime = atoi(c);

	b->bPressed = true;
	b->bWasPressed = true;
}

void IN_LongKeyDown( 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;
	}
	
	b->iPressMode = BUTTON_PRESS_SHORT;

	if ( b->bPressed )
		return;		// still down

	// save timestamp for partial frame summing
	c = Cmd_Argv(2);
	b->downtime = atoi(c);

	b->bPressed = true;
	b->bWasPressed = true;

}


void IN_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->bPressed = false;
		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
	}

	// save timestamp for partial frame summing
	c = Cmd_Argv(2);
	uptime = atoi(c);
	if ( uptime ) {
		b->msec += uptime - b->downtime;
	} else {
		b->msec += frame_msec / 2;
	}

	b->bPressed = false;
	b->presstime = 0;
}



/*
===============
CL_KeyState

Returns the fraction of the frame that the key was down
===============
*/
float CL_KeyState( kbutton_t *key ) {
	float		val;
	int			msec;

	msec = key->msec;
	key->msec = 0;

	if ( key->bPressed ) {
		// still down
		if ( !key->downtime ) {
			msec = com_frameTime;
		} else {
			msec += com_frameTime - key->downtime;
		}
		key->downtime = com_frameTime;
	}

/*	if (msec) {
		Com_Printf ("%i ", msec);
	}*/

	val = (float)msec / frame_msec;
	if ( val < 0 ) {
		val = 0;
	}
	if ( val > 1 ) {
		val = 1;
	}

	return val;
}



void IN_UpDown(void) {IN_KeyDown(&in_up);}
void IN_UpUp(void) {IN_KeyUp(&in_up);}
void IN_DownDown(void) {IN_KeyDown(&in_down);}
void IN_DownUp(void) {IN_KeyUp(&in_down);}
void IN_LeftDown(void) {IN_KeyDown(&in_left);}
void IN_LeftUp(void) {IN_KeyUp(&in_left);}
void IN_RightDown(void) {IN_KeyDown(&in_right);}
void IN_RightUp(void) {IN_KeyUp(&in_right);}
void IN_ForwardDown(void) {IN_KeyDown(&in_forward);}
void IN_ForwardUp(void) {IN_KeyUp(&in_forward);}
void IN_BackDown(void) {IN_KeyDown(&in_back);}
void IN_BackUp(void) {IN_KeyUp(&in_back);}
void IN_LookupDown(void) {IN_KeyDown(&in_lookup);}
void IN_LookupUp(void) {IN_KeyUp(&in_lookup);}
void IN_LookdownDown(void) {IN_KeyDown(&in_lookdown);}
void IN_LookdownUp(void) {IN_KeyUp(&in_lookdown);}
void IN_MoveleftDown(void) {IN_KeyDown(&in_moveleft);}
void IN_MoveleftUp(void) {IN_KeyUp(&in_moveleft);}
void IN_MoverightDown(void) {IN_KeyDown(&in_moveright);}
void IN_MoverightUp(void) {IN_KeyUp(&in_moveright);}

void IN_SpeedDown(void) {IN_KeyDown(&in_speed);}
void IN_SpeedUp(void) {IN_KeyUp(&in_speed);}
void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);}
void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);}

void IN_Button0Down(void)
{

#ifdef __ANDROID__
	int weaponX = in_mouseX * 640 / cls.glconfig.vidWidth;

	if ( cg_holdingUsableItem->integer &&
			in_mouseY < cls.glconfig.vidHeight / 6 &&
			in_mouseX > cls.glconfig.vidWidth * 5 / 6 ) {
		// Use item
		IN_KeyDown(&in_buttons[2]);
		IN_KeyUp(&in_buttons[2]);
	} else if (	in_androidWeaponSelectionBarActive && ( (
				!cg_weaponBarAtBottom->integer &&
				weaponX > 320 - cg_weaponBarActiveWidth->integer &&
				weaponX < 320 + cg_weaponBarActiveWidth->integer ) || (
				cg_weaponBarAtBottom->integer &&
				weaponX > 640 - cg_weaponBarActiveWidth->integer * 2 ) ) ) {
		char cmd[64] = "weapon ";
		int count = ( !cg_weaponBarAtBottom->integer ? weaponX - 320 + cg_weaponBarActiveWidth->integer : weaponX - 640 + cg_weaponBarActiveWidth->integer * 2 ) / 40;
		char * c = cg_weaponBarActiveWeapons->string, * c2;
		int i;

		for ( i = 0; i < count; i++ ) {
			c = strchr ( c, '/' );
			if ( c == NULL )
				return;
			c++;
		}
		c2 = strchr ( c, '/' );
		if ( c2 == NULL )
			return;
		strncat ( cmd, c, c2 - c );
		Cbuf_AddText( cmd );
	} else {
		if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING ) {
			IN_KeyDown(&in_buttons[0]);
		} else {
			// Ignore mouse keypresses, process only keyboard
			int k = -1;
			if ( Cmd_Argv(1)[0] )
				k = atoi(Cmd_Argv(1));
			if ( k != K_MOUSE1 )
				IN_KeyDown(&in_buttons[0]);
			else {
				in_mouseSwipingActive = 1;
				in_swipeTime = 0;
				in_swipeAngleRotate = cl.viewangles[YAW];
				in_swipeActivated = 0;
				if ( cg_touchscreenControls->integer == TOUCHSCREEN_TAP_TO_FIRE ) {
					int tapArea = TOUCHSCREEN_TAP_AREA / 2;
					if (	cl.touchscreenAttackButtonPos[4] > 0.0f &&
							in_mouseX > in_tapMouseX - tapArea &&
							in_mouseX < in_tapMouseX + tapArea &&
							in_mouseY > in_tapMouseY - tapArea &&
							in_mouseY < in_tapMouseY + tapArea ) { // TODO: make it octagon, not square
						IN_KeyDown(&in_buttons[0]);
					}
					cl.touchscreenAttackButtonPos[4] = 0.0f;
				}
			}
		}
	}
#else
	IN_KeyDown(&in_buttons[0]);
#endif
}
void IN_Button0Up(void)
{
#ifdef __ANDROID__
	cl.touchscreenAttackButtonPos[4] = 0.0f;
	if ( in_buttons[0].bPressed )
		in_attackButtonReleased = 1;
	if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING ) {
		IN_KeyUp(&in_buttons[0]);
	} else {
		// Ignore mouse keypresses, process only keyboard
		int k = -1;
		if ( Cmd_Argv(1)[0] )
			k = atoi(Cmd_Argv(1));
		if ( k != K_MOUSE1 )
			IN_KeyUp(&in_buttons[0]);
		else {
			float angleDiff = AngleSubtract( cl.viewangles[YAW], in_swipeAngleRotate ); // It will normalize the resulting angle
			in_mouseSwipingActive = 0;
			if ( in_swipeTime < 300 && fabs(angleDiff) > in_swipeSensitivity->value && in_swipeAngle->value ) {
				in_swipeAngleRotate = angleDiff > 0 ? in_swipeAngle->value - angleDiff : -in_swipeAngle->value - angleDiff;
				in_swipeActivated = 1;
			}
			if ( cg_touchscreenControls->integer == TOUCHSCREEN_TAP_TO_FIRE ) {
				int weaponX = in_mouseX * 640 / cls.glconfig.vidWidth;
				IN_KeyUp(&in_buttons[0]);
				in_tapMouseX = in_mouseX;
				in_tapMouseY = in_mouseY;
				cl.touchscreenAttackButtonPos[2] = cl.touchscreenAttackButtonPos[3] = TOUCHSCREEN_TAP_AREA;
				cl.touchscreenAttackButtonPos[0] = in_mouseX - cl.touchscreenAttackButtonPos[2] * 0.5f;
				cl.touchscreenAttackButtonPos[1] = in_mouseY - cl.touchscreenAttackButtonPos[3] * 0.5f;
				cl.touchscreenAttackButtonPos[4] = 0.75f;
				if ( ( Key_GetCatcher( ) & ~KEYCATCH_CGAME || clc.state != CA_ACTIVE ) || (
					in_androidWeaponSelectionBarActive && ( (
					!cg_weaponBarAtBottom->integer &&
					weaponX > 320 - cg_weaponBarActiveWidth->integer &&
					weaponX < 320 + cg_weaponBarActiveWidth->integer ) || (
					cg_weaponBarAtBottom->integer &&
					weaponX > 640 - cg_weaponBarActiveWidth->integer * 2 ) ) ) ) {
					// We're inside UI, or toggling weapons, disable on-screen button
					cl.touchscreenAttackButtonPos[4] = 0.0f;
				}
			}
		}
	}
#else
	IN_KeyUp(&in_buttons[0]);
#endif
}
void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);}
void IN_Button1Up(void) {IN_KeyUp(&in_buttons[1]);}
void IN_Button2Down(void) {IN_LongKeyDown(&in_buttons[2]);}
void IN_Button2Up(void) {IN_KeyUp(&in_buttons[2]);}
void IN_Button3Down(void) {IN_KeyDown(&in_buttons[3]);}
void IN_Button3Up(void) {IN_KeyUp(&in_buttons[3]);}
void IN_Button4Down(void) {IN_KeyDown(&in_buttons[4]);}
void IN_Button4Up(void) {IN_KeyUp(&in_buttons[4]);}
void IN_Button5Down(void) {IN_KeyDown(&in_buttons[5]);}
void IN_Button5Up(void) {IN_KeyUp(&in_buttons[5]);}
void IN_Button6Down(void) {IN_KeyDown(&in_buttons[6]);}
void IN_Button6Up(void) {IN_KeyUp(&in_buttons[6]);}
void IN_Button7Down(void) {IN_KeyDown(&in_buttons[7]);}
void IN_Button7Up(void) {IN_KeyUp(&in_buttons[7]);}
void IN_Button8Down(void) {IN_KeyDown(&in_buttons[8]);}
void IN_Button8Up(void) {IN_KeyUp(&in_buttons[8]);}
void IN_Button9Down(void) {IN_KeyDown(&in_buttons[9]);}
void IN_Button9Up(void) {IN_KeyUp(&in_buttons[9]);}
void IN_Button10Down(void) {IN_KeyDown(&in_buttons[10]);}
void IN_Button10Up(void) {IN_KeyUp(&in_buttons[10]);}
void IN_Button11Down(void) {IN_KeyDown(&in_buttons[11]);}
void IN_Button11Up(void) {IN_KeyUp(&in_buttons[11]);}
void IN_Button12Down(void) {IN_KeyDown(&in_buttons[12]);}
void IN_Button12Up(void) {IN_KeyUp(&in_buttons[12]);}
void IN_Button13Down(void) {IN_KeyDown(&in_buttons[13]);}
void IN_Button13Up(void) {IN_KeyUp(&in_buttons[13]);}
void IN_Button14Down(void) {IN_KeyDown(&in_buttons[14]);}
void IN_Button14Up(void) {IN_KeyUp(&in_buttons[14]);}

void IN_CenterViewDown (void) {
	//in_cameraAngles[PITCH] = -SHORT2ANGLE(cl.snap.ps.delta_angles[PITCH]);

	// User released joystick, then pressed the centerview button - it will rotate to the last joystick direction
	if ( cl.joystickAxis[0] == 0 && cl.joystickAxis[1] == 0 ) 
	{
		in_swipeActivated = 1;
		in_joystickJumpTriggerTime = 0; // Do not jump if user rotated view and immediately put finger back on joystick
	}
}

void IN_CenterViewUp (void) {
}

//==========================================================================

cvar_t	*cl_yawspeed;
cvar_t	*cl_pitchspeed;
cvar_t	*cl_pitchAutoCenter;

cvar_t	*cl_anglespeedkey;


/*
================
CL_AdjustAngles

Moves the local angle positions
================
*/
void CL_AdjustAngles( void ) {

#ifdef __ANDROID__
	float right = CL_KeyState (&in_right), left = CL_KeyState (&in_left);
	float up = CL_KeyState (&in_lookup), down = CL_KeyState (&in_lookdown);
	float speed = cls.unscaledFrametime * cl_sensitivity->value * cl.cgameSensitivity * 0.04f;
	vec3_t angles;

	if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING ) 
		VectorCopy(in_cameraAngles, angles);
	else 
		VectorCopy(cl.viewangles, angles);
	

	if ( left > 0 || right > 0 || up > 0 || down > 0 ) {
		angles[YAW] -= speed * right;
		angles[YAW] += speed * left;
		angles[PITCH] -= speed * up;
		angles[PITCH] += speed * down;
	}

	speed /= 32767.0f;

	if ( abs(cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_X]) > 4096 ) {
		int rescaled = (abs(cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_X]) - 4096) *
						(cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_X] > 0 ? 1 : -1);
		angles[YAW] -= speed * rescaled;
	}
	// Slower vertical movement
	if ( abs(cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_Y]) > 12288 ) {
		int rescaled = (abs(cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_Y]) - 12288) *
						(cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_Y] > 0 ? 1 : -1) *
						(m_pitch->value < 0 ? -1 : 1);
		angles[PITCH] += speed * rescaled;
	}
		angles[ROLL] = 0;

	// Swipe touchscreen gesture
	if ( in_swipeActivated ) {
		float diff = cls.unscaledFrametime * in_swipeSpeed * ( ( in_swipeAngleRotate > 0 ) ? 1 : -1 );
		if ( fabs( in_swipeAngleRotate ) <= fabs( diff ) ) {
			diff = in_swipeAngleRotate;
			in_swipeActivated = 0;
			in_swipeAngleRotate = 0;
		} else {
			in_swipeAngleRotate -= diff;
		}
		angles[YAW] += diff;
	}
	in_swipeTime += cls.unscaledFrametime;

	if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING ) {
		VectorCopy(angles, in_cameraAngles);
	} else {
		VectorCopy(angles, cl.viewangles);
	}
#else
	float	speed;
	
	if ( in_speed.bPressed ) 
		speed = 0.001 * cls.frametime * cl_anglespeedkey->value;
	else 
		speed = 0.001 * cls.frametime;
	

	if ( !in_strafe.bPressed ) {
		cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right);
		cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left);
	}

	cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_lookup);
	cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown);
#endif
}

/*
================
CL_KeyMove

Sets the usercmd_t based on key states
================
*/
void CL_KeyMove( usercmd_t *cmd ) {
	int		movespeed;
	int		forward, side, up;

	forward = 0;
	side = 0;
	up = 0;

	//
	// adjust for speed key / running
	// the walking flag is to keep animations consistant
	// even during acceleration and develeration
	//
	if ( in_speed.bPressed ) {
		cmd->buttons |= BUTTON_WALKING;
		movespeed = 64;
	} 
	else {
		movespeed = 127;
		cmd->buttons &= ~BUTTON_WALKING;
	}

	if ( in_strafe.bPressed ) {
		side += movespeed * CL_KeyState (&in_right);
		side -= movespeed * CL_KeyState (&in_left);
	}

	side += movespeed * CL_KeyState (&in_moveright);
	side -= movespeed * CL_KeyState (&in_moveleft);

	up += movespeed * CL_KeyState (&in_up);
	up -= movespeed * CL_KeyState (&in_down);

	forward += movespeed * CL_KeyState (&in_forward);
	forward -= movespeed * CL_KeyState (&in_back);

	cmd->forwardmove = ClampChar( forward );
	cmd->rightmove = ClampChar( side );
	cmd->upmove = ClampChar( up );
}

#define SCALE( src, border, from, to ) src = ( (border) + ( (src) - (border) ) * ( (to) - (border) ) / ( (from) - (border) ) )
static void CL_AdjustCrosshairPosNearEdges( int * dx, int * dy ) {
	int x = *dx;
	int y = *dy;
	// TODO: hardcoded values, make them configurable
	int border = cls.glconfig.vidHeight / 6;
	int offset = border / 2 * in_swipeFreeCrosshairOffset->value;

	/*if ( com_gameplatform->integer == 2 )
		return;*/

	in_androidCameraYawSpeed = in_androidCameraPitchSpeed = in_androidWeaponSelectionBarActive = 0;

	if ( cg_touchscreenControls->integer != TOUCHSCREEN_SWIPE_FREE_AIMING ) {
		if ( !cg_weaponBarAtBottom->integer ) {
			if ( y < border )
				in_androidWeaponSelectionBarActive = 1;
		} else {
			if ( y > cls.glconfig.vidHeight - border )
				in_androidWeaponSelectionBarActive = 1;
		}
		return;
	}

	if ( x < border * 3 ) {
		if ( x < border * 2 )
			in_androidCameraYawSpeed = 1;

		if ( in_swipeFreeStickyEdges->integer )	//--------------------
			SCALE( x, border * 3, border * 2, offset );

	} else if ( x > cls.glconfig.vidWidth - border * 2 ) {
		if ( x > cls.glconfig.vidWidth - border )
			in_androidCameraYawSpeed = -1;
		
		if ( in_swipeFreeStickyEdges->integer )//---------------------
			SCALE( x, cls.glconfig.vidWidth - border * 2, cls.glconfig.vidWidth - border, cls.glconfig.vidWidth + offset );
	}

	if ( y < border * 2 ) {
		if ( y < border ) 
		{
			in_androidCameraPitchSpeed = -1;
			if ( !cg_weaponBarAtBottom->integer )
				in_androidWeaponSelectionBarActive = 1;
		}
		
		if ( in_swipeFreeStickyEdges->integer )
			SCALE( y, border * 2, border, offset );
	} else if ( y > cls.glconfig.vidHeight - border * 2 ) {
		if ( y > cls.glconfig.vidHeight - border ) {
			in_androidCameraPitchSpeed = 1;
			if ( cg_weaponBarAtBottom->integer )
				in_androidWeaponSelectionBarActive = 1;
		}
		if ( in_swipeFreeStickyEdges->integer )
			SCALE( y, cls.glconfig.vidHeight - border * 2, cls.glconfig.vidHeight - border, cls.glconfig.vidHeight + offset );
	}

	// Offset crosshair, so it won't be right under finger
	x = x - offset;
	y = y - offset;

	// Boundary checks
	SCALE (x, 0, x, cls.glconfig.vidWidth - 1);
	SCALE (y, 0, y, cls.glconfig.vidHeight - 1);

	// Return the values
	*dx = x;
	*dy = y;
}

/*
=================
CL_MouseEvent
=================
*/
void CL_MouseEvent( int dx, int dy, int time ) 
{
	in_mouseX = dx;
	in_mouseY = dy;
#ifdef __ANDROID__

	if ( Key_GetCatcher( ) & KEYCATCH_UI ) {
		// Hack to make UI downloaded from random server work - move to upper-left corner, then move to desired position
		VM_Call( uivm, UI_MOUSE_EVENT, -10000, -10000);
		VM_Call( uivm, UI_MOUSE_EVENT, dx * SCREEN_WIDTH / cls.glconfig.vidWidth, dy * SCREEN_HEIGHT / cls.glconfig.vidHeight); 
	} else if( cgvm ) {
		CL_AdjustCrosshairPosNearEdges( &dx, &dy );
		VM_Call( cgvm, CG_MOUSE_EVENT, dx, dy );
	}
#else
	if ( Key_GetCatcher( ) & KEYCATCH_UI ) {
		VM_Call( uivm, UI_MOUSE_EVENT, dx, dy );
	} else if (cgvm) {
		/*if (UCMD_IsButtonPressed (BUTTON_ATTACK) )
			VM_Call (cgvm, CG_MOUSE_EVENT, dx, dy);
		else
		{*/
			cl.mouseDx[cl.mouseIndex] += dx;
			cl.mouseDy[cl.mouseIndex] += dy;
		//}
	}
#endif
}

/*
=================
CL_Mouse2Event
=================
*/
void CL_Mouse2Event( int x, int y, int time ) {
	if ( (cg_touchscreenControls->integer != TOUCHSCREEN_SWIPE_FREE_AIMING) && in_multitouchActive ) {
		int dx = x - in_multitouchX;
		int dy = y - in_multitouchY;
		cl.viewangles[YAW] -= (float)dx * cl_sensitivity->value * cl.cgameSensitivity * 0.05f;
		cl.viewangles[PITCH] += (float)dy * cl_sensitivity->value * cl.cgameSensitivity * 0.05f;
	}
	in_multitouchX = x;
	in_multitouchY = y;
}

void IN_MultitouchDown(void) {
	if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING ) {
		int dx = in_multitouchX - in_mouseX;
		int dy = in_multitouchY - in_mouseY;
		if ( abs( dx ) > abs( dy ) ) {
			in_androidCameraMultitouchYawSpeed = ( dx < 0 ) ? 1 : -1;
		} else {
			Com_QueueEvent( 0, SE_KEY, ( dy < 0 ) ? '/' : K_BACKSPACE , true, 0, NULL );
		}
	} else {
		in_multitouchActive = 1;
	}
}

void IN_MultitouchUp(void) {
	if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING ) {
		in_androidCameraMultitouchYawSpeed = 0;
		Com_QueueEvent( 0, SE_KEY, '/', false, 0, NULL );
		Com_QueueEvent( 0, SE_KEY, K_BACKSPACE, false, 0, NULL );
	} else {
		in_multitouchActive = 0;
	}
}

/*
=================
CL_JoystickEvent

Joystick values stay set until changed
=================
*/
void CL_JoystickEvent( int axis, int value, int time ) 
{
	if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) 
		Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis );
	
	cl.joystickAxis[axis] = value;
}

static void CL_ScaleMovementCmdToMaximiumForStrafeJump( usercmd_t *cmd ) 
{
	// Normalize them to 127
	if ( abs(cmd->forwardmove) > abs(cmd->rightmove) ) {
		cmd->rightmove = ClampChar( (short)cmd->rightmove * 127 / abs(cmd->forwardmove) );
		cmd->forwardmove = (cmd->forwardmove > 0) ? 127 : -127;
	} else if ( abs(cmd->rightmove) > abs(cmd->forwardmove) ) {
		cmd->forwardmove = ClampChar( (short)cmd->forwardmove * 127 / abs(cmd->rightmove) );
		cmd->rightmove = (cmd->rightmove > 0) ? 127 : -127;
	}
}

/*
=================
CL_JoystickMove
=================
*/
void CL_JoystickMove( usercmd_t *cmd ) {
#ifdef __ANDROID__

	static int oldRightMove = 0, oldForwardMove = 0, oldJump = 0;
	float angle;

	if ( cl.joystickAxis[JOY_AXIS_SCREENJOY_X] == 0 && cl.joystickAxis[JOY_AXIS_SCREENJOY_Y] == 0 ) {
		oldJump = 0;
		if ( in_joystickJumpTriggerTime > 0 ) {
			in_joystickJumpTriggerTime -= cls.unscaledFrametime;
			if ( in_joystickJumpTriggerTime * 2 > j_androidJoystickJumpTime->integer ) {
				cmd->rightmove = ClampChar( cmd->rightmove + oldRightMove );
				cmd->forwardmove = ClampChar( cmd->forwardmove + oldForwardMove );
			}
		}
		if ( abs(cl.joystickAxis[JOY_AXIS_GAMEPADLEFT_X]) > 8192 || abs(cl.joystickAxis[JOY_AXIS_GAMEPADLEFT_Y]) > 8192 ) {
			angle = RAD2DEG( atan2( cl.joystickAxis[JOY_AXIS_GAMEPADLEFT_X], cl.joystickAxis[JOY_AXIS_GAMEPADLEFT_Y] ) );
			angle -= 90.0f;
			angle = DEG2RAD( angle );

			cmd->forwardmove = ClampChar( cmd->forwardmove + sin( angle ) * 127.0f );
			cmd->rightmove = ClampChar( cmd->rightmove + cos( angle ) * 127.0f );
			CL_ScaleMovementCmdToMaximiumForStrafeJump( cmd );
		}
	} else {
		if ( in_joystickJumpTriggerTime > 0 && in_joystickJumpTriggerTime < j_androidJoystickJumpTime->integer ) {
			oldJump = 127;
		}
		cmd->upmove = ClampChar( cmd->upmove + oldJump );
		in_joystickJumpTriggerTime = j_androidJoystickJumpTime->integer;

		angle = RAD2DEG( atan2( cl.joystickAxis[JOY_AXIS_SCREENJOY_X], cl.joystickAxis[JOY_AXIS_SCREENJOY_Y] ) );
		/*if( !in_swipeActivated && cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING ) {
			in_swipeAngleRotate = angle + 180.0f;
			if ( in_swipeAngleRotate > 180.0f )
				in_swipeAngleRotate -= 360.0f;
		}*/
		angle -= 90.0f;
		if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING )
			angle += in_cameraAngles[YAW] - SHORT2ANGLE( cl.snap.ps.delta_angles[YAW] ) - cl.viewangles[YAW];
		angle = DEG2RAD( angle );

		cmd->forwardmove = ClampChar( cmd->forwardmove + sin( angle ) * 127.0f );
		cmd->rightmove = ClampChar( cmd->rightmove + cos( angle ) * 127.0f );
		CL_ScaleMovementCmdToMaximiumForStrafeJump( cmd );
		oldForwardMove = cmd->forwardmove;
		oldRightMove = cmd->rightmove;
	}

	if ( cl.joystickAxis[JOY_AXIS_GAMEPADLEFT_TRIGGER] > 20000 )
		cmd->upmove = ClampChar( cmd->upmove + 127 );

	if ( cl.cgameUserCmdValue == WP_RAILGUN ) {
		if ( cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_TRIGGER] > 10000 )
			cmd->buttons |= BUTTON_ATTACK; // Zoom
		if ( cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_TRIGGER] > 30000 )
			in_attackButtonReleased = 1; // Fire
			
	} else if ( cl.joystickAxis[JOY_AXIS_GAMEPADRIGHT_TRIGGER] > 20000 ) // Fire button pressed
		cmd->buttons |= BUTTON_ATTACK;
#else

	float	anglespeed;

	if (in_speed.bPressed)
	{
		cmd->buttons |= BUTTON_WALKING;
		anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value;
	}
	else
		anglespeed = 0.001 * cls.frametime;
	

	if ( !in_strafe.bPressed ) {
		cl.viewangles[YAW] += anglespeed * j_yaw->value * cl.joystickAxis[j_yaw_axis->integer];
		cmd->rightmove = ClampChar( cmd->rightmove + (int) (j_side->value * cl.joystickAxis[j_side_axis->integer]) );
	} else {
		cl.viewangles[YAW] += anglespeed * j_side->value * cl.joystickAxis[j_side_axis->integer];
		cmd->rightmove = ClampChar( cmd->rightmove + (int) (j_yaw->value * cl.joystickAxis[j_yaw_axis->integer]) );
	}


#ifdef _DEFINE_ADD_MOUSELOOK_CMD
	if ( in_mlooking ) {
		cl.viewangles[PITCH] += anglespeed * j_forward->value * cl.joystickAxis[j_forward_axis->integer];
		cmd->forwardmove = ClampChar( cmd->forwardmove + (int) (j_pitch->value * cl.joystickAxis[j_pitch_axis->integer]) );
	} else {
#endif
		cl.viewangles[PITCH] += anglespeed * j_pitch->value * cl.joystickAxis[j_pitch_axis->integer];
		cmd->forwardmove = ClampChar( cmd->forwardmove + (int) (j_forward->value * cl.joystickAxis[j_forward_axis->integer]) );
//	}

	cmd->upmove = ClampChar( cmd->upmove + (int) (j_up->value * cl.joystickAxis[j_up_axis->integer]) );

#endif
}

/*
=================
CL_MouseMove
=================
*/

void CL_MouseMove(usercmd_t *cmd)
{
	
#ifdef __ANDROID__
	if ( !cgvm )
		return;

	if ( cg_touchscreenControls->integer != TOUCHSCREEN_SWIPE_FREE_AIMING ) 
	{
		static int oldMouseX, oldMouseY;
		int dx = in_mouseX - oldMouseX;
		int dy = in_mouseY - oldMouseY;
		
		if ( in_mouseSwipingActive ) {
			cl.viewangles[YAW] -= (float)dx * cl_sensitivity->value * cl.cgameSensitivity * 0.05f;
			cl.viewangles[PITCH] += (float)dy * cl_sensitivity->value * cl.cgameSensitivity * 0.05f;
		}

		oldMouseX = in_mouseX;
		oldMouseY = in_mouseY;
		// Fade-out the on-screen attack button, until it disappears
		if ( cl.touchscreenAttackButtonPos[4] > 0.0f ) 
		{
			cl.touchscreenAttackButtonPos[4] -= cls.unscaledFrametime * 0.001f;
		
			if( cl.touchscreenAttackButtonPos[4] < 0.10f )
				cl.touchscreenAttackButtonPos[4] = 0.0f;
		}
		return;
	}

	if ( (in_androidCameraYawSpeed || in_androidCameraPitchSpeed || in_androidCameraMultitouchYawSpeed ) && in_buttons[0].bPressed ) 
	{
		float yaw = ( in_androidCameraYawSpeed + in_androidCameraMultitouchYawSpeed ) * cls.unscaledFrametime * 0.15f;
		float pitchSpeed = ( in_cameraAngles[PITCH] < -20 ) ? 0.0015f : ( in_cameraAngles[PITCH] < 45 ) ? 0.001f : 0.003f; // More sensitivity near the edges
		float pitch = in_androidCameraPitchSpeed * cls.unscaledFrametime * cl_pitchspeed->value * pitchSpeed;

		in_cameraAngles[YAW] += yaw;
		in_cameraAngles[PITCH] += pitch;
	}

	if ( in_cameraAngles[PITCH] != 0 && cl_pitchAutoCenter->integer ) {
		in_cameraAngles[PITCH] += j_androidAutoCenterViewSpeed->value * cls.unscaledFrametime * ( ( in_cameraAngles[PITCH] > 0 ) ? -1 : 1 );
		if ( fabs( in_cameraAngles[PITCH] ) < j_androidAutoCenterViewSpeed->value * cls.unscaledFrametime * 2.0f )
			in_cameraAngles[PITCH] = 0;
	}
#else
	float mx, my;

// allow mouse smoothing
	if (m_filter->integer)
	{
		mx = (cl.mouseDx[0] + cl.mouseDx[1]) * 0.5f;
		my = (cl.mouseDy[0] + cl.mouseDy[1]) * 0.5f;
	}
	else
	{
		mx = cl.mouseDx[cl.mouseIndex];
		my = cl.mouseDy[cl.mouseIndex];
	}
	
	cl.mouseIndex ^= 1;
	cl.mouseDx[cl.mouseIndex] = 0;
	cl.mouseDy[cl.mouseIndex] = 0;

	if (mx == 0.0f && my == 0.0f)
		return;
	
	if (cl_mouseAccel->value != 0.0f)
	{
		if(cl_mouseAccelStyle->integer == 0)
		{
			float accelSensitivity;
			float rate;
			
			rate = sqrt(mx * mx + my * my) / (float) frame_msec;

			accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value;
			mx *= accelSensitivity;
			my *= accelSensitivity;
			
			if(cl_showMouseRate->integer)
				Com_Printf("rate: %f, accelSensitivity: %f\n", rate, accelSensitivity);
		}
		else
		{
			float rate[2];
			float power[2];

			// sensitivity remains pretty much unchanged at low speeds
			// cl_mouseAccel is a power value to how the acceleration is shaped
			// cl_mouseAccelOffset is the rate for which the acceleration will have doubled the non accelerated amplification
			// NOTE: decouple the config cvars for independent acceleration setup along X and Y?

			rate[0] = fabs(mx) / (float) frame_msec;
			rate[1] = fabs(my) / (float) frame_msec;
			power[0] = powf(rate[0] / cl_mouseAccelOffset->value, cl_mouseAccel->value);
			power[1] = powf(rate[1] / cl_mouseAccelOffset->value, cl_mouseAccel->value);

			mx = cl_sensitivity->value * (mx + ((mx < 0) ? -power[0] : power[0]) * cl_mouseAccelOffset->value);
			my = cl_sensitivity->value * (my + ((my < 0) ? -power[1] : power[1]) * cl_mouseAccelOffset->value);

			if(cl_showMouseRate->integer)
				Com_Printf("ratex: %f, ratey: %f, powx: %f, powy: %f\n", rate[0], rate[1], power[0], power[1]);
		}
	}
	else
	{
		mx *= cl_sensitivity->value;
		my *= cl_sensitivity->value;
	}

	// ingame FOV
	mx *= cl.cgameSensitivity;
	my *= cl.cgameSensitivity;

	// add mouse X/Y movement to cmd
	if(in_strafe.bPressed)
		cmd->rightmove = ClampChar(cmd->rightmove + m_side->value * mx);
	else
		cl.viewangles[YAW] -= m_yaw->value * mx;

	if (/*in_mlooking || cl_freelook->integer &&*/ !in_strafe.bPressed)
		cl.viewangles[PITCH] += m_pitch->value * my;
	else
		cmd->forwardmove = ClampChar(cmd->forwardmove - m_forward->value * my);
#endif

}

void CL_SetAimingAngles( const vec3_t angles )
{
	VectorCopy( angles, cl.viewangles );
}

void CL_SetCameraAngles( const vec3_t angles )
{
	//Com_Printf ("CL_SetCameraAngles %f %f -> %f %f\n", in_cameraAngles[YAW], in_cameraAngles[PITCH], angles[YAW], angles[PITCH]);
	VectorCopy( angles, in_cameraAngles );
}

/*
==============
CL_CmdButtons
==============
*/
void CL_CmdButtons( usercmd_t *cmd ) {
	int		i;
	static int zoomDeferred = 0;

	//
	// figure button bits
	// send a button bit even if the key was pressed and released in
	// less than a frame
	//	
	for (i = 0 ; i < 15 ; i++) 
	{
		if ( in_buttons[i].bPressed || in_buttons[i].bWasPressed ) 
			cmd->buttons |= BIT(i);
		in_buttons[i].bWasPressed = false;
	}


#ifdef __ANDROID__
	// Do not zoom out and fire immediately, it will skew aiming, because input events not passed to cgame yet
	if ( zoomDeferred > 0 ) {
		zoomDeferred--;
		if ( !zoomDeferred )
			Cbuf_AddText( "+zoom\n" );
	}
	if ( zoomDeferred < 0 ) 
	{
		zoomDeferred++;
		if ( !zoomDeferred )
			Cbuf_AddText( "-zoom\n" );
	}

	// Auto-zoom for railgun
	if ( cg_railgunAutoZoom->integer ) 
	{
		if ( cl.cgameUserCmdValue == WP_RAILGUN && (cmd->buttons & BUTTON_ATTACK) ) 
		{
			if ( !in_railgunZoomActive && 
				( cg_touchscreenControls->integer != TOUCHSCREEN_SWIPE_FREE_AIMING || // Prevent zooming in if we're rotating view
				!( in_androidCameraYawSpeed || in_androidCameraPitchSpeed ||
					in_androidCameraMultitouchYawSpeed || in_androidWeaponSelectionBarActive ))) 
			{
				in_railgunZoomActive = true;
				zoomDeferred = 1;
			}
		} 
		else if ( in_railgunZoomActive ) {
			in_railgunZoomActive = false;
			zoomDeferred = -1;
		}
		if ( cl.cgameUserCmdValue == WP_RAILGUN ) {
			if ( cmd->buttons & BUTTON_ATTACK )
				cmd->buttons &= ~BUTTON_ATTACK; // Do not fire immediately when railgun selected, zoom instead
			if ( in_attackButtonReleased )
				cmd->buttons |= BUTTON_ATTACK; // Fire when button is released
		}
		in_attackButtonReleased = 0;
	}

	if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING ) 
	{
		if ( in_androidCameraYawSpeed || in_androidCameraPitchSpeed ||
				in_androidCameraMultitouchYawSpeed || in_androidWeaponSelectionBarActive )
			cmd->buttons &= ~BUTTON_ATTACK; // Stop firing when we are rotating camera
	}
#endif

	if ( Key_GetCatcher( ) ) 
		cmd->buttons |= BUTTON_TALK;
	

	// allow the game to know if any key at all is
	// currently pressed, even if it isn't bound to anything
	if ( anykeydown && Key_GetCatcher( ) == 0 ) 
		cmd->buttons |= BUTTON_ANY;
	
}


/*
==============
CL_FinishMove
==============
*/
void CL_FinishMove( usercmd_t *cmd ) {
	int		i;

	// copy the state that the cgame is currently sending
	cmd->weapon = cl.cgameUserCmdValue;

	// send the current server time so the amount of movement
	// can be determined without allowing cheating
	cmd->serverTime = cl.serverTime;

	for (i=0 ; i<3 ; i++) {
		cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
	}
}

/*
=================
CL_CreateCmd
=================
*/
usercmd_t CL_CreateCmd( void ) {
	
	vec3_t		oldAngles;
	usercmd_t	cmd;

	VectorCopy( cl.viewangles, oldAngles );

	// keyboard angle adjustment
	CL_AdjustAngles ();
	
	Com_Memset( &cmd, 0, sizeof( cmd ) );

	// get basic movement from keyboard
	CL_KeyMove( &cmd );

	// get basic movement from mouse
	CL_MouseMove( &cmd );

	// get basic movement from joystick
	CL_JoystickMove( &cmd );

	CL_CmdButtons( &cmd );

	// check to make sure the angles haven't wrapped
	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;
	} 

#ifdef __ANDROID__
// Process Screen Moving if we're touching extremities.
	if ( cg_touchscreenControls->integer == TOUCHSCREEN_SWIPE_FREE_AIMING && cgvm ) 
	{
		VM_Call( cgvm, CG_ADJUST_CAMERA_ANGLES, (int) (in_cameraAngles[YAW] * 1000), (int) (in_cameraAngles[PITCH] * 1000) );

		if ( in_cameraAngles[YAW] > 180.0f )
			in_cameraAngles[YAW] -= 360.0f;
		else if ( in_cameraAngles[YAW] < -180.0f )
			in_cameraAngles[YAW] += 360.0f;

		in_cameraAngles[PITCH] = COM_Clamp (-90, 90, in_cameraAngles[PITCH] );
	}
#endif

	// store out the final values
	CL_FinishMove( &cmd );

	// draw debug graphs of turning for mouse testing
	if ( cl_debugMove->integer ) {
		if ( cl_debugMove->integer == 1 ) {
			SCR_DebugGraph( abs(cl.viewangles[YAW] - oldAngles[YAW]) );
		}
		if ( cl_debugMove->integer == 2 ) {
			SCR_DebugGraph( abs(cl.viewangles[PITCH] - oldAngles[PITCH]) );
		}
	}

	return cmd;
}


/*
=================
CL_CreateNewCommands

Create a new usercmd_t structure for this frame
=================
*/
void CL_CreateNewCommands( void ) {
	int			cmdNum;

	// no need to create usercmds until we have a gamestate
	if ( clc.state < CA_PRIMED )
		return;

	frame_msec = com_frameTime - old_com_frameTime;

	// if running less than 5fps, truncate the extra time to prevent
	// unexpected moves after a hitch
	if ( frame_msec > 200 )
		frame_msec = 200;

	old_com_frameTime = com_frameTime;


	// generate a command for this frame
	cl.cmdNumber++;
	cmdNum = cl.cmdNumber & CMD_MASK;
	cl.cmds[cmdNum] = CL_CreateCmd ();
}

/*
=================
CL_ReadyToSendPacket

Returns false if we are over the maxpackets limit
and should choke back the bandwidth a bit by not sending
a packet this frame.  All the commands will still get
delivered in the next packet, but saving a header and
getting more delta compression will reduce total bandwidth.
=================
*/
bool CL_ReadyToSendPacket( void ) {
	int		oldPacketNum;
	int		delta;

	// don't send anything if playing back a demo
	if ( clc.demoplaying || clc.state == CA_CINEMATIC )
		return false;
	
	// If we are downloading, we send no less than 50ms between packets
	if ( *clc.downloadTempName &&
		cls.realtime - clc.lastPacketSentTime < 50 ) {
		return false;
	}

	// if we don't have a valid gamestate yet, only send
	// one packet a second
	if ( clc.state != CA_ACTIVE && 
		clc.state != CA_PRIMED && 
		!*clc.downloadTempName &&
		cls.realtime - clc.lastPacketSentTime < 1000 ) {
		return false;
	}

	// send every frame for loopbacks
	if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) {
		return true;
	}

	// send every frame for LAN
	if ( cl_lanForcePackets->integer && Sys_IsLANAddress( clc.netchan.remoteAddress ) ) {
		return true;
	}

	// check for exceeding cl_maxpackets
	if ( cl_maxpackets->integer < 15 ) {
		Cvar_Set( "cl_maxpackets", "15" );
	} else if ( cl_maxpackets->integer > 125 ) {
		Cvar_Set( "cl_maxpackets", "125" );
	}
	oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK;
	delta = cls.realtime -  cl.outPackets[ oldPacketNum ].p_realtime;
	if ( delta < 1000 / cl_maxpackets->integer ) {
		// the accumulated commands will go out in the next packet
		return false;
	}

	return true;
}

/*
===================
CL_WritePacket

Create and send the command packet to the server
Including both the reliable commands and the usercmds

During normal gameplay, a client packet will contain something like:

4	sequence number
2	qport
4	serverid
4	acknowledged sequence number
4	clc.serverCommandSequence
<optional reliable commands>
1	clc_move or clc_moveNoDelta
1	command count
<count * usercmds>

===================
*/
void CL_WritePacket( void ) {
	msg_t		buf;
	byte		data[MAX_MSGLEN];
	int			i, j;
	usercmd_t	*cmd, *oldcmd;
	usercmd_t	nullcmd;
	int			packetNum;
	int			oldPacketNum;
	int			count, key;

	// don't send anything if playing back a demo
	if ( clc.demoplaying || clc.state == CA_CINEMATIC ) {
		return;
	}

	Com_Memset( &nullcmd, 0, sizeof(nullcmd) );
	oldcmd = &nullcmd;

	MSG_Init( &buf, data, sizeof(data) );

	MSG_Bitstream( &buf );
	// write the current serverId so the server
	// can tell if this is from the current gameState
	MSG_WriteLong( &buf, cl.serverId );

	// write the last message we received, which can
	// be used for delta compression, and is also used
	// to tell if we dropped a gamestate
	MSG_WriteLong( &buf, clc.serverMessageSequence );

	// write the last reliable message we received
	MSG_WriteLong( &buf, clc.serverCommandSequence );

	// write any unacknowledged clientCommands
	for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) {
		MSG_WriteByte( &buf, clc_clientCommand );
		MSG_WriteLong( &buf, i );
		MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
	}

	// we want to send all the usercmds that were generated in the last
	// few packet, so even if a couple packets are dropped in a row,
	// all the cmds will make it to the server


	if ( cl_packetdup->integer < 0 ) {
		Cvar_Set( "cl_packetdup", "0" );
	} else if ( cl_packetdup->integer > 5 ) {
		Cvar_Set( "cl_packetdup", "5" );
	}
	oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK;
	count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber;
	if ( count > MAX_PACKET_USERCMDS ) {
		count = MAX_PACKET_USERCMDS;
		Com_Printf("MAX_PACKET_USERCMDS\n");
	}

	if ( count >= 1 ) {
		if ( cl_showSend->integer )
			Com_Printf( "(%i)", count );

		// begin a client move command
		if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting
			|| clc.serverMessageSequence != cl.snap.messageNum ) {
			MSG_WriteByte (&buf, clc_moveNoDelta);
		} else
			MSG_WriteByte (&buf, clc_move);

		// write the command count
		MSG_WriteByte( &buf, count );

		// use the checksum feed in the key
		key = clc.checksumFeed;
		// also use the message acknowledge
		key ^= clc.serverMessageSequence;
		// also use the last acknowledged server command in the key
		key ^= MSG_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32);

		// write all the commands, including the predicted command
		for ( i = 0 ; i < count ; i++ ) {
			j = (cl.cmdNumber - count + i + 1) & CMD_MASK;
			cmd = &cl.cmds[j];
			MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd);
			oldcmd = cmd;
		}
	}

	//
	// deliver the message
	//
	packetNum = clc.netchan.outgoingSequence & PACKET_MASK;
	cl.outPackets[ packetNum ].p_realtime = cls.realtime;
	cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime;
	cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber;
	clc.lastPacketSentTime = cls.realtime;

	if ( cl_showSend->integer )
		Com_Printf2( PRINT_DEBUG|PRINT_LOG, "%i ", buf.cursize );

	CL_Netchan_Transmit (&clc.netchan, &buf);	
}

/*
=================
CL_SendCmd

Called every frame to builds and sends a command packet to the server.
=================
*/
void CL_SendCmd( void ) {
	// don't send any message if not connected
	if ( clc.state < CA_CONNECTED )
		return;

	// don't send commands if paused
	if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer )
		return;

	// we create commands even if a demo is playing,
	CL_CreateNewCommands();

	// don't send a packet if the last packet was sent too recently
	if ( !CL_ReadyToSendPacket() ) {
		if ( cl_showSend->integer ) 
			Com_Printf( ". " );
		return;
	}

	CL_WritePacket();
}

static inputlist_t inputList[] = 
{
	{	"+centerview",	IN_CenterViewDown },
	{	"-centerview",	IN_CenterViewUp },
	{	"+moveup",		IN_UpDown },
	{	"-moveup",		IN_UpUp },
	{	"+movedown",	IN_DownDown },
	{	"-movedown",	IN_DownUp },
	{	"+left",		IN_LeftDown },
	{	"-left",		IN_LeftUp },
	{	"+right",		IN_RightDown },
	{	"-right",		IN_RightUp },
	{	"+forward",		IN_ForwardDown },
	{	"-forward",		IN_ForwardUp },
	{	"+back",		IN_BackDown },
	{	"-back",		IN_BackUp },
	{	"+lookup",		IN_LookupDown },
	{	"-lookup",		IN_LookupUp },
	{	"+lookdown",	IN_LookdownDown },
	{	"-lookdown",	IN_LookdownUp },
	{	"+strafe",		IN_StrafeDown },
	{	"-strafe",		IN_StrafeUp },
	{	"+moveleft",	IN_MoveleftDown },
	{	"-moveleft",	IN_MoveleftUp },
	{	"+moveright",	IN_MoverightDown },
	{	"-moveright",	IN_MoverightUp },
	{	"+walk",		IN_SpeedDown },
	{	"-walk",		IN_SpeedUp },

	// Buttons 
	{	"+attack",		IN_Button0Down },
	{	"-attack",		IN_Button0Up },
	{	"+aim",			IN_Button2Down },
	{	"-aim",			IN_Button2Up },
	{	"+reload",		IN_Button3Down },
	{	"-reload",		IN_Button3Up },
	{	"+button4",		IN_Button4Down },
	{	"-button4",		IN_Button4Up },
	{	"+button5",		IN_Button5Down },
	{	"-button5",		IN_Button5Up },
	{	"+button6",		IN_Button6Down },
	{	"-button6",		IN_Button6Up },
	{	"+button7",		IN_Button7Down },
	{	"-button7",		IN_Button7Up },
	{	"+button8",		IN_Button8Down },
	{	"-button8",		IN_Button8Up },
	{	"+button9",		IN_Button9Down },
	{	"-button9",		IN_Button9Up },
	{	"+button10",	IN_Button10Down },
	{	"-button10",	IN_Button10Up },
	{	"+button11",	IN_Button11Down },
	{	"-button11",	IN_Button11Up },
	{	"+button12",	IN_Button12Down },
	{	"-button12",	IN_Button12Up },
	{	"+button13",	IN_Button13Down },
	{	"-button13",	IN_Button13Up },
	{	"+button14",	IN_Button14Down },
	{	"-button14",	IN_Button14Up }
#ifdef __ANDROID__
	,
	{	"+multitouch",	IN_MultitouchDown},
	{	"-multitouch",	IN_MultitouchUp}
#endif
#ifdef _DEFINE_ADD_MOUSELOOK_CMD
	,
	{	"+mlook",		IN_MLookDown},
	{	"-mlook",		IN_MLookUp}
#endif
};

static int  inputListSize = ARRAY_LEN( inputList );

/*
============
CL_InitInput
============
*/
void CL_InitInput( void ) {
	int i;
	inputlist_t	*tabl;

	for (i = 0, tabl = inputList; i < inputListSize ; i++, tabl++)
	{
		Cmd_AddCommand (tabl->name, tabl->function);
	}

	cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0);
	cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0);
}

/*
============
CL_ShutdownInput
============
*/
void CL_ShutdownInput(void)
{
	int i;
	inputlist_t	*tabl;

	for (i = 0, tabl = inputList; i < inputListSize ; i++, tabl++)
	{
		Cmd_RemoveCommand (tabl->name);
	}
}
