/*
cl_pmove.c - client-side player physic
Copyright (C) 2010 Uncle Mike

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 3 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.
*/

#include "common.h"
#include "client.h"
#include "const.h"
#include "cl_tent.h"
#include "pm_local.h"
#include "particledef.h"
#include "studio.h"

#define MAX_FORWARD			6	// forward probes for set idealpitch
#define MIN_CORRECTION_DISTANCE	0.25f	// use smoothing if error is > this
#define MIN_PREDICTION_EPSILON	0.5f	// complain if error is > this and we have cl_showerror set
#define MAX_PREDICTION_ERROR		64.0f	// above this is assumed to be a teleport, don't smooth, etc.

/*
=============
CL_ClearPhysEnts

=============
*/
void CL_ClearPhysEnts( void )
{
	clgame.pmove->numtouch = 0;
	clgame.pmove->numvisent = 0;
	clgame.pmove->nummoveent = 0;
	clgame.pmove->numphysent = 0;
}

/*
=============
CL_PushPMStates

=============
*/
void GAME_EXPORT CL_PushPMStates( void )
{
	if( clgame.pushed ) return;
	clgame.oldphyscount = clgame.pmove->numphysent;
	clgame.oldviscount  = clgame.pmove->numvisent;
	clgame.pushed = true;
}

/*
=============
CL_PopPMStates

=============
*/
void GAME_EXPORT CL_PopPMStates( void )
{
	if( !clgame.pushed ) return;
	clgame.pmove->numphysent = clgame.oldphyscount;
	clgame.pmove->numvisent  = clgame.oldviscount;
	clgame.pushed = false;
}

/*
=============
CL_PushTraceBounds

=============
*/
void GAME_EXPORT CL_PushTraceBounds( int hullnum, const float *mins, const float *maxs )
{
	hullnum = bound( 0, hullnum, 3 );
	VectorCopy( mins, clgame.pmove->player_mins[hullnum] );
	VectorCopy( maxs, clgame.pmove->player_maxs[hullnum] );
}

/*
=============
CL_PopTraceBounds

=============
*/
void GAME_EXPORT CL_PopTraceBounds( void )
{
	memcpy( clgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins ));
	memcpy( clgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs ));
}

/*
===============
CL_IsPredicted
===============
*/
qboolean CL_IsPredicted( void )
{
	if( cl_nopred->value || cl.intermission )
		return false;

	// never predict the quake demos
	if( cls.demoplayback == DEMO_QUAKE1 )
		return false;
	return true;
}

/*
===============
CL_SetLastUpdate
===============
*/
void CL_SetLastUpdate( void )
{
	cls.lastupdate_sequence = cls.netchan.incoming_sequence;
}

/*
===============
CL_RedoPrediction
===============
*/
void CL_RedoPrediction( void )
{
	if ( cls.netchan.incoming_sequence != cls.lastupdate_sequence )
	{
		CL_PredictMovement( true );
		CL_CheckPredictionError();
	}
}

/*
===============
CL_SetIdealPitch
===============
*/
void CL_SetIdealPitch( void )
{
	float	angleval, sinval, cosval;
	int	i, j, step, dir, steps;
	float	z[MAX_FORWARD];
	vec3_t	top, bottom;
	pmtrace_t	tr;

	if( cl.local.onground == -1 )
		return;

	angleval = cl.viewangles[YAW] * M_PI2 / 360.0f;
	SinCos( angleval, &sinval, &cosval );

	// Now move forward by 36, 48, 60, etc. units from the eye position and drop lines straight down
	// 160 or so units to see what's below
	for( i = 0; i < MAX_FORWARD; i++ )
	{
		top[0] = cl.simorg[0] + cosval * (i + 3.0f) * 12.0f;
		top[1] = cl.simorg[1] + sinval * (i + 3.0f) * 12.0f;
		top[2] = cl.simorg[2] + cl.viewheight[2];

		bottom[0] = top[0];
		bottom[1] = top[1];
		bottom[2] = top[2] - 160.0f;

		// skip any monsters (only world and brush models)
		tr = CL_TraceLine( top, bottom, PM_STUDIO_BOX );
		if( tr.allsolid ) return; // looking at a wall, leave ideal the way is was

		if( tr.fraction == 1.0f )
			return;	// near a dropoff

		z[i] = top[2] + tr.fraction * (bottom[2] - top[2]);
	}

	dir = 0;
	steps = 0;

	for( j = 1; j < i; j++ )
	{
		step = z[j] - z[j-1];
		if( step > -ON_EPSILON && step < ON_EPSILON )
			continue;

		if( dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ))
			return; // mixed changes

		steps++;
		dir = step;
	}

	if( !dir )
	{
		cl.local.idealpitch = 0.0f;
		return;
	}

	if( steps < 2 ) return;
	cl.local.idealpitch = -dir * cl_idealpitchscale->value;
}

/*
==================
CL_PlayerTeleported

check for instant movement in case
we don't want interpolate this
==================
*/
qboolean CL_PlayerTeleported( local_state_t *from, local_state_t *to )
{
	int	len, maxlen;
	vec3_t	delta;

	VectorSubtract( to->playerstate.origin, from->playerstate.origin, delta );

	// compute potential max movement in units per frame and compare with entity movement
	maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));
	len = VectorLength( delta );

	return (len > maxlen);
}

