//===========================================================================
//
// Name:			ai_cast_think.c
// Function:		Wolfenstein AI Character Thinking
// Programmer:		Ridah
// Tab Size:		4 (real tabs)
//===========================================================================

#include "../game/g_local.h"
#include "../game/q_shared.h"
#include "../game/botlib.h"		//bot lib interface
#include "../game/be_aas.h"
#include "../game/be_ea.h"
#include "../game/be_ai_gen.h"
#include "../game/be_ai_goal.h"
#include "../game/be_ai_move.h"
#include "../botai/botai.h"			//bot ai interface

#include "ai_cast.h"

/*
The lowest level of Cast AI thinking.
*/

/*
============
AICast_ProcessAIFunctions
============
*/
void AICast_ProcessAIFunctions( cast_state_t *cs, float thinktime )
{
	int		i;
	char	*funcname;

	//check for air
	BotCheckAir(cs->bs);
	//if the cast has no ai function
	if (!cs->aifunc) {
		AIFunc_DefaultStart(cs);
	}
	//
	// call AI funcs for this cast
	//
	AICast_DBG_InitAIFuncs();
	//
	// only allow looping in debug mode (since it's much slower)
	for (i = 0; i < (aicast_debug.integer ? MAX_AIFUNCS : 1); i++)
	{
		if (!(funcname = cs->aifunc(cs))) {
			break;
		} else {
			trap_BotResetAvoidReach(cs->bs->ms);	// reset avoidreach
			cs->thinkFuncChangeTime = level.time;
			AICast_DBG_AddAIFunc( cs, funcname );
		}
	}
	//
	//if the cast executed too many AI functions
	//
	if (aicast_debug.integer && i >= MAX_AIFUNCS) {
		AICast_DBG_ListAIFuncs( cs, 10 );	// print the last 10 funcs called
	}
}


/*
==============
AICast_ChangeViewAngles
==============
*/
void AICast_ChangeViewAngles(cast_state_t *cs, float thinktime) {
	float diff, factor, maxchange, anglespeed;
	int i;
	bot_state_t *bs;

	bs = cs->bs;
	//
	// restoire locked viewangles if required
	if (cs->aiFlags & AIFL_VIEWLOCKED) {
		VectorCopy( cs->viewlock_viewangles, bs->ideal_viewangles );
	}
	//
	if (bs->ideal_viewangles[PITCH] > 180) {
		bs->ideal_viewangles[PITCH] -= 360;
	}
	//
	maxchange = cs->attributes[YAW_SPEED];//300;
	if (cs->aiState >= AISTATE_COMBAT) {
		factor = 2.0;
		maxchange *= 2.0;
	} else {
		factor = 0.7;
	}
	//
	if (cs->lockViewAnglesTime < level.time) {
		maxchange *= thinktime;
		for (i = 0; i < 3; i++) {
			diff = fabs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]));
			anglespeed = diff * factor;
			if (anglespeed > maxchange) anglespeed = maxchange;
			bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i],
											bs->ideal_viewangles[i], anglespeed);
			//BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);`
			//bs->viewangles[i] = bs->ideal_viewangles[i];
		}
	}
	if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360;
	//elementary action: view
	trap_EA_View(bs->client, bs->viewangles);
}


/*
==============
AICast_InputToUserCommand
==============
*/
static int serverTime;
void AICast_InputToUserCommand(cast_state_t *cs, bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3]) {
	vec3_t angles, forward, right, up;
	short temp;
	int j;
	signed char movechar;
	gentity_t *ent;

	ent = &g_entities[cs->entityNum];

	//clear the whole structure
	memset(ucmd, 0, sizeof(usercmd_t));
	//
	//Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
	//the duration for the user command in milli seconds
	ucmd->serverTime = serverTime;
	//crouch/movedown
	if (aiDefaults[cs->aiCharacter].attributes[ATTACK_CROUCH]) {	// only crouch if this character is physically able to
		//RF, disabled this bit so truck guy in forest_6 doesn't get stuck and then gib
		if (/*cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE &&*/ bi->actionflags & ACTION_CROUCH)
			ucmd->upmove -= 127;
	}
	//
	// actions not effected by script pausing
	//
	// set zoom button
	if (cs->aiFlags & AIFL_ZOOMING) {
		ucmd->wbuttons |= WBUTTON_ZOOM;
	}
	//set the buttons
	if (bi->actionflags & ACTION_ATTACK) {
		vec3_t ofs;
		// don't fire if we are not facing the right direction yet
		if ((cs->triggerReleaseTime < level.time) &&
			(	(cs->lockViewAnglesTime >= level.time) ||
				(fabs(AngleDifference(cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW])) < 20)) &&
			// check for radid luger firing by skilled users (release fire between shots)
			(((level.time + cs->entityNum*500)/2000)%2 || !(rand()%(1+g_gameskill.integer)) || (cs->attributes[ATTACK_SKILL] < 0.5) || (cs->bs->weaponnum != WP_LUGER) || (cs->bs->cur_ps.weaponTime == 0) || (cs->bs->cur_ps.releasedFire)) )
		{
			ucmd->buttons |= BUTTON_ATTACK;
			// do some swaying around for some weapons
			AICast_WeaponSway( cs, ofs );
			VectorAdd( bi->viewangles, ofs, bi->viewangles );
		}
	}
	//
	//set the view angles
	//NOTE: the ucmd->angles are the angles WITHOUT the delta angles
	ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]);
	ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]);
	ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]);
	//subtract the delta angles
	for (j = 0; j < 3; j++) {
		temp = ucmd->angles[j] - delta_angles[j];
		ucmd->angles[j] = temp;
	}


//----(SA)	modified slightly for DM/DK
	ucmd->weapon = bi->weapon;
	//
	// relaxed mode show no weapons
	if (cs->aiState <= AISTATE_QUERY) {
		if(WEAPS_ONE_HANDED & (1<<ucmd->weapon) )	// one-handed wepons don't draw, others do
			ucmd->weapon = WP_NONE;
	}
