//
// NPC_move.cpp
//

// leave this line at the top for all NPC_xxxx.cpp files...
#include "g_headers.h"

#include "b_local.h"
#include "g_nav.h"
#include "anims.h"

extern qboolean NPC_ClearPathToGoal( vec3_t dir, gentity_t *goal );
extern qboolean NAV_MoveDirSafe( gentity_t *self, usercmd_t *cmd, float distScale = 1.0f );

void CG_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color );

qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2);
extern int GetTime ( int lastTime );

navInfo_t	frameNavInfo;
extern qboolean FlyingCreature( gentity_t *ent );
extern qboolean PM_InKnockDown( playerState_t *ps );

extern cvar_t	*g_navSafetyChecks;

extern qboolean Boba_Flying( gentity_t *self );
extern qboolean PM_InRoll( playerState_t *ps );

#define	APEX_HEIGHT		200.0f
#define	PARA_WIDTH		(sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT))
#define	JUMP_SPEED		200.0f




static qboolean NPC_TryJump();




static qboolean NPC_Jump( vec3_t dest, int goalEntNum )
{//FIXME: if land on enemy, knock him down & jump off again
	float	targetDist, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed, 
	float originalShotSpeed, shotSpeed, speedStep = 50.0f, minShotSpeed = 30.0f, maxShotSpeed = 500.0f;
	qboolean belowBlocked = qfalse, aboveBlocked = qfalse;
	vec3_t	targetDir, shotVel, failCase; 
	trace_t	trace;
	trajectory_t	tr;
	qboolean	blocked;
	int		elapsedTime, timeStep = 250, hitCount = 0, aboveTries = 0, belowTries = 0, maxHits = 10;
	vec3_t	lastPos, testPos, bottom;

	VectorSubtract( dest, NPC->currentOrigin, targetDir );
	targetDist = VectorNormalize( targetDir );
	//make our shotSpeed reliant on the distance
	originalShotSpeed = targetDist;//DistanceHorizontal( dest, NPC->currentOrigin )/2.0f;
	if ( originalShotSpeed > maxShotSpeed )
	{
		originalShotSpeed = maxShotSpeed;
	}
	else if ( originalShotSpeed < minShotSpeed )
	{
		originalShotSpeed = minShotSpeed;
	}
	shotSpeed = originalShotSpeed;

	while ( hitCount < maxHits )
	{
		VectorScale( targetDir, shotSpeed, shotVel );
		travelTime = targetDist/shotSpeed;
		shotVel[2] += travelTime * 0.5 * NPC->client->ps.gravity;

		if ( !hitCount )		
		{//save the first one as the worst case scenario
			VectorCopy( shotVel, failCase );
		}

		if ( 1 )//tracePath )
		{//do a rough trace of the path
			blocked = qfalse;

			VectorCopy( NPC->currentOrigin, tr.trBase );
			VectorCopy( shotVel, tr.trDelta );
			tr.trType = TR_GRAVITY;
			tr.trTime = level.time;
			travelTime *= 1000.0f;
			VectorCopy( NPC->currentOrigin, lastPos );
			
			//This may be kind of wasteful, especially on long throws... use larger steps?  Divide the travelTime into a certain hard number of slices?  Trace just to apex and down?
			for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep )
			{
				if ( (float)elapsedTime > travelTime )
				{//cap it
					elapsedTime = floor( travelTime );
				}
				EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
				//FUCK IT, always check for do not enter...
				gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
				/*
				if ( testPos[2] < lastPos[2] 
					&& elapsedTime < floor( travelTime ) )
				{//going down, haven't reached end, ignore botclip
					gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask );
				}
				else
				{//going up, check for botclip
					gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
				}
				*/

				if ( trace.allsolid || trace.startsolid )
				{//started in solid
					if ( NAVDEBUG_showCollision )
					{
						CG_DrawEdge( lastPos, trace.endpos, EDGE_RED_TWOSECOND );
					}
					return qfalse;//you're hosed, dude
				}
				if ( trace.fraction < 1.0f )
				{//hit something
					if ( NAVDEBUG_showCollision )
					{
						CG_DrawEdge( lastPos, trace.endpos, EDGE_RED_TWOSECOND );	// TryJump
					}
					if ( trace.entityNum == goalEntNum )
					{//hit the enemy, that's bad!
						blocked = qtrue;
						/*
						if ( g_entities[goalEntNum].client && g_entities[goalEntNum].client->ps.groundEntityNum == ENTITYNUM_NONE )
						{//bah, would collide in mid-air, no good
							blocked = qtrue;
						}
						else
						{//he's on the ground, good enough, I guess
							//Hmm, don't want to land on him, though...?
						}
						*/
						break;
					}
					else 
					{
						if ( trace.contents & CONTENTS_BOTCLIP )
						{//hit a do-not-enter brush
							blocked = qtrue;
							break;
						}
						if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, dest ) < 4096 )//hit within 64 of desired location, should be okay
						{//close enough!
							break;
						}
						else
						{//FIXME: maybe find the extents of this brush and go above or below it on next try somehow?
							impactDist = DistanceSquared( trace.endpos, dest );
							if ( impactDist < bestImpactDist )
							{
								bestImpactDist = impactDist;
								VectorCopy( shotVel, failCase );
							}
							blocked = qtrue;
							break;
						}
					}
				}
				else
				{
					if ( NAVDEBUG_showCollision )
					{
						CG_DrawEdge( lastPos, testPos, EDGE_WHITE_TWOSECOND );	// TryJump
					}
				}
				if ( elapsedTime == floor( travelTime ) )
				{//reached end, all clear
					if ( trace.fraction >= 1.0f )
					{//hmm, make sure we'll land on the ground...
						//FIXME: do we care how far below ourselves or our dest we'll land?
						VectorCopy( trace.endpos, bottom );
						bottom[2] -= 128;
						gi.trace( &trace, trace.endpos, NPC->mins, NPC->maxs, bottom, NPC->s.number, NPC->clipmask );
						if ( trace.fraction >= 1.0f )
						{//would fall too far
							blocked = qtrue;
						}
					}
					break;
				}
				else
				{
					//all clear, try next slice
					VectorCopy( testPos, lastPos );
				}
			}
			if ( blocked )
			{//hit something, adjust speed (which will change arc)
				hitCount++;
				//alternate back and forth between trying an arc slightly above or below the ideal
				if ( (hitCount%2) && !belowBlocked )
				{//odd
					belowTries++;
					shotSpeed = originalShotSpeed - (belowTries*speedStep);
				}
				else if ( !aboveBlocked )
				{//even
					aboveTries++;
					shotSpeed = originalShotSpeed + (aboveTries*speedStep);
				}
				else
				{//can't go any higher or lower
					hitCount = maxHits;
					break;
				}
				if ( shotSpeed > maxShotSpeed )
				{
					shotSpeed = maxShotSpeed;
					aboveBlocked = qtrue;
				}
				else if ( shotSpeed < minShotSpeed )
				{
					shotSpeed = minShotSpeed;
					belowBlocked = qtrue;
				}
			}
			else
			{//made it!
				break;
			}
		}
		else
		{//no need to check the path, go with first calc
			break;
		}
	}

	if ( hitCount >= maxHits )
	{//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?)
		return qfalse;
		//NOTE: or try failcase?
		//VectorCopy( failCase, NPC->client->ps.velocity );
		//return qtrue;
	}
	VectorCopy( shotVel, NPC->client->ps.velocity );
	return qtrue;
}