/*
===================
CL_CheckPredictionError
===================
*/
void CL_CheckPredictionError( void )
{
	int		frame, cmd;
	static int	pos = 0;
	vec3_t		delta;
	float		dist;

	if( !CL_IsPredicted( ))
		return;

	// calculate the last usercmd_t we sent that the server has processed
	frame = ( cls.netchan.incoming_acknowledged ) & CL_UPDATE_MASK;
	cmd = cl.parsecountmod;

	// compare what the server returned with what we had predicted it to be
	VectorSubtract( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame], delta );
	dist = VectorLength( delta );

	// save the prediction error for interpolation
	if( dist > MAX_PREDICTION_ERROR )
	{
		if( cl_showerror->value && host_developer.value )
			Con_NPrintf( 10 + ( ++pos & 3 ), "^3player teleported:^7 %.3f units\n", dist );

		// a teleport or something or gamepaused
		VectorClear( cl.local.prediction_error );
	}
	else
	{
		if( cl_showerror->value && dist > MIN_PREDICTION_EPSILON && host_developer.value )
			Con_NPrintf( 10 + ( ++pos & 3 ), "^1prediction error:^7 %.3f units\n", dist );

		VectorCopy( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame] );

		// save for error interpolation
		VectorCopy( delta, cl.local.prediction_error );

		// GoldSrc checks for singleplayer
		// we would check for local server
		if( dist > MIN_CORRECTION_DISTANCE && !SV_Active() )
			cls.correction_time = cl_smoothtime->value;
	}
}

/*
=============
CL_SetUpPlayerPrediction

Calculate the new position of players, without other player clipping
We do this to set up real player prediction.
Players are predicted twice, first without clipping other players,
then with clipping against them.
This sets up the first phase.
=============
*/
void GAME_EXPORT CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient )
{
	entity_state_t	*state;
	predicted_player_t	*player;
	cl_entity_t	*ent;
	int		i;

	for( i = 0; i < MAX_CLIENTS; i++ )
	{
		state = &cl.frames[cl.parsecountmod].playerstate[i];
		player = &cls.predicted_players[i];

		player->active = false;

		if( state->messagenum != cl.parsecount )
			continue; // not present this frame

		if( !state->modelindex )
			continue;

		player->active = true;
		player->movetype = state->movetype;
		player->solid = state->solid;
		player->usehull = state->usehull;

		if( FBitSet( state->effects, EF_NODRAW ) && !bIncludeLocalClient && ( cl.playernum == i ))
			continue;

		// note that the local player is special, since he moves locally
		// we use his last predicted postition
		if( cl.playernum == i )
		{
			VectorCopy( state->origin, player->origin );
			VectorCopy( state->angles, player->angles );
		}
		else
		{
			ent = CL_GetEntityByIndex( i + 1 );

			CL_ComputePlayerOrigin( ent );

			VectorCopy( ent->origin, player->origin );
			VectorCopy( ent->angles, player->angles );
		}
	}
}

void CL_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, pmtrace_t *tr )
{
	Assert( tr != NULL );

	if( clgame.dllFuncs.pfnClipMoveToEntity != NULL )
	{
		// do custom sweep test
		clgame.dllFuncs.pfnClipMoveToEntity( pe, start, mins, maxs, end, tr );
	}
	else
	{
		// function is missed, so we didn't hit anything
		tr->allsolid = false;
	}
}

static void CL_CopyEntityToPhysEnt( physent_t *pe, entity_state_t *state, qboolean visent )
{
	model_t	*mod = CL_ModelHandle( state->modelindex );

	pe->player = 0;

	if( state->number >= 1 && state->number <= cl.maxclients )
		pe->player = state->number;

	if( pe->player )
	{
		// client or bot
		Q_snprintf( pe->name, sizeof( pe->name ), "player %i", pe->player - 1 );
	}
	else
	{
		// otherwise copy the modelname
		Q_strncpy( pe->name, mod->name, sizeof( pe->name ));
	}

	pe->model = pe->studiomodel = NULL;

	VectorCopy( state->mins, pe->mins );
	VectorCopy( state->maxs, pe->maxs );

	if( state->solid == SOLID_BBOX )
	{
		if( FBitSet( mod->flags, STUDIO_TRACE_HITBOX ))
			pe->studiomodel = mod;
	}
	else
	{
		if( pe->solid != SOLID_BSP && ( mod != NULL ) && ( mod->type == mod_studio ))
			pe->studiomodel = mod;
		else pe->model = mod;
	}

	// rare case: not solid entities in vistrace
	if( visent && VectorIsNull( pe->mins ))
	{
		VectorCopy( mod->mins, pe->mins );
		VectorCopy( mod->maxs, pe->maxs );
	}

	pe->info = state->number;
	VectorCopy( state->origin, pe->origin );
	VectorCopy( state->angles, pe->angles );

	pe->solid = state->solid;
	pe->rendermode = state->rendermode;
	pe->skin = state->skin;
	pe->frame = state->frame;
	pe->sequence = state->sequence;

	memcpy( &pe->controller[0], &state->controller[0], sizeof( pe->controller ));
	memcpy( &pe->blending[0], &state->blending[0], sizeof( pe->blending ));

	pe->movetype = state->movetype;
	pe->takedamage = (pe->player) ? DAMAGE_YES : DAMAGE_NO;
	pe->team = state->team;
	pe->classnumber = state->playerclass;
	pe->blooddecal = 0;	// unused in GoldSrc

	// for mods
	pe->iuser1 = state->iuser1;
	pe->iuser2 = state->iuser2;
	pe->iuser3 = state->iuser3;
	pe->iuser4 = state->iuser4;
	pe->fuser1 = state->fuser1;
	pe->fuser2 = state->fuser2;
	pe->fuser3 = state->fuser3;
	pe->fuser4 = state->fuser4;

	VectorCopy( state->vuser1, pe->vuser1 );
	VectorCopy( state->vuser2, pe->vuser2 );
	VectorCopy( state->vuser3, pe->vuser3 );
	VectorCopy( state->vuser4, pe->vuser4 );
}