//----(SA)	end

	//
	if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
	if (bi->actionflags & ACTION_RELOAD) ucmd->wbuttons |= WBUTTON_RELOAD;
	//
	// if we are locked down, don't do anything
	//
	if (cs->pauseTime > level.time) {
		return;
	}
	//
	// if scripted pause, no movement
	//
	if (cs->castScriptStatus.scriptNoMoveTime > level.time) {
		return;
	}
	//
	// if viewlock, wait until we are facing ideal angles before we move
	if (cs->aiFlags & AIFL_VIEWLOCKED) {
		if (fabs(AngleDifference(cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW])) > 10) {
			return;
		}
	}
	//
	if (bi->actionflags & ACTION_DELAYEDJUMP) {
		bi->actionflags |= ACTION_JUMP;
		bi->actionflags &= ~ACTION_DELAYEDJUMP;
	}
	//
	// only move if we are in combat or we are facing where our ideal angles
	if (bi->speed) {
		if ((!(cs->aiFlags & AIFL_WALKFORWARD) && cs->bs->enemy >= 0) || (fabs(AngleDifference(cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW])) < 60)) {
			//NOTE: movement is relative to the REAL view angles
			//get the horizontal forward and right vector
			//get the pitch in the range [-180, 180]
			if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH];
			else angles[PITCH] = 0;
			angles[YAW] = bi->viewangles[YAW];
			angles[ROLL] = 0;
			AngleVectors(angles, forward, right, up);
			//bot input speed is in the range [0, 400]
			bi->speed = bi->speed * 127 / 400;
			//set the view independent movement
			ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed;
			ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed;

			// RF, changed this to fix stim soldier flying attack
			if (!ucmd->upmove)	// only change it if we don't already have an upmove set
				ucmd->upmove = DotProduct(up, bi->dir) * bi->speed;
			//if (!ucmd->upmove)	// only change it if we don't already have an upmove set
			//	ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed;
		}
	}
	//
	//normal keyboard movement
	if (cs->actionFlags & CASTACTION_WALK)
		movechar = 70;
	else
		movechar = 127;
	if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove = movechar;
	if (!(cs->aiFlags & AIFL_WALKFORWARD)) {	// only do other movements if we are allowed to
		if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove = -movechar;
		if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove = -movechar;
		if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove = movechar;
	}
	//jump/moveup
	if (bi->actionflags & ACTION_JUMP) ucmd->upmove = 127;	// JUMP always takes preference over ducking
	if (bi->actionflags & ACTION_MOVEDOWN) ucmd->upmove = -127;	// JUMP always takes preference over ducking
	if (bi->actionflags & ACTION_MOVEUP) ucmd->upmove = 127;	// JUMP always takes preference over ducking
	//
	//Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
}


/*
==============
AICast_UpdateInput
==============
*/
void AICast_UpdateInput(cast_state_t *cs, int time) {
	bot_input_t bi;
	bot_state_t	*bs;
	int j;
	float	speed;

	bs = cs->bs;

	//add the delta angles to the bot's current view angles
	for (j = 0; j < 3; j++) {
		bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
	}
	//
	AICast_ChangeViewAngles(cs, (float) time / 1000);
	//
	if (cs->pauseTime > level.time) {
		trap_EA_View(bs->client, bs->viewangles);
		trap_EA_GetInput(bs->client, (float) time / 1000, &bi);
		AICast_InputToUserCommand(cs, &bi, &bs->lastucmd, bs->cur_ps.delta_angles);
		g_entities[cs->bs->entitynum].client->ps.pm_flags &= ~PMF_RESPAWNED;
		//
		//subtract the delta angles
		for (j = 0; j < 3; j++) {
			bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
		}
		//
		return;
	}
	//
	trap_EA_GetInput(bs->client, (float) time / 1000, &bi);
	//
	// restrict the speed according to the character and their current speedScale
	// HACK, don't slow down while crouching
	if (bi.actionflags & ACTION_CROUCH && cs->speedScale < 1.0)
		cs->speedScale = 1.0;
	//
	// check some Cast AI specific movement flags
	if (cs->actionFlags & CASTACTION_WALK) {
		if (cs->speedScale > (cs->attributes[WALKING_SPEED]/cs->attributes[RUNNING_SPEED])) {
			cs->speedScale = (cs->attributes[WALKING_SPEED]/cs->attributes[RUNNING_SPEED]);
		}
	}
	// don't ever let the speed get too low
	if (cs->speedScale < 0.25) {
		cs->speedScale = 0.25;
	}
	if (cs->speedScale > 1.2) {
		cs->speedScale = 1.2;
	}
	//
	speed = cs->speedScale * cs->attributes[RUNNING_SPEED];
	//
	//if (speed <= (cs->attributes[WALKING_SPEED] + (cs->attributes[WALKING_SPEED] + 50 < cs->attributes[RUNNING_SPEED] ? 50 : -1)))	// do a fast shuffle if slightly over walking speed
	if (speed <= cs->attributes[WALKING_SPEED])
	{
		cs->actionFlags |= CASTACTION_WALK;
	}
	//
	// we use 300 here, because the default player speed is 300, so Cast AI's can't move faster than that
	if ((bi.speed/400.0) > (speed / 300.0)) {
		bi.speed = 400.0 * (speed / 300.0);
		if (bi.speed > 400.0)
			bi.speed = 400.0;	// just in case, we should never exceed this
	}
	//
	// do a fast shuffle if slightly over walking speed
	if (bi.speed <= (400.0/300.0)*(cs->attributes[WALKING_SPEED] + (cs->attributes[WALKING_SPEED] + 50 < cs->attributes[RUNNING_SPEED] ? 50 : -1)))
	{
		cs->actionFlags |= CASTACTION_WALK;
	}
	//
	AICast_InputToUserCommand(cs, &bi, &bs->lastucmd, bs->cur_ps.delta_angles);
	//
	// check some Cast AI specific movement flags
	if (cs->actionFlags & CASTACTION_WALK) {
		bs->lastucmd.buttons |= BUTTON_WALKING;	// play the walking animation
	}
	//
	//subtract the delta angles
	for (j = 0; j < 3; j++) {
		bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
	}
	//
	// make sure the respawn flag is disabled (causes problems after multiple "map xxx" commands)
	g_entities[cs->bs->entitynum].client->ps.pm_flags &= ~PMF_RESPAWNED;
	// set the aiState
	g_entities[cs->bs->entitynum].client->ps.aiState = cs->aiState;
}