#define NPC_JUMP_PREP_BACKUP_DIST 34.0f

trace_t		mJumpTrace;



qboolean NPC_CanTryJump()
{
	if (!(NPCInfo->scriptFlags&SCF_NAV_CAN_JUMP)	||		// Can't Jump
		(NPCInfo->scriptFlags&SCF_NO_ACROBATICS)	||		// If Can't Jump At All
		(level.time<NPCInfo->jumpBackupTime)		||		// If Backing Up, Don't Try The Jump Again
		(level.time<NPCInfo->jumpNextCheckTime)		||		// Don't Even Try To Jump Again For This Amount Of Time
		(NPCInfo->jumpTime)							||		// Don't Jump If Already Going
		(PM_InKnockDown(&NPC->client->ps))			||		// Don't Jump If In Knockdown
		(PM_InRoll(&NPC->client->ps))				||		// ... Or Roll
		(NPC->client->ps.groundEntityNum==ENTITYNUM_NONE)	// ... Or In The Air
		)
	{
		return qfalse;
	}
	return qtrue;
}

qboolean NPC_TryJump(const vec3_t& pos,	float max_xy_dist, float max_z_diff)
{
	if (NPC_CanTryJump())
	{
		NPCInfo->jumpNextCheckTime	= level.time + Q_irand(1000, 2000);

		VectorCopy(pos, NPCInfo->jumpDest);

		// Can't Try To Jump At A Point In The Air
		//-----------------------------------------
		{
			vec3_t	groundTest;
			VectorCopy(pos, groundTest);
			groundTest[2]	+= (NPC->mins[2]*3);
			gi.trace(&mJumpTrace, NPCInfo->jumpDest, vec3_origin, vec3_origin, groundTest, NPC->s.number, NPC->clipmask );
			if (mJumpTrace.fraction >= 1.0f)
			{
				return qfalse;	//no ground = no jump
			}
		}
		NPCInfo->jumpTarget			= 0;
		NPCInfo->jumpMaxXYDist		= (max_xy_dist)?(max_xy_dist):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?1200:750);
		NPCInfo->jumpMazZDist		= (max_z_diff)?(max_z_diff):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?-1000:-450);
		NPCInfo->jumpTime			= 0;
		NPCInfo->jumpBackupTime		= 0;
		return NPC_TryJump();
	}
	return qfalse;
}