/*
====================
CL_AddLinksToPmove

collect solid entities
====================
*/
void CL_AddLinksToPmove( frame_t *frame )
{
	entity_state_t	*state;
	model_t		*model;
	physent_t		*pe;
	int		i;

	if( !frame->valid ) return;

	for( i = 0; i < frame->num_entities; i++ )
	{
		state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];

		if( state->number >= 1 && state->number <= cl.maxclients )
			continue;

		if( !state->modelindex )
			continue;

		model = CL_ModelHandle( state->modelindex );
		if( !model ) continue;

		if(( state->owner != 0 ) && ( state->owner == cl.playernum + 1 ))
			continue;

		if(( model->hulls[1].lastclipnode || model->type == mod_studio ) && clgame.pmove->numvisent < MAX_PHYSENTS )
		{
			pe = &clgame.pmove->visents[clgame.pmove->numvisent];
			CL_CopyEntityToPhysEnt( pe, state, true );
			clgame.pmove->numvisent++;
		}

		if( state->solid == SOLID_TRIGGER || ( state->solid == SOLID_NOT && state->skin >= CONTENTS_EMPTY ))
			continue;

		// dead body
		if( state->mins[2] == 0.0f && state->maxs[2] == 1.0f )
			continue;

		// can't collide with zeroed hull
		if( VectorIsNull( state->mins ) && VectorIsNull( state->maxs ))
			continue;

		if( state->solid == SOLID_NOT && state->skin == CONTENTS_LADDER )
		{
			if( clgame.pmove->nummoveent >= MAX_MOVEENTS )
				continue;

			pe = &clgame.pmove->moveents[clgame.pmove->nummoveent];
			CL_CopyEntityToPhysEnt( pe, state, false );
			clgame.pmove->nummoveent++;
		}
		else
		{
			if( !model->hulls[1].lastclipnode && model->type != mod_studio )
				continue;

			// reserve slots for all the clients
			if( clgame.pmove->numphysent >= ( MAX_PHYSENTS - cl.maxclients ))
				continue;

			pe = &clgame.pmove->physents[clgame.pmove->numphysent];
			CL_CopyEntityToPhysEnt( pe, state, false );
			clgame.pmove->numphysent++;
		}
	}
}

/*
===============
CL_SetSolidEntities

Builds all the pmove physents for the current frame
===============
*/
void CL_SetSolidEntities( void )
{
	physent_t	*pe = clgame.pmove->physents;

	// setup physents
	clgame.pmove->numvisent = 1;
	clgame.pmove->numphysent = 1;
	clgame.pmove->nummoveent = 0;

	memset( clgame.pmove->physents, 0, sizeof( physent_t ));
	memset( clgame.pmove->visents, 0, sizeof( physent_t ));

	pe->model = cl.worldmodel;
	if( pe->model ) Q_strncpy( pe->name, pe->model->name, sizeof( pe->name ));
	pe->takedamage = DAMAGE_YES;
	pe->solid = SOLID_BSP;

	// share to visents
	clgame.pmove->visents[0] = clgame.pmove->physents[0];

	// add all other entities exlucde players
	CL_AddLinksToPmove( &cl.frames[cl.parsecountmod] );
}

/*
===============
CL_SetSolidPlayers

Builds all the pmove physents for the current frame
Note that CL_SetUpPlayerPrediction() must be called first!
pmove must be setup with world and solid entity hulls before calling
(via CL_PredictMove)
===============
*/
void GAME_EXPORT CL_SetSolidPlayers( int playernum )
{
	entity_state_t	*state;
	predicted_player_t	*player;
	physent_t		*pe;
	int		i;

	if( !cl_solid_players->value )
		return;

	for( i = 0; i < MAX_CLIENTS; i++ )
	{
		state = &cl.frames[cl.parsecountmod].playerstate[i];
		player = &cls.predicted_players[i];

		if( playernum == -1 )
		{
			if( i != cl.playernum && !player->active )
				continue;
		}
		else
		{
			if( !player->active )
				continue;	// not present this frame

			// the player object never gets added
			if( playernum == i )
				continue;
		}

		if( player->solid == SOLID_NOT )
			continue;	// dead body

		if( clgame.pmove->numphysent >= MAX_PHYSENTS )
			break;

		pe = &clgame.pmove->physents[clgame.pmove->numphysent];
		CL_CopyEntityToPhysEnt( pe, state, false );
		clgame.pmove->numphysent++;

		// some fields needs to be override from cls.predicted_players
		VectorCopy( player->origin, pe->origin );
		VectorCopy( player->angles, pe->angles );
		VectorCopy( clgame.pmove->player_mins[player->usehull], pe->mins );
		VectorCopy( clgame.pmove->player_maxs[player->usehull], pe->maxs );
		pe->movetype = player->movetype;
		pe->solid = player->solid;
	}
}