/*
============
AICast_Think

  entry point for all cast AI
============
*/
void AICast_Think( int client, float thinktime )
{
	gentity_t		*ent;
	cast_state_t	*cs;
	int				i;
	int				animIndex;
	animation_t		*anim;

//	if (saveGamePending || (strlen( g_missionStats.string ) > 2 )) {
//		return;
//	}
	
	//
	// get the cast ready for processing
	//
	cs = AICast_GetCastState( client );
	ent = &g_entities[client];
	//
	// make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data)
	trap_AAS_SetCurrentWorld( cs->aasWorldIndex );
	//
	// make sure we have a valid navigation system
	//
	if (!trap_AAS_Initialized()) {
		return;
	}
	//
	trap_EA_ResetInput(client, NULL);
	cs->aiFlags &= ~AIFL_VIEWLOCKED;
	//cs->bs->weaponnum = ent->client->ps.weapon;
	//
	// turn off flags that are set each frame if needed
	ent->client->ps.eFlags &= ~(EF_NOSWINGANGLES|EF_MONSTER_EFFECT|EF_MONSTER_EFFECT2|EF_MONSTER_EFFECT3);
	// conditional flags
	if (ent->aiCharacter == AICHAR_ZOMBIE) {
		if (COM_BitCheck( ent->client->ps.weapons, WP_MONSTER_ATTACK1)) {
			cs->aiFlags |= AIFL_NO_FLAME_DAMAGE;
			SET_FLAMING_ZOMBIE( ent->s, 1 );
		} else {
			SET_FLAMING_ZOMBIE( ent->s, 0 );
		}
	}
	//
	// if we're dead, do special stuff only
	if (ent->health <= 0 || cs->revivingTime || cs->rebirthTime)
	{
		//
		if (cs->revivingTime && cs->revivingTime < level.time) {
			// start us thinking again
			ent->client->ps.pm_type = PM_NORMAL;
			cs->revivingTime = 0;
		}
		//
		if (cs->rebirthTime && cs->rebirthTime < level.time) {
			vec3_t mins, maxs;
			int touch[10], numTouch;
			float	oldmaxZ;

			oldmaxZ = ent->r.maxs[2];

			// make sure the area is clear
			AIChar_SetBBox( ent, cs );

			VectorAdd( ent->r.currentOrigin, ent->r.mins, mins );
			VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs );
			trap_UnlinkEntity( ent );

			numTouch = trap_EntitiesInBox( mins, maxs, touch, 10 );

			if (numTouch) {
				for (i=0; i<numTouch; i++) {
					//if (!g_entities[touch[i]].client || g_entities[touch[i]].r.contents == CONTENTS_BODY)
					if (g_entities[touch[i]].r.contents & MASK_PLAYERSOLID)
						break;
				}
				if (i==numTouch)
					numTouch = 0;
			}

			if (numTouch == 0) {	// ok to spawn

				// give them health when they start reviving, so we won't gib after
				// just a couple shots while reviving
				ent->health =
					ent->client->ps.stats[STAT_HEALTH] =
					ent->client->ps.stats[STAT_MAX_HEALTH] =
						((cs->attributes[STARTING_HEALTH] - 50) > 30 ? (cs->attributes[STARTING_HEALTH] - 50) : 30);

				ent->r.contents = CONTENTS_BODY;
				ent->clipmask = MASK_PLAYERSOLID;
				ent->takedamage = qtrue;
				ent->waterlevel = 0;
				ent->watertype = 0;
				ent->flags = 0;
				ent->die = AICast_Die;
				ent->client->ps.eFlags &= ~EF_DEAD;
				ent->s.eFlags &= ~EF_DEAD;

				cs->rebirthTime = 0;
				cs->deathTime = 0;

				// play the revive animation
				cs->revivingTime = level.time + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_REVIVE, qfalse, qtrue );;
			} else {
				// can't spawn yet, so set bbox back, and wait
				ent->r.maxs[2] = oldmaxZ;
				ent->client->ps.maxs[2] = ent->r.maxs[2];
			}
			trap_LinkEntity( ent );
		}
		// ZOMBIE should set effect flag if really dead
		if (cs->aiCharacter == AICHAR_ZOMBIE && !ent->r.contents) {
			ent->client->ps.eFlags |= EF_MONSTER_EFFECT2;
		}
		//
		if (ent->health > GIB_HEALTH && cs->deathTime && cs->deathTime < (level.time - 3000)) {
/*
			// been dead for long enough, set our animation to the end frame
			switch ( ent->s.legsAnim & ~ANIM_TOGGLEBIT ) {
			case BOTH_DEATH1:
			case BOTH_DEAD1:
				anim = BOTH_DEAD1;
				break;
			case BOTH_DEATH2:
			case BOTH_DEAD2:
				anim = BOTH_DEAD2;
				break;
			case BOTH_DEATH3:
			case BOTH_DEAD3:
				anim = BOTH_DEAD3;
				break;
			default:
				G_Error( "%s has unknown death animation\n", ent->classname);
			}
			ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
			ent->client->ps.legsAnim = ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
*/
			cs->deathTime = 0;
			ent->r.svFlags &= ~SVF_BROADCAST;
		}
		//
		// no more thinking required
		return;
	}
	//
	// set some anim conditions
	if (cs->secondDeadTime) {
		BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qtrue, qfalse );
	} else {
		BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qfalse, qfalse );
	}
	// set health value
	if (ent->health <= 0.25*cs->attributes[STARTING_HEALTH]) {
		BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 3, qfalse );
	} else if (ent->health <= 0.5*cs->attributes[STARTING_HEALTH]) {
		BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 2, qfalse );
	} else {
		BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 1, qfalse );
	}
	//
	cs->speedScale = 1.0;			// reset each frame, set if required
	cs->actionFlags = 0;			// FIXME: move this to a Cast AI movement init function!
	//retrieve the current client state
	BotAI_GetClientState( client, &(cs->bs->cur_ps) );
	//
	// setup movement speeds for the given state
	// walking
	animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALK );
	if (animIndex >= 0) {
		anim = BG_GetAnimationForIndex( cs->entityNum, animIndex );
		cs->attributes[WALKING_SPEED] = anim->moveSpeed;
	}
	// crouching
	animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALKCR );
	if (animIndex >= 0) {
		anim = BG_GetAnimationForIndex( cs->entityNum, animIndex );
		cs->attributes[CROUCHING_SPEED] = anim->moveSpeed;
	}
	// running
	animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_RUN );
	if (animIndex >= 0) {
		anim = BG_GetAnimationForIndex( cs->entityNum, animIndex );
		cs->attributes[RUNNING_SPEED] = anim->moveSpeed;
	}
	// update crouch speed scale
	ent->client->ps.crouchSpeedScale = cs->attributes[CROUCHING_SPEED] / cs->attributes[RUNNING_SPEED];
	//
	// only enable headlook if we want to this frame
	ent->client->ps.eFlags &= ~EF_HEADLOOK;
	if (cs->bs->enemy >= 0) {
		ent->client->ps.eFlags &= ~EF_STAND_IDLE2;	// never use alt idle if fighting
	}
	//
	// check for dead leader
	if (cs->leaderNum >= 0 && g_entities[cs->leaderNum].health <= 0) {
		cs->leaderNum = -1;
	}
	//
#if 0
	// HACK for village2, if they are stuck, find a good position (there is a friendly guy placed inside a table)
	{
		trace_t tr;
		vec3_t org;
		trap_Trace( &tr, cs->bs->cur_ps.origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, cs->bs->cur_ps.origin, cs->entityNum, CONTENTS_SOLID );
		while (tr.startsolid) {
			VectorCopy( cs->bs->cur_ps.origin, org );
			org[0] += 96 * crandom();
			org[1] += 96 * crandom();
			org[2] += 16 * crandom();
			trap_Trace( &tr, org, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, org, cs->entityNum, CONTENTS_SOLID );
			G_SetOrigin( &g_entities[cs->entityNum], org );
			VectorCopy( org, g_entities[cs->entityNum].client->ps.origin );
		}
	}