qboolean NPC_TryJump(gentity_t *goal,	float max_xy_dist, float max_z_diff)
{
	if (NPC_CanTryJump())
	{
		NPCInfo->jumpNextCheckTime	= level.time + Q_irand(1000, 3000);

		// Can't Jump At Targets In The Air
		//---------------------------------
		if (goal->client && goal->client->ps.groundEntityNum==ENTITYNUM_NONE)
		{
			return qfalse;
		}
		VectorCopy(goal->currentOrigin, NPCInfo->jumpDest);
		NPCInfo->jumpTarget			= goal;
		NPCInfo->jumpMaxXYDist		= (max_xy_dist)?(max_xy_dist):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?1200:750);
		NPCInfo->jumpMazZDist		= (max_z_diff)?(max_z_diff):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?-1000:-400);
		NPCInfo->jumpTime			= 0;
		NPCInfo->jumpBackupTime		= 0;
		return NPC_TryJump();
	}
	return qfalse;
}

void	 NPC_JumpAnimation()
{
	int	jumpAnim = BOTH_JUMP1;

	if ( NPC->client->NPC_class == CLASS_BOBAFETT 
		|| (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER)
		|| NPC->client->NPC_class == CLASS_ROCKETTROOPER
		||( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) )
	{//can't do acrobatics
		jumpAnim = BOTH_FORCEJUMP1;
	}
	else if (NPC->client->NPC_class != CLASS_HOWLER)
	{
		if ( NPC->client->NPC_class == CLASS_ALORA && Q_irand( 0, 3 ) )
		{
			jumpAnim = Q_irand( BOTH_ALORA_FLIP_1, BOTH_ALORA_FLIP_3 );
		}
		else
		{
			jumpAnim = BOTH_FLIP_F;
		}
	}
	NPC_SetAnim( NPC, SETANIM_BOTH, jumpAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
}

extern void JET_FlyStart(gentity_t* actor);

void	 NPC_JumpSound()
{
	if ( NPC->client->NPC_class == CLASS_HOWLER )
	{
		//FIXME: can I delay the actual jump so that it matches the anim...?
	}
	else if ( NPC->client->NPC_class == CLASS_BOBAFETT 
		|| NPC->client->NPC_class == CLASS_ROCKETTROOPER )
	{
		// does this really need to be here?
		JET_FlyStart(NPC);
	}
	else
	{
		G_SoundOnEnt( NPC, CHAN_BODY, "sound/weapons/force/jump.wav" );
	}
}

qboolean NPC_TryJump()
{
	vec3_t	targetDirection;
	float	targetDistanceXY;
	float	targetDistanceZ;

	// Get The Direction And Distances To The Target
	//-----------------------------------------------
	VectorSubtract(NPCInfo->jumpDest, NPC->currentOrigin, targetDirection);
	targetDirection[2]	= 0.0f;
	targetDistanceXY	= VectorNormalize(targetDirection);
	targetDistanceZ		= NPCInfo->jumpDest[2] - NPC->currentOrigin[2];

	if ((targetDistanceXY>NPCInfo->jumpMaxXYDist) ||
		(targetDistanceZ<NPCInfo->jumpMazZDist))
	{
		return qfalse;
	}


	// Test To See If There Is A Wall Directly In Front Of Actor, If So, Backup Some
	//-------------------------------------------------------------------------------
	if (TIMER_Done(NPC, "jumpBackupDebounce"))
	{
		vec3_t	actorProjectedTowardTarget;
		VectorMA(NPC->currentOrigin, NPC_JUMP_PREP_BACKUP_DIST, targetDirection, actorProjectedTowardTarget);
		gi.trace(&mJumpTrace, NPC->currentOrigin, vec3_origin, vec3_origin, actorProjectedTowardTarget, NPC->s.number, NPC->clipmask);
		if ((mJumpTrace.fraction < 1.0f) ||
			(mJumpTrace.allsolid) ||
			(mJumpTrace.startsolid))
		{
			if (NAVDEBUG_showCollision)
			{
				CG_DrawEdge(NPC->currentOrigin, actorProjectedTowardTarget, EDGE_RED_TWOSECOND);	// TryJump
			}

			// TODO: We may want to test to see if it is safe to back up here?
			NPCInfo->jumpBackupTime = level.time + 1000;
			TIMER_Set(NPC, "jumpBackupDebounce", 5000);
			return qtrue;
		}
	}


//	bool	Wounded					= (NPC->health < 150);
//	bool	OnLowerLedge			= ((targetDistanceZ<-80.0f) && (targetDistanceZ>-200.0f));
//	bool	WithinNormalJumpRange	= ((targetDistanceZ<32.0f)  && (targetDistanceXY<200.0f));
	bool	WithinForceJumpRange	= ((fabsf(targetDistanceZ)>0) || (targetDistanceXY>128));

/*	if (Wounded && OnLowerLedge)
	{
		ucmd.forwardmove	= 127;
		VectorClear(NPC->client->ps.moveDir);
		TIMER_Set(NPC, "duck", -level.time);
		return qtrue;
	}
	
	if (WithinNormalJumpRange)
	{
		ucmd.upmove			= 127;
		ucmd.forwardmove	= 127;
		VectorClear(NPC->client->ps.moveDir);
		TIMER_Set(NPC, "duck", -level.time);
		return qtrue;
	}
*/

	if (!WithinForceJumpRange)
	{
		return qfalse;
	}



	// If There Is Any Chance That This Jump Will Land On An Enemy, Try 8 Different Traces Around The Target
	//-------------------------------------------------------------------------------------------------------
	if (NPCInfo->jumpTarget)
	{
		float	minSafeRadius	= (NPC->maxs[0]*1.5f) + (NPCInfo->jumpTarget->maxs[0]*1.5f);
		float	minSafeRadiusSq	= (minSafeRadius * minSafeRadius);

		if (DistanceSquared(NPCInfo->jumpDest, NPCInfo->jumpTarget->currentOrigin)<minSafeRadiusSq)
		{
			vec3_t	startPos;
			vec3_t	floorPos;
			VectorCopy(NPCInfo->jumpDest, startPos);

			floorPos[2] = NPCInfo->jumpDest[2] + (NPC->mins[2]-32);

			for (int sideTryCount=0; sideTryCount<8; sideTryCount++)
			{
				NPCInfo->jumpSide++;
				if ( NPCInfo->jumpSide > 7 )
				{
					NPCInfo->jumpSide = 0;
				}

				switch ( NPCInfo->jumpSide )
				{
				case 0:
					NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius;
					NPCInfo->jumpDest[1] = startPos[1];
					break;
				case 1:
					NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius;
					NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius;
					break;
				case 2:
					NPCInfo->jumpDest[0] = startPos[0];
					NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius;
					break;
				case 3:
					NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius;
					NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius;
					break;
				case 4:
					NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius;
					NPCInfo->jumpDest[1] = startPos[1];
					break;
				case 5:
					NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius;
					NPCInfo->jumpDest[1] = startPos[1] - minSafeRadius;
					break;
				case 6:
					NPCInfo->jumpDest[0] = startPos[0];
					NPCInfo->jumpDest[1] = startPos[1] - minSafeRadius;
					break;
				case 7:
					NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius;
					NPCInfo->jumpDest[1] = startPos[1] -=minSafeRadius;
					break;
				}

				floorPos[0] = NPCInfo->jumpDest[0];
				floorPos[1] = NPCInfo->jumpDest[1];

				gi.trace(&mJumpTrace, NPCInfo->jumpDest, NPC->mins, NPC->maxs, floorPos, (NPCInfo->jumpTarget)?(NPCInfo->jumpTarget->s.number):(NPC->s.number), (NPC->clipmask|CONTENTS_BOTCLIP));
				if ((mJumpTrace.fraction<1.0f) && 
					(!mJumpTrace.allsolid) && 
					(!mJumpTrace.startsolid))
				{
					break;
				}

				if ( NAVDEBUG_showCollision )
				{
					CG_DrawEdge( NPCInfo->jumpDest, floorPos, EDGE_RED_TWOSECOND );
				}
			}

			// If All Traces Failed, Just Try Going Right Back At The Target Location
			//------------------------------------------------------------------------
			if ((mJumpTrace.fraction>=1.0f) || 
				(mJumpTrace.allsolid) || 
				(mJumpTrace.startsolid))
			{
				VectorCopy(startPos, NPCInfo->jumpDest);
			}
		}
	}
	
	// Now, Actually Try The Jump To The Dest Target
	//-----------------------------------------------
	if (NPC_Jump(NPCInfo->jumpDest, (NPCInfo->jumpTarget)?(NPCInfo->jumpTarget->s.number):(NPC->s.number)))
	{
		// We Made IT!
		//-------------
		NPC_JumpAnimation();
		NPC_JumpSound();

		NPC->client->ps.forceJumpZStart		 = NPC->currentOrigin[2];
		NPC->client->ps.pm_flags			|= PMF_JUMPING;
		NPC->client->ps.weaponTime			 = NPC->client->ps.torsoAnimTimer;
		NPC->client->ps.forcePowersActive	|= ( 1 << FP_LEVITATION );
		ucmd.forwardmove					 = 0;
		NPCInfo->jumpTime					 = 1;

		VectorClear(NPC->client->ps.moveDir);
		TIMER_Set(NPC, "duck", -level.time);

		return qtrue;
	}
	return qfalse;
}

qboolean NPC_Jumping()
{
	if ( NPCInfo->jumpTime )
	{
		if ( !(NPC->client->ps.pm_flags & PMF_JUMPING )//forceJumpZStart )
			&& !(NPC->client->ps.pm_flags&PMF_TRIGGER_PUSHED))
		{//landed
			NPCInfo->jumpTime = 0;
		}
		else
		{
		//	if (NPCInfo->jumpTarget)
		//	{
		//		NPC_FaceEntity(NPCInfo->jumpTarget, qtrue);
		//	}
		//	else
			{
				NPC_FacePosition(NPCInfo->jumpDest, qtrue);
			}
			return qtrue;
		}
	}
	return qfalse;
}

qboolean NPC_JumpBackingUp()
{
	if (NPCInfo->jumpBackupTime)
	{
		if (level.time<NPCInfo->jumpBackupTime)
		{
			STEER::Activate(NPC);
			STEER::Flee(NPC, NPCInfo->jumpDest);
			STEER::DeActivate(NPC, &ucmd);
			NPC_FacePosition(NPCInfo->jumpDest, qtrue);
			NPC_UpdateAngles( qfalse, qtrue );
			return qtrue;
		}

		NPCInfo->jumpBackupTime = 0;
		return NPC_TryJump();
	}
	return false;
}




/*
-------------------------
NPC_CheckCombatMove
-------------------------
*/

inline qboolean NPC_CheckCombatMove( void )
{
	//return NPCInfo->combatMove;
	if ( ( NPCInfo->goalEntity && NPC->enemy && NPCInfo->goalEntity == NPC->enemy ) || ( NPCInfo->combatMove ) )
	{
		return qtrue;
	}

	if ( NPCInfo->goalEntity && NPCInfo->watchTarget )
	{
		if ( NPCInfo->goalEntity != NPCInfo->watchTarget )
		{
			return qtrue;
		}
	}

	return qfalse;
}

/*
-------------------------
NPC_LadderMove
-------------------------
*/

static void NPC_LadderMove( vec3_t dir )
{
	//FIXME: this doesn't guarantee we're facing ladder
	//ALSO: Need to be able to get off at top
	//ALSO: Need to play an anim
	//ALSO: Need transitionary anims?
	
	if ( ( dir[2] > 0 ) || ( dir[2] < 0 && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) )
	{
		//Set our movement direction
		ucmd.upmove = (dir[2] > 0) ? 127 : -127;

		//Don't move around on XY
		ucmd.forwardmove = ucmd.rightmove = 0;
	}
}

/*
-------------------------
NPC_GetMoveInformation
-------------------------
*/

inline qboolean NPC_GetMoveInformation( vec3_t dir, float *distance )
{
	//NOTENOTE: Use path stacks!

	//Make sure we have somewhere to go
	if ( NPCInfo->goalEntity == NULL )
		return qfalse;

	//Get our move info
	VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir );
	*distance = VectorNormalize( dir );
	
	VectorCopy( NPCInfo->goalEntity->currentOrigin, NPCInfo->blockedTargetPosition );

	return qtrue;
}