/*
=============
CL_WaterEntity

=============
*/
int GAME_EXPORT CL_WaterEntity( const float *rgflPos )
{
	physent_t		*pe;
	hull_t		*hull;
	vec3_t		test, offset;
	int		i, oldhull;

	if( !rgflPos ) return -1;

	oldhull = clgame.pmove->usehull;

	for( i = 0; i < clgame.pmove->numphysent; i++ )
	{
		pe = &clgame.pmove->physents[i];

		if( pe->solid != SOLID_NOT ) // disabled ?
			continue;

		// only brushes can have special contents
		if( !pe->model || pe->model->type != mod_brush )
			continue;

		// check water brushes accuracy
		clgame.pmove->usehull = 2;
		hull = PM_HullForBsp( pe, clgame.pmove, offset );
		clgame.pmove->usehull = oldhull;

		// offset the test point appropriately for this hull.
		VectorSubtract( rgflPos, offset, test );

		if( FBitSet( pe->model->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( pe->angles ))
		{
			matrix4x4	matrix;

			Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );
			Matrix4x4_VectorITransform( matrix, rgflPos, test );
		}

		// test hull for intersection with this model
		if( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY )
			continue;

		// found water entity
		return pe->info;
	}
	return -1;
}

/*
=============
CL_TraceLine

a simple engine traceline
=============
*/
pmtrace_t CL_TraceLine( vec3_t start, vec3_t end, int flags )
{
	int	old_usehull;
	pmtrace_t	tr;

	old_usehull = clgame.pmove->usehull;
	clgame.pmove->usehull = 2;
	tr = PM_PlayerTraceExt( clgame.pmove, start, end, flags, clgame.pmove->numphysent, clgame.pmove->physents, -1, NULL );
	clgame.pmove->usehull = old_usehull;

	return tr;
}

/*
=============
CL_VisTraceLine

trace by visible objects (thats can be non-solid)
=============
*/
pmtrace_t *CL_VisTraceLine( vec3_t start, vec3_t end, int flags )
{
	int		old_usehull;
	static pmtrace_t	tr;

	old_usehull = clgame.pmove->usehull;
	clgame.pmove->usehull = 2;
	tr = PM_PlayerTraceExt( clgame.pmove, start, end, flags, clgame.pmove->numvisent, clgame.pmove->visents, -1, NULL );
	clgame.pmove->usehull = old_usehull;

	return &tr;
}

/*
=============
CL_GetWaterEntity

returns water brush where inside pos
=============
*/
cl_entity_t *CL_GetWaterEntity( const float *rgflPos )
{
	int	entnum;

	entnum = CL_WaterEntity( rgflPos );
	if( entnum <= 0 ) return NULL; // world or not water

	return CL_GetEntityByIndex( entnum );
}

int GAME_EXPORT CL_TestLine( const vec3_t start, const vec3_t end, int flags )
{
	return PM_TestLineExt( clgame.pmove, clgame.pmove->physents, clgame.pmove->numphysent, start, end, flags );
}

static int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace )
{
	return PM_TestPlayerPosition( clgame.pmove, pos, ptrace, NULL );
}

static void GAME_EXPORT pfnStuckTouch( int hitent, pmtrace_t *tr )
{
	int	i;

	for( i = 0; i < clgame.pmove->numtouch; i++ )
	{
		if( clgame.pmove->touchindex[i].ent == hitent )
			return;
	}

	if( clgame.pmove->numtouch >= MAX_PHYSENTS )
		return;

	VectorCopy( clgame.pmove->velocity, tr->deltavelocity );
	tr->ent = hitent;

	clgame.pmove->touchindex[clgame.pmove->numtouch++] = *tr;
}

static int GAME_EXPORT pfnPointContents( float *p, int *truecontents )
{
	int	cont, truecont;

	truecont = cont = PM_PointContents( clgame.pmove, p );
	if( truecontents ) *truecontents = truecont;

	if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN )
		cont = CONTENTS_WATER;
	return cont;
}

static int GAME_EXPORT pfnTruePointContents( float *p )
{
	return PM_TruePointContents( clgame.pmove, p );
}

static int GAME_EXPORT pfnHullPointContents( struct hull_s *hull, int num, float *p )
{
	return PM_HullPointContents( hull, num, p );
}

static pmtrace_t GAME_EXPORT pfnPlayerTrace( float *start, float *end, int traceFlags, int ignore_pe )
{
	return PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL );
}

pmtrace_t *PM_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe )
{
	static pmtrace_t	tr;
	int		old_usehull;

	old_usehull = clgame.pmove->usehull;
	clgame.pmove->usehull = usehull;

	switch( flags )
	{
	case PM_TRACELINE_PHYSENTSONLY:
		tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL );
		break;
	case PM_TRACELINE_ANYVISIBLE:
		tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, ignore_pe, NULL );
		break;
	}

	clgame.pmove->usehull = old_usehull;

	return &tr;
}