#endif
	//add the delta angles to the cast's current view angles
	for (i = 0; i < 3; i++) {
		cs->bs->viewangles[i] = AngleMod(cs->bs->viewangles[i] + SHORT2ANGLE(cs->bs->cur_ps.delta_angles[i]));
	}
	//
	//increase the local time of the cast
	cs->bs->ltime += thinktime;
	//
	cs->bs->thinktime = thinktime;
	//origin of the cast
	VectorCopy(cs->bs->cur_ps.origin, cs->bs->origin);
	//eye coordinates of the cast
	VectorCopy(cs->bs->cur_ps.origin, cs->bs->eye);
	cs->bs->eye[2] += cs->bs->cur_ps.viewheight;
	//get the area the cast is in
	cs->bs->areanum = BotPointAreaNum(cs->bs->origin);
	// clear flags each frame
	cs->bs->flags = 0;
	//
	// check enemy health
	if (cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0) {
		cs->bs->enemy = -1;
	}
	//
	// if the previous movetype was temporary, set it back
	if (cs->movestateType == MSTYPE_TEMPORARY)
	{
		cs->movestate = MS_DEFAULT;
		cs->movestateType = MSTYPE_NONE;
	}
	// crouching?
	if (	(cs->bs->attackcrouch_time > trap_AAS_Time()) &&
			((cs->lastAttackCrouch > level.time - 500) || (cs->thinkFuncChangeTime < level.time - 1000))) {
		// if we are not moving, and we are firing, always stand, unless we are allowed to crouch + fire
		if (VectorLength( cs->bs->cur_ps.velocity ) || (cs->lastWeaponFired < level.time - 2000) || (cs->aiFlags & AIFL_ATTACK_CROUCH)) {
			cs->lastAttackCrouch = level.time;
			trap_EA_Crouch( cs->bs->client );
		}
	}
	//
	//if (cs->bs->enemy >= 0) {
		//update the attack inventory values
		AICast_UpdateBattleInventory(cs, cs->bs->enemy);
	//}
	//
	// if we don't have ammo for the current weapon, get rid of it
	if (!(COM_BitCheck( cs->bs->cur_ps.weapons, cs->bs->weaponnum)) || !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum )) {
		// select a weapon
		AICast_ChooseWeapon( cs, qfalse );
		// if still no ammo, select a blank weapon
		//if (!AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum )) {
		//	cs->bs->weaponnum = WP_NONE;
		//}
	}
	//
	// in query mode, we do special handling (pause scripting, check for transition to alert/combat, etc)
	if (cs->aiState == AISTATE_QUERY) {
		AICast_QueryThink( cs );
	}
	else if (cs->pauseTime < level.time) {
		// do the thinking
		AICast_ProcessAIFunctions( cs, thinktime );
		//
		// make sure the correct weapon is selected
		trap_EA_SelectWeapon(cs->bs->client, cs->bs->weaponnum);
		//
		// process current script if it exists
		cs->castScriptStatusCurrent = cs->castScriptStatus;
		AICast_ScriptRun( cs, qfalse );
	}
	//
	// set special movestate if necessary
	if (cs->movestateType != MSTYPE_NONE) {
		switch (cs->movestate) {
		case MS_WALK:
			cs->actionFlags |= CASTACTION_WALK;
			break;
		case MS_CROUCH:
			trap_EA_Crouch( cs->entityNum );
			break;
    default:
      break; // TTimo gcc: MS_DEFAULT MS_RUN not handled in switch
		}
	}
	//
	//subtract the delta angles
	for (i = 0; i < 3; i++) {
		cs->bs->viewangles[i] = AngleMod(cs->bs->viewangles[i] - SHORT2ANGLE(cs->bs->cur_ps.delta_angles[i]));
	}
}

/*
============
AICast_StartFrame

  Think any clients that need thinking
============
*/
void CopyToBodyQue( gentity_t *ent );

void AICast_StartFrame ( int time)
{
	int i, elapsed, count, clCount;
	cast_state_t	*cs;
	int		castcount;
	static int	lasttime;
	static vmCvar_t	aicast_disable;
	gentity_t *ent;

	if (trap_Cvar_VariableIntegerValue( "savegame_loading" ))
		return;

	if (saveGamePending)
		return;

	// if waiting at intermission, don't think
	if (strlen( g_missionStats.string ) > 1 )
		return;

	if (!aicast_disable.handle)
	{
		trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT );
	}
	else
	{
		trap_Cvar_Update( &aicast_disable );
		if (aicast_disable.integer) {
			return;
		}
	}

	trap_Cvar_Update( &aicast_debug );
	trap_Cvar_Update( &aicast_debugname );
	trap_Cvar_Update( &aicast_scripts );

	// no need to think during the intermission
	if (level.intermissiontime)
		return;
	//
	// make sure the AAS gets updated
	trap_BotLibStartFrame((float) time / 1000);
	//
	//
	elapsed = time - lasttime;
	if (elapsed == 0) {
		return;			// no time has elapsed
	}

//G_Printf( "AI startframe: %i\n", time );

	if (elapsed < 0) {
		elapsed = 0;
		lasttime = time;
	}
	// don't let the framerate drop below 10
	if (elapsed > 100) {
		elapsed = 100;
	}
	//AICast_SightUpdate( (int)((float)SIGHT_PER_SEC * ((float)elapsed / 1000)) );
	//
	count = 0;
	castcount = 0;
	clCount = 0;
	ent = g_entities;
	//
	//update the AI characters
  // TTimo gcc: left-hand operand of comma expression has no effect
	// initial line was: for (i = 0; i < aicast_maxclients, clCount < level.numPlayingClients; i++, ent++)
  for (i = 0; ( i < aicast_maxclients ) && ( clCount < level.numPlayingClients ) ; i++, ent++)
	{
		if (ent->client)
			clCount++;
		//
		cs = AICast_GetCastState(i);
		// is this a cast AI?
		if (cs->bs)
		{
			if (ent->inuse) {
				if (ent->aiInactive == qfalse) {
					//
					elapsed = time - cs->lastThink;
					//
					// if they're moving/firing think every frame
					if ((elapsed >= 50) &&
						(	(	(	(!VectorCompare(ent->client->ps.velocity, vec3_origin)) ||
									(ent->client->buttons) ||
									(elapsed >= aicast_thinktime)) &&
								(count <= aicast_maxthink)) ||
							(elapsed >= aicast_thinktime*2)))
					{
						// make it think now
						AICast_Think( i, (float)elapsed / 1000 );
						cs->lastThink = time;
						//
						count++;
					}
					// check for any debug info updates
					AICast_DebugFrame( cs );
				} else if (cs->aiFlags & AIFL_WAITINGTOSPAWN) {
					// check f the space is clear yet
					ent->AIScript_AlertEntity( ent );
				}
			} else {
				trap_UnlinkEntity( ent );
			}
			//
			// see if we've checked all cast AI's
			if (++castcount >= numcast)
				break;
		}
	}
	//
	lasttime = time;
}