/*
-------------------------
NAV_GetLastMove
-------------------------
*/

void NAV_GetLastMove( navInfo_t &info )
{
	info = frameNavInfo;
}


void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir )
{
	vec3_t	forward, right;

	AngleVectors( self->currentAngles, forward, right, NULL );

	dir[2] = 0;
	VectorNormalize( dir );
	//NPCs cheat and store this directly because converting movement into a ucmd loses precision
	VectorCopy( dir, self->client->ps.moveDir );

	float fDot = DotProduct( forward, dir ) * 127.0f;
	float rDot = DotProduct( right, dir ) * 127.0f;
	//Must clamp this because DotProduct is not guaranteed to return a number within -1 to 1, and that would be bad when we're shoving this into a signed byte
	if ( fDot > 127.0f )
	{
		fDot = 127.0f;
	}
	if ( fDot < -127.0f )
	{
		fDot = -127.0f;
	}
	if ( rDot > 127.0f )
	{
		rDot = 127.0f;
	}
	if ( rDot < -127.0f )
	{
		rDot = -127.0f;
	}
	cmd->forwardmove = floor(fDot);
	cmd->rightmove = floor(rDot);

	/*
	vec3_t	wishvel;
	for ( int i = 0 ; i < 3 ; i++ ) 
	{
		wishvel[i] = forward[i]*cmd->forwardmove + right[i]*cmd->rightmove;
	}
	VectorNormalize( wishvel );
	if ( !VectorCompare( wishvel, dir ) )
	{
		Com_Printf( "PRECISION LOSS: %s != %s\n", vtos(wishvel), vtos(dir) );
	}
	*/
}