static hull_t *pfnHullForBsp( physent_t *pe, float *offset )
{
	return PM_HullForBsp( pe, clgame.pmove, offset );
}

static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, trace_t *trace )
{
	int	old_usehull;
	vec3_t	start_l, end_l;
	vec3_t	offset, temp;
	qboolean	rotated;
	matrix4x4	matrix;
	hull_t	*hull;

	old_usehull = clgame.pmove->usehull;
	clgame.pmove->usehull = 2;

	hull = PM_HullForBsp( pe, clgame.pmove, offset );

	clgame.pmove->usehull = old_usehull;

	if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles ))
		rotated = true;
	else rotated = false;

 	if( rotated )
 	{
 		Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );
 		Matrix4x4_VectorITransform( matrix, start, start_l );
 		Matrix4x4_VectorITransform( matrix, end, end_l );
 	}
 	else
 	{
 		VectorSubtract( start, offset, start_l );
 		VectorSubtract( end, offset, end_l );
 	}

	PM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, (pmtrace_t *)trace );
	trace->ent = NULL;

	if( rotated )
	{
		VectorCopy( trace->plane.normal, temp );
		Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist );
	}

	VectorLerp( start, trace->fraction, end, trace->endpos );

	return trace->fraction;
}

static const char *pfnTraceTexture( int ground, float *vstart, float *vend )
{
	physent_t *pe;

	if( ground < 0 || ground >= clgame.pmove->numphysent )
		return NULL; // bad ground

	pe = &clgame.pmove->physents[ground];
	return PM_TraceTexture( pe, vstart, vend );
}

static void GAME_EXPORT pfnPlaySound( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch )
{
	if( !clgame.pmove->runfuncs )
		return;

	S_StartSound( NULL, clgame.pmove->player_index + 1, channel, S_RegisterSound( sample ), volume, attenuation, pitch, fFlags );
}

static void GAME_EXPORT pfnPlaybackEventFull( int flags, int clientindex, word eventindex, float delay, float *origin,
	float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )
{
	CL_PlaybackEvent( flags, NULL, eventindex, delay, origin, angles, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 );
}

static pmtrace_t GAME_EXPORT pfnPlayerTraceEx( float *start, float *end, int traceFlags, pfnIgnore pmFilter )
{
	return PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pmFilter );
}

static int GAME_EXPORT pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, pfnIgnore pmFilter )
{
	return PM_TestPlayerPosition( clgame.pmove, pos, ptrace, pmFilter );
}

static pmtrace_t *pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter )
{
	static pmtrace_t	tr;
	int		old_usehull;

	old_usehull = clgame.pmove->usehull;
	clgame.pmove->usehull = usehull;

	switch( flags )
	{
	case PM_TRACELINE_PHYSENTSONLY:
		tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, -1, pmFilter );
		break;
	case PM_TRACELINE_ANYVISIBLE:
		tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, -1, pmFilter );
		break;
	}

	clgame.pmove->usehull = old_usehull;

	return &tr;
}

/*
===============
CL_InitClientMove

===============
*/
void CL_InitClientMove( void )
{
	int	i;

	Pmove_Init ();

	clgame.pmove->server = false;	// running at client
	clgame.pmove->movevars = &clgame.movevars;
	clgame.pmove->runfuncs = false;

	// enumerate client hulls
	for( i = 0; i < MAX_MAP_HULLS; i++ )
	{
		if( clgame.dllFuncs.pfnGetHullBounds( i, host.player_mins[i], host.player_maxs[i] ))
			Con_Reportf( "CL: hull%i, player_mins: %g %g %g, player_maxs: %g %g %g\n", i,
			host.player_mins[i][0], host.player_mins[i][1], host.player_mins[i][2],
			host.player_maxs[i][0], host.player_maxs[i][1], host.player_maxs[i][2] );
	}

	memcpy( clgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins ));
	memcpy( clgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs ));

	// common utilities
	clgame.pmove->PM_Info_ValueForKey = Info_ValueForKey;
	clgame.pmove->PM_Particle = CL_Particle; // ref should be initialized here already
	clgame.pmove->PM_TestPlayerPosition = pfnTestPlayerPosition;
	clgame.pmove->Con_NPrintf = Con_NPrintf;
	clgame.pmove->Con_DPrintf = Con_DPrintf;
	clgame.pmove->Con_Printf = Con_Printf;
	clgame.pmove->Sys_FloatTime = Sys_DoubleTime;
	clgame.pmove->PM_StuckTouch = pfnStuckTouch;
	clgame.pmove->PM_PointContents = pfnPointContents;
	clgame.pmove->PM_TruePointContents = pfnTruePointContents;
	clgame.pmove->PM_HullPointContents = pfnHullPointContents;
	clgame.pmove->PM_PlayerTrace = pfnPlayerTrace;
	clgame.pmove->PM_TraceLine = PM_TraceLine;
	clgame.pmove->RandomLong = COM_RandomLong;
	clgame.pmove->RandomFloat = COM_RandomFloat;
	clgame.pmove->PM_GetModelType = pfnGetModelType;
	clgame.pmove->PM_GetModelBounds = pfnGetModelBounds;
	clgame.pmove->PM_HullForBsp = (void*)pfnHullForBsp;
	clgame.pmove->PM_TraceModel = pfnTraceModel;
	clgame.pmove->COM_FileSize = COM_FileSize;
	clgame.pmove->COM_LoadFile = COM_LoadFile;
	clgame.pmove->COM_FreeFile = COM_FreeFile;
	clgame.pmove->memfgets = COM_MemFgets;
	clgame.pmove->PM_PlaySound = pfnPlaySound;
	clgame.pmove->PM_TraceTexture = pfnTraceTexture;
	clgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull;
	clgame.pmove->PM_PlayerTraceEx = pfnPlayerTraceEx;
	clgame.pmove->PM_TestPlayerPositionEx = pfnTestPlayerPositionEx;
	clgame.pmove->PM_TraceLineEx = pfnTraceLineEx;
	clgame.pmove->PM_TraceSurface = pfnTraceSurface;

	// initalize pmove
	clgame.dllFuncs.pfnPlayerMoveInit( clgame.pmove );
}