/*
============
AICast_StartServerFrame

  Do movements, sighting, etc
============
*/
void AICast_StartServerFrame ( int time)
{
	int i, elapsed, count, clCount;
	cast_state_t	*cs;
	int		castcount;
	static int	lasttime;
	static vmCvar_t	aicast_disable;
	gentity_t *ent;
	cast_state_t *pcs;
//	int		oldLegsTimer;

	if (trap_Cvar_VariableIntegerValue( "savegame_loading" ))
		return;

	if (g_gametype.integer != GT_SINGLE_PLAYER)
		return;

	if (saveGamePending)
		return;

	// if waiting at intermission, don't think
	if (strlen( g_missionStats.string ) > 1 ) {
		return;
	}

	if (!aicast_disable.handle)
	{
		trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT );
	}
	else
	{
		trap_Cvar_Update( &aicast_disable );
		if (aicast_disable.integer) {
			return;
		}
	}

	trap_Cvar_Update( &aicast_debug );

	// no need to think during the intermission
	if (level.intermissiontime)
		return;
	//
	// make sure the AAS gets updated
	trap_BotLibStartFrame((float) time / 1000);
	//
	//
	elapsed = time - lasttime;
	if (elapsed == 0) {
		return;			// no time has elapsed
	}

	pcs = AICast_GetCastState( 0 );

//G_Printf( "AI startserverframe: %i\n", time );

	if (elapsed < 0) {
		elapsed = 0;
		lasttime = time;
	}
	// don't let the framerate drop below 10
	if (elapsed > 100) {
		elapsed = 100;
	}
	//
	// process player's current script if it exists
	AICast_ScriptRun( AICast_GetCastState(0), qfalse );
	//
	AICast_SightUpdate( (int)((float)SIGHT_PER_SEC * ((float)elapsed / 1000)) );
	//
	count = 0;
	castcount = 0;
	clCount = 0;
	ent = g_entities;
	//
	//update the AI characters
  // TTimo gcc: left-hand operand of comma expression has no effect
	// initial line: for (i = 0; i < aicast_maxclients, clCount < level.numPlayingClients; i++, ent++)
  for (i = 0; ( i < aicast_maxclients ) && ( clCount < level.numPlayingClients ) ; i++, ent++)
	{
		if (ent->client)
			clCount++;
		//
		cs = AICast_GetCastState(i);
		// is this a cast AI?
		if (cs->bs)
		{
			if (ent->aiInactive == qfalse && ent->inuse) {
				//
				elapsed = level.time - cs->lastMoveThink;
				//
				// optimization, if they're not in the player's PVS, and they aren't trying to move, then don't bother thinking
				if (	((ent->health > 0) && (elapsed > 300))
					||	(g_entities[0].client && g_entities[0].client->cameraPortal)
					||	(cs->vislist[0].visible_timestamp == cs->vislist[0].lastcheck_timestamp)
					||	(pcs->vislist[cs->entityNum].visible_timestamp == pcs->vislist[cs->entityNum].lastcheck_timestamp)
					||	(VectorLength(ent->client->ps.velocity) > 0)
					||	(cs->bs->lastucmd.forwardmove || cs->bs->lastucmd.rightmove || cs->bs->lastucmd.upmove > 0 || cs->bs->lastucmd.buttons || cs->bs->lastucmd.wbuttons)
					||	(trap_InPVS(cs->bs->origin, g_entities[0].s.pos.trBase)))	// do pvs check last, since it's the most expensive to call
				{
//					oldLegsTimer = ent->client->ps.legsTimer;
					//
					// send it's movement commands
					//
					serverTime = time;
					AICast_UpdateInput(cs, elapsed);
					trap_BotUserCommand(cs->bs->client, &(cs->bs->lastucmd));
					cs->lastMoveThink = level.time;
					//
					// check for anim changes that may require us to stay still
					//
/*					if (oldLegsTimer != ent->client->ps.legsTimer) {
						// dont move until they are finished
						if (cs->castScriptStatus.scriptNoMoveTime < level.time + ent->client->ps.legsTimer) {
							cs->castScriptStatus.scriptNoMoveTime = level.time + ent->client->ps.legsTimer;
						}
					}
*/				}
			} else {
				trap_UnlinkEntity( ent );
			}
			//
			// see if we've checked all cast AI's
			if (++castcount >= numcast)
				break;
		}
	}
	//
	lasttime = time;
}

/*
==============
AICast_PredictMovement

  Simulates movement over a number of frames, returning the end position
==============
*/
void AICast_PredictMovement( cast_state_t *cs, int numframes, float frametime, aicast_predictmove_t *move, usercmd_t *ucmd, int checkHitEnt )
{
	int			frame, i;
	playerState_t	ps;
	pmove_t		pm;
	trace_t		tr;
	vec3_t		end, thisHitVec, lastOrg, projPoint;
	qboolean	checkReachMarker;
    vec3_t startHitVec = { 0.f, 0.f, 0.f };  // wolfcam-rtcw gcc warning

//int pretime = Sys_MilliSeconds();
//G_Printf("PredictMovement: %f duration, %i frames\n", frametime, numframes );

	if (cs->bs)
		ps = cs->bs->cur_ps;
	else
		ps = g_entities[cs->entityNum].client->ps;

	ps.eFlags |= EF_DUMMY_PMOVE;

	move->stopevent = PREDICTSTOP_NONE;

	if (checkHitEnt >= 0 && !Q_stricmp(g_entities[checkHitEnt].classname, "ai_marker")) {
		checkReachMarker = qtrue;
		VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, ps.origin, startHitVec );
		VectorCopy( ps.origin, lastOrg );
	} else {
		checkReachMarker = qfalse;
	}

	// don't let the frametime be too high
//	while (frametime > 0.2) {
//		numframes *= 2;
//		frametime /= 2;
//	}

	for (frame=0; frame<numframes; frame++)
	{
		memset (&pm, 0, sizeof(pm));
		pm.ps = &ps;
		pm.cmd = *ucmd;
		pm.oldcmd = *ucmd;
		pm.ps->commandTime = 0;
		pm.cmd.serverTime = (int)(1000.0*frametime);
		pm.tracemask = g_entities[cs->entityNum].clipmask;//MASK_PLAYERSOLID;
		
		pm.trace = trap_TraceCapsule;//trap_Trace;
		pm.pointcontents = trap_PointContents;
		pm.debugLevel = qfalse;
		pm.noFootsteps = qtrue;
		// RF, not needed for prediction
		//pm.noWeapClips = qtrue;	// (SA) AI's ignore weapon clips

		// perform a pmove
		Pmove (&pm);

		if (checkHitEnt >= 0) {
			// if we've hit the checkent, abort
			if (checkReachMarker) {
				VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, pm.ps->origin, thisHitVec );
				if (DotProduct( startHitVec, thisHitVec ) < 0) {
					// project the marker onto the movement vec, and check distance
					ProjectPointOntoVector( g_entities[checkHitEnt].r.currentOrigin, lastOrg, pm.ps->origin, projPoint );
					if (VectorDistance( g_entities[checkHitEnt].r.currentOrigin, projPoint ) < 8) {
						move->stopevent = PREDICTSTOP_HITENT;
						goto done;
					}
				}
				// use this position as the base for the next test
				//VectorCopy( thisHitVec, startHitVec );
				VectorCopy( pm.ps->origin, lastOrg );
			}
			// if we didnt reach the marker, then check for something that blocked us
			for (i=0; i<pm.numtouch; i++) {
				if (pm.touchents[i] == pm.ps->groundEntityNum)
					continue;
				if (pm.touchents[i] == checkHitEnt) {
					move->stopevent = PREDICTSTOP_HITENT;
					goto done;
				} else if (pm.touchents[i] < MAX_CLIENTS ||
					(pm.touchents[i] != ENTITYNUM_WORLD && (g_entities[pm.touchents[i]].s.eType != ET_MOVER || g_entities[pm.touchents[i]].moverState != MOVER_POS1))) {
					// we have hit another entity, so abort
					move->stopevent = PREDICTSTOP_HITCLIENT;
					goto done;
				} else if (!Q_stricmp(g_entities[pm.touchents[i]].classname, "script_mover")) {
					// avoid script_mover's
					move->stopevent = PREDICTSTOP_HITCLIENT;
					goto done;
				}
			}
		}
	}