/*
-------------------------
NPC_MoveToGoal

  Now assumes goal is goalEntity, was no reason for it to be otherwise
-------------------------
*/

#if	AI_TIMERS
extern int navTime;
#endif//	AI_TIMERS
qboolean NPC_MoveToGoal( qboolean tryStraight ) //FIXME: tryStraight not even used!  Stop passing it
{
#if	AI_TIMERS
	int	startTime = GetTime(0);
#endif//	AI_TIMERS
	
	if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->client->ps.legsAnim >= BOTH_PAIN1 ) && ( NPC->client->ps.legsAnim <= BOTH_PAIN18 ) && NPC->client->ps.legsAnimTimer > 0 ) )
	{//If taking full body pain, don't move
		return qtrue;
	}

	if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON )
	{//If in an emplaced gun, never try to navigate!
		return qtrue;
	}

	if( NPC->s.eFlags & EF_HELD_BY_RANCOR )
	{//If in a rancor's hand, never try to navigate!
		return qtrue;
	}
	if( NPC->s.eFlags & EF_HELD_BY_WAMPA )
	{//If in a wampa's hand, never try to navigate!
		return qtrue;
	}
	if( NPC->s.eFlags & EF_HELD_BY_SAND_CREATURE )
	{//If in a worm's mouth, never try to navigate!
		return qtrue;
	}

	if ( NPC->watertype & CONTENTS_LADDER )
	{//Do we still want to do this?
		vec3_t	dir;
		VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir );
		VectorNormalize( dir );
		NPC_LadderMove( dir );
	}


	bool	moveSuccess = true;
	STEER::Activate(NPC);
	{
		// Attempt To Steer Directly To Our Goal
		//---------------------------------------
		moveSuccess = STEER::GoTo(NPC,  NPCInfo->goalEntity, NPCInfo->goalRadius);

		// Perhaps Not Close Enough?  Try To Use The Navigation Grid
		//-----------------------------------------------------------
		if (!moveSuccess)
		{
			moveSuccess = NAV::GoTo(NPC, NPCInfo->goalEntity);
			if (!moveSuccess)
			{
				STEER::Stop(NPC);
			}
		}
	}
	STEER::DeActivate(NPC, &ucmd);


	#if	AI_TIMERS
		navTime += GetTime( startTime );
	#endif//	AI_TIMERS
	return moveSuccess;
}

/*
-------------------------
void NPC_SlideMoveToGoal( void )

  Now assumes goal is goalEntity, if want to use tempGoal, you set that before calling the func
-------------------------
*/
qboolean NPC_SlideMoveToGoal( void )
{
	float	saveYaw = NPC->client->ps.viewangles[YAW];

	NPCInfo->combatMove = qtrue;
	
	qboolean ret = NPC_MoveToGoal( qtrue );

	NPCInfo->desiredYaw	= saveYaw;

	return ret;
}


/*
-------------------------
NPC_ApplyRoff
-------------------------
*/

void NPC_ApplyRoff(void)
{
	PlayerStateToEntityState( &NPC->client->ps, &NPC->s );
	VectorCopy ( NPC->currentOrigin, NPC->lastOrigin );

	// use the precise origin for linking
	gi.linkentity(NPC);
}