static void PM_CheckMovingGround( clientdata_t *cd, entity_state_t *state, float frametime )
{
	if(!( cd->flags & FL_BASEVELOCITY ))
	{
		// apply momentum (add in half of the previous frame of velocity first)
		VectorMA( cd->velocity, 1.0f + (frametime * 0.5f), state->basevelocity, cd->velocity );
		VectorClear( state->basevelocity );
	}
	cd->flags &= ~FL_BASEVELOCITY;
}

void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, qboolean runfuncs, double time )
{
	entity_state_t	*ps;
	clientdata_t	*cd;

	ps = &from->playerstate;
	cd = &from->client;

	pmove->player_index = ps->number - 1;
	pmove->multiplayer = (cl.maxclients > 1);
	pmove->runfuncs = runfuncs;
	pmove->time = time * 1000.0f;
	pmove->frametime = ucmd->msec / 1000.0f;
	VectorCopy( ps->origin, pmove->origin );
	VectorCopy( ps->angles, pmove->angles );
	VectorCopy( pmove->angles, pmove->oldangles );
	VectorCopy( cd->velocity, pmove->velocity );
	VectorCopy( ps->basevelocity, pmove->basevelocity );
	VectorCopy( cd->view_ofs, pmove->view_ofs );
	VectorClear( pmove->movedir );
	pmove->flDuckTime = cd->flDuckTime;
	pmove->bInDuck = cd->bInDuck;
	pmove->usehull = ps->usehull;
	pmove->flTimeStepSound = cd->flTimeStepSound;
	pmove->iStepLeft = ps->iStepLeft;
	pmove->flFallVelocity = ps->flFallVelocity;
	pmove->flSwimTime = cd->flSwimTime;
	VectorCopy( cd->punchangle, pmove->punchangle );
	pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code
	pmove->effects = ps->effects;
	pmove->flags = cd->flags;
	pmove->gravity = ps->gravity;
	pmove->friction = ps->friction;
	pmove->oldbuttons = ps->oldbuttons;
	pmove->waterjumptime = cd->waterjumptime;
	pmove->dead = (cl.local.health <= 0);
	pmove->deadflag = cd->deadflag;
	pmove->spectator = (cls.spectator != 0);
	pmove->movetype = ps->movetype;
	pmove->onground = ps->onground;
	pmove->waterlevel = cd->waterlevel;
	pmove->watertype = cd->watertype;
	pmove->maxspeed = clgame.movevars.maxspeed;
	pmove->clientmaxspeed = cd->maxspeed;
	pmove->iuser1 = cd->iuser1;
	pmove->iuser2 = cd->iuser2;
	pmove->iuser3 = cd->iuser3;
	pmove->iuser4 = cd->iuser4;
	pmove->fuser1 = cd->fuser1;
	pmove->fuser2 = cd->fuser2;
	pmove->fuser3 = cd->fuser3;
	pmove->fuser4 = cd->fuser4;
	VectorCopy( cd->vuser1, pmove->vuser1 );
	VectorCopy( cd->vuser2, pmove->vuser2 );
	VectorCopy( cd->vuser3, pmove->vuser3 );
	VectorCopy( cd->vuser4, pmove->vuser4 );
	pmove->cmd = *ucmd;	// copy current cmds

	Q_strncpy( pmove->physinfo, cls.physinfo, MAX_INFO_STRING );
}

void CL_FinishPMove( playermove_t *pmove, local_state_t *to )
{
	entity_state_t	*ps;
	clientdata_t	*cd;

	ps = &to->playerstate;
	cd = &to->client;

	cd->flags = pmove->flags;
	cd->bInDuck = pmove->bInDuck;
	cd->flTimeStepSound = pmove->flTimeStepSound;
	cd->flDuckTime = pmove->flDuckTime;
	cd->flSwimTime = (int)pmove->flSwimTime;
	cd->waterjumptime = (int)pmove->waterjumptime;
	cd->watertype = pmove->watertype;
	cd->waterlevel = pmove->waterlevel;
	cd->maxspeed = pmove->clientmaxspeed;
	cd->deadflag = pmove->deadflag;
	VectorCopy( pmove->velocity, cd->velocity );
	VectorCopy( pmove->view_ofs, cd->view_ofs );
	VectorCopy( pmove->origin, ps->origin );
	VectorCopy( pmove->angles, ps->angles );
	VectorCopy( pmove->basevelocity, ps->basevelocity );
	VectorCopy( pmove->punchangle, cd->punchangle );
	ps->oldbuttons = pmove->cmd.buttons;
	ps->friction = pmove->friction;
	ps->movetype = pmove->movetype;
	ps->onground = pmove->onground;
	ps->effects = pmove->effects;
	ps->usehull = pmove->usehull;
	ps->iStepLeft = pmove->iStepLeft;
	ps->flFallVelocity = pmove->flFallVelocity;
	cd->iuser1 = pmove->iuser1;
	cd->iuser2 = pmove->iuser2;
	cd->iuser3 = pmove->iuser3;
	cd->iuser4 = pmove->iuser4;
	cd->fuser1 = pmove->fuser1;
	cd->fuser2 = pmove->fuser2;
	cd->fuser3 = pmove->fuser3;
	cd->fuser4 = pmove->fuser4;
	VectorCopy( pmove->vuser1, cd->vuser1 );
	VectorCopy( pmove->vuser2, cd->vuser2 );
	VectorCopy( pmove->vuser3, cd->vuser3 );
	VectorCopy( pmove->vuser4, cd->vuser4 );
}