done:

	// hack, if we are above ground, chances are it's because we only did one frame, and gravity isn't applied until
	// after the frame, so try and drop us down some
	if (move->groundEntityNum == ENTITYNUM_NONE) {
		VectorCopy( move->endpos, end );
		end[2] -= 32;
		trap_Trace( &tr, move->endpos, pm.mins, pm.maxs, end, pm.ps->clientNum, pm.tracemask );
		if (!tr.startsolid && !tr.allsolid && tr.fraction < 1) {
			VectorCopy( tr.endpos, pm.ps->origin );
			pm.ps->groundEntityNum = tr.entityNum;
		}
	}

	// copy off the results
	VectorCopy( pm.ps->origin, move->endpos );
	move->frames = numframes;
	//move->presencetype = cs->bs->presencetype;
	VectorCopy( pm.ps->velocity, move->velocity );
	move->numtouch = pm.numtouch;
	memcpy( move->touchents, pm.touchents, sizeof(pm.touchents) );
	move->groundEntityNum = pm.ps->groundEntityNum;

//G_Printf("PredictMovement: %i ms\n", -pretime + Sys_MilliSeconds() );
}

/*
============
AICast_GetAvoid
============
*/
qboolean AICast_GetAvoid( cast_state_t *cs, bot_goal_t *goal, vec3_t outpos, qboolean reverse, int blockEnt )
{
	float	yaw, oldyaw, distmoved, bestmoved, bestyaw;
	vec3_t	bestpos = { 0.f, 0.f, 0.f };  // wolfcam-rtcw gcc warning
	aicast_predictmove_t	castmove;
	usercmd_t	ucmd;
	qboolean	enemyVisible;
	float		angleDiff;
  // TTimo might be used uninitialized
	int			starttraveltime = 0;
  int besttraveltime, traveltime;
	int			invert;
	float		inc;
	qboolean	averting=qfalse;
	float		maxYaw, simTime;
	static int lastTime;
	//
	// if we are in the air, no chance of avoiding
	if (cs->bs->cur_ps.groundEntityNum == ENTITYNUM_NONE && g_entities[cs->entityNum].waterlevel <= 1) {
		return qfalse;
	}
	//
	if (cs->lastAvoid > level.time-rand()%500)
		return qfalse;
	cs->lastAvoid = level.time + 50 + rand()%500;
	//
	if (lastTime == level.time)
		return qfalse;
	lastTime = level.time;

	// if they have an enemy, and can currently see them, don't move out of their view
	enemyVisible =	(cs->bs->enemy >= 0) &&
					(AICast_CheckAttack( cs, cs->bs->enemy, qfalse ));
	//
	// look for a good direction to move out of the way
	bestmoved = 0;
	bestyaw = 360;
	besttraveltime = 9999999;
	if (goal)
		starttraveltime = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, goal->areanum, cs->travelflags );
	memcpy( &ucmd, &cs->bs->lastucmd, sizeof(usercmd_t) );
	ucmd.forwardmove = 127;
	ucmd.rightmove = 0;
	ucmd.upmove = 0;
	if (cs->dangerEntity >= 0 && cs->dangerEntityValidTime >= level.time) {
		averting = qtrue;
	} else if (!goal) {
		averting = qtrue;	// not heading for a goal, so we must be getting out of someone's way
	}
	//
	maxYaw = 0;
	simTime = 1.2;
	//
	if (averting) {
		// avoiding danger, go anywhere!
		angleDiff = 300;
		inc = 60;
		invert = 1;
	} else {
		if (level.time%1000 < 500)
			invert = 1;
		else
			invert = -1;
		angleDiff = 140;
		inc = 35;
	}
	if (blockEnt > aicast_maxclients) {
		maxYaw = angleDiff;
		simTime = 0.5;
	}
	//
	for (yaw=-angleDiff*invert; yaw*invert<=maxYaw; yaw+=inc*invert) {
		if (!averting && !yaw)
			continue;
		oldyaw = cs->bs->cur_ps.viewangles[YAW];
		cs->bs->cur_ps.viewangles[YAW] += yaw + reverse*180;
		//
		ucmd.angles[YAW] = ANGLE2SHORT(AngleMod(cs->bs->cur_ps.viewangles[YAW]));
		//
		AICast_PredictMovement( cs, 5, 0.4, &castmove, &ucmd, -1 );
		// if we have a danger entity, try and get away from it at all costs
		if (cs->dangerEntity >= 0 && cs->dangerEntityValidTime >= level.time) {
			distmoved = Distance( castmove.endpos, cs->dangerEntityPos );
		} else if (goal) {
			//distmoved = 99999 - trap_AAS_AreaTravelTimeToGoalArea( BotPointAreaNum(castmove.endpos), castmove.endpos, goal->areanum, cs->travelflags );
			distmoved = 99999 - Distance( castmove.endpos, goal->origin );
		} else {
			distmoved = Distance( castmove.endpos, cs->bs->cur_ps.origin );
		}
		if (	(distmoved > bestmoved)
			//&&	((cs->bs->origin[2] - castmove.endpos[2]) < 64)	// allow up, but not down (falling)
			&&	(castmove.groundEntityNum != ENTITYNUM_NONE))
		{
			// they all passed, check any other stuff
			if (!enemyVisible || AICast_CheckAttackAtPos(cs->entityNum, cs->bs->enemy, castmove.endpos, qfalse, qfalse ))
			{
				if (!goal || (traveltime = trap_AAS_AreaTravelTimeToGoalArea( BotPointAreaNum(castmove.endpos), castmove.endpos, goal->areanum, cs->travelflags )) < (starttraveltime+200)) {
					bestyaw = yaw;
					bestmoved = distmoved;
					besttraveltime = traveltime;
					VectorCopy( castmove.endpos, bestpos );
				}
			}
		}
		//
		cs->bs->cur_ps.viewangles[YAW] = oldyaw;
	}
	//
	if (bestmoved > 0) {
		VectorCopy( bestpos, outpos );
		return qtrue;
	} else {
		return qfalse;
	}