/*
=================
CL_RunUsercmd

Runs prediction code for user cmd
=================
*/
void CL_RunUsercmd( local_state_t *from, local_state_t *to, usercmd_t *u, qboolean runfuncs, double *time, unsigned int random_seed )
{
	usercmd_t		cmd;

	if( u->msec > 50 )
	{
		local_state_t	temp;
		usercmd_t		split;

		memset( &temp, 0, sizeof( temp ));

		split = *u;
		split.msec /= 2;
		CL_RunUsercmd( from, &temp, &split, runfuncs, time, random_seed );
		split.impulse = split.weaponselect = 0;
		CL_RunUsercmd( &temp, to, &split, runfuncs, time, random_seed );
		return;
	}

	cmd = *u;	// deal with local copy
	*to = *from;

	if( CL_IsPredicted( ))
	{
		// setup playermove state
		CL_SetupPMove( clgame.pmove, from, &cmd, runfuncs, *time );

		// motor!
		clgame.dllFuncs.pfnPlayerMove( clgame.pmove, false );

		// copy results back to client
		CL_FinishPMove( clgame.pmove, to );

		if( clgame.pmove->onground > 0 && clgame.pmove->onground < clgame.pmove->numphysent )
			cl.local.lastground = clgame.pmove->physents[clgame.pmove->onground].info;
		else cl.local.lastground = clgame.pmove->onground; // world(0) or in air(-1)
	}

	clgame.dllFuncs.pfnPostRunCmd( from, to, &cmd, runfuncs, *time, random_seed );

	*time += (double)cmd.msec / 1000.0;
}


/*
=================
CL_MoveSpectatorCamera

spectator movement code
=================
*/
void CL_MoveSpectatorCamera( void )
{
	double	time = cl.time;

	if( !cls.spectator )
		return;

	CL_SetUpPlayerPrediction( false, true );
	CL_SetSolidPlayers( cl.playernum );
	CL_RunUsercmd( &cls.spectator_state, &cls.spectator_state, cl.cmd, true, &time, (uint)( time * 100.0 ));

	VectorCopy( cls.spectator_state.client.velocity, cl.simvel );
	VectorCopy( cls.spectator_state.client.origin, cl.simorg );
	VectorCopy( cls.spectator_state.client.punchangle, cl.punchangle );
	VectorCopy( cls.spectator_state.client.view_ofs, cl.viewheight );
}