//G_Printf("GetAvoid: %i ms\n", -pretime + Sys_MilliSeconds() );
}

/*
============
AICast_Blocked
============
*/
void AICast_Blocked(cast_state_t *cs, bot_moveresult_t *moveresult, int activate, bot_goal_t *goal)
{
	vec3_t	pos, dir;
	aicast_predictmove_t	move;
	usercmd_t ucmd;
	bot_input_t bi;
	cast_state_t *ocs;
	int i, blockEnt = -1;
	bot_goal_t ogoal;

	if (cs->blockedAvoidTime < level.time) {
		if (cs->blockedAvoidTime < level.time - 300) {
			if (VectorCompare( cs->bs->cur_ps.velocity, vec3_origin ) && !cs->bs->lastucmd.forwardmove && !cs->bs->lastucmd.rightmove) {
				// not moving, don't bother checking
				cs->blockedAvoidTime = level.time-1;
				return;
			}
			// are we going to hit someone soon?
			trap_EA_GetInput(cs->entityNum, (float) level.time / 1000, &bi);
			AICast_InputToUserCommand( cs, &bi, &ucmd, cs->bs->cur_ps.delta_angles );
			AICast_PredictMovement( cs, 1, 0.6, &move, &ucmd, (goal && goal->entitynum > -1) ? goal->entitynum : cs->entityNum );

			// blocked if we hit a client (or non-stationary mover) other than our enemy or goal
			if (move.stopevent != PREDICTSTOP_HITCLIENT) {
				// not blocked
				cs->blockedAvoidTime = level.time-1;
				return;
			}

			// if we stopped passed our goal, ignore it
			if (goal) {
				if (VectorDistance( cs->bs->origin, goal->origin ) < VectorDistance( cs->bs->origin, move.endpos )) {
					vec3_t v1, v2;
					VectorSubtract( goal->origin, cs->bs->origin, v1 );
					VectorSubtract( goal->origin, move.endpos, v2 );
					VectorNormalize( v1 );
					VectorNormalize( v2 );
					if (DotProduct( v1, v2 ) < 0) {
						// we went passed the goal, so assume we can reach it
						cs->blockedAvoidTime = level.time-1;
						return;
					}
				}
			}

			// try and get them to move, in case we can't get around them
			blockEnt = -1;
			for (i=0; i<move.numtouch; i++) {
				if (move.touchents[i] >= MAX_CLIENTS) {
					if (!Q_stricmp(g_entities[move.touchents[i]].classname, "script_mover")) {
						// avoid script_mover's
						blockEnt = move.touchents[i];
					}
					// if we are close to the impact point, then avoid this entity
					else if (VectorDistance( cs->bs->origin, move.endpos ) < 10) {
						//G_Printf("AI (%s) avoiding %s\n", g_entities[cs->entityNum].aiName, g_entities[move.touchents[i]].classname );
						blockEnt = move.touchents[i];
					}
					continue;
				}
				//
				ocs = AICast_GetCastState( move.touchents[i] );
				if (!ocs->bs) {
					blockEnt = move.touchents[i];
				}
				// reject this blocker if we are following or going to them
				else if (cs->followEntity != ocs->entityNum) {
					// if they are moving away from us already, let them go
					if (VectorLength(ocs->bs->cur_ps.velocity) > 10) {
						vec3_t v1, v2;

						VectorSubtract( ocs->bs->origin, cs->bs->origin, v2 );
						VectorNormalize(v2);
						VectorNormalize2(ocs->bs->cur_ps.velocity, v1);

						if (DotProduct(v1, v2) > 0.0) {
							continue;
						}
					}
					//
					// if they recently were asked to avoid us, then they're probably not listening
					if (ocs->obstructingTime > level.time-500)
						blockEnt = move.touchents[i];
					//
					// if they are not avoiding, ignore
					if (!(ocs->aiFlags & AIFL_NOAVOID)) {
						continue;
					}
					//
					// they should avoid us
					if (ocs->leaderNum >= 0)
					{
						ogoal.entitynum = ocs->leaderNum;
						VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin );
						if (AICast_GetAvoid(ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum))
						{
							// give them time to move somewhere else
							ocs->obstructingTime = level.time + 1000;
						} else {
							// make sure they don't call GetAvoid() for another few frames to let others avoid also
							ocs->obstructingTime = level.time - 1;
							blockEnt = move.touchents[i];
						}
					} else {
						if (AICast_GetAvoid(ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum))
						{
							// give them time to move somewhere else
							ocs->obstructingTime = level.time + 1000;
						} else {
							// make sure they don't call GetAvoid() for another few frames to let others avoid also
							ocs->obstructingTime = level.time - 1;
							blockEnt = move.touchents[i];
						}
					}
				}
			}

		} else {
			return;
		}

		if (blockEnt < 0) {
			// nothing found to be worth avoding
			cs->blockedAvoidTime = level.time-1;
			return;
		}

		// something is blocking our path
		if (g_entities[blockEnt].aiName && g_entities[blockEnt].client) {
			int oldId = cs->castScriptStatus.scriptId;
			AICast_ScriptEvent( cs, "blocked", g_entities[blockEnt].aiName );
			if (oldId != cs->castScriptStatus.scriptId) {
				// the script has changed, so assume the scripting is handling the avoidance
				return;
			}
		}

		// avoid geometry and props, but assume clients will get out the way
		if (/*blockEnt > MAX_CLIENTS &&*/ AICast_GetAvoid( cs, goal, pos, qfalse, blockEnt )) {
			VectorSubtract( pos, cs->bs->cur_ps.origin, dir );
			VectorNormalize( dir );
			cs->blockedAvoidYaw = vectoyaw( dir );
			if (blockEnt >= MAX_CLIENTS)
				cs->blockedAvoidTime = level.time + 100 + rand()%200;
			else
				cs->blockedAvoidTime = level.time + 300 + rand()%400;
		} else {
			cs->blockedAvoidTime = level.time-1;	// don't look again for another few frames
			return;
		}
	}
	
	VectorClear(pos);
	pos[YAW] = cs->blockedAvoidYaw;
	AngleVectors( pos, dir, NULL, NULL );

	if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE)
		trap_EA_Jump(cs->bs->entitynum);

	trap_EA_Move(cs->bs->entitynum, dir, 200);//400);

	vectoangles(dir, cs->bs->ideal_viewangles);
	cs->bs->ideal_viewangles[2] *= 0.5;
}

/*
================
AICast_EvaluatePmove

  Avoidance after the event (leaders instruct AI's to get out the way, AI's instruct other non-moving AI's to get out the way)
================
*/
void AICast_EvaluatePmove( int clientnum, pmove_t *pm )
{
	cast_state_t	*cs, *ocs;
	int	i, ent;
	bot_goal_t ogoal;

	//vec3_t pos, dir;
	cs = AICast_GetCastState( clientnum );
	// make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data)
	trap_AAS_SetCurrentWorld( cs->aasWorldIndex );

	// NOTE: this is only enabled for real clients, so their followers get out of their way
	//if (cs->bs)
	//	return;

	// look through the touchent's to see if we've bumped into something we should avoid, or react to
	for (i=0; i<pm->numtouch; i++)
	{
		// mark the time, so they can deal with the obstruction in their own think functions
		cs->blockedTime = level.time;

		if (pm->touchents[i] == pm->ps->groundEntityNum)
			continue;

		// if they are an AI Cast, inform them of our disposition, and hope that they are reasonable
		// enough to assist us in our desire to move beyond our current position
		if (pm->touchents[i] < aicast_maxclients )
		{
			if (!AICast_EntityVisible( cs, pm->touchents[i], qtrue )) {
				continue;
			}

			// if we are inspecting the body, abort if we touch anything
			if (cs->bs && cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0) {
				cs->bs->enemy = -1;
			}

			// anything we touch, should see us
			AICast_UpdateVisibility( &g_entities[pm->touchents[i]], &g_entities[cs->entityNum], qfalse, qtrue );

			ocs = AICast_GetCastState( pm->touchents[i] );
			if (	(ocs->bs) &&
					(!(ocs->aiFlags & AIFL_NOAVOID)) &&
					((ocs->leaderNum == cs->entityNum) || (VectorLength(ocs->bs->velocity) < 5)) &&
					(ocs->obstructingTime < (level.time + 100))) {
				// if they are moving away from us already, let them go
				if (VectorLength(ocs->bs->cur_ps.velocity) > 10) {
					vec3_t v1, v2;

					VectorSubtract( ocs->bs->origin, g_entities[clientnum].client->ps.velocity, v2 );
					VectorNormalize(v2);
					VectorNormalize2(ocs->bs->cur_ps.velocity, v1);

					if (DotProduct(v1, v2) > 0.0) {
						continue;
					}
				}
				if (ocs->leaderNum >= 0) {
					VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin );
					ogoal.areanum = BotPointAreaNum(ogoal.origin);
					ogoal.entitynum = ocs->leaderNum;
					if (ocs->bs && AICast_GetAvoid(ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum))
					{	// give them time to move somewhere else
						ocs->obstructingTime = level.time + 1000;
					}
				} else {
					if (ocs->bs && AICast_GetAvoid(ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum))
					{	// give them time to move somewhere else
						ocs->obstructingTime = level.time + 1000;
					}
				}
			}
		} else if (cs->bs) {
			// if we are blocked by a brush entity, see if we can activate it
			ent = pm->touchents[i];
			if (g_entities[ent].s.modelindex > 0 && g_entities[ent].s.eType == ET_MOVER) {
				//find the bsp entity which should be activated in order to remove
				//the blocking entity
				
				if (!g_entities[ent].isProp 
					&& Q_stricmp (g_entities[ent].classname, "func_static")
					&& Q_stricmp (g_entities[ent].classname, "func_button")
					&& Q_stricmp (g_entities[ent].classname, "func_tram"))
					G_Activate( &g_entities[ent], &g_entities[cs->entityNum] );

			}
		}
	}
}

/*
==============
AICast_RequestCrouchAttack
==============
*/
qboolean AICast_RequestCrouchAttack( cast_state_t *cs, vec3_t org, float time )
{
	if (cs->attributes[ATTACK_CROUCH] > 0 && AICast_CheckAttackAtPos(cs->entityNum, cs->bs->enemy, org, qtrue, qfalse )) {
		if (time) {
			cs->bs->attackcrouch_time = trap_AAS_Time() + time;
		}
		return qtrue;
	}

	return qfalse;
}

/*
==============
AICast_QueryThink
==============
*/
void AICast_QueryThink( cast_state_t *cs )
{
	gentity_t *ent;
	qboolean visible;
	cast_state_t *ocs;
	vec3_t	vec;

	ent = &g_entities[cs->entityNum];
	ocs = AICast_GetCastState( cs->bs->enemy );

	// never crouch while in this state (by choice anyway)
	cs->bs->attackcrouch_time = 0;

	// look at where we last (thought we) saw them
	VectorSubtract( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->origin, vec );
	VectorNormalize( vec );
	vectoangles( vec, cs->bs->ideal_viewangles );

	// are they visible now?
	visible = AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, g_entities[cs->bs->enemy].r.currentOrigin, cs->bs->enemy, qfalse );

	// make sure we dont process the sighting of this enemy by going into query mode again, without them being visible again after we leave here
	cs->vislist[cs->bs->enemy].flags &= ~AIVIS_PROCESS_SIGHTING;

	// look towards where we last saw them
	AICast_AimAtEnemy( cs );

	// if visible and alert time has expired, go POSTAL
	if ((cs->queryAlertSightTime < 0) || ((cs->queryAlertSightTime < level.time) && visible)) {
		if (!cs->queryAlertSightTime) {
			// set the "short reaction" condition
			BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse );
		}
		AICast_StateChange( cs, AISTATE_COMBAT );
		BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse );
		AIFunc_BattleStart( cs );
		return;
	}

	// if they've fired since the start of the query mode, go POSTAL
	if (ocs->lastWeaponFired > cs->queryStartTime) {
		// set the "short reaction" condition
		BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse );
		AICast_StateChange( cs, AISTATE_COMBAT );
		BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse );
		AIFunc_BattleStart( cs );
		return;
	}

	// if not visible, then kill the Lock On timer
	if ((cs->queryAlertSightTime > 0) && !visible) {
		cs->queryAlertSightTime = 0;
	}

	// if the query has expired, go back to relaxed
	if (!ent->client->ps.legsTimer) {
		AICast_StateChange( cs, AISTATE_RELAXED);
	}
}

/*
================
AICast_DeadClipWalls
================
*/
void AICast_DeadClipWalls( cast_state_t *cs ) {
/*
	//animation_t *anim;
	orientation_t or;
	vec3_t	src, vel;
	trace_t	tr;

	// get the death animation we are currently playing
	//anim = BG_GetAnimationForIndex( cs->entityNum, (cs->bs->cur_ps.torsoAnim & ~ANIM_TOGGLEBIT) );

	// find the head position
	trap_GetTag( cs->entityNum, "tag_head", &or );
	// move up a tad
	or.origin[2] += 3;

	// trace from the base of our bounding box, to the head
	VectorCopy( cs->bs->origin, src );
	src[2] -= cs->bs->cur_ps.mins[2] + 3;
	trap_Trace( &tr, src, vec3_origin, vec3_origin, or.origin, cs->entityNum, MASK_SOLID );

	// if we hit something, move away from it
	if (!tr.startsolid && !tr.allsolid && tr.fraction < 1.0) {
		VectorScale( tr.plane.normal, 80, vel );
		vel[2] = 0;
		VectorAdd( g_entities[cs->entityNum].client->ps.velocity, vel, g_entities[cs->entityNum].client->ps.velocity );
	}
*/
}