/*
=================
CL_PredictMovement

Sets cl.predicted.origin and cl.predicted.angles
=================
*/
void CL_PredictMovement( qboolean repredicting )
{
	runcmd_t		*to_cmd = NULL, *from_cmd;
	local_state_t	*from = NULL, *to = NULL;
	uint		current_command;
	uint		current_command_mod;
	frame_t		*frame = NULL;
	uint		i, stoppoint;
	qboolean		runfuncs;
	double		f = 1.0;
	cl_entity_t	*ent;
	double		time;

	if( cls.state != ca_active || cls.spectator )
		return;

	if( cls.demoplayback && !repredicting )
		CL_DemoInterpolateAngles();

	CL_SetUpPlayerPrediction( false, false );

	if( cls.state != ca_active || !cl.validsequence )
		return;

	if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK )
		return;

	// this is the last frame received from the server
	frame = &cl.frames[cl.parsecountmod];

	if( !CL_IsPredicted( ))
	{
		VectorCopy( frame->clientdata.velocity, cl.simvel );
		VectorCopy( frame->clientdata.origin, cl.simorg );
		VectorCopy( frame->clientdata.punchangle, cl.punchangle );
		VectorCopy( frame->clientdata.view_ofs, cl.viewheight );
		cl.local.usehull = frame->playerstate[cl.playernum].usehull;
		cl.local.waterlevel = frame->clientdata.waterlevel;

		if( FBitSet( frame->clientdata.flags, FL_ONGROUND ))
			cl.local.onground = frame->playerstate[cl.playernum].onground;
		else cl.local.onground = -1;
	}

	from = &cl.predicted_frames[cl.parsecountmod];
	from_cmd = &cl.commands[cls.netchan.incoming_acknowledged & CL_UPDATE_MASK];
	memcpy( from->weapondata, frame->weapondata, sizeof( from->weapondata ));
	from->playerstate = frame->playerstate[cl.playernum];
	from->client = frame->clientdata;
	if( !frame->valid ) return;

	time = frame->time;
	stoppoint = ( repredicting ) ? 0 : 1;
	cl.local.repredicting = repredicting;
	cl.local.onground = -1;

	// predict forward until cl.time <= to->senttime
	CL_PushPMStates();
	CL_SetSolidPlayers( cl.playernum );

	for( i = 1; i < CL_UPDATE_MASK && cls.netchan.incoming_acknowledged + i < cls.netchan.outgoing_sequence + stoppoint; i++ )
	{
		current_command = cls.netchan.incoming_acknowledged + i;
		current_command_mod = current_command & CL_UPDATE_MASK;

		to = &cl.predicted_frames[(cl.parsecountmod + i) & CL_UPDATE_MASK];
		to_cmd = &cl.commands[current_command_mod];
		runfuncs = ( !repredicting && !to_cmd->processedfuncs );

		CL_RunUsercmd( from, to, &to_cmd->cmd, runfuncs, &time, current_command );
		VectorCopy( to->playerstate.origin, cl.local.predicted_origins[current_command_mod] );
		to_cmd->processedfuncs = true;

		if( to_cmd->senttime >= host.realtime )
			break;

		from = to;
		from_cmd = to_cmd;
	}

	CL_PopPMStates();

	if(( i == CL_UPDATE_MASK ) || ( !to && !repredicting ))
	{
		cl.local.repredicting = false;
		return; // net hasn't deliver packets in a long time...
	}

	if( !to )
	{
		to = from;
		to_cmd = from_cmd;
	}

	if( !CL_IsPredicted( ))
	{
		// keep onground actual
		if( FBitSet( frame->clientdata.flags, FL_ONGROUND ))
			cl.local.onground = frame->playerstate[cl.playernum].onground;
		else cl.local.onground = -1;

		if( !repredicting || !CVAR_TO_BOOL( cl_lw ))
			cl.local.viewmodel = to->client.viewmodel;
		cl.local.repredicting = false;
		cl.local.moving = false;
		return;
	}

	// now interpolate some fraction of the final frame
	if( to_cmd->senttime != from_cmd->senttime )
		f = bound( 0.0, (host.realtime - from_cmd->senttime) / (to_cmd->senttime - from_cmd->senttime) * 0.1, 1.0 );
	else f = 0.0;

	if( CL_PlayerTeleported( from, to ))
	{
		VectorCopy( to->client.velocity, cl.simvel );
		VectorCopy( to->playerstate.origin, cl.simorg );
		VectorCopy( to->client.punchangle, cl.punchangle );
		VectorCopy( to->client.view_ofs, cl.viewheight );
	}
	else
	{
		VectorLerp( from->playerstate.origin, f, to->playerstate.origin, cl.simorg );
		VectorLerp( from->client.velocity, f, to->client.velocity, cl.simvel );
		VectorLerp( from->client.punchangle, f, to->client.punchangle, cl.punchangle );

		if( from->playerstate.usehull == to->playerstate.usehull )
			VectorLerp( from->client.view_ofs, f, to->client.view_ofs, cl.viewheight );
		else VectorCopy( to->client.view_ofs, cl.viewheight );
	}

	cl.local.waterlevel = to->client.waterlevel;
	cl.local.usehull = to->playerstate.usehull;
	if( !repredicting || !CVAR_TO_BOOL( cl_lw ))
		cl.local.viewmodel = to->client.viewmodel;

	if( FBitSet( to->client.flags, FL_ONGROUND ))
	{
		ent = CL_GetEntityByIndex( cl.local.lastground );
		cl.local.onground = cl.local.lastground;
		cl.local.moving = false;

		if( ent )
		{
			vec3_t delta;

			delta[0] = ent->curstate.origin[0] - ent->prevstate.origin[0];
			delta[1] = ent->curstate.origin[1] - ent->prevstate.origin[1];
			delta[2] = 0.0f;

			if( VectorLength( delta ) > 0.0f )
			{
				cls.correction_time = 0;
				cl.local.moving = true;
			}
		}
	}
	else
	{
		cl.local.onground = -1;
		cl.local.moving = 0;
	}

	if( cls.correction_time > 0 && !cl_nosmooth->value && cl_smoothtime->value )
	{
		vec3_t	delta;
		float	frac;

		// only decay timer once per frame
		if( !repredicting )
			cls.correction_time -= host.frametime;

		// Make sure smoothtime is postive
		if( cl_smoothtime->value <= 0.0f )
			Cvar_DirectSet( cl_smoothtime, "0.1" );

		// Clamp from 0 to cl_smoothtime.value
		cls.correction_time = bound( 0.0, cls.correction_time, cl_smoothtime->value );

		// Compute backward interpolation fraction along full correction
		frac = 1.0f - cls.correction_time / cl_smoothtime->value;

		// Determine how much error we still have to make up for
		VectorSubtract( cl.simorg, cl.local.lastorigin, delta );

		// Scale the error by the backlerp fraction
		VectorScale( delta, frac, delta );

		// Go some fraction of the way
		// FIXME, Probably can't do this any more
		VectorAdd( cl.local.lastorigin, delta, cl.simorg );
	}

	VectorCopy( cl.simorg, cl.local.lastorigin );
	cl.local.repredicting = false;
}
