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

Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).  

RTCW MP Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

RTCW MP Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with RTCW MP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

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

/*
 * name:		ai_cast_funcs.c
 *
 * desc:		Wolfenstein AI Character Decision Making
 *
*/


#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"

/*
This file contains the generic thinking states for the characters.

Different types of movement or behaviour will be represented by
a seperate thinking function, which may or may not pass control
over to a new behaviour function.

If control is passed onto a new function, the string name of the
current function is returned, mostly for debugging purposes.

!!! NOTE: control must not be passed to a new AI func from outside of
this file. A new AI func must only be called from within another AI func.

This gives us the ability to keep all code related to sections of AI
self-contained, so adding new features to the AI will be less likely to
step on other areas of AI.
*/

static int enemies[MAX_CLIENTS], numEnemies;

// this is used to prevent try/abort/try/abort/etc grenade flush behaviour
static int lastGrenadeFlush = 0;

#define AICAST_LEADERDIST_MAX   240     // try and stay at least this close to them when nothing else to do
#define AICAST_LEADERDIST_MIN   64      // get this close if we have a clear line of sight to them

char *AIFunc_BattleChase( cast_state_t *cs );
char *AIFunc_Battle( cast_state_t *cs );

static bot_moveresult_t *moveresult;

/*
============
AIFunc_Restore()

  restores the last aifunc that was backed up
============
*/
char *AIFunc_Restore( cast_state_t *cs ) {
	// if the old aifunc was BattleChase, set it back to Battle, in case we have found a good position
	if ( cs->oldAifunc == AIFunc_BattleChase ) {
		cs->oldAifunc = AIFunc_Battle;
	}
	cs->aifunc = cs->oldAifunc;
	return cs->aifunc( cs );
}

/*
============
AICast_GetRandomViewAngle()
============
*/
float AICast_GetRandomViewAngle( cast_state_t *cs, float tracedist ) {
	int cnt, passent, contents_mask;
	vec3_t vec, dir, start, end;
	trace_t trace;
	float bestdist, bestyaw;

	cnt = 0;
	VectorClear( vec );
	//
	VectorCopy( cs->bs->origin, start );
	start[2] += cs->bs->cur_ps.viewheight;
	//
	passent = cs->bs->entitynum;
	contents_mask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WATER | CONTENTS_SLIME;
//	contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WATER;
	bestdist = 0;
	bestyaw = 0;
	//
	while ( cnt++ < 4 )
	{
		vec[YAW] = random() * 360.0;
		//
		AngleVectors( vec, dir, NULL, NULL );
		VectorMA( start, tracedist, dir, end );
		//
		trap_Trace( &trace, start, NULL, NULL, end, passent, contents_mask );
		//
		if ( trace.fraction >= 1 ) {
			return vec[YAW];
		} else if ( !bestdist || bestdist < trace.fraction ) {
			bestdist = trace.fraction;
			bestyaw = vec[YAW];
		}
	}
	//
	if ( bestdist ) {
		return bestyaw;
	}
	// just return their current direction
	return cs->bs->ideal_viewangles[YAW];
}

/*
============
AICast_MoveToPos()

  returns a pointer to the moveresult it used to make the move, so we can investigate it
  outside of this function
============
*/
bot_moveresult_t *AICast_MoveToPos( cast_state_t *cs, vec3_t pos, int entnum ) {
	bot_goal_t goal;
	vec3_t /*target,*/ dir;
	static bot_moveresult_t lmoveresult;
	int tfl;
	bot_state_t *bs;

//int pretime = Sys_MilliSeconds();

	moveresult = NULL;

	if ( cs->castScriptStatus.scriptNoMoveTime > level.time ) {
		return NULL;
	}
	if ( cs->pauseTime > level.time ) {
		return NULL;
	}
	//
	bs = cs->bs;
	tfl = cs->travelflags;
	//if in lava or slime the bot should be able to get out
	if ( BotInLava( bs ) ) {
		tfl |= TFL_LAVA;
	}
	if ( BotInSlime( bs ) ) {
		tfl |= TFL_SLIME;
	}
	//
	//create the chase goal
	memset( &goal, 0, sizeof( goal ) );
	goal.entitynum = entnum;
	goal.areanum = BotPointAreaNum( pos );
	VectorCopy( pos, goal.origin );
	VectorSet( goal.mins, -8, -8, -8 );
	VectorSet( goal.maxs, 8, 8, 8 );
	if ( entnum == cs->followEntity && !cs->followSlowApproach ) {
		goal.flags |= GFL_NOSLOWAPPROACH;   // just speed right passed it
	}
	//
	// debugging, show the route
	if ( aicast_debug.integer == 2 && ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) {
		trap_AAS_RT_ShowRoute( cs->bs->origin, cs->bs->areanum, goal.areanum );
	}
	//
	//initialize the movement state
	BotSetupForMovement( bs );
	//if this is a slow moving creature, don't use avoidreach
	if ( cs->attributes[RUNNING_SPEED] < 100 ) {
		//reset the avoid reach, otherwise bot is stuck in current area
		trap_BotResetAvoidReach( bs->ms );
	} else if ( !VectorCompare( cs->lastMoveToPosGoalOrg, pos ) ) {
		//reset the avoid reach, otherwise bot is stuck in current area
		trap_BotResetAvoidReach( bs->ms );
		VectorCopy( pos, cs->lastMoveToPosGoalOrg );
	}
	//move towards the goal
	trap_BotMoveToGoal( &lmoveresult, bs->ms, &goal, tfl );
	//if the movement failed
	if ( lmoveresult.failure ) {
		//reset the avoid reach, otherwise bot is stuck in current area
		trap_BotResetAvoidReach( bs->ms );
		//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", lmoveresult.traveltype);
		// clear all movement
		trap_EA_Move( cs->entityNum, vec3_origin, 0 );
	}
	//
	if ( lmoveresult.flags & ( MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) {
		VectorCopy( lmoveresult.ideal_viewangles, bs->ideal_viewangles );
		VectorCopy( bs->ideal_viewangles, cs->viewlock_viewangles );
		cs->aiFlags |= AIFL_VIEWLOCKED;
	} else if ( !( cs->bs->flags & BFL_ATTACKED ) )       { // if we are attacking, don't change angles
		bot_input_t bi;

		trap_EA_GetInput( bs->client, 0.1, &bi );
		if ( VectorLength( lmoveresult.movedir ) < 0.5 ) {
			VectorSubtract( goal.origin, bs->origin, dir );
			vectoangles( dir, bs->ideal_viewangles );
		} else {
			// use our velocity if we are moving
			if ( VectorNormalize2( cs->bs->cur_ps.velocity, dir ) > 1 ) {
				vectoangles( dir, bs->ideal_viewangles );
			} else {
				vectoangles( lmoveresult.movedir, bs->ideal_viewangles );
			}
		}
		bs->ideal_viewangles[2] *= 0.5;
// disabled, needs work
/*
		if (lmoveresult.flags & MOVERESULT_FUTUREVIEW) {
			if (AngleDifference(bs->ideal_viewangles[1], lmoveresult.ideal_viewangles[1]) > 45)
				bs->ideal_viewangles[1] += 45;
			else if (AngleDifference(bs->ideal_viewangles[1], lmoveresult.ideal_viewangles[1]) < -45)
				bs->ideal_viewangles[1] -= 45;
			else
				bs->ideal_viewangles[1] = lmoveresult.ideal_viewangles[1];
			bs->ideal_viewangles[1] = AngleNormalize360( bs->ideal_viewangles[1] );
			bs->ideal_viewangles[0] = lmoveresult.ideal_viewangles[0];
			bs->ideal_viewangles[0] = 0.5*AngleNormalize180( bs->ideal_viewangles[0] );
		}
*/
	}
	// this must go last so we face the direction we avoid move
	AICast_Blocked( cs, &lmoveresult, qfalse, &goal );

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

// debug, print movement info
	if ( 0 ) { // (SA) added to hide the print
		bot_input_t bi;

		trap_EA_GetInput( cs->bs->client, (float) level.time / 1000, &bi );
		G_Printf( "spd: %i\n", (int)bi.speed );
	}

	return &lmoveresult;
}

/*
============
AICast_SpeedScaleForDistance()
============
*/
float AICast_SpeedScaleForDistance( cast_state_t *cs, float startdist, float idealDist ) {
#define PREDICT_TIME_WALK   0.2
#define PREDICT_TIME_CROUCH 0.2
#define PREDICT_TIME_RUN    0.3
	float speed, dist;

	dist = startdist - idealDist;
	if ( dist < 1 ) {
		dist = 1;
	}

	// if walking
	if ( cs->movestate == MS_WALK ) {
		speed = cs->attributes[WALKING_SPEED];
		if ( speed * PREDICT_TIME_WALK > dist ) {
			return 0.2 + 0.8 * ( dist / ( speed * PREDICT_TIME_WALK ) );
		} else {
			return 1.0;
		}
	} else
	// if crouching
	if ( cs->movestate == MS_CROUCH || cs->bs->attackcrouch_time > trap_AAS_Time() ) {
		speed = cs->attributes[RUNNING_SPEED] * cs->bs->cur_ps.crouchSpeedScale;
		if ( speed * PREDICT_TIME_CROUCH > dist ) {
			return 0.3 + 0.7 * ( dist / ( speed * PREDICT_TIME_CROUCH ) );
		} else {
			return 1.0;
		}
	} else
	// running
	{
		speed = cs->attributes[RUNNING_SPEED];
		if ( speed * PREDICT_TIME_RUN > dist ) {
			return 0.2 + 0.8 * ( dist / ( speed * PREDICT_TIME_RUN ) );
		} else {
			return 1.0;
		}
	}
}

/*
============
AIFunc_Idle()

  The cast AI is standing around, contemplating the meaning of life
============
*/
char *AIFunc_Idle( cast_state_t *cs ) {

	// we are in an idle state, looking for something to do

	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}
	//
	// do we need to go to our leader?
	if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
		return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
	}
	//
	// look for things we should attack
	numEnemies = AICast_ScanForEnemies( cs, enemies );
	if ( numEnemies == -1 ) {     // query mode
		return NULL;
	} else if ( numEnemies == -2 )     { // inspection may be required
		char *retval;
		// TTimo gcc: suggest parentheses around assignment used as truth value
		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
			return retval;
		}
	} else if ( numEnemies == -3 )     { // bullet impact
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectBulletImpactStart( cs );
		}
	} else if ( numEnemies == -4 )     { // audible event
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
		}
	} else if ( numEnemies > 0 )     {
		int i;

		cs->bs->enemy = -1;
		// choose an enemy
		for ( i = 0; i < numEnemies; i++ ) {
			if ( Distance( cs->bs->origin, cs->vislist[enemies[i]].visible_pos ) > 16 ) { // if we are really close to the last place we saw them, no point trying to attack, since we'll just end up back here
				if ( cs->bs->enemy < 0 ) {
					cs->bs->enemy = enemies[i];
				} else if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
					cs->bs->enemy = enemies[i];
					return AIFunc_BattleStart( cs );
				}
			}
		}
		if ( cs->bs->enemy >= 0 ) {
			if ( ( ( cs->leaderNum < 0 ) || ( cs->thinkFuncChangeTime < level.time - 3000 ) ) && AICast_WantsToChase( cs ) ) {  // don't leave our leader as soon as we get to them
				return AIFunc_BattleStart( cs );
			} else if ( AICast_EntityVisible( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qtrue ) || AICast_CheckAttack( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qfalse ) ) {
				// if we are tactical enough, look for a hiding spot
				if ( !( cs->leaderNum >= 0 ) && cs->attributes[TACTICAL] > 0.4 && cs->attributes[AGGRESSION] < 1.0 ) {
					// they can see us, and we want to hide from them
					if ( !AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) {
						// go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time
						cs->takeCoverTime = level.time + 2000 + rand() % 3000;
						return AIFunc_BattleTakeCoverStart( cs );
					}
				}
				// attack them if nothing else to do, since they can attack us here
				return AIFunc_BattleStart( cs );
			} else if ( cs->leaderNum < 0 ) {     // we should pursue if no leader, and not wanting to hide
				return AIFunc_BattleStart( cs );
			} else {
				// they can't see us anyway, so ignore them
				cs->lastEnemy = cs->bs->enemy;  // at least face them if they come to get us
				cs->bs->enemy = -1;
				// crouching makes us look like we are hiding, which is what we are doing
				if ( cs->attributes[ATTACK_CROUCH] > 0.5 ) {
					cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
				}
			}
		}
	}
	//
	// if we are in combat mode, then we should relax, since we dont have an enemy
	if ( cs->aiState >= AISTATE_COMBAT ) {
		AICast_StateChange( cs, AISTATE_ALERT );
	}
	//
	// if we couldn't find anything, see if our previous enemy is still around, if so, go find them
	// this is an attempt to prevent guys from running away to hide from something, never to
	// be seen again. They shouldn't really "forget" that they are indeed soldiers.
	if ( !( cs->leaderNum >= 0 ) && cs->lastEnemy >= 0 && g_entities[cs->lastEnemy].health > 0 && cs->vislist[cs->lastEnemy].real_visible_timestamp < level.time - 5000 &&
		 cs->takeCoverTime < level.time - 5000 ) {
		cs->bs->enemy = cs->lastEnemy;  // just go to the place we last saw them
		return AIFunc_BattleStart( cs );
	}
	//
	// if we've recently been in a fight, keep looking around, so we don't look stupid
	if ( cs->lastEnemy >= 0 ) {
		//
		// if we like to crouch, then do so, since we are in a battle situation
/*		if (cs->lastEnemy > 0) {	// not the player
			if (cs->attributes[ATTACK_CROUCH] > 0.3) {
				cs->bs->attackcrouch_time = trap_AAS_Time() + 2;
			}
		} else {	// if we were attacking the player, relax a bit more
			if (cs->attributes[ATTACK_CROUCH] > 0.6) {
				cs->bs->attackcrouch_time = trap_AAS_Time() + 2;
			}
		}
*/                                                                                                                                                                                                                                                                                                                                    // if we only just recently saw them, face that direction
		if ( ( g_entities[cs->lastEnemy].health > 0 ) && cs->vislist[cs->lastEnemy].visible_timestamp > ( level.time - 20000 )
			 && AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, cs->vislist[cs->lastEnemy].visible_pos, cs->lastEnemy, qfalse ) ) {
			vec3_t dir;
			//
			VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir );
			if ( VectorLength( dir ) < 1 ) {
				cs->bs->ideal_viewangles[PITCH] = 0;
			} else {
				VectorNormalize( dir );
				vectoangles( dir, cs->bs->ideal_viewangles );
				cs->bs->ideal_viewangles[PITCH] = AngleNormalize180( cs->bs->ideal_viewangles[PITCH] ) * 0.5;
			}
			// if we like to crouch, then do so, since we are in a battle situation
//			if (cs->attributes[ATTACK_CROUCH] > 0.1) {
//				cs->bs->attackcrouch_time = trap_AAS_Time() + 2;
//			}
		} else if ( /*cs->castScriptStatus.castScriptEventIndex < 0 &&*/
			cs->attributes[TACTICAL] && cs->nextIdleAngleChange < level.time ) {
			// wait a second before changing again
			if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) {

				// FIXME: This could be changed to use some AAS sampling, which would:
				//
				//	Given a src area, pick a random dest area which is visible from that area
				//	and return it's position, which we'd then use to set the next view angles
				//
				//	This would result in more efficient, more realistic behaviour, since they'd
				//	also use PITCH angles to look at areas above/below them

				cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 );

				if ( abs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) {
					cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500;
				} else { // do really fast
					cs->nextIdleAngleChange = level.time + 500;
				}

				// adjust with time
				cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
				/// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0);

				cs->bs->ideal_viewangles[PITCH] = 0;
			}
		} else if ( cs->idleYawChange )     {
			cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
			cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) );
		}
	}

	// check for a movement we should be making
	if ( cs->obstructingTime > level.time ) {
		AICast_MoveToPos( cs, cs->obstructingPos, -1 );
		if ( cs->movestate != MS_CROUCH ) {
			cs->movestate = MS_WALK;
		}
		cs->movestateType = MSTYPE_TEMPORARY;
	}

	// set head look flag if no enemy
	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
	}

	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}

	return NULL;
}

/*
============
AIFunc_IdleStart()
============
*/
char *AIFunc_IdleStart( cast_state_t *cs ) {
	g_entities[cs->entityNum].flags &= ~FL_AI_GRENADE_KICK;
	// stop following
	cs->followEntity = -1;
	// if our enemy has just died, inspect the body
	if ( cs->bs->enemy >= 0 ) {
		if ( g_entities[cs->entityNum].aiTeam == AITEAM_NAZI && g_entities[cs->bs->enemy].health <= 0 ) {
			return AIFunc_InspectBodyStart( cs );
		} else {
			cs->bs->enemy = -1;
		}
	}
	// make sure we don't avoid any areas when we start again
	trap_BotInitAvoidReach( cs->bs->ms );

	// randomly choose idle animation
//----(SA)	try always using the 'casual' stand on spawn and change to crouching one when 'alerted'
	if ( cs->aiFlags & AIFL_STAND_IDLE2 ) {
//		if (rand()%2 || (cs->lastEnemy < 0 && cs->aiFlags & AIFL_TALKING))
		g_entities[cs->entityNum].client->ps.eFlags |= EF_STAND_IDLE2;
//		else
//			g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2;
	}

	cs->aifunc = AIFunc_Idle;
	return "AIFunc_Idle";
}

/*
============
AIFunc_InspectFriendly()
============
*/
char *AIFunc_InspectFriendly( cast_state_t *cs ) {
	gentity_t   *followent, *ent;
	bot_state_t *bs;
	vec3_t destorg;
	float dist;
	qboolean moved = qfalse;

	ent = &g_entities[cs->entityNum];

	// if we have an enemy, attack now!
	if ( cs->bs->enemy >= 0 ) {
		return AIFunc_BattleStart( cs );
	}

	cs->followEntity = cs->inspectNum;
	cs->followDist = 64;

	cs->scriptPauseTime = level.time + 4000;    // wait for at least this long before resuming any scripted walking, etc

	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// go to a position that cannot be seen from the dangerPos
			cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
			cs->bs->attackcrouch_time = 0;
			cs->castScriptStatus.scriptGotoId = -1;
			cs->movestate = MS_DEFAULT;
			cs->movestateType = MSTYPE_NONE;
			return AIFunc_AvoidDangerStart( cs );
		}
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}

	followent = &g_entities[cs->followEntity];

	// if the entity is not ready yet
	if ( !followent->inuse ) {
		// if it's a connecting client, wait
		if (    cs->followEntity < MAX_CLIENTS
				&&  (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
						|| ( level.time < 3000 ) ) ) {
			return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist );
		} else    // stop following it
		{
			AICast_EndChase( cs );
			return AIFunc_IdleStart( cs );
		}
	}

	if ( followent->client ) {
		VectorCopy( followent->client->ps.origin, destorg );
	} else {
		VectorCopy( followent->r.currentOrigin, destorg );
	}

	// they are ready, are they inside range? FIXME: make configurable
	dist = Distance( destorg, cs->bs->origin );
	if ( !( dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) ) {
		//
		// go to them
		//
		bs = cs->bs;

		// set this flag so we know when we;ve just reached them
		cs->aiFlags |= AIFL_MISCFLAG1;

		// move straight to them if we can
		if (    !moved &&
				( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) {
			aicast_predictmove_t move;
			vec3_t dir;
			bot_input_t bi;
			usercmd_t ucmd;
			trace_t tr;
			qboolean simTest = qfalse;

			if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) {
				simTest = qtrue;
			}

			if ( !simTest ) {
				// trace will eliminate most unsuccessful paths
				trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
				if ( tr.entityNum == cs->followEntity || tr.fraction == 1 ) {
					simTest = qtrue;
				}
			}

			if ( simTest ) {
				// try walking straight to them
				VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
				VectorNormalize( dir );
				if ( !ent->waterlevel ) {
					dir[2] = 0;
				}
				trap_EA_Move( cs->entityNum, dir, 400 );
				trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
				AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
				AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, cs->followEntity );

				if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
					vectoangles( dir, cs->bs->ideal_viewangles );
					cs->bs->ideal_viewangles[2] *= 0.5;
					moved = qtrue;
				}
			}
		}
		//
		if ( !moved ) {
			// use AAS routing
			moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, cs->followEntity );
			// if we cant get there, forget it
			if ( moveresult && moveresult->failure ) {
				return AIFunc_DefaultStart( cs );
			}
		}

		// should we slow down?
		if ( cs->followDist && cs->followSlowApproach ) {
			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist );
		}
/*
		// check for a movement we should be making
		if (cs->obstructingTime > level.time)
		{
			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
			if (cs->movestate != MS_CROUCH) {
				cs->movestate = MS_WALK;
			}
			cs->movestateType = MSTYPE_TEMPORARY;
		}
*/
	} else if ( cs->aiFlags & AIFL_MISCFLAG1 ) {
		cs->aiFlags &= ~AIFL_MISCFLAG1;
		if ( g_entities[cs->inspectNum].health <= 0 ) {
			// call a script event
			cs->aiFlags &= ~AIFL_DENYACTION;
			AICast_ForceScriptEvent( cs, "inspectbodyend", g_entities[cs->inspectNum].aiName );
			if ( cs->aiFlags & AIFL_DENYACTION ) {
				// relinguish control back to scripting
				return AIFunc_DefaultStart( cs );
			}
		}
	}

	{
		int numEnemies;
		//
		// look for things we should attack
		numEnemies = AICast_ScanForEnemies( cs, enemies );
		if ( numEnemies == -1 ) { // query mode
			return NULL;
		} else if ( numEnemies == -2 )     { // inspection
			// only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone
			if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) {
				return AIFunc_InspectFriendlyStart( cs, enemies[0] );
			}
		}
		// RF, disabled this, if we are interrupted, scripts might not work right, and anyway, this is only a bullet, not as if it's a dead guy or anything
		//else if (numEnemies == -3)	// bullet impact
		//{
		//	if (cs->aiState < AISTATE_COMBAT) {
		//		return AIFunc_InspectBulletImpactStart( cs );
		//	}
		//}
		else if ( numEnemies > 0 ) {
			int i;

			cs->bs->enemy = enemies[0]; // just attack the first one
			// override with a visible enemy
			for ( i = 1; i < numEnemies; i++ ) {
				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
					cs->bs->enemy = enemies[i];
					break;
				} else if ( cs->bs->enemy < 0 ) {
					cs->lastEnemy = enemies[i];
				}
			}

			return AIFunc_BattleStart( cs );
		}
	}

	if ( cs->nextIdleAngleChange < level.time ) {
		// wait a second before changing again
		if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) {

			// FIXME: This could be changed to use some AAS sampling, which would:
			//
			//	Given a src area, pick a random dest area which is visible from that area
			//	and return it's position, which we'd then use to set the next view angles
			//
			//	This would result in more efficient, more realistic behaviour, since they'd
			//	also use PITCH angles to look at areas above/below them

			cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 );

			if ( abs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) {
				cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500;
			} else { // do really fast
				cs->nextIdleAngleChange = level.time + 500;
			}

			// adjust with time
			cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
			/// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0);

			cs->bs->ideal_viewangles[PITCH] = 0;
		}
	} else if ( cs->idleYawChange )     {
		cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
		cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) );
	}

	// set head look flag if no enemy
	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
	}

	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[cs->bs->cur_ps.weapon] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}

	return NULL;
}

/*
============
AIFunc_InspectFriendlyStart
============
*/
char *AIFunc_InspectFriendlyStart( cast_state_t *cs, int entnum ) {
	cast_state_t *ocs;

	ocs = AICast_GetCastState( entnum );

	// we are about to deal with the request for inspection
	cs->vislist[entnum].flags &= ~AIVIS_INSPECT;
	cs->scriptPauseTime = level.time + 4000;    // wait for at least this long before resuming any scripted walking, etc

	if ( ocs->aiState >= AISTATE_COMBAT || g_entities[entnum].health <= 0 ) {
		// mark this character as having been inspected
		cs->vislist[entnum].flags |= AIVIS_INSPECTED;
	}

	// what should we do? wait here? hide? go see them?

	// if dead, go see them
	if ( g_entities[entnum].health <= 0 ) {
		cs->inspectNum = entnum;
		cs->aifunc = AIFunc_InspectFriendly;
		return "AIFunc_InspectFriendlyStart";
	}

	// not dead, so call scripting event
	AICast_ForceScriptEvent( cs, "inspectfriendlycombatstart", g_entities[entnum].aiName );
	if ( cs->aiFlags & AIFL_DENYACTION ) {
		// ignore this friendly forever and ever amen
		cs->vislist[entnum].flags |= AIVIS_INSPECTED;
		return NULL;
	}

	// if they are in combat, then act according to aggressiveness
	if ( ocs->aiState >= AISTATE_COMBAT ) {
		if ( cs->attributes[AGGRESSION] < 0.3 ) {
			if ( !AICast_GetTakeCoverPos( cs, entnum, g_entities[entnum].client->ps.origin, cs->takeCoverPos ) ) {
				cs->takeCoverTime = level.time + 10000; // hide for 10 seconds
				cs->scriptPauseTime = cs->takeCoverTime;
				// crouch there if possible
				if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
					cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0;
				}
				return AIFunc_BattleTakeCoverStart( cs );
			}
		}
	}

	// if still around, then we need to go to them
	cs->inspectNum = entnum;
	cs->aifunc = AIFunc_InspectFriendly;
	return "AIFunc_InspectFriendly";
}

/*
============
AIFunc_InspectBulletImpact()
============
*/
char *AIFunc_InspectBulletImpact( cast_state_t *cs ) {
	gentity_t *ent;
	vec3_t v1;
	gclient_t *client;
	//
	client = &level.clients[cs->entityNum];
	//
	ent = &g_entities[cs->entityNum];
	//
	cs->bulletImpactIgnoreTime = level.time + 800;
	//
	// wait until we are looking at the impact
	if ( cs->aiFlags & AIFL_MISCFLAG2 ) {
		// pause any scripting
		cs->scriptPauseTime = level.time + 1000;
		// look at bullet impact
		VectorSubtract( cs->bulletImpactEnd, cs->bs->origin, v1 );
		VectorNormalize( v1 );
		vectoangles( v1, cs->bs->ideal_viewangles );
		//
		// if we are facing that direction, we've looked at the impact point
		if ( fabs( cs->bs->ideal_viewangles[YAW] - cs->bs->viewangles[YAW] ) < 1 ) {
			cs->aiFlags &= ~AIFL_MISCFLAG2;
		}
		return NULL;
	} else if ( cs->aiFlags & AIFL_MISCFLAG1 ) {
		// clear the flag now
		cs->aiFlags &= ~AIFL_MISCFLAG1;
		// start looking back at bullet
		VectorSubtract( cs->bulletImpactStart, cs->bs->origin, v1 );
		VectorNormalize( v1 );
		vectoangles( v1, cs->bs->ideal_viewangles );
		if ( cs->aiState < AISTATE_ALERT ) {
			// change to alert state
			if ( !AICast_StateChange( cs, AISTATE_ALERT ) ) {
				// stop doing whatever we are doing, and return control to scripting
				cs->scriptPauseTime = 0;
				return AIFunc_IdleStart( cs );
			}
			// make sure we didnt change thinkfunc
			if ( cs->aifunc != AIFunc_InspectBulletImpact ) {
				//G_Error( "scripting passed control out of AIFunc_InspectBulletImpact(), this is bad" );
				return NULL;
			}
		}
		// pause any scripting
		if ( ent->client->ps.legsTimer ) {
			cs->scriptPauseTime = level.time + ent->client->ps.legsTimer;
		} else { // just wait for a few seconds looking at the source
			cs->scriptPauseTime = level.time + 3500;
		}
	}
	// are we done?
	if ( cs->scriptPauseTime < level.time ) {
		return AIFunc_IdleStart( cs );
	}
	//
	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}
	//
	// check for enemies
	{
		int numEnemies;
		//
		// look for things we should attack
		numEnemies = AICast_ScanForEnemies( cs, enemies );
		if ( numEnemies == -2 ) { // inspection
			// only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone
			if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) {

				return AIFunc_InspectFriendlyStart( cs, enemies[0] );
			}
		} else if ( numEnemies > 0 )     {
			int i;

			cs->bs->enemy = enemies[0]; // just attack the first one
			// override with a visible enemy
			for ( i = 1; i < numEnemies; i++ ) {
				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
					cs->bs->enemy = enemies[i];
					break;
				} else if ( cs->bs->enemy < 0 ) {
					cs->lastEnemy = enemies[i];
				}
			}

			return AIFunc_BattleStart( cs );
		}
	}
	//
	return NULL;
}

/*
============
AIFunc_InspectBulletImpactStart()
============
*/
char *AIFunc_InspectBulletImpactStart( cast_state_t *cs ) {
	int oldScriptIndex;
	// set the impact timer so we ignore bullets while inspecting this one
	cs->bulletImpactIgnoreTime = level.time + 5000;
	// pause any scripting
	cs->scriptPauseTime = level.time + 1000;
	// set this so we know if we've started the trace back to the bullet origin
	cs->aiFlags |= AIFL_MISCFLAG1;
	cs->aiFlags |= AIFL_MISCFLAG2;
	//
	// call the script event
	oldScriptIndex = cs->scriptCallIndex;
	AICast_ScriptEvent( cs, "bulletimpactsound", "" );
	if ( oldScriptIndex == cs->scriptCallIndex ) {
		// no script event, so call the animation script
		BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_BULLETIMPACT, qfalse, qtrue );
	}
	//
	cs->aifunc = AIFunc_InspectBulletImpact;
	return "AIFunc_InspectBulletImpact";
}

/*
============
AIFunc_InspectAudibleEvent()
============
*/
char *AIFunc_InspectAudibleEvent( cast_state_t *cs ) {
	gentity_t   *ent;
	bot_state_t *bs;
	vec3_t destorg;
	float dist;
	qboolean moved = qfalse;

	ent = &g_entities[cs->entityNum];

	// if we have an enemy, attack now!
	if ( cs->bs->enemy >= 0 ) {
		return AIFunc_BattleStart( cs );
	}

	cs->followDist = 64;

	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// go to a position that cannot be seen from the dangerPos
			cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
			cs->bs->attackcrouch_time = 0;
			cs->castScriptStatus.scriptGotoId = -1;
			cs->movestate = MS_DEFAULT;
			cs->movestateType = MSTYPE_NONE;
			return AIFunc_AvoidDangerStart( cs );
		}
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}

	VectorCopy( cs->audibleEventOrg, destorg );

	// they are ready, are they inside range? FIXME: make configurable
	dist = Distance( destorg, cs->bs->origin );
	if ( !( dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) ) {
		//
		// go to them
		//
		bs = cs->bs;

		// set this flag so we know when we;ve just reached them
		cs->aiFlags |= AIFL_MISCFLAG1;

		// if not overly aggressive, pursue with caution
		if ( cs->attributes[AGGRESSION] <= 0.8 ) {
			cs->movestate = MS_CROUCH;
			cs->movestateType = MSTYPE_TEMPORARY;
		}

		// move straight to them if we can
		if (    !moved &&
				( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) {
			aicast_predictmove_t move;
			vec3_t dir;
			bot_input_t bi;
			usercmd_t ucmd;
			trace_t tr;
			qboolean simTest = qfalse;

			if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) {
				simTest = qtrue;
			}

			if ( !simTest ) {
				// trace will eliminate most unsuccessful paths
				trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, destorg, cs->entityNum, g_entities[cs->entityNum].clipmask );
				if ( tr.fraction == 1 ) {
					simTest = qtrue;
				}
			}

			if ( simTest ) {
				// try walking straight to them
				gentity_t *gent;
				//
				gent = G_Spawn();
				VectorCopy( destorg, gent->r.currentOrigin );
				//
				VectorSubtract( destorg, cs->bs->origin, dir );
				VectorNormalize( dir );
				if ( !ent->waterlevel ) {
					dir[2] = 0;
				}
				trap_EA_Move( cs->entityNum, dir, 400 );
				trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
				AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
				AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, gent->s.number );
				//
				if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
					vectoangles( dir, cs->bs->ideal_viewangles );
					cs->bs->ideal_viewangles[2] *= 0.5;
					moved = qtrue;
				}
				//
				G_FreeEntity( gent );
			}
		}
		//
		if ( !moved ) {
			// use AAS routing
			AICast_MoveToPos( cs, destorg, -1 );
			// if we cant get there, forget it
			if ( moveresult && moveresult->failure ) {
				return AIFunc_DefaultStart( cs );
			}
		}

		// should we slow down?
		if ( cs->followDist && cs->followSlowApproach ) {
			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist );
		}
/*
		// check for a movement we should be making
		if (cs->obstructingTime > level.time)
		{
			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
			if (cs->movestate != MS_CROUCH) {
				cs->movestate = MS_WALK;
			}
			cs->movestateType = MSTYPE_TEMPORARY;
		}
*/
	} else if ( cs->aiFlags & AIFL_MISCFLAG1 ) {
		cs->aiFlags &= ~AIFL_MISCFLAG1;
		// call a script event
		cs->aiFlags &= ~AIFL_DENYACTION;
		AICast_ForceScriptEvent( cs, "inspectsoundend", g_entities[cs->audibleEventEnt].aiName );
		if ( cs->aiFlags & AIFL_DENYACTION ) {
			// relinguish control back to scripting
			return AIFunc_DefaultStart( cs );
		}
	} else {
		// look around randomly
		if ( cs->battleHuntViewTime < level.time ) {
			cs->battleHuntViewTime = level.time + 700 + rand() % 1000;
			// set a random viewangle
			cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( 45.0 + random() * 45.0 ) * ( 2 * ( rand() % 2 ) - 1 ) );
		}
		//
		if ( cs->scriptPauseTime < level.time ) {
			// we're done waiting around here
			return AIFunc_DefaultStart( cs );
		}
	}

	{
		int numEnemies;
		//
		// look for things we should attack
		numEnemies = AICast_ScanForEnemies( cs, enemies );
		if ( numEnemies == -1 ) { // query mode
			return NULL;
		} else if ( numEnemies == -2 )     { // inspection
			// only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone
			if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) {
				return AIFunc_InspectFriendlyStart( cs, enemies[0] );
			}
		} else if ( numEnemies > 0 )     {
			int i;

			cs->bs->enemy = enemies[0]; // just attack the first one
			// override with a visible enemy
			for ( i = 1; i < numEnemies; i++ ) {
				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
					cs->bs->enemy = enemies[i];
					break;
				} else if ( cs->bs->enemy < 0 ) {
					cs->lastEnemy = enemies[i];
				}
			}

			return AIFunc_BattleStart( cs );
		}
	}

	// set head look flag if no enemy
	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
	}

	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[cs->bs->cur_ps.weapon] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}

	return NULL;
}

/*
============
AIFunc_InspectAudibleEventStart
============
*/
char *AIFunc_InspectAudibleEventStart( cast_state_t *cs, int entnum ) {
	cast_state_t *ocs;
	int oldScriptIndex;

	ocs = AICast_GetCastState( entnum );

	// we have now processed the audible event (whether we act on it or not)
	cs->audibleEventTime = -9999;

	// trigger a script event, which has the ability to deny the request
	oldScriptIndex = cs->scriptCallIndex;
	AICast_ForceScriptEvent( cs, "inspectsoundstart", g_entities[cs->audibleEventEnt].aiName );
	if ( cs->aiFlags & AIFL_DENYACTION ) {
		return NULL;
	}

	// if not in alert mode, go there now
	if ( cs->aiState < AISTATE_ALERT ) {
		AICast_StateChange( cs, AISTATE_ALERT );
	}

	if ( oldScriptIndex == cs->scriptCallIndex ) {
		BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_INSPECTSOUND, qfalse, qtrue );
	}

	// pause the scripting for now
	cs->scriptPauseTime = level.time + 4000;    // wait for at least this long before resuming any scripted walking, etc

	// what should we do? wait here? hide? go see them?

	// if dead, go see them
	if ( g_entities[entnum].health <= 0 ) {
		cs->inspectNum = entnum;
		cs->aifunc = AIFunc_InspectFriendly;
		return "AIFunc_InspectFriendlyStart";
	}

	// if they are in combat, then act according to aggressiveness
	if ( ocs->aiState >= AISTATE_COMBAT ) {
		if ( cs->attributes[AGGRESSION] < 0.3 ) {
			if ( !AICast_GetTakeCoverPos( cs, entnum, g_entities[entnum].client->ps.origin, cs->takeCoverPos ) ) {
				cs->takeCoverTime = level.time + 10000; // hide for 10 seconds
				cs->scriptPauseTime = cs->takeCoverTime;
				// crouch there if possible
				if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
					cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0;
				}
				return AIFunc_BattleTakeCoverStart( cs );
			}
		}
	}

	cs->aifunc = AIFunc_InspectAudibleEvent;
	return "AIFunc_InspectAudibleEvent";
}

/*
============
AIFunc_ChaseGoalIdle()
============
*/
char *AIFunc_ChaseGoalIdle( cast_state_t *cs ) {
	gentity_t   *followent;
	vec3_t dir;

	if ( cs->followEntity < 0 ) {
		AICast_EndChase( cs );
		return AIFunc_IdleStart( cs );
	}

	followent = &g_entities[cs->followEntity];

	// CHECK: will this interfere with scripting?
	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// go to a position that cannot be seen from the dangerPos
			cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
			cs->bs->attackcrouch_time = 0;
			return AIFunc_AvoidDangerStart( cs );
		}
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}
	//
	// if the player is not ready yet, wait
	if ( !followent->inuse ) {
		return NULL;
	}

	// has the scripting stopped asking us to pursue this goal?
	if ( cs->followIsGoto && ( cs->followTime < level.time ) ) {
		return AIFunc_Idle( cs );
	}

	// they are ready, are they outside range?
	if ( Distance( followent->r.currentOrigin, cs->bs->origin ) > cs->followDist ) {
		return AIFunc_ChaseGoalStart( cs, cs->followEntity, cs->followDist, qtrue );
	}

	// check for a movement we should be making
	if ( cs->obstructingTime > level.time ) {
		AICast_MoveToPos( cs, cs->obstructingPos, -1 );
		cs->speedScale = cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED];
	}
	// if we have an enemy, fire if they're visible
	else if ( cs->bs->enemy >= 0 ) {
		//attack the enemy if possible
		AICast_ProcessAttack( cs );
	}
	// if we had an enemy recently, face them
	else if ( cs->lastEnemy >= 0 ) {
		vec3_t dir;
		//
		VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir );
		if ( VectorLength( dir ) < 1 ) {
			cs->bs->ideal_viewangles[PITCH] = 0;
		} else {
			VectorNormalize( dir );
			vectoangles( dir, cs->bs->ideal_viewangles );
		}
		// reload?
		if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
			trap_EA_Reload( cs->entityNum );
		}
	} else if ( followent->client )     {
		// face them
		VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
		dir[2] += followent->client->ps.viewheight - g_entities[cs->bs->entitynum].client->ps.viewheight;
		VectorNormalize( dir );
		vectoangles( dir, cs->bs->ideal_viewangles );
	}

	// look for things we should attack
	numEnemies = AICast_ScanForEnemies( cs, enemies );
	if ( numEnemies == -1 ) { // query mode
		return NULL;
	} else if ( numEnemies == -2 )     { // inspection may be required
		char *retval;
		// TTimo gcc: suggest parentheses around assignment used as truth value
		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
			return retval;
		}
	} else if ( numEnemies == -3 )     { // bullet impact
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectBulletImpactStart( cs );
		}
	} else if ( numEnemies == -4 )     { // audible event
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
		}
	} else if ( numEnemies > 0 )     {
		cs->bs->enemy = enemies[0]; // just attack the first one
	}

	// set head look flag if no enemy
	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
	}

	return NULL;
}

/*
============
AIFunc_ChaseGoalIdleStart()
============
*/
char *AIFunc_ChaseGoalIdleStart( cast_state_t *cs, int entitynum, float reachdist ) {
	// make sure we don't avoid any areas when we start again
	trap_BotInitAvoidReach( cs->bs->ms );

	// if we are following someone, always use the default (ready for action) anim
	if ( entitynum < MAX_CLIENTS ) {
		g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2;
	} else {
		// randomly choose idle animation
//----(SA)	try always using the 'casual' stand on spawn and change to crouching one when 'alerted'
		if ( cs->aiFlags & AIFL_STAND_IDLE2 ) {
//			if (cs->lastEnemy < 0)
			g_entities[cs->entityNum].client->ps.eFlags |= EF_STAND_IDLE2;
//			else
//				g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2;
		}
	}

	cs->followEntity = entitynum;
	cs->followDist = reachdist;
	cs->aifunc = AIFunc_ChaseGoalIdle;
	return "AIFunc_ChaseGoalIdle";
}

/*
============
AIFunc_ChaseGoal()
============
*/
char *AIFunc_ChaseGoal( cast_state_t *cs ) {
	gentity_t   *followent, *ent;
	bot_state_t *bs;
	vec3_t destorg;
	float dist;
	qboolean moved = qfalse;

	ent = &g_entities[cs->entityNum];

	if ( cs->followEntity < 0 ) {
		AICast_EndChase( cs );
		return AIFunc_IdleStart( cs );
	}

	// CHECK: will this mess with scripting?
	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// go to a position that cannot be seen from the dangerPos
			cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
			cs->bs->attackcrouch_time = 0;
			cs->castScriptStatus.scriptGotoId = -1;
			cs->movestate = MS_DEFAULT;
			cs->movestateType = MSTYPE_NONE;
			return AIFunc_AvoidDangerStart( cs );
		}
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}

	followent = &g_entities[cs->followEntity];

	// if the entity is not ready yet
	if ( !followent->inuse ) {
		// if it's a connecting client, wait
		if (    cs->followEntity < MAX_CLIENTS
				&&  (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
						|| ( level.time < 3000 ) ) ) {
			return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist );
		} else    // stop following it
		{
			AICast_EndChase( cs );
			return AIFunc_IdleStart( cs );
		}
	}

	// has the scripting stopped asking us to pursue this goal?
	if ( cs->followIsGoto && ( cs->followTime < level.time ) ) {
		return AIFunc_Idle( cs );
	}

	if ( followent->client ) {
		VectorCopy( followent->client->ps.origin, destorg );
	} else {
		VectorCopy( followent->r.currentOrigin, destorg );
	}

	// they are ready, are they inside range? FIXME: make configurable
	dist = Distance( destorg, cs->bs->origin );
	if ( cs->followSlowApproach && dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) {
		// if this is a scripted GOTO, stop following now
		if ( cs->followEntity == cs->castScriptStatus.scriptGotoEnt ) {
			AICast_EndChase( cs );
			return AIFunc_IdleStart( cs );
		}
		// if we have reached our leader
		else
		{
			if ( cs->followEntity == cs->leaderNum ) {
				if ( dist < AICAST_LEADERDIST_MIN ) {
					AICast_EndChase( cs );
					return AIFunc_IdleStart( cs );
				} else {
					trace_t tr;
					// if we have a clear line to our leader, move closer, since there may be others following also
					trap_Trace( &tr, cs->bs->origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, g_entities[cs->followEntity].r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
					if ( tr.entityNum != cs->followEntity ) {
						AICast_EndChase( cs );
						return AIFunc_IdleStart( cs );
					}
					// if we have crouching ability, then use it while we are just moving closer
					if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
						cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0;
					}
				}
			} else
			{
				return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist );
			}
		}
	}
	//
	// go to them
	//
	bs = cs->bs;
	//
	// RF, disabled this, MIKE sees dead people
	//if (followent->client && followent->health <= 0) {
	//	AICast_EndChase( cs );
	//	return AIFunc_IdleStart(cs);
	//}

	// move straight to them if we can
	if (    !moved &&
			( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) {
		aicast_predictmove_t move;
		vec3_t dir;
		bot_input_t bi;
		usercmd_t ucmd;
		trace_t tr;
		qboolean simTest = qfalse;

		if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) {
			simTest = qtrue;
		}

		if ( !simTest ) {
			// trace will eliminate most unsuccessful paths
			trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
			if ( tr.entityNum == cs->followEntity || tr.fraction == 1 ) {
				simTest = qtrue;
			}
		}

		if ( simTest ) {
			// try walking straight to them
			VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
			VectorNormalize( dir );
			if ( !ent->waterlevel ) {
				dir[2] = 0;
			}
			trap_EA_Move( cs->entityNum, dir, 400 );
			trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
			AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
			AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, cs->followEntity );

			if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
				vectoangles( dir, cs->bs->ideal_viewangles );
				cs->bs->ideal_viewangles[2] *= 0.5;
				moved = qtrue;
			}
		}
	}
	//
	if ( !moved ) {
		// use AAS routing
		moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, cs->followEntity );
		if ( moveresult && moveresult->failure ) {
			// shit?
		}
	}

	// should we slow down?
	if ( cs->followDist && cs->followSlowApproach && cs->followDist < 48 ) {
		cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist );
	}

	// check for a movement we should be making
	if ( cs->obstructingTime > level.time ) {
		AICast_MoveToPos( cs, cs->obstructingPos, -1 );
		if ( cs->movestate != MS_CROUCH ) {
			cs->movestate = MS_WALK;
		}
		cs->movestateType = MSTYPE_TEMPORARY;
	}

	// if we have an enemy, fire if they're visible
	if ( cs->bs->enemy >= 0 ) { //attack the enemy if possible
		AICast_ProcessAttack( cs );
	} else {
		int numEnemies;
		//
		// look for things we should attack
		numEnemies = AICast_ScanForEnemies( cs, enemies );
		if ( numEnemies == -1 ) { // query mode
			return NULL;
		} else if ( numEnemies == -2 )     { // inspection may be required
			char *retval;
			// TTimo gcc: suggest parentheses around assignment used as truth value
			if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
				return retval;
			}
		} else if ( numEnemies == -3 )     { // bullet impact
			if ( cs->aiState < AISTATE_COMBAT ) {
				return AIFunc_InspectBulletImpactStart( cs );
			}
		} else if ( numEnemies == -4 )     { // audible event
			if ( cs->aiState < AISTATE_COMBAT ) {
				return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
			}
		} else if ( numEnemies > 0 )     {
			int i;

			cs->bs->enemy = enemies[0]; // just attack the first one
			// override with a visible enemy
			for ( i = 1; i < numEnemies; i++ ) {
				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
					cs->bs->enemy = enemies[i];
					break;
				} else if ( cs->bs->enemy < 0 ) {
					cs->lastEnemy = enemies[i];
				}
			}
		}
		// reload?
		if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
			trap_EA_Reload( cs->entityNum );
		}
	}

	// set head look flag if no enemy
	if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) {
		g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK;
	}

	return NULL;

}

/*
============
AIFunc_ChaseGoalStart()
============
*/
char *AIFunc_ChaseGoalStart( cast_state_t *cs, int entitynum, float reachdist, qboolean slowApproach ) {
	cs->followEntity = entitynum;
	cs->followDist = reachdist;
	cs->followIsGoto = qfalse;
	cs->followSlowApproach = slowApproach;
	cs->aifunc = AIFunc_ChaseGoal;
	return "AIFunc_ChaseGoal";
}

/*
============
AIFunc_DoorMarker()
============
*/
char *AIFunc_DoorMarker( cast_state_t *cs ) {
	gentity_t   *followent, *door;
	bot_state_t *bs;
	vec3_t destorg;
	float dist;
	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}

	followent = &g_entities[cs->doorMarker];

	// if the entity is not ready yet
	if ( !followent->inuse ) {
		cs->doorMarkerTime = 0;
		//return AIFunc_DefaultStart( cs );
		return AIFunc_Restore( cs );
	}

	// if the door is open or idle
	door = &g_entities[cs->doorEntNum];
	if (    ( !door->key ) &&
			( door->s.apos.trType == TR_STATIONARY && door->s.pos.trType == TR_STATIONARY ) ) {
		cs->doorMarkerTime = 0;
		//return AIFunc_DefaultStart( cs );
		return AIFunc_Restore( cs );
	}

	// if we have an enemy, fire if they're visible
	if ( cs->bs->enemy >= 0 ) { //attack the enemy if possible
		AICast_ProcessAttack( cs );
	}

	// they are ready, are they inside range? FIXME: make configurable
	dist = Distance( destorg, cs->bs->origin );
	if ( dist < 12 ) {
		// check for a movement we should be making
		if ( cs->obstructingTime > level.time ) {
			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
		}
		// if the door is locked, resume
		if ( followent->key ) {
			return AIFunc_Restore( cs );
		}
		return NULL;
	}

	// go to it
	//
	bs = cs->bs;
	//
	moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, followent->s.number );
	// if we cant get there, forget it
	if ( moveresult && moveresult->failure ) {
		return AIFunc_Restore( cs );
	}
	// should we slow down?
	if ( cs->followDist ) {
		cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist );
	}
	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}
	return NULL;

}

/*
============
AIFunc_DoorMarkerStart()
============
*/
char *AIFunc_DoorMarkerStart( cast_state_t *cs, int doornum, int markernum ) {
	cs->doorEntNum = doornum;
	cs->doorMarker = markernum;
	cs->oldAifunc = cs->aifunc;
	cs->aifunc = AIFunc_DoorMarker;
	return "AIFunc_DoorMarker";
}

/*
=============
AIFunc_BattleRoll()
=============
*/
char *AIFunc_BattleRoll( cast_state_t *cs ) {
	gclient_t *client = &level.clients[cs->entityNum];
	vec3_t dir;
	//
	// record the time
	cs->lastRollMove = level.time;
	client->ps.eFlags |= EF_NOSWINGANGLES;
	//
	if ( !client->ps.torsoTimer ) {
		if ( cs->battleRollTime < level.time ) {
			return AIFunc_Restore( cs );
		} else {
			// attack?
			if ( cs->bs->enemy >= 0 ) {
				AICast_ProcessAttack( cs );
			}
		}
	}
	if ( g_entities[cs->entityNum].health <= 0 ) {
		return AIFunc_DefaultStart( cs );
	}
	if ( ( cs->bs->enemy >= 0 ) && ( client->ps.torsoTimer < 400 ) && ( cs->takeCoverTime < level.time ) ) {
		// start turning towards our enemy
		AICast_AimAtEnemy( cs );
		if ( client->ps.torsoTimer ) {
			AICast_ProcessAttack( cs );
		}
	}
	//
	// all characters so far only move during the first second of animation
	if ( cs->thinkFuncChangeTime > level.time - 800 ) {
		// just move in the direction of our ideal_viewangles
		AngleVectors( cs->bs->ideal_viewangles, dir, NULL, NULL );
		trap_EA_Move( cs->entityNum, dir, 300 );
		// if we are crouching, move a little faster than normal
		if ( cs->bs->attackcrouch_time > trap_AAS_Time() ) {
			cs->speedScale = 1.5;
		}
	} else if ( cs->takeCoverTime > level.time ) {
		//
		// if we are taking Cover, use this position, if it's bad, we'll just look for a better spot once we're done here
		VectorCopy( cs->bs->origin, cs->takeCoverPos );
	}
	//
	return NULL;
}

/*
=============
AIFunc_BattleRollStart()
=============
*/
char *AIFunc_BattleRollStart( cast_state_t *cs, vec3_t vec ) {
	int duration;
	// TTimo unused
	//gclient_t *client = &level.clients[cs->entityNum];
	//
	// backup the current thinkfunc, so we can return to it when done
	cs->oldAifunc = cs->aifunc;
	//
	// face the direction of movement
	vectoangles( vec, cs->bs->ideal_viewangles );
	// do the roll
	duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_ROLL, qfalse, qfalse );
	//
	if ( duration < 0 ) { // it failed
		return NULL;
	}
	// add some duration to make sure it fully plays out
	duration += 100;
	g_entities[cs->entityNum].client->ps.legsTimer = duration;
	g_entities[cs->entityNum].client->ps.torsoTimer = duration;
	//
	cs->noAttackTime = level.time + duration - 200;
	// set the duration
	cs->battleRollTime = level.time + duration;
	// move into crouch position
	cs->bs->attackcrouch_time = trap_AAS_Time() + ( 0.001 * duration ) + 1.0;
	// record the time
	cs->lastRollMove = level.time;
	//
	// make sure we move this frame
	AIFunc_BattleRoll( cs );
	//
	cs->aifunc = AIFunc_BattleRoll;
	return "AIFunc_BattleRoll";
}

/*
=============
AIFunc_BattleDiveStart()
=============
*/
char *AIFunc_BattleDiveStart( cast_state_t *cs, vec3_t vec ) {
	int duration;
	// TTimo unused
	//gclient_t *client = &level.clients[cs->entityNum];
	//
	// backup the current thinkfunc, so we can return to it when done
	cs->oldAifunc = cs->aifunc;
	//
	// face the direction of movement
	vectoangles( vec, cs->bs->ideal_viewangles );
	// do the roll
	duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_DIVE, qfalse, qfalse );
	//
	if ( duration < 0 ) { // it failed
		return NULL;
	}
	//
	cs->noAttackTime = level.time + duration - 200;
	// set the duration
	cs->battleRollTime = level.time + duration;
	// move into crouch position
	cs->bs->attackcrouch_time = trap_AAS_Time() + ( 0.001 * duration ) + 1.0;
	// record the time
	cs->lastRollMove = level.time;
	//
	// make sure we move this frame
	AIFunc_BattleRoll( cs );
	//
	cs->aifunc = AIFunc_BattleRoll;
	return "AIFunc_BattleRoll";
}

/*
=============
AIFunc_FlipMove()
=============
*/
char *AIFunc_FlipMove( cast_state_t *cs ) {
	gclient_t *client = &level.clients[cs->entityNum];
	vec3_t dir;
	//
	if ( !client->ps.torsoTimer ) {
		cs->bs->attackcrouch_time = 0;
		return AIFunc_Restore( cs );
	}
	if ( g_entities[cs->entityNum].health <= 0 ) {
		return AIFunc_DefaultStart( cs );
	}
	//
	// just move in the direction of our ideal_viewangles
	AngleVectors( cs->bs->ideal_viewangles, dir, NULL, NULL );
	trap_EA_Move( cs->entityNum, dir, 400 );
	// if we are crouching, move a little faster than normal
	if ( cs->bs->attackcrouch_time > trap_AAS_Time() ) {
		cs->speedScale = 1.5;
	}
	//
	return NULL;
}

/*
=============
AIFunc_FlipMoveStart()
=============
*/
char *AIFunc_FlipMoveStart( cast_state_t *cs, vec3_t vec ) {
	int duration;
	// TTimo unused
	//gclient_t *client = &level.clients[cs->entityNum];
	//
	// backup the current thinkfunc, so we can return to it when done
	cs->oldAifunc = cs->aifunc;
	//
	// record the time
	cs->lastRollMove = level.time;
	// face the direction of movement
	vectoangles( vec, cs->bs->ideal_viewangles );
	cs->noAttackTime = level.time + 1200;
	// do the roll
	duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_ROLL, qfalse, qfalse );
	//
	if ( duration < 0 ) { // it failed
		return NULL;
	}
	// move into crouch position
	cs->bs->attackcrouch_time = trap_AAS_Time() + 0.8;
	//
	// make sure we move this frame
	AIFunc_FlipMove( cs );
	//
	cs->aifunc = AIFunc_FlipMove;
	return "AIFunc_FlipMove";
}

/*
=============
AIFunc_BattleHunt()
=============
*/
char *AIFunc_BattleHunt( cast_state_t *cs ) {
	const float chaseDist = 32;
	gentity_t   *followent, *ent;
	bot_state_t *bs;
	vec3_t destorg;
	qboolean moved = qfalse;
	// TTimo unused
	//gclient_t *client = &level.clients[cs->entityNum];
	char *rval;
	// TTimo might be used uninitialized
	float dist = 0;
	cast_state_t *ocs;
	int     *ammo, i;

	ent = &g_entities[cs->entityNum];

	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}
	//
	bs = cs->bs;
	//
	if ( bs->enemy < 0 ) {
		return AIFunc_IdleStart( cs );
	}
	//
	ocs = AICast_GetCastState( cs->bs->enemy );
	//
	if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) {
		cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
	}
	//
	followent = &g_entities[bs->enemy];
	//
	// if the entity is not ready yet
	if ( !followent->inuse ) {
		// if it's a connecting client, wait
		if ( !(   ( bs->enemy < MAX_CLIENTS )
				  && (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
						 || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking
			bs->enemy = -1;
		}

		return AIFunc_IdleStart( cs );
	}
	//
	// if we can see them, go back to an attack state
	AICast_ChooseWeapon( cs, qtrue );   // enable special weapons, if we cant get them, change back
	if (    AICast_EntityVisible( cs, bs->enemy, qtrue )    // take into account reaction time
			&&  AICast_CheckAttack( cs, bs->enemy, qfalse )
			&&  cs->obstructingTime < level.time ) {
		if ( AICast_StopAndAttack( cs ) ) {
			// TTimo gcc: suggest parentheses around assignment used as truth value
			if ( ( rval = AIFunc_BattleStart( cs ) ) ) {
				return rval;
			}
		} else {    // just attack them now and keep chasing
			AICast_ProcessAttack( cs );
		}
		AICast_ChooseWeapon( cs, qfalse );
	} else
	{
		int numEnemies, shouldAttack;

		AICast_ChooseWeapon( cs, qfalse );

		ammo = cs->bs->cur_ps.ammo;
		shouldAttack = qfalse;
		numEnemies = AICast_ScanForEnemies( cs, enemies );
		if ( numEnemies == -1 ) { // query mode
			return NULL;
		} else if ( numEnemies == -2 )     { // inspection may be required
			char *retval;
			if ( cs->aiState < AISTATE_COMBAT ) {
				// TTimo gcc: suggest parentheses around assignment used as truth value
				if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
					return retval;
				}
			}
		} else if ( numEnemies == -3 )     { // bullet impact
			if ( cs->aiState < AISTATE_COMBAT ) {
				return AIFunc_InspectBulletImpactStart( cs );
			}
		} else if ( numEnemies == -4 )     { // audible event
			if ( cs->aiState < AISTATE_COMBAT ) {
				return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
			}
		} else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) )     {
			if ( numEnemies > 0 ) {
				// default to the first known enemy, overwrite if we find a clearer shot
				cs->bs->enemy = enemies[0];
				//
				for ( i = 0; i < numEnemies; i++ ) {
					if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
						cs->bs->enemy = enemies[i];
						shouldAttack = qtrue;
						break;
					} else if ( cs->bs->enemy < 0 ) {
						cs->lastEnemy = enemies[i];
					}
				}
				// note: next frame we'll process this new enemy an begin an attack if necessary
			}
		}
		AICast_ChooseWeapon( cs, qfalse );
	}

	// have we spent enough time in combat mode?
	if ( cs->aiState == AISTATE_COMBAT ) {
		if ( cs->vislist[bs->enemy].visible_timestamp < level.time - COMBAT_TIMEOUT ) {
			AICast_StateChange( cs, AISTATE_ALERT );
		}
	}

	// while hunting, use crouch mode if possible
	if ( cs->attributes[ATTACK_CROUCH] >= 0.1 ) {
		cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
	}

	if ( cs->battleHuntPauseTime ) {
		if ( cs->battleHuntPauseTime < level.time ) {
			// pausetime has expired, so go into ambush mode
			if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], cs->takeCoverPos ) ) {
				// wait in ambush, for them to return
				VectorCopy( cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], cs->combatGoalOrigin );
				return AIFunc_BattleAmbushStart( cs );
			}
			// couldn't find a spot, so just stay here?
			VectorCopy( cs->bs->origin, cs->combatGoalOrigin );
			VectorCopy( cs->bs->origin, cs->takeCoverPos );
			return AIFunc_BattleAmbushStart( cs );
		} else {
			// stay here, looking around
			if ( cs->battleHuntViewTime < level.time ) {
				cs->battleHuntViewTime = level.time + 700 + rand() % 1000;
				// set a random viewangle
				cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( 45.0 + random() * 45.0 ) * ( 2 * ( rand() % 2 ) - 1 ) );
				cs->bs->ideal_viewangles[PITCH] = 0;
			}
		}
	} else {
		// cycle through markers
		VectorCopy( cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], destorg );
		if ( ( dist = Distance( destorg, cs->bs->origin ) ) < chaseDist ) {
			if ( cs->battleChaseMarker == ( cs->vislist[bs->enemy].chase_marker_count - 1 ) ) {
				cs->battleHuntPauseTime = level.time + 4000;
				cs->battleHuntViewTime = level.time + 1000;
			} else {
				cs->battleChaseMarker += cs->battleChaseMarkerDir;
				if ( cs->battleChaseMarker > cs->vislist[bs->enemy].chase_marker_count ) {
					cs->battleChaseMarkerDir *= -1;
					cs->battleChaseMarker = cs->vislist[bs->enemy].chase_marker_count - 1;
				} else if ( cs->battleChaseMarker < 0 )     {
					cs->battleChaseMarkerDir *= -1;
					cs->battleChaseMarker = 0;
				}
			}
		}
		//
		if ( cs->battleHuntPauseTime < level.time ) {
			// just go to them
			if ( !moved && cs->leaderNum < 0 ) {
				moveresult = AICast_MoveToPos( cs, destorg, bs->enemy );
				if ( moveresult && moveresult->failure ) {    // no path, so go back to idle behaviour
					// try to go to ambush mode
					cs->bs->enemy = -1;
					return AIFunc_DefaultStart( cs );
				} else {
					moved = qtrue;
				}
			}
		}
	}
	//
	// slow down real close to the goal, so we don't go passed it
	cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, chaseDist );

	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}

	return NULL;
}

/*
=============
AIFunc_BattleHuntStart()
=============
*/
char *AIFunc_BattleHuntStart( cast_state_t *cs ) {
	cs->combatGoalTime = 0;
	cs->battleChaseMarker = 0;
	cs->battleHuntPauseTime = 0;
	//
	cs->aifunc = AIFunc_BattleHunt;
	return "AIFunc_BattleHunt";
}

/*
=============
AIFunc_BattleAmbush()
=============
*/
char *AIFunc_BattleAmbush( cast_state_t *cs ) {
	bot_state_t *bs;
	vec3_t destorg, vec;
	float dist, moveDist;
	int enemies[MAX_CLIENTS], numEnemies, i;
	qboolean shouldAttack, idleYaw;
	aicast_predictmove_t move;
	int     *ammo;
	vec3_t dir;
	// TTimo unused
	//gclient_t	*client = &level.clients[cs->entityNum];
	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}

	// we need to move towards it
	bs = cs->bs;
	//
	// note: removing this will cause problems down below!
	if ( bs->enemy < 0 ) {
		return AIFunc_IdleStart( cs );
	}
	//
	// have we spent enough time in combat mode?
	if ( cs->aiState == AISTATE_COMBAT ) {
		if ( cs->vislist[bs->enemy].visible_timestamp < level.time - COMBAT_TIMEOUT ) {
			AICast_StateChange( cs, AISTATE_ALERT );
		}
	}
	// while hunting, use crouch mode if possible
	if ( cs->attributes[ATTACK_CROUCH] >= 0.1 ) {
		cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
	}
	//
	VectorCopy( cs->takeCoverPos, destorg );
	VectorSubtract( destorg, cs->bs->origin, vec );
	vec[2] *= 0.2;
	dist = VectorLength( vec );
	//
	// update the chase marker
	if ( cs->vislist[cs->bs->enemy].chase_marker_count > 0 ) {
		VectorCopy( cs->vislist[cs->bs->enemy].chase_marker[cs->vislist[cs->bs->enemy].chase_marker_count - 1], cs->combatGoalOrigin );
	}
	//
	// look for things we should attack
	// if we are out of ammo, we shouldn't bother trying to attack (and we should keep hiding)
	ammo = cs->bs->cur_ps.ammo;
	shouldAttack = qfalse;
	numEnemies = AICast_ScanForEnemies( cs, enemies );
	if ( numEnemies == -1 ) { // query mode
		return NULL;
	} else if ( numEnemies == -2 )     { // inspection may be required
		char *retval;
		// TTimo gcc: suggest parentheses around assignment used as truth value
		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
			return retval;
		}
	} else if ( numEnemies == -3 )     { // bullet impact
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectBulletImpactStart( cs );
		}
	} else if ( numEnemies == -4 )     { // audible event
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
		}
	} else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) )     {
		if ( numEnemies > 0 ) {
			// default to the first known enemy, overwrite if we find a clearer shot
			cs->bs->enemy = enemies[0];
			//
			for ( i = 0; i < numEnemies; i++ ) {
				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
					cs->bs->enemy = enemies[i];
					shouldAttack = qtrue;
					break;
				} else if ( cs->bs->enemy < 0 ) {
					cs->lastEnemy = enemies[i];
				}
			}
		}

	} else {    // keep hiding forever

		cs->takeCoverTime = level.time + 1000;

	}
	//
	memset( &move, 0, sizeof( move ) );
	//
	// are we close enough to the goal?
	if ( VectorLength( cs->takeCoverPos ) > 1 && dist > 8 && ( cs->obstructingTime < level.time ) && !shouldAttack ) {
		const float simTime = 0.8;
		float enemyDist;
		//
		// we haven't reached it yet, make sure we at least wait there for a few seconds after arriving
		cs->takeCoverTime = level.time + 2000 + rand() % 2000;
		//
		moveresult = AICast_MoveToPos( cs, destorg, -1 );
		if ( moveresult ) {
			//if the movement failed
			if ( moveresult->failure ) {
				//reset the avoid reach, otherwise bot is stuck in current area
				trap_BotResetAvoidReach( bs->ms );
				// couldn't get there, so stop trying to get there
				VectorClear( cs->takeCoverPos );
				dist = 0;
			}
			//
			if ( moveresult->blocked ) {
				// abort the TakeCover
				VectorClear( cs->takeCoverPos );
				dist = 0;
			}
		}
		//
		// NOTE: this is also used by hidepos prediction (below)
		// if we are going to bump into something soon, abort it
		AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, -1 );
		enemyDist = Distance( cs->bs->origin, g_entities[cs->bs->enemy].s.pos.trBase );
		VectorSubtract( move.endpos, cs->bs->origin, vec );
		moveDist = VectorNormalize( vec );
		//
		if (    ( move.numtouch && move.touchents[0] < aicast_maxclients )    // hit something
				// or moved closer to the enemy
				||  (   ( enemyDist < 128 )
						&&  ( ( enemyDist - 1 ) > ( Distance( move.endpos, g_entities[cs->bs->enemy].s.pos.trBase ) ) ) ) ) {
			// abort the manouver
			VectorClear( cs->takeCoverPos );
			dist = 0;
		}
		//
		// we should slow down on approaching the destination point
		else if ( dist < 64 ) {
			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 );
		}

		// don't actually hide, check if we are about to, so we can hide right here
		if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) ) {
			if ( move.numtouch || !AICast_VisibleFromPos( move.endpos, cs->entityNum, cs->combatGoalOrigin, cs->bs->enemy, qfalse ) ) {
				// abort the manouver, reached a good spot
				cs->aiFlags |= AIFL_MISCFLAG1;
				VectorCopy( cs->bs->origin, cs->takeCoverPos );
			}
		}

	} else {
		//
		// check for a movement we should be making
		if ( cs->obstructingTime > level.time ) {
			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
		}
		// if we have some enemies that we can attack immediately (without going anywhere to chase them)
		if ( shouldAttack ) {
			return AIFunc_BattleStart( cs );
		}
		// do we need to go to our leader?
		else if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
			// wait until we've been hiding for long enough
			if ( level.time > cs->takeCoverTime ) {
				return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
			}
		}
		// else, crouch while we hide
		if ( cs->attributes[ATTACK_CROUCH] > 0.1 || cs->crouchHideFlag ) {
			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
		}
	}
	//
	idleYaw = qtrue;
	// if we can see the place we are hiding from, look at it
	if ( AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, cs->combatGoalOrigin, cs->lastEnemy, qfalse ) ) {
		// face the position we are retreating from
		VectorSubtract( cs->combatGoalOrigin, cs->bs->origin, dir );
		dir[2] = 0;
		if ( VectorNormalize( dir ) > 4 ) {
			idleYaw = qfalse;
			vectoangles( dir, cs->bs->ideal_viewangles );
		}

	}
	//
	if ( idleYaw ) {  // look around randomly (but not straight into walls)

		if ( cs->nextIdleAngleChange < level.time ) {
			// wait a second before changing again
			if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) {

				// FIXME: This could be changed to use some AAS sampling, which would:
				//
				//	Given a src area, pick a random dest area which is visible from that area
				//	and return it's position, which we'd then use to set the next view angles
				//
				//	This would result in more efficient, more realistic behaviour, since they'd
				//	also use PITCH angles to look at areas above/below them

				cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 );

				if ( abs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) {
					cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500;
				} else { // do really fast
					cs->nextIdleAngleChange = level.time + 500;
				}

				// adjust with time
				cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
				/// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0);

				cs->bs->ideal_viewangles[PITCH] = 0;
			}
		} else if ( cs->idleYawChange )     {
			cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] );
			cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) );
		}

	}
	//
	if ( !cs->crouchHideFlag ) {  // no enemy, and no need to crouch, so stop crouching
		if ( cs->bs->attackcrouch_time > trap_AAS_Time() + 1 ) {
			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
		}
	}

	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}

	return NULL;
}

/*
=============
AIFunc_BattleAmbushStart()
=============
*/
char *AIFunc_BattleAmbushStart( cast_state_t *cs ) {
	if ( !AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum ) ) {
		// always run to the cover point
		cs->bs->attackcrouch_time = 0;
	} else if ( cs->bs->attackcrouch_time > trap_AAS_Time() + 1.0 ) {
		cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0;
	}

	//
	// start a crouch attack?
	if ( cs->attributes[ATTACK_CROUCH] > 0.1 && cs->bs->attackcrouch_time >= trap_AAS_Time() ) {
		// continue
		cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0;
	}
	// if we arent crouching, start crouching soon after we start retreating
	if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
		cs->aiFlags |= AIFL_ATTACK_CROUCH;
	} else {
		cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
	}

	// miscflag1 used to set predicted point as our goal, so we dont keep setting this over and over
	cs->aiFlags &= ~AIFL_MISCFLAG1;

	cs->aifunc = AIFunc_BattleAmbush;
	return "AIFunc_BattleAmbush";
}

/*
============
AIFunc_BattleChase()
============
*/
char *AIFunc_BattleChase( cast_state_t *cs ) {
	const float chaseDist = 32;
	gentity_t   *followent, *ent;
	bot_state_t *bs;
	vec3_t destorg;
	qboolean moved = qfalse;
	gclient_t *client = &level.clients[cs->entityNum];
	char *rval;
	float dist;
	cast_state_t *ocs;

	ent = &g_entities[cs->entityNum];

	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}

	bs = cs->bs;
	//
	if ( bs->enemy < 0 ) {
		return AIFunc_IdleStart( cs );
	}
	//
	// Retreat?
	if ( AICast_WantToRetreat( cs ) ) {
		if  ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) {
			// go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time
			cs->takeCoverTime = level.time + 2000 + rand() % 3000;
			return AIFunc_BattleTakeCoverStart( cs );
		}
	}
	//
	ocs = AICast_GetCastState( cs->bs->enemy );
	//
	if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) {
		cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
	}
	//
	followent = &g_entities[bs->enemy];
	//
	// if the entity is not ready yet
	if ( !followent->inuse ) {
		// if it's a connecting client, wait
		if ( !(   ( bs->enemy < MAX_CLIENTS )
				  && (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
						 || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking
			bs->enemy = -1;
		}

		return AIFunc_IdleStart( cs );
	}
	//
	// if we can see them, go back to an attack state
	AICast_ChooseWeapon( cs, qtrue );   // enable special weapons, if we cant get them, change back
	if (    AICast_EntityVisible( cs, bs->enemy, qtrue )    // take into account reaction time
			&&  AICast_CheckAttack( cs, bs->enemy, qfalse )
			&&  cs->obstructingTime < level.time ) {
		if ( AICast_StopAndAttack( cs ) ) {
			// TTimo gcc: suggest parentheses around assignment used as truth value
			if ( ( rval = AIFunc_BattleStart( cs ) ) ) {
				return rval;
			}
		} else {    // just attack them now and keep chasing
			AICast_ProcessAttack( cs );
		}
		AICast_ChooseWeapon( cs, qfalse );
	} else
	{
		AICast_ChooseWeapon( cs, qfalse );
		// not visible, go to their previously visible position
		/*
		if (!cs->vislist[bs->enemy].visible_timestamp || Distance( bs->origin, cs->vislist[bs->enemy].visible_pos ) < 16)
		{
			// we're done attacking, go back to default state, which in turn will recall previous state
			//
			return AIFunc_DefaultStart( cs );
		}
		*/
	}
	//
	// find the chase position
	if ( followent->client ) {
		// go to the last visible position
		VectorCopy( cs->vislist[bs->enemy].visible_pos, destorg );
		// if we have reached it, go into hunt mode
		if ( ( dist = Distance( destorg, cs->bs->origin ) ) < chaseDist ) {
			// if we haven't been hunted for a while, do so
			if ( ocs->lastBattleHunted < level.time - 5000 ) {
				ocs->lastBattleHunted = level.time;
				return AIFunc_BattleHuntStart( cs );
			}
			// otherwise, go into ambush mode
			if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) {
				VectorCopy( cs->vislist[cs->bs->enemy].real_visible_pos, cs->combatGoalOrigin );
				return AIFunc_BattleAmbushStart( cs );
			}
			// couldn't find a spot, so just stay here?
			VectorCopy( cs->bs->origin, cs->combatGoalOrigin );
			VectorCopy( cs->bs->origin, cs->takeCoverPos );
			return AIFunc_BattleAmbushStart( cs );
		}
	} else    // assume we know where other entities are
	{
		VectorCopy( followent->s.pos.trBase, destorg );
		dist = Distance( cs->bs->origin, destorg );
	}
	//
	// if the enemy is inside a CONTENTS_DONOTENTER brush, and we are close enough, stop chasing them
	if ( AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) && VectorDistance( cs->bs->origin, destorg ) < 384 ) {
		if ( trap_PointContents( destorg, cs->bs->enemy ) & ( CONTENTS_DONOTENTER | CONTENTS_DONOTENTER_LARGE ) ) {
			// just stay here, and hope they move out of the brush without finding a spot where they can hit us but we can't hit them
			return NULL;
		}
	}
	//
	// is there someone else we can go for instead?
	numEnemies = AICast_ScanForEnemies( cs, enemies );
	if ( numEnemies == -1 ) { // query mode
		return NULL;
	} else if ( numEnemies == -2 )     { // inspection may be required
		char *retval;
		// TTimo gcc: suggest parentheses around assignment used as truth value
		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
			return retval;
		}
	}
	AICast_ChooseWeapon( cs, qtrue );   // enable special weapons, if we cant get them, change back
	if ( numEnemies > 0 ) {
		int i;
		for ( i = 0; i < numEnemies; i++ ) {
			if ( enemies[i] != cs->bs->enemy && AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
				cs->bs->enemy = enemies[i];
				return AIFunc_BattleStart( cs );
			}
		}
	}
	AICast_ChooseWeapon( cs, qfalse );

	//
	// if we only recently saw them, face them
	//
	if ( cs->vislist[cs->bs->enemy].visible_timestamp > level.time - 3000 ) {
		AICast_AimAtEnemy( cs );    // be ready for an attack if they become visible again
		//if (cs->attributes[ATTACK_CROUCH] > 0.1) {	// crouching for combat
		//	cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0;
		//}
	}

	//
	// Lob a Grenade?
	// if we haven't thrown a grenade in a bit, go into "grenade flush mode"
	if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) &&
		 ( cs->castScriptStatus.castScriptEventIndex < 0 ) &&
		 ( cs->startGrenadeFlushTime < level.time - 3000 ) &&
		 ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) &&
		 ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) &&
		 ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) &&
		 ( ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) &&
		 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) > 100 ) &&
		 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) < 1400 ) ) {
		// try and flush them out with a grenade
		//G_Printf("pineapple?\n");
		return AIFunc_GrenadeFlushStart( cs );
	} else if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) &&
				( cs->castScriptStatus.castScriptEventIndex < 0 ) &&
				( cs->startGrenadeFlushTime < level.time - 2000 ) &&
				( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_PINEAPPLE ) ) &&
				( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) &&
				( cs->weaponFireTimes[WP_GRENADE_PINEAPPLE] < level.time - (int)( aicast_skillscale * 3000 ) ) &&
				( ( cs->bs->weaponnum == WP_GRENADE_PINEAPPLE ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) &&
				( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) > 100 ) &&
				( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) < 1400 ) ) {
		// try and flush them out with a grenade
		//G_Printf("pineapple?\n");
		return AIFunc_GrenadeFlushStart( cs );
	}
	//
	// Flaming Zombie? Shoot flames while running
	if ( ( cs->aiCharacter == AICHAR_ZOMBIE ) &&
		 ( IS_FLAMING_ZOMBIE( ent->s ) ) &&
		 ( fabs( cs->bs->ideal_viewangles[YAW] - cs->bs->viewangles[YAW] ) < 5 ) ) {
		if ( fabs( sin( ( level.time + cs->entityNum * 314 ) / 1000 ) * cos( ( level.time + cs->entityNum * 267 ) / 979 ) ) < 0.5 ) {
			ent->s.time = level.time + 800;
		}
	}
	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}

	if ( dist < chaseDist ) {
		return NULL;
	}

	//
	// go to them
	//
	// ...........................................................
	// Do the movement..
	//
	// move straight to them if we can
	if (    !moved && cs->leaderNum < 0 &&
			( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) &&
			AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) ) {
		aicast_predictmove_t move;
		vec3_t dir;
		bot_input_t bi;
		usercmd_t ucmd;
		trace_t tr;

		// trace will eliminate most unsuccessful paths
		trap_Trace( &tr, cs->bs->origin, g_entities[cs->entityNum].r.mins, g_entities[cs->entityNum].r.maxs, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
		if ( tr.entityNum == followent->s.number ) {
			// try walking straight to them
			VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
			VectorNormalize( dir );
			if ( !ent->waterlevel ) {
				dir[2] = 0;
			}
			trap_EA_Move( cs->entityNum, dir, 400 );
			trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
			AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
			AICast_PredictMovement( cs, 5, 2.0, &move, &ucmd, bs->enemy );

			if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
				vectoangles( dir, cs->bs->ideal_viewangles );
				cs->bs->ideal_viewangles[2] *= 0.5;
				moved = qtrue;
			}
		}
	}
	//
	// if they are visible, but not attackable, look for a spot where we can attack them, and head
	// for there. This should prevent AI's getting stuck in a bunch.
	if ( !moved && cs->bs->weaponnum >= WP_LUGER && cs->bs->weaponnum <= WP_SPEARGUN_CO2 && cs->attributes[TACTICAL] >= 0.1 ) {
		//
		// check for another movement we should be making
		if ( cs->obstructingTime > level.time ) {
			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
			moved = qtrue;
		}
		//
		if ( cs->leaderNum >= 0 ) {
			if ( cs->combatGoalTime < level.time ) {
				if ( cs->attackSpotTime < level.time ) {
					cs->attackSpotTime = level.time + 500 + rand() % 500;
					if ( trap_AAS_FindAttackSpotWithinRange( cs->entityNum, cs->leaderNum, cs->bs->enemy, MAX_LEADER_DIST, AICAST_TFL_DEFAULT, cs->combatGoalOrigin ) ) {
						cs->combatGoalTime = level.time + 2000;
					}
				}
			}
			if ( cs->combatGoalTime > level.time ) {
				if ( Distance( cs->combatGoalOrigin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
					// go find a new combatSpot
					cs->combatGoalTime = 0;
				} else {
					// go to the combat spot
					moveresult = AICast_MoveToPos( cs, cs->combatGoalOrigin, -1 );
					if ( moveresult && moveresult->failure ) {    // no path, so go back to idle behaviour
						cs->combatGoalTime = 0;
					} else {
						moved = qtrue;
						if ( Distance( cs->bs->origin, cs->combatGoalOrigin ) < 32 ) {
							cs->combatGoalTime = 0;
						}
					}
				}
			} else {
				// we can't find a way to get to our enemy, so go back to our leader if outside range
				// do we need to go to our leader?
				if ( Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
					return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
				}
			}
		} else {
			if ( cs->combatGoalTime < level.time ) {
				if ( cs->attackSpotTime < level.time ) {
					cs->attackSpotTime = level.time + 500 + rand() % 500;
					if ( trap_AAS_FindAttackSpotWithinRange( cs->entityNum, cs->entityNum, cs->bs->enemy, 512, AICAST_TFL_DEFAULT, cs->combatGoalOrigin ) ) {
						cs->combatGoalTime = level.time + 2000;
					}
				}
			}
			if ( cs->combatGoalTime > level.time ) {
				// go to the combat spot
				moveresult = AICast_MoveToPos( cs, cs->combatGoalOrigin, -1 );
				if ( moveresult && moveresult->failure ) {    // no path, so go back to idle behaviour
					cs->combatGoalTime = 0;
				} else {
					moved = qtrue;
					if ( Distance( cs->bs->origin, cs->combatGoalOrigin ) < 32 ) {
						cs->combatGoalTime = 0;
					}
				}
			}
		}
	}
	// just go to them
	if ( !moved && cs->leaderNum < 0 ) {
		moveresult = AICast_MoveToPos( cs, destorg, bs->enemy );
		if ( moveresult && moveresult->failure ) {    // no path, so try and hude from them
			// pausetime has expired, so go into ambush mode
			if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) {
				// wait in ambush, for them to return
				VectorCopy( cs->bs->origin, cs->combatGoalOrigin );
				return AIFunc_BattleAmbushStart( cs );
			}
			// couldn't find a spot, so just stay here?
			VectorCopy( cs->bs->origin, cs->combatGoalOrigin );
			VectorCopy( cs->bs->origin, cs->takeCoverPos );
			return AIFunc_BattleAmbushStart( cs );
		} else {
			moved = qtrue;
		}
	}
	//
	// slow down real close to the goal, so we don't go passed it
	cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, chaseDist );
	//
	// ...........................................................
	// speed up over some time
	#define BATTLE_CHASE_ACCEL_TIME     300
	if ( ( cs->attributes[RUNNING_SPEED] > 170 ) && ( cs->bs->weaponnum != WP_GAUNTLET ) && ( level.time < ( cs->startBattleChaseTime + BATTLE_CHASE_ACCEL_TIME ) ) ) {
		float ideal;

		ideal = 0.5 + 0.5 * ( 1.0 - ( (float)( ( cs->startBattleChaseTime + BATTLE_CHASE_ACCEL_TIME ) - level.time ) / BATTLE_CHASE_ACCEL_TIME ) );
		if ( ideal < cs->speedScale ) {
			cs->speedScale = ideal;
		}
	}
	//
	// if we are going to reach them soon, predict the attack
	{
		float simTime = 1.0;
		aicast_predictmove_t move;
		float moveDist;
		vec3_t vec;
		//
		if ( cs->bs->weaponnum == WP_GAUNTLET ) {
			simTime = 0.5;
		}
		//
		AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, cs->bs->enemy );
		VectorSubtract( move.endpos, cs->bs->origin, vec );
		moveDist = VectorNormalize( vec );
		//
		if ( cs->bs->weaponnum == WP_GAUNTLET ) {
			if ( move.stopevent == PREDICTSTOP_HITENT ) {
				AICast_AimAtEnemy( cs );
				trap_EA_Attack( bs->client );
				bs->flags |= BFL_ATTACKED;
			}
		}
		//
		// do we went to play a diving animation into a cover position?
		else if (   ( cs->attributes[TACTICAL] > 0.85 && cs->aiFlags & AIFL_ROLL_ANIM && !client->ps.torsoTimer && !client->ps.legsTimer && cs->lastRollMove < level.time - 800 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.8 && move.groundEntityNum == ENTITYNUM_WORLD ) &&
					( AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, move.endpos, cs->bs->attackcrouch_time > trap_AAS_Time(), qfalse ) ) ) {
			return AIFunc_BattleRollStart( cs, vec );
		}
		//
		else if ( cs->aiFlags & AIFL_FLIP_ANIM && cs->lastRollMove < level.time - 800 && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.9 && move.groundEntityNum == ENTITYNUM_WORLD && cs->bs->attackcrouch_time < trap_AAS_Time() ) {
			int destarea, simarea, starttravel, simtravel;
			// if we'll be closer after the move, proceed
			destarea = BotPointAreaNum( destorg );
			simarea = BotPointAreaNum( move.endpos );
			starttravel = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, destarea, cs->travelflags );
			simtravel = trap_AAS_AreaTravelTimeToGoalArea( simarea, move.endpos, destarea, cs->travelflags );
			if ( simtravel < starttravel ) {
				return AIFunc_FlipMoveStart( cs, vec );
			}
		}
		// slow down? so we don't go too far from behind the obstruction which is protecting us
		else if ( ( cs->obstructingTime < level.time ) && ( cs->attributes[TACTICAL] > 0.1 ) &&
				  ( AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->enemy, move.endpos, cs->entityNum, qfalse ) ) ) {
			// start a crouch attack?
			//if (cs->attributes[ATTACK_CROUCH] > 0.1) {
			//	cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0;
			//else
			cs->bs->attackcrouch_time = 0;
			if ( cs->bs->cur_ps.viewheight > cs->bs->cur_ps.crouchViewHeight && cs->attributes[RUNNING_SPEED] * cs->speedScale > 120 ) {
				cs->speedScale = 120.0 * cs->attributes[RUNNING_SPEED];
			}
			// also face them, ready for the attack
			AICast_AimAtEnemy( cs );
			/* disabled, causes them to use up ammo in the clip before they get visible
			if ((cs->castScriptStatus.scriptNoAttackTime < level.time) && (cs->noAttackTime < level.time)) {
				// if we are using a bullet weapon, start firing now
				switch (cs->bs->weaponnum) {
				case WP_MP40:
				case WP_VENOM:
				case WP_THOMPSON:
				case WP_STEN:	//----(SA)	added
					trap_EA_Attack(cs->entityNum);
				}
			}
			*/
		}
	}

	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}

	return NULL;
}

/*
============
AIFunc_BattleChaseStart()
============
*/
char *AIFunc_BattleChaseStart( cast_state_t *cs ) {
	cs->startBattleChaseTime = level.time;
	cs->combatGoalTime = 0;
	cs->battleChaseMarker = -99;
	cs->battleChaseMarkerDir = 1;
	// don't wait too long before taking cover, if we just aborted one
	if ( cs->takeCoverTime > level.time ) {
		cs->takeCoverTime = level.time + 1500 + rand() % 500;
	}
	//
	// start a crouch attack?
	if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
		cs->aiFlags |= AIFL_ATTACK_CROUCH;
	} else {
		cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
	}
	//
	cs->aifunc = AIFunc_BattleChase;
	return "AIFunc_BattleChase";
}

/*
============
AIFunc_AvoidDanger()
============
*/
char *AIFunc_AvoidDanger( cast_state_t *cs ) {
	bot_state_t *bs;
	vec3_t destorg, vec;
	float dist;
	int enemies[MAX_CLIENTS], numEnemies, i;
	qboolean shouldAttack;
	gentity_t *ent;
	trace_t tr;
	vec3_t end;
	gentity_t *danger;

	// we need to move towards it
	bs = cs->bs;
	ent = g_entities + cs->entityNum;
	//
	// TODO: if we are on fire, play the correct torso animation
	if ( ent->s.onFireEnd > level.time ) {
		// set the animation, and a short timer, but long enough to last until the next frame
		//if (g_cheats.integer) G_Printf( "TODO: torso onfire animation\n" );
	}
	//
	// is the danger gone?
	if ( cs->dangerEntityValidTime < level.time ) {
		return AIFunc_DefaultStart( cs );
	}
	//
	// special case: if it's a grenade, and it's going to land near us with some time left before it
	// explodes, try and kick it back
	//
	danger = &g_entities[cs->dangerEntity];
	if ( ent->s.onFireEnd < level.time ) {
		if ( ( danger->s.weapon == WP_GRENADE_LAUNCHER || danger->s.weapon == WP_GRENADE_PINEAPPLE ) &&
			 ( danger->nextthink - level.time > 1500 ) &&
			 ( level.lastGrenadeKick < level.time - 3000 ) &&
			 ( cs->aiFlags & AIFL_CATCH_GRENADE ) &&
			 !( danger->flags & FL_AI_GRENADE_KICK ) ) {
			// if it was thrown by a friend of ours, leave it alone
			if ( !AICast_SameTeam( cs, danger->r.ownerNum ) ) {
				if ( G_PredictMissile( danger, danger->nextthink - level.time, cs->takeCoverPos, qfalse ) ) {
					// make sure it's a valid position, and drop it down to the ground
					cs->takeCoverPos[2] += -ent->r.mins[2] + 12;
					VectorCopy( cs->takeCoverPos, end );
					end[2] -= 90;
					trap_Trace( &tr, cs->takeCoverPos, ent->r.mins, ent->r.maxs, end, cs->entityNum, MASK_SOLID );
					VectorCopy( tr.endpos, cs->takeCoverPos );
					if ( !tr.startsolid && ( tr.fraction < 1.0 ) &&
						 VectorDistance( cs->bs->origin, cs->takeCoverPos ) < cs->attributes[RUNNING_SPEED] * 0.0004 * ( danger->nextthink - level.time - 2000 ) ) {

						// check for a clear path to the grenade
						trap_Trace( &tr, cs->bs->origin, ent->r.mins, ent->r.maxs, cs->takeCoverPos, cs->entityNum, MASK_SOLID );

						if ( VectorDistance( tr.endpos, cs->takeCoverPos ) < 8 ) {
							danger->flags |= FL_AI_GRENADE_KICK;
							ent->flags |= FL_AI_GRENADE_KICK;
							level.lastGrenadeKick = level.time;
							return AIFunc_GrenadeKickStart( cs );   // we should decide our course of action within this start function (dive or return grenade)
						}
					}
				}
			}
			// if it's really close to us, and we're heading for it, may as well pick it up
			if ( VectorLength( danger->s.pos.trDelta ) < 10 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 128 &&
				 ( level.lastGrenadeKick < level.time - 3000 ) &&
				 ( cs->aiFlags & AIFL_CATCH_GRENADE ) ) {
				vec3_t vec;
				VectorSubtract( danger->r.currentOrigin, cs->bs->origin, vec );
				if ( DotProduct( vec, cs->bs->velocity ) > 0 ) {
					danger->flags |= FL_AI_GRENADE_KICK;
					ent->flags |= FL_AI_GRENADE_KICK;
					level.lastGrenadeKick = level.time;
					return AIFunc_GrenadeKickStart( cs );   // we should decide our course of action within this start function (dive or return grenade)
				}
			}
		}
	}
	//
	if ( g_entities[cs->dangerEntity].inuse ) {
		// is our current destination still safe?
		if ( Distance( cs->dangerEntityPos, cs->takeCoverPos ) < cs->dangerDist &&
			 AICast_VisibleFromPos( cs->dangerEntityPos, cs->dangerEntity, cs->takeCoverPos, cs->entityNum, qfalse ) ) {
			//G_Printf("current coverPos is dangerous, looking for a better place..\n" );
			if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
				// just run away from it ???
			}
		}
	} else {
		// the entity isn't here anymore, so stop hiding
		cs->dangerEntityValidTime = -1;
		return AIFunc_DefaultStart( cs );
	}
	//
	VectorCopy( cs->takeCoverPos, destorg );
	VectorSubtract( destorg, cs->bs->origin, vec );
	vec[2] *= 0.2;
	dist = VectorLength( vec );
	//
	shouldAttack = qfalse;
	if ( ent->s.onFireEnd < level.time ) {
		// look for things we should attack
		numEnemies = AICast_ScanForEnemies( cs, enemies );
		if ( numEnemies > 0 ) {
			// default to the first known enemy, overwrite if we find a clearer shot
			cs->bs->enemy = enemies[0];
			//
			for ( i = 0; i < numEnemies; i++ ) {
				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
					cs->bs->enemy = enemies[i];
					shouldAttack = qtrue;
					break;
				} else if ( cs->bs->enemy < 0 ) {
					cs->lastEnemy = enemies[i];
				}
			}
		}
	}
	//
	// if we are now safe from the danger, stop running away
	if ( cs->dangerEntity >= MAX_CLIENTS && Distance( cs->dangerEntityPos, cs->bs->origin ) > cs->dangerDist * 1.5 ) {
		// don't move, wait for danger to pass
	} else
	// are we close enough to the goal?
	if ( dist > 8 ) {
		moveresult = AICast_MoveToPos( cs, destorg, -1 );
		if ( moveresult ) {
			//if the movement failed
			if ( moveresult->failure || moveresult->blocked ) {
				//reset the avoid reach, otherwise bot is stuck in current area
				trap_BotResetAvoidReach( bs->ms );
				if ( g_entities[cs->dangerEntity].inuse ) {
					// find a better spot?
					AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos );
				} else {
					VectorCopy( cs->bs->origin, cs->takeCoverPos );
				}
			}
		}
		if ( ent->s.onFireEnd < level.time ) {
			// slow down real close to the goal, so we don't go passed it
			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 );
		}
		//
		// pretend we can still see them while we run to our hide pos, this way they are less likely
		// to forget about their enemy once they get there
		if ( ent->s.onFireEnd < level.time && cs->bs->enemy >= 0 && cs->vislist[cs->bs->enemy].real_visible_timestamp && ( cs->vislist[cs->bs->enemy].real_visible_timestamp > level.time - 10000 ) ) {
			AICast_UpdateVisibility( &g_entities[cs->entityNum], &g_entities[cs->bs->enemy], qfalse, cs->vislist[cs->bs->enemy].real_visible_timestamp == cs->vislist[cs->bs->enemy].lastcheck_timestamp );
		}

	} else {
		// set our origin as the hidepos, that way if we are still in danger, we should find a better spot
		VectorCopy( cs->bs->origin, cs->takeCoverPos );
		// check for a movement we should be making
		if ( cs->obstructingTime > level.time ) {
			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
		}

		// if we are on fire, never stop
		if ( ent->s.onFireEnd > level.time ) {
			VectorCopy( cs->bs->origin, cs->dangerEntityPos );
			cs->dangerEntityValidTime = level.time + 10000;
		}

	}
	//
	// if we should be attacking something on our way
	if ( shouldAttack ) {
		//attack the enemy if possible
		AICast_ProcessAttack( cs );
	} else { //if (dist < 48) {
		// if we've recently been in a fight, look towards the enemy
		if ( cs->lastEnemy >= 0 ) {
			// if we only just recently saw them, face that direction
			if ( cs->vislist[cs->lastEnemy].visible_timestamp > ( level.time - 20000 ) ) {
				vec3_t dir;
				//
				VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir );
				VectorNormalize( dir );
				vectoangles( dir, cs->bs->ideal_viewangles );
			}
		}
	}

	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
		trap_EA_Reload( cs->entityNum );
	}

	return NULL;
}

/*
============
AIFunc_AvoidDangerStart()
============
*/
char *AIFunc_AvoidDangerStart( cast_state_t *cs ) {
	//
	//if (!AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum )) {
	// always run to the cover point
	cs->bs->attackcrouch_time = 0;
	//}
	// make sure we move if we are allowed (scripting will overwrite this if necessary)
	cs->castScriptStatus.scriptNoMoveTime = 0;
	// resume following once danger has gone
	cs->castScriptStatus.scriptGotoId = -1;
	//
	cs->aifunc = AIFunc_AvoidDanger;
	return "AIFunc_AvoidDanger";
}

/*
============
AIFunc_BattleTakeCover()
============
*/
char *AIFunc_BattleTakeCover( cast_state_t *cs ) {
	bot_state_t *bs;
	vec3_t destorg, vec;
	float dist, moveDist;
	int enemies[MAX_CLIENTS], numEnemies, i;
	qboolean shouldAttack;
	aicast_predictmove_t move;
	int     *ammo;
	gclient_t   *client = &level.clients[cs->entityNum];
	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}

	// we need to move towards it
	bs = cs->bs;
	//
	// note: removing this will cause problems down below!
	if ( bs->enemy < 0 ) {
		return AIFunc_IdleStart( cs );
	}
	//
	VectorCopy( cs->takeCoverPos, destorg );
	VectorSubtract( destorg, cs->bs->origin, vec );
	vec[2] *= 0.2;
	dist = VectorLength( vec );
	//
	// look for things we should attack
	// if we are out of ammo, we shouldn't bother trying to attack (and we should keep hiding)
	ammo = cs->bs->cur_ps.ammo;
	shouldAttack = qfalse;
	numEnemies = AICast_ScanForEnemies( cs, enemies );
	if ( numEnemies == -1 ) { // query mode
		return NULL;
	} else if ( numEnemies == -2 )     { // inspection may be required
		char *retval;
		// TTimo gcc: suggest parentheses around assignment used as truth value
		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
			return retval;
		}
	} else if ( numEnemies == -3 )     { // bullet impact
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectBulletImpactStart( cs );
		}
	} else if ( numEnemies == -4 )     { // audible event
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
		}
	} else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) )     {
		if ( numEnemies > 0 ) {
			// default to the first known enemy, overwrite if we find a clearer shot
			cs->bs->enemy = enemies[0];
			//
			for ( i = 0; i < numEnemies; i++ ) {
				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
					cs->bs->enemy = enemies[i];
					shouldAttack = qtrue;
					break;
				} else if ( cs->bs->enemy < 0 ) {
					cs->lastEnemy = enemies[i];
				}
			}
		}
	}
	//
	if ( !shouldAttack ) {
		// if the enemy can see our hide position, find a better spot
		if ( AICast_VisibleFromPos( g_entities[bs->enemy].client->ps.origin, bs->enemy, cs->takeCoverPos, bs->entitynum, qfalse ) ) {
			if ( !AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos ) ) {
				// shit!! umm.. try and fire?
				return AIFunc_BattleStart( cs );
			} else {    // recalc distance
				VectorCopy( cs->takeCoverPos, destorg );
				VectorSubtract( destorg, cs->bs->origin, vec );
				vec[2] *= 0.2;
				dist = VectorLength( vec );
			}
		} else if ( dist < 8 )     {
			// if they can see us, find a better spot
			if ( AICast_EntityVisible( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qtrue ) || AICast_CheckAttack( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qfalse ) ) {
				if ( !AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos ) ) {
					// shit!! umm.. try and fire?
					return AIFunc_BattleStart( cs );
				} else {    // recalc distance
					VectorCopy( cs->takeCoverPos, destorg );
					VectorSubtract( destorg, cs->bs->origin, vec );
					vec[2] *= 0.2;
					dist = VectorLength( vec );
				}
			}
		}
		//cs->takeCoverTime = level.time + 1000;
	}
	//
	// pretend we can still see them while we run to our hide pos, this way they are less likely
	// to forget about their enemy once they get there
// DISABLED: doesn't work well with new AI system
	//if (cs->bs->enemy >= 0 && cs->vislist[cs->bs->enemy].real_visible_timestamp && (cs->vislist[cs->bs->enemy].real_visible_timestamp > level.time - 2000)) {
	//	AICast_UpdateVisibility( &g_entities[cs->entityNum], &g_entities[cs->bs->enemy], qfalse, cs->vislist[cs->bs->enemy].real_visible_timestamp == cs->vislist[cs->bs->enemy].lastcheck_timestamp );
	//}
	//
	memset( &move, 0, sizeof( move ) );
	//
	// are we close enough to the goal?
	if ( VectorLength( cs->takeCoverPos ) > 1 && dist > 8 ) {
		const float simTime = 0.8;
		float enemyDist;
		//
		// we haven't reached it yet, make sure we at least wait there for a few seconds after arriving
		cs->takeCoverTime = level.time + 2000 + rand() % 2000;
		//
		moveresult = AICast_MoveToPos( cs, destorg, -1 );
		if ( moveresult ) {
			//if the movement failed
			if ( moveresult->failure ) {
				//reset the avoid reach, otherwise bot is stuck in current area
				trap_BotResetAvoidReach( bs->ms );
				// couldn't get there, so stop trying to get there
				VectorClear( cs->takeCoverPos );
				dist = 0;
			}
			//
			if ( moveresult->blocked ) {
				// abort the TakeCover
				VectorClear( cs->takeCoverPos );
				dist = 0;
			}
		}
		//
		// if we are going to bump into something soon, abort it
		AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, -1 );
		enemyDist = Distance( cs->bs->origin, g_entities[cs->bs->enemy].s.pos.trBase );
		VectorSubtract( move.endpos, cs->bs->origin, vec );
		moveDist = VectorNormalize( vec );
		//
		if (    ( move.numtouch && move.touchents[0] < aicast_maxclients )    // hit something
				// or moved closer to the enemy
				||  (   ( enemyDist < 128 )
						&&  ( ( enemyDist - 1 ) > ( Distance( move.endpos, g_entities[cs->bs->enemy].s.pos.trBase ) ) ) ) ) {
			// abort the manouver
			VectorClear( cs->takeCoverPos );
			dist = 0;
		}
		//
		// do we went to play a rolling animation into a cover position?
		else if (   ( cs->aiFlags & AIFL_DIVE_ANIM && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && cs->lastRollMove < level.time - 800 && move.numtouch == 0 && moveDist > 64 && move.groundEntityNum == ENTITYNUM_WORLD ) &&
					( shouldAttack && !AICast_VisibleFromPos( g_entities[cs->bs->enemy].s.pos.trBase, cs->bs->enemy, move.endpos, cs->entityNum, qfalse ) ) ) {
			VectorClear( cs->takeCoverPos );    // stay there when done rolling
			return AIFunc_BattleDiveStart( cs, vec );
		}
		//
		// we should slow down on approaching the destination point
		else if ( dist < 64 ) {
			cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 );
		}
		//
		// if they cant see us, then stay here
		if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) && !AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].real_visible_pos, cs->bs->enemy, move.endpos, cs->entityNum, qfalse )
			 && !AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].real_visible_pos, cs->bs->enemy, cs->bs->origin, cs->entityNum, qfalse ) ) {
			VectorCopy( move.endpos, cs->takeCoverPos );
			dist = 0;
			cs->aiFlags |= AIFL_MISCFLAG1;  // dont do this again
		}
		//
		if ( cs->aiFlags & AIFL_FLIP_ANIM && cs->lastRollMove < level.time - 800 && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.9 && move.groundEntityNum == ENTITYNUM_WORLD && cs->bs->attackcrouch_time < trap_AAS_Time() ) {
			int destarea, simarea, starttravel, simtravel;
			// if we'll be closer after the move, proceed
			destarea = BotPointAreaNum( destorg );
			simarea = BotPointAreaNum( move.endpos );
			starttravel = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, destarea, cs->travelflags );
			simtravel = trap_AAS_AreaTravelTimeToGoalArea( simarea, move.endpos, destarea, cs->travelflags );
			if ( simtravel < starttravel ) {
				return AIFunc_FlipMoveStart( cs, vec );
			}
		}
		// set crouching status
		//if (dist && (cs->thinkFuncChangeTime < level.time - 2000) && (cs->crouchHideFlag || cs->aiFlags & AIFL_ATTACK_CROUCH)) {
		if ( cs->crouchHideFlag || cs->aiFlags & AIFL_ATTACK_CROUCH ) {
			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
		}

	} else {
		//
		// have we been Taking Cover for enough time?
		if ( level.time > cs->takeCoverTime ) {
			return AIFunc_DefaultStart( cs );
		}
		//
		// check for a movement we should be making
		if ( cs->obstructingTime > level.time ) {
			VectorClear( cs->takeCoverPos );
			AICast_MoveToPos( cs, cs->obstructingPos, -1 );
		}
		// if we have some enemies that we can attack immediately (without going anywhere to chase them)
		if ( shouldAttack ) {
			return AIFunc_BattleStart( cs );
		}
		// if we have some enemies in sight, but they can't attack us, flee if possible, otherwise if we are not afraid, go attack them
		else if ( numEnemies ) {

			// we can't hit them and they cant hit us, so dont bother doing anything

			//if (!AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos )) {
			//if (!AICast_WantsToTakeCover(cs, qfalse))
			//return AIFunc_BattleStart( cs );
			//}
		}
		// do we need to go to our leader?
		else if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
			// wait until we've been hiding for long enough
			if ( level.time > cs->takeCoverTime ) {
				return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
			}
		}

		// else, crouch while we hide
		if ( cs->attributes[ATTACK_CROUCH] > 0.1 || cs->crouchHideFlag ) {
			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
		}
	}
	//
	// if we should be attacking something on our way
	if ( shouldAttack ) {
		vec3_t vec, dir;
		float dist;
		//
		// if they are close, and we're heading for them, we should abort this manouver
		VectorSubtract( g_entities[bs->enemy].client->ps.origin, bs->origin, vec );
		if ( ( dist = VectorNormalize( vec ) ) < 256 ) {
			if ( VectorNormalize2( bs->velocity, dir ) > 20 ) {   // we are moving
				if ( DotProduct( dir, vec ) > 0 ) {
					// abort
					return AIFunc_BattleStart( cs );
				}
			}
		}
		//
		// if the enemy can see our hide position, abort the manouver
		if ( ( cs->thinkFuncChangeTime < level.time - 1000 ) && ( AICast_VisibleFromPos( g_entities[bs->enemy].client->ps.origin, bs->enemy, cs->takeCoverPos, bs->entitynum, qfalse ) ) ) {
			// abort
			return AIFunc_BattleStart( cs );
		}
		//
		// if we are tactical and can crouch, do so
		if ( !move.numtouch && ( dist > 128 ) && cs->attributes[TACTICAL] > 0.4 && cs->attributes[ATTACK_CROUCH] > 0.1 &&
			 ( cs->bs->attackcrouch_time >= trap_AAS_Time() ) ) {
			cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
		}
		//
		//attack the enemy if possible
		AICast_ProcessAttack( cs );
		//
	} else /*if (dist < 48)*/ {
		// if we've recently been in a fight, look towards the enemy
		if ( cs->bs->enemy >= 0 ) {
			AICast_AimAtEnemy( cs );
		} else if ( cs->lastEnemy >= 0 )     {
			// if we are not moving, face them
			if ( VectorLength( cs->bs->cur_ps.velocity ) < 50 ) {
				vec3_t dir;
				//
				VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir );
				VectorNormalize( dir );
				vectoangles( dir, cs->bs->ideal_viewangles );
			}
		} else if ( !cs->crouchHideFlag )     { // no enemy, and no need to crouch, so stop crouching
			//if (cs->bs->attackcrouch_time > trap_AAS_Time() + 1) {
			//	cs->bs->attackcrouch_time = trap_AAS_Time() + 1;
			//}
		}
		// reload?
		if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) {
			trap_EA_Reload( cs->entityNum );
		}
	}

	return NULL;
}

/*
============
AIFunc_BattleTakeCoverStart()
============
*/
char *AIFunc_BattleTakeCoverStart( cast_state_t *cs ) {
// debugging
#ifdef DEBUG
	if ( cs->attributes[AGGRESSION] >= 1.0 ) {
		AICast_Printf( 0, "AI taking cover with full aggression!\n" );
	}
#endif

	if ( !AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum ) ) {
		// always run to the cover point
		cs->bs->attackcrouch_time = 0;
		cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
	} else {
		// if we arent crouching, start crouching soon after we start retreating
		if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) {
			cs->aiFlags |= AIFL_ATTACK_CROUCH;
		} else {
			cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
		}
	}

	// miscflag1 used to set predicted point as our goal, so we dont keep setting this over and over
	cs->aiFlags &= ~AIFL_MISCFLAG1;

	cs->aifunc = AIFunc_BattleTakeCover;
	return "AIFunc_BattleTakeCover";
}

/*
============
AIFunc_GrenadeFlush()
============
*/
char *AIFunc_GrenadeFlush( cast_state_t *cs ) {
	vec3_t dir;
	gentity_t   *followent, *grenade, *ent;
	bot_state_t *bs;
	vec3_t destorg, endPos;
	qboolean moved = qfalse;
	int hitclient;
	qboolean attacked = qfalse;
	float dist, oldyaw;
	int grenadeType;
	int     *ammo;

	bs = cs->bs;
	ent = &g_entities[cs->entityNum];
	//
	// if we are throwing the grenade, keep holding down fire

	// (SA) probably read the fweapon from t
	if ( cs->grenadeFlushFiring ) {
		if ( cs->weaponFireTimes[cs->grenadeFlushFiring] < cs->thinkFuncChangeTime ) {
			// have we switched weapons?
			if ( cs->bs->weaponnum != cs->grenadeFlushFiring ) {
				// damn
				hitclient = -1;
			} else {
				// keep checking it's ok
				grenade = weapon_grenadelauncher_fire( ent, WP_GRENADE_LAUNCHER );
				hitclient = AICast_SafeMissileFire( grenade, grenade->nextthink - level.time, cs->bs->enemy, destorg, cs->entityNum, NULL );
				G_FreeEntity( grenade );
			}
			if ( hitclient == -1 ) {  // doh
				//G_Printf("aborted grenade\n");
				cs->castScriptStatus.scriptNoMoveTime = 0;
				cs->lockViewAnglesTime = 0;
				AICast_ChooseWeapon( cs, qfalse );
				return AIFunc_DefaultStart( cs );
			}
			if ( !cs->bs->cur_ps.grenadeTimeLeft ) {
				// hold fire button down
				trap_EA_Attack( bs->client );
				bs->flags |= BFL_ATTACKED;
			}
			cs->lockViewAnglesTime = level.time + 500;
			return NULL;
		}
		// the grenade/pineapple has been released!
		cs->lockViewAnglesTime = -1;
		cs->startGrenadeFlushTime = level.time + 2000 + rand() % 2000;    // dont throw one again for a bit
		return AIFunc_DefaultStart( cs );
	}
	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}
	//
	if ( cs->bs->weaponnum && ( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) {
		return AIFunc_IdleStart( cs );
	}
	//
	if ( bs->enemy < 0 ) {
		return AIFunc_IdleStart( cs );
	}
	//
	// if we have started a script, abort the grenade flush
	if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) {
		return AIFunc_IdleStart( cs );
	}
	// trying for too long?
	if ( cs->startGrenadeFlushTime < level.time - 4000 ) {
		cs->startGrenadeFlushTime = level.time;
		return AIFunc_IdleStart( cs );
	}
	//
	followent = &g_entities[bs->enemy];
	//
	// if the entity is not ready yet
	if ( !followent->inuse ) {
		// if it's a connecting client, wait
		if ( !(   ( bs->enemy < MAX_CLIENTS )
				  && (   ( followent->client && followent->client->pers.connected == CON_CONNECTING )
						 || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking
			bs->enemy = -1;
		}
		return AIFunc_IdleStart( cs );
	}
	//
	// if we can see them, go back to an attack state after some time
	if (    AICast_CheckAttack( cs, bs->enemy, qfalse )
			&&  cs->obstructingTime < level.time ) { // give us some time to throw the grenade, otherwise go back to attack state
		if ( ( cs->grenadeFlushEndTime > 0 && cs->grenadeFlushEndTime < level.time ) ) {
			//G_Printf("aborting, enemy is attackable\n");
			return AIFunc_BattleStart( cs );
		} else if ( cs->grenadeFlushEndTime < 0 ) {
			cs->grenadeFlushEndTime = level.time + 1500;
		}
		//attack the enemy if possible
		AICast_ProcessAttack( cs );
		attacked = qtrue;
	} else {
		// not visible, go to their previously visible position
		if ( !cs->vislist[bs->enemy].visible_timestamp || Distance( bs->origin, cs->vislist[bs->enemy].real_visible_pos ) < 16 ) {
			// we're done attacking, go back to default state, which in turn will recall previous state
			//
			// face the direction they currently are from this position (bit of a hack, but it looks best)
			VectorSubtract( g_entities[bs->enemy].client->ps.origin, cs->vislist[bs->enemy].visible_pos, dir );
			vectoangles( dir, cs->bs->ideal_viewangles );
			//
			//G_Printf("aborting, reached visible pos\n");
			return AIFunc_DefaultStart( cs );
		}
	}

	// is there someone else we can go for instead?
	numEnemies = AICast_ScanForEnemies( cs, enemies );
	if ( numEnemies == -1 ) { // query mode
		return NULL;
	} else if ( numEnemies == -2 )     { // inspection may be required
		char *retval;
		// TTimo gcc: suggest parentheses around assignment used as truth value
		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
			return retval;
		}
	} else if ( !( cs->bs->flags & BFL_ATTACKED ) && numEnemies > 0 )       {
		int i;
		for ( i = 0; i < numEnemies; i++ ) {
			if ( enemies[i] != cs->bs->enemy && AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
				cs->bs->enemy = enemies[i];
				//G_Printf("aborting, other enemy\n");
				return AIFunc_BattleStart( cs );
			}
		}
	}

	if ( followent->client ) { // go to the last visible position
		VectorCopy( cs->vislist[bs->enemy].visible_pos, destorg );
	} else    // assume we know where other entities are
	{
		VectorCopy( followent->s.pos.trBase, destorg );
	}
	//
	dist = VectorDistance( destorg, cs->bs->origin );
	//
	if ( cs->vislist[bs->enemy].lastcheck_timestamp > cs->vislist[bs->enemy].real_visible_timestamp ||
		 dist > 128 ) {
		//
		// go to them
		//
		if ( followent->client && followent->health <= 0 ) {
			cs->bs->enemy = -1;
			//G_Printf("aborting, enemy dead\n");
			return AIFunc_DefaultStart( cs );
		}
		//
		// ...........................................................
		// Do the movement..
		//
		// move straight to them if we can
		if ( ( cs->leaderNum < 0 ) &&
			 ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) {
			aicast_predictmove_t move;
			vec3_t dir;
			bot_input_t bi;
			usercmd_t ucmd;
			trace_t tr;

			// trace will eliminate most unsuccessful paths
			trap_Trace( &tr, cs->bs->origin, g_entities[cs->entityNum].r.mins, g_entities[cs->entityNum].r.maxs, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask );
			if ( tr.entityNum == followent->s.number ) {
				// try walking straight to them
				VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir );
				VectorNormalize( dir );
				if ( !ent->waterlevel ) {
					dir[2] = 0;
				}
				trap_EA_Move( cs->entityNum, dir, 400 );
				trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
				AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
				AICast_PredictMovement( cs, 5, 2.0, &move, &ucmd, bs->enemy );

				if ( move.stopevent == PREDICTSTOP_HITENT ) { // success!
					vectoangles( dir, cs->bs->ideal_viewangles );
					cs->bs->ideal_viewangles[2] *= 0.5;
					moved = qtrue;
				}
			}
		}
		// just go to them
		if ( !moved ) {
			moveresult = AICast_MoveToPos( cs, destorg, bs->enemy );
			if ( moveresult && moveresult->failure ) {    // no path, so go back to idle behaviour
				cs->bs->enemy = -1;
				//G_Printf("aborting, movement failure\n");
				return AIFunc_DefaultStart( cs );
			} else {
				moved = qtrue;
			}
		}
	}
	//
	// ...........................................................
	// if we throw a grenade from here, will it get their last visible position?
	//
	ammo = cs->bs->cur_ps.ammo;
	if ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) {
		grenadeType = WP_GRENADE_LAUNCHER;
	} else if ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) {
		grenadeType = WP_GRENADE_PINEAPPLE;
	} else { // not enough ammo, abort
		return AIFunc_DefaultStart( cs );
	}

	CalcMuzzlePoints( ent, grenadeType );
	// fire a dummy grenade
	grenade = weapon_grenadelauncher_fire( ent, WP_GRENADE_LAUNCHER );
	// check to see what will happen
	hitclient = AICast_SafeMissileFire( grenade, grenade->nextthink - level.time, cs->bs->enemy, destorg, cs->entityNum, endPos );
	// kill the grenade
	G_FreeEntity( grenade );
	if ( !attacked ) {
		cs->bs->weaponnum = grenadeType;    // select grenade launcher
	}
	// set our angles for the next frame
	oldyaw = cs->bs->ideal_viewangles[YAW];
	AICast_AimAtEnemy( cs );
	// if we can't see them, keep facing our movement dir, but use the pitch information
	if ( !AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) ) {
		cs->bs->ideal_viewangles[YAW] = oldyaw;
	}

	if ( hitclient == 1 ) {
		// it will hit their last visible position
		// give us some time to aim and adjust
		if ( cs->thinkFuncChangeTime < level.time - 200 ) {
			trap_EA_Attack( bs->client );
			bs->flags |= BFL_ATTACKED;
			cs->bs->weaponnum = grenadeType;    // select grenade launcher
			cs->grenadeFlushFiring = cs->bs->weaponnum;
			// pause for a bit, so the grenade comes out correctly
			cs->lockViewAnglesTime = level.time + 1200;
			if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 1200 ) {
				cs->castScriptStatus.scriptNoMoveTime = level.time + 1200;
			}
			return NULL;
		}
	} else if ( hitclient == -1 ) {
		// hit a friendly
		cs->startGrenadeFlushTime = level.time + 3000;  // don't try again for a while
		//G_Printf("aborting, too dangerous\n");
		return AIFunc_DefaultStart( cs );
	} else if ( hitclient == -2 ) {
		// went too far, so angle down a bit
		cs->bs->ideal_viewangles[PITCH] += 15 * random();
	} else {
		if ( cs->thinkFuncChangeTime < level.time - 200 ) {
			// if it went reasonably close to them, but safe from us, then fire away
			if ( Distance( endPos, cs->bs->origin ) > 100 + Distance( endPos, g_entities[cs->bs->enemy].r.currentOrigin ) ) {
				trap_EA_Attack( bs->client );
				bs->flags |= BFL_ATTACKED;
				cs->bs->weaponnum = grenadeType;    // select grenade launcher
				cs->grenadeFlushFiring = cs->bs->weaponnum;
				// pause for a bit, so the grenade comes out correctly
				cs->lockViewAnglesTime = level.time + 1200;
				if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 1200 ) {
					cs->castScriptStatus.scriptNoMoveTime = level.time + 1200;
				}
				return NULL;
			}
		}
		cs->bs->ideal_viewangles[PITCH] += -10 * random();
	}
	//
	return NULL;
}

/*
============
AIFunc_GrenadeFlushStart()
============
*/
char *AIFunc_GrenadeFlushStart( cast_state_t *cs ) {
	lastGrenadeFlush = level.time; // + rand()%2000;
	cs->startGrenadeFlushTime = level.time;
	cs->grenadeFlushEndTime = -1;
	cs->lockViewAnglesTime = 0;
	cs->combatGoalTime = 0;
	cs->grenadeFlushFiring = qfalse;
	// don't wait too long before taking cover, if we just aborted one
	if ( cs->takeCoverTime > level.time + 1000 ) {
		cs->takeCoverTime = level.time + 500 + rand() % 500;
	}
	//
	cs->aifunc = AIFunc_GrenadeFlush;
	return "AIFunc_GrenadeFlush";
}

/*
============
AIFunc_BattleMG42()
============
*/
char *AIFunc_BattleMG42( cast_state_t *cs ) {
	bot_state_t *bs;
	gentity_t *mg42, *ent;
	vec3_t angles, vec, bestangles;

	mg42 = &g_entities[cs->mountedEntity];
	ent = &g_entities[cs->entityNum];
	bs = cs->bs;

	// have we dismounted the MG42?
	if ( !g_entities[cs->entityNum].active ) {
		return AIFunc_DefaultStart( cs );
	}

	// if enemy is dead, stop attacking them
	if ( g_entities[bs->enemy].health <= 0 ) {
		bs->enemy = -1;
	}

	//if no enemy, or our current enemy isn't attackable, look for a better enemy
	if ( bs->enemy >= 0 ) {
		if ( cs->vislist[bs->enemy].real_visible_timestamp && cs->vislist[bs->enemy].real_visible_timestamp > ( level.time - 5000 ) ) {
			VectorSubtract( cs->vislist[bs->enemy].real_visible_pos, mg42->r.currentOrigin, vec );
		} else if ( cs->vislist[bs->enemy].visible_timestamp && cs->vislist[bs->enemy].visible_timestamp > ( level.time - 5000 ) ) {
			VectorSubtract( cs->vislist[bs->enemy].visible_pos, mg42->r.currentOrigin, vec );
		} else { // just aim straight forward
			AngleVectors( mg42->s.angles, vec, NULL, NULL );
		}

		VectorNormalize( vec );
		vectoangles( vec, angles );
		angles[PITCH] = AngleNormalize180( angles[PITCH] );
		VectorCopy( angles, bestangles );
	}

	if (    bs->enemy < 0 ||
			!AICast_CheckAttack( cs, bs->enemy, qfalse ) ||
			( fabs( AngleDifference( angles[YAW], mg42->s.angles[YAW] ) ) > mg42->harc ) ||
			( angles[PITCH] < 0 && angles[PITCH] + 5 < -mg42->varc ) ||
			( angles[PITCH] > 0 && angles[PITCH] - 5 > 5.0 ) ) {
		qboolean shouldAttack;

		// look for a better enemy
		numEnemies = AICast_ScanForEnemies( cs, enemies );
		if ( numEnemies == -1 ) { // query mode
			return NULL;
		}
		if ( numEnemies == -2 ) { // inspection may be required
			char *retval;
			// TTimo gcc: suggest parentheses around assignment used as truth value
			if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
				return retval;
			}
		}
		shouldAttack = qfalse;
		if ( numEnemies > 0 ) {
			int i;
			// default to the first known enemy, overwrite if we find a clearer shot
			cs->bs->enemy = enemies[0];
			//
			for ( i = 0; i < numEnemies; i++ ) {
				if ( !cs->vislist[enemies[i]].real_visible_timestamp ||
					 ( cs->vislist[enemies[i]].real_visible_timestamp < level.time - 2000 ) ) {
					// we can't see them, ignore them
					continue;
				}

				// if they are in the range
				if ( cs->vislist[enemies[i]].real_visible_timestamp > ( level.time - 5000 ) ) {
					VectorSubtract( cs->vislist[enemies[i]].real_visible_pos, mg42->r.currentOrigin, vec );
				} else {
					VectorSubtract( cs->vislist[enemies[i]].visible_pos, mg42->r.currentOrigin, vec );
				}
				VectorNormalize( vec );
				vectoangles( vec, angles );
				angles[PITCH] = AngleNormalize180( angles[PITCH] );
				if ( !(  ( fabs( AngleDifference( angles[YAW], mg42->s.angles[YAW] ) ) > mg42->harc ) ||
						 ( angles[YAW] < 0 && angles[YAW] + 2 < -mg42->varc ) ||
						 ( angles[YAW] > 0 && angles[YAW] - 2 > 5.0 ) ) ) {
					if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) {
						VectorCopy( angles, bestangles );
						cs->bs->enemy = enemies[i];
						shouldAttack = qtrue;
						break;
					} else if ( AICast_CheckAttack( cs, enemies[i], qtrue ) ) {
						// keep firing at anything behind solids, in case they find a position where they can shoot us, but our checkattack() doesn't find a clear shot
						VectorCopy( angles, bestangles );
						cs->bs->enemy = enemies[i];
						shouldAttack = qtrue;
					}
				}
			}
		}

		if ( !shouldAttack ) {
			// keep firing at anything behind solids, in case they find a position where they can shoot us, but our checkattack() doesn't find a clear shot
			if ( bs->enemy < 0 || !AICast_CheckAttack( cs, bs->enemy, qtrue ) ||
				 (   !cs->vislist[bs->enemy].real_visible_timestamp ||
					 ( cs->vislist[bs->enemy].real_visible_timestamp < level.time - 2000 ) ) ) {
				// face straight forward
				cs->bs->ideal_viewangles[PITCH] = 0;
				return NULL;
			}
		}
	}
	//
	// hold down fire, and track them
	//
	// TODO: play a special "holding mg42" torso animation
	//
	VectorCopy( angles, bs->ideal_viewangles );
	if ( cs->triggerReleaseTime < level.time ) {
		trap_EA_Attack( bs->client );
		bs->flags |= BFL_ATTACKED;

		if ( cs->triggerReleaseTime < level.time - 3000 ) {
			cs->triggerReleaseTime = level.time + 700 + rand() % 700;
		}
	}
	//
	return NULL;
}

/*
============
AIFunc_BattleMG42Start()
============
*/
char *AIFunc_BattleMG42Start( cast_state_t *cs ) {
	cs->aifunc = AIFunc_BattleMG42;
	return "AIFunc_BattleMG42";
}

/*
============
AIFunc_InspectBody()

  go up to the enemy, and have a good look at them, randomly taunt them
============
*/
char *AIFunc_InspectBody( cast_state_t *cs ) {
	bot_state_t *bs;
	vec3_t destorg, enemyOrg;
	//
	// stop crouching
	cs->bs->attackcrouch_time = 0;
	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}
	//
	// if running a script
	if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) {
		cs->bs->enemy = -1;
		return AIFunc_IdleStart( cs );
	}
	//
	bs = cs->bs;
	//
	if ( bs->enemy < 0 ) {
		return AIFunc_IdleStart( cs );
	}
	//
	// look for things we should attack
	numEnemies = AICast_ScanForEnemies( cs, enemies );
	if ( numEnemies == -1 ) { // query mode
		return NULL;
	} else if ( numEnemies == -2 )     { // inspection may be required
		char *retval;
		// TTimo gcc: suggest parentheses around assignment used as truth value
		if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
			return retval;
		}
	} else if ( numEnemies == -3 )     { // bullet impact
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectBulletImpactStart( cs );
		}
	} else if ( numEnemies == -4 )     { // audible event
		if ( cs->aiState < AISTATE_COMBAT ) {
			return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt );
		}
	} else if ( numEnemies > 0 )     {
		cs->bs->enemy = enemies[0]; // just attack the first one
		return AIFunc_BattleStart( cs );
	}
	//
	VectorCopy( cs->vislist[bs->enemy].visible_pos, enemyOrg );
	if ( ( cs->inspectBodyTime < 0 ) && ( Distance( cs->bs->origin, enemyOrg ) > 64 ) ) {
		// if they were gibbed, don't go all the way
		if ( g_entities[cs->bs->enemy].health < GIB_HEALTH && ( Distance( cs->bs->origin, enemyOrg ) < 180 ) ) {
			cs->inspectBodyTime = level.time + 1000 + rand() % 1000;
			trap_EA_Gesture( cs->entityNum );
			G_AddEvent( &g_entities[cs->entityNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersSoundScript ) );
		}
		// walk to them
		if ( cs->movestate != MS_CROUCH ) {
			cs->movestate = MS_WALK;
		}
		cs->movestateType = MSTYPE_TEMPORARY;
		//
		moveresult = AICast_MoveToPos( cs, enemyOrg, -1 );
		//if the movement failed
		if ( !moveresult || moveresult->failure || moveresult->blocked ) {
			//reset the avoid reach, otherwise bot is stuck in current area
			trap_BotResetAvoidReach( bs->ms );
			// couldn't get there, so stop trying to get there
			cs->bs->enemy = -1;
			return AIFunc_IdleStart( cs );
		}
		if ( Distance( cs->bs->origin, enemyOrg ) < 180 ) {
			// look down at them
			VectorSubtract( enemyOrg, cs->bs->origin, destorg );
			destorg[2] -= 20;
			VectorNormalize( destorg );
			vectoangles( destorg, cs->bs->ideal_viewangles );
		}
	} else if ( cs->inspectBodyTime < 0 ) {
		// just reached them
		cs->inspectBodyTime = level.time + 1000 + rand() % 1000;
		trap_EA_Gesture( cs->entityNum );
		G_AddEvent( &g_entities[cs->entityNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersSoundScript ) );
	} else if ( cs->inspectBodyTime < level.time ) {
		vec3_t vec;
		VectorSubtract( cs->startOrigin, cs->bs->origin, vec );
		vec[2] = 0;
		// ready to go back to start position
		if ( VectorLength( vec ) > 64 ) {
			if ( cs->movestate != MS_CROUCH ) {
				cs->movestate = MS_WALK;
			}
			cs->movestateType = MSTYPE_TEMPORARY;
			moveresult = AICast_MoveToPos( cs, cs->startOrigin, -1 );
			//if the movement failed
			if ( !moveresult || moveresult->failure || moveresult->blocked ) {
				//reset the avoid reach, otherwise bot is stuck in current area
				trap_BotResetAvoidReach( bs->ms );
				// couldn't get there, so stop trying to get there
				cs->bs->enemy = -1;
				return AIFunc_IdleStart( cs );
			}
			// stay looking at them for a bit after starting to walk back
			if ( cs->inspectBodyTime + 750 > level.time ) {
				// look down at them
				VectorSubtract( enemyOrg, cs->bs->origin, destorg );
				destorg[2] -= 20;
				VectorNormalize( destorg );
				vectoangles( destorg, cs->bs->ideal_viewangles );
			}
		} else {
			cs->attackSNDtime = level.time;
			cs->bs->enemy = -1;
			return AIFunc_IdleStart( cs );
		}
	}
	//
	return NULL;
}

/*
============
AIFunc_InspectBodyStart()
============
*/
char *AIFunc_InspectBodyStart( cast_state_t *cs ) {
	static int lastInspect;
	//
	// if an inspection was already started not long ago, forget it
	if ( lastInspect <= level.time && lastInspect > level.time - 1000 ) {
		cs->inspectBodyTime = 1;    // go back to start position
	} else {
		lastInspect = level.time;
		cs->inspectBodyTime = -1;
	}
	cs->aifunc = AIFunc_InspectBody;
	return "AIFunc_InspectBody";
}

/*
============
AIFunc_GrenadeKick()
============
*/
char *AIFunc_GrenadeKick( cast_state_t *cs ) {
	bot_state_t *bs;
	vec3_t destorg, vec;
	float dist, speed;
	int enemies[MAX_CLIENTS], numEnemies, i;
	qboolean shouldAttack;
	int     *ammo;
	gentity_t *danger;
	gentity_t *ent;
	vec3_t end;
	trace_t tr;
	vec3_t dir;
	int weapon;

	// !!! NOTE: the only way control should pass out of here, is by calling AIFunc_DefaultStart()

	ent = &g_entities[cs->entityNum];
	danger = &g_entities[cs->dangerEntity];

	weapon = cs->grenadeKickWeapon;

	// just to be sure, give us the grenade launcher
	//ent->client->ps.weapons |= (1 << weapon);

	//
	// NOTE: ignore all danger, since we are trying to solve the situation anyway
	//
	// we need to move towards it
	bs = cs->bs;
	//
	// are we throwing it back?
	if ( cs->grenadeFlushFiring ) {
		// wait until the animation is done
		if ( !ent->client->ps.legsTimer ) {
			return AIFunc_DefaultStart( cs );
		}
		// wait till its finished
		return NULL;
		/*
		cs->bs->weaponnum = weapon;	// select grenade launcher
		//
		if (cs->weaponFireTimes[weapon] < cs->thinkFuncChangeTime) {
			if (!cs->bs->cur_ps.grenadeTimeLeft) {
				// hold fire button down
				AICast_AimAtEnemy( cs );
				trap_EA_Attack(bs->client);
				bs->flags |= BFL_ATTACKED;
			}
			//
			return NULL;
		}
		// the grenade has been released!
		//
		// modify the explode time
		g_entities[ent->grenadeFired].nextthink = ent->grenadeExplodeTime;
		if (g_entities[ent->grenadeFired].nextthink < level.time + 200) {	// cut them some slack
			g_entities[ent->grenadeFired].nextthink = level.time + 200 + rand()%500;
		}
		// make sure no-one tries to throw this back again (hot potatoe syndrome)
		g_entities[ent->grenadeFired].flags |= FL_AI_GRENADE_KICK;
		//
		cs->grenadeFlushFiring = qfalse;
		cs->lockViewAnglesTime = -1;
		cs->startGrenadeFlushTime = level.time + 2000 + rand()%2000;	// dont throw one again for a bit
		level.lastGrenadeKick = level.time;
		return AIFunc_DefaultStart( cs );
		*/
	}
	//
/*
	// have we caught the grenade?
	if (!(ent->flags & FL_AI_GRENADE_KICK)) {
		// select grenades
		cs->bs->weaponnum = weapon;	// select grenade launcher
		AICast_AimAtEnemy( cs );
		// hold fire
		trap_EA_Attack(bs->client);
		bs->flags |= BFL_ATTACKED;
		cs->grenadeFlushFiring = qtrue;
		//
		return NULL;
	}
*/
	//
	// is it about to explode in our face?
	if ( level.time > danger->nextthink - (int)( 2.0 * VectorDistance( cs->bs->origin, danger->r.currentOrigin ) ) ) {
		// abort!!
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = danger->nextthink + 1000;
		cs->bs->attackcrouch_time = 0;
		level.lastGrenadeKick = level.time;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	/*
	// are we close enough to start crouching?
	if (danger->s.pos.trDelta[2] < 40 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 48 && (danger->r.currentOrigin[2] < cs->bs->origin[2]) &&
		VectorLength(danger->s.pos.trDelta) < 40) {
		// crouch to pick it up
		cs->bs->attackcrouch_time = trap_AAS_Time() + 0.3;
	}
	*/
	cs->bs->attackcrouch_time = 0;  // animation is played from standing start
	//
	// are we close enough to pick it up?
	if ( /*cs->grenadeGrabFlag <= 0 || */
		( danger->s.pos.trDelta[2] < 20 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 48 && ( danger->r.currentOrigin[2] < cs->bs->origin[2] ) &&
		  VectorLength( danger->s.pos.trDelta ) < 50 ) ) {
		//
		// we have a choice here, either pick up and return, or just kick it
//		if ((cs->grenadeGrabFlag == -1) || (cs->grenadeGrabFlag == qtrue && level.time > danger->nextthink - 2000)) {	// kick

		// play the kick anim
		if ( cs->grenadeGrabFlag == qtrue ) {
			AICast_AimAtEnemy( cs );
			// play the kick anim
			BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_KICKGRENADE, qfalse, qtrue );
			cs->grenadeGrabFlag = -1;
			// stop the grenade from moving away
			danger->s.pos.trDelta[0] = 0;
			danger->s.pos.trDelta[1] = 0;
			if ( danger->s.pos.trDelta[2] > 0 ) {
				danger->s.pos.trDelta[2] = 0;
			}
		} else if ( ent->client->ps.legsTimer < 800 ) {
			// send the grenade on its way
			cs->grenadeFlushFiring = qtrue;
			AngleVectors( cs->bs->viewangles, dir, NULL, NULL );
			dir[2] = 0.4;
			VectorNormalize( dir );
			speed = 400;
			if ( cs->bs->enemy >= 0 ) {
				speed = 1.5 * VectorDistance( danger->r.currentOrigin, g_entities[cs->bs->enemy].r.currentOrigin );
				if ( speed > 650 ) {
					speed = 650;
				}
			}
			VectorScale( dir, speed, danger->s.pos.trDelta );
			danger->r.ownerNum = ent->s.number;     // we are now the owner, let it pass through us
			danger->s.pos.trTime = level.time - 50;         // move a bit on the very first frame
			VectorCopy( danger->r.currentOrigin, danger->s.pos.trBase );
			danger->s.pos.trType = TR_GRAVITY;
			SnapVector( danger->s.pos.trDelta );                // save net bandwidth
		}
/*
		} else { // throw

			if (cs->grenadeGrabFlag == qtrue) {
				AICast_AimAtEnemy( cs );
				// play the pickup anim
				BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_PICKUPGRENADE, qfalse, qtrue );
				cs->grenadeGrabFlag = qfalse;
				// stop the grenade from moving away
				danger->s.pos.trDelta[0] = 0;
				danger->s.pos.trDelta[1] = 0;
				if (danger->s.pos.trDelta[2] > 0) {
					danger->s.pos.trDelta[2] = 0;
				}
			} else if (ent->client->ps.legsTimer < 400) {
				// send the grenade on its way
				cs->grenadeFlushFiring = qtrue;
				AngleVectors( cs->bs->viewangles, dir, NULL, NULL );
				dir[2] = 0.4;
				VectorNormalize( dir );
				speed = 500;
				if (cs->bs->enemy >= 0) {
					speed = 2*VectorDistance(danger->r.currentOrigin, g_entities[cs->bs->enemy].r.currentOrigin);
					if (speed > 650)
						speed = 650;
				}
				VectorScale( dir, speed, danger->s.pos.trDelta );
				trap_LinkEntity( danger );
			} else if (ent->client->ps.legsTimer < 800) {
				// stop showing the grenade
				trap_UnlinkEntity( danger );
			}
		}
*/
		//
		return NULL;
	}
	//
	cs->grenadeGrabFlag = qtrue;    // we must play the anim before we can grab it
	//
	// is the danger gone?
	if ( level.time > cs->dangerEntityValidTime || !danger->inuse ) {
		return AIFunc_DefaultStart( cs );
	}
	//
	// update the predicted position of the grenade
	if ( G_PredictMissile( danger, danger->nextthink - level.time, cs->takeCoverPos, qfalse ) ) {
		// make sure it's a valid position, and drop it down to the ground
		cs->takeCoverPos[2] += -ent->r.mins[2] + 8;
		VectorCopy( cs->takeCoverPos, end );
		end[2] -= 80;
		trap_Trace( &tr, cs->takeCoverPos, ent->r.mins, ent->r.maxs, end, cs->entityNum, MASK_SOLID );
		if ( tr.startsolid ) {    // not a valid position, abort
			level.lastGrenadeKick = level.time;
			return AIFunc_DefaultStart( cs );
		}
		VectorCopy( tr.endpos, cs->takeCoverPos );
	} else {    // prediction failed, so use current position
		VectorCopy( danger->r.currentOrigin, cs->takeCoverPos );
		cs->takeCoverPos[2] += 16;  // lift it off the floor
	}

	VectorCopy( cs->takeCoverPos, destorg );
	VectorSubtract( destorg, cs->bs->origin, vec );
	//vec[2] *= 0.2;
	dist = VectorLength( vec );
	//
	// look for things we should attack
	// if we are out of ammo, we shouldn't bother trying to attack
	ammo = cs->bs->cur_ps.ammo;
	shouldAttack = qfalse;
	numEnemies = 0;
	if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) {
		numEnemies = AICast_ScanForEnemies( cs, enemies );
		if ( numEnemies == -1 ) { // query mode
			return NULL;
		}
		if ( numEnemies == -2 ) { // inspection may be required
			char *retval;
			// TTimo gcc: suggest parentheses around assignment used as truth value
			if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) {
				return retval;
			}
		}
		if ( numEnemies > 0 ) {
			// default to the first known enemy, overwrite if we find a clearer shot
			cs->bs->enemy = enemies[0];
			//
			for ( i = 0; i < numEnemies; i++ ) {
				if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) {
					cs->bs->enemy = enemies[i];
					shouldAttack = qtrue;
					break;
				} else if ( cs->bs->enemy < 0 ) {
					cs->lastEnemy = enemies[i];
				}
			}
		}
	}
	//
	// are we close enough to the goal?
	if ( dist > 12 ) { // not close enough
					  //
		moveresult = AICast_MoveToPos( cs, destorg, -1 );
		if ( moveresult ) {
			//if the movement failed
			if ( moveresult->failure ) {
				//reset the avoid reach, otherwise bot is stuck in current area
				trap_BotResetAvoidReach( bs->ms );
				// couldn't get there, so stop trying to get there
				level.lastGrenadeKick = level.time;
				return AIFunc_DefaultStart( cs );
			}
			//
			if ( moveresult->blocked ) {  // abort if we get blocked at any point
				level.lastGrenadeKick = level.time;
				return AIFunc_DefaultStart( cs );
			}
		}
		// we should slow down on approaching it
		cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 );
	}
	//
	// if we are close, put our weapon away and get ready to catch it
	if ( VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 128 ) {
		// put weapon away, select grenades
		// FIXME: does this fail if we don't have any grenades?
		cs->bs->weaponnum = weapon; // select grenade launcher
		shouldAttack = qfalse;  // don't attack until we've caught it
	}

	// if we should be attacking something on our way
	if ( shouldAttack ) {
		vec3_t vec, dir;

		//attack the enemy if possible
		AICast_ProcessAttack( cs );
		//
		// if they are close, and we're heading for them, we should abort this manouver
		VectorSubtract( g_entities[bs->enemy].client->ps.origin, bs->origin, vec );
		if ( VectorNormalize( vec ) < 64 ) {
			if ( VectorNormalize2( bs->velocity, dir ) > 20 ) {   // we are moving
				if ( DotProduct( dir, vec ) > 0 ) {
					// abort
					level.lastGrenadeKick = level.time;
					return AIFunc_DefaultStart( cs );
				}
			}
		}
	} else {
		// face the direction that the grenade is coming
		VectorSubtract( danger->r.currentOrigin, cs->bs->origin, dir );
		dir[2] = 0;
		VectorNormalize( dir );
		vectoangles( dir, cs->bs->ideal_viewangles );
	}

	return NULL;
}

/*
=============
AIFunc_GrenadeKickStart()
=============
*/
char *AIFunc_GrenadeKickStart( cast_state_t *cs ) {
	gentity_t *danger;
	gentity_t *ent;
	//gentity_t *trav;
	//int numFriends, i;

	//G_Printf( "Excuse me, you dropped something\n" );

	ent = &g_entities[cs->entityNum];
	danger = &g_entities[cs->dangerEntity];
	// should we dive onto the grenade?
	/*
	if (danger->s.pos.trDelta[2] < 30) {
		// count the number of friends near us
		numFriends = 0;
		for (i=0, trav=g_entities; i<aicast_maxclients; i++, trav++) {
			if (!trav->inuse)
				continue;
			if (trav->aiInactive)
				continue;
			if (trav->health <= 0)
				continue;
			if (!AICast_SameTeam( cs, i ))
				continue;
			if (VectorDistance( cs->takeCoverPos, trav->r.currentOrigin ) > 200)
				continue;
			numFriends++;
		}
		// if there are enough friends around, and we have a clear path to the position, sacrifice ourself!
		if (numFriends > 2) {
			trace_t tr;
			trap_Trace( &tr, cs->bs->origin, ent->r.mins, ent->r.maxs, cs->takeCoverPos, cs->entityNum, MASK_SOLID );
			if (tr.fraction == 1.0 && !tr.startsolid) {
				return AIFunc_GrenadeDiveStart( cs );
			}
		}
	}
	*/
	//
	// we have decided to kick or throw the grenade away
	cs->grenadeKickWeapon = danger->s.weapon;
	cs->grenadeFlushFiring = qfalse;
	cs->aifunc = AIFunc_GrenadeKick;
	return "AIFunc_GrenadeKick";
}

/*
============
AIFunc_Battle()
============
*/
char *AIFunc_Battle( cast_state_t *cs ) {
	bot_moveresult_t moveresult;
	int tfl;
	bot_state_t *bs;
	gentity_t *ent, *enemy;

	ent = &g_entities[cs->entityNum];
	enemy = &g_entities[cs->bs->enemy];

	// if we are not in combat mode, then go there now!
	if ( cs->aiState < AISTATE_COMBAT ) {
		AICast_StateChange( cs, AISTATE_COMBAT );   // just go straight to combat mode
	}
	//
	// do we need to avoid a danger?
	if ( cs->dangerEntityValidTime >= level.time ) {
		if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) {
			// shit??
		}
		// go to a position that cannot be seen from the dangerPos
		cs->takeCoverTime = cs->dangerEntityValidTime + 1000;
		cs->bs->attackcrouch_time = 0;
		return AIFunc_AvoidDangerStart( cs );
	}
	//
	// are we waiting for a door?
	if ( cs->doorMarkerTime > level.time - 100 ) {
		return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum );
	}
	//
	// do we need to go to our leader?
	if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) {
		return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue );
	}
	bs = cs->bs;
	//if no enemy
	if ( bs->enemy < 0 ) {
		// go back to whatever our default action is
		return AIFunc_DefaultStart( cs );
	}
	//
	if ( enemy->health <= 0 ) {
		// go back to whatever our default action is
		if ( g_entities[cs->entityNum].aiTeam == AITEAM_NAZI ) {
			return AIFunc_InspectBodyStart( cs );
		} else {
			return AIFunc_DefaultStart( cs );
		}
	}
	//
	// if we are crouching, don't stay down for too long after we finish fighting
	if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) {
		bs->attackcrouch_time = trap_AAS_Time() + 1;
	} else {
		bs->attackcrouch_time = 0;  // only set it if we need it
	}
	//
	// if the enemy is no longer visible
	if (    ( cs->bs->cur_ps.weaponTime < 100 )   // if reloading, don't chase until ready
			&&  ( cs->castScriptStatus.scriptNoMoveTime < level.time )
			&&  ( !AICast_EntityVisible( cs, bs->enemy, qtrue ) || !AICast_CheckAttack( cs, bs->enemy, qfalse ) ) ) {

		// if we are heading for a combatGoal, give us some time to get there
		if ( cs->combatGoalTime > level.time ) {
			if ( cs->combatGoalTime > level.time + 3000 ) {
				cs->combatGoalTime = level.time + 2000 + rand() % 1000;
				cs->combatSpotDelayTime = level.time + 4000 + rand() % 3000;
			}
		} else
		if ( cs->leaderNum >= 0 ) {
			// chase them, nothing else to do
			return AIFunc_BattleChaseStart( cs );
		} else
		// if we weren't moving, it is likely they have dodged back behind something, ready to duck out and take another
		// shot. so, we could fool them by hiding from the position we last saw them from, in the hope that when they
		// return to fire at us, we won't be in their sight.
		if (    cs->attributes[TACTICAL] > 0.3
				&&  cs->attributes[AGGRESSION] < 1.0
				&&  cs->attributes[AGGRESSION] < ( random() + 0.5 * cs->attributes[TACTICAL] )
				&&  ( cs->takeCoverTime < level.time )
				&&  AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) {
			// start taking cover
			cs->takeCoverTime = level.time + 2000 + rand() % 4000;    // only move a little bit
			//cs->bs->attackcrouch_time = 0;		// get out of here real quick
			return AIFunc_BattleTakeCoverStart( cs );
		} else
		// if we haven't thrown a grenade in a bit, go into "grenade flush mode"
		if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) &&
			 ( cs->castScriptStatus.castScriptEventIndex < 0 ) &&
			 (   (   ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) &&
					 ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) &&
					 ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) ) ||
				 (   ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_PINEAPPLE ) ) &&
					( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) &&
					( cs->weaponFireTimes[WP_GRENADE_PINEAPPLE] < level.time - (int)( aicast_skillscale * 3000 ) ) ) ) &&
			 !( cs->bs->weaponnum && ( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) &&
			 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) > 100 ) &&
			 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 1200 ) &&
			 ( AICast_WantsToChase( cs ) ) ) {
			// try and flush them out with a grenade
			//G_Printf("get outta there..\n");
			return AIFunc_GrenadeFlushStart( cs );
		} else
		// not visible, should we chase them?
		if ( AICast_WantsToChase( cs ) ) {
			// chase them
			return AIFunc_BattleChaseStart( cs );
		} else
		// Take Cover?
		if (    AICast_WantsToTakeCover( cs, qfalse )
				&&  ( cs->takeCoverTime < level.time )
				&&  AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) {
			// go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time
			cs->takeCoverTime = level.time + 4000 + rand() % 2000;
			//cs->bs->attackcrouch_time = 0;
			return AIFunc_BattleTakeCoverStart( cs );
		} else
		{
			// chase them, nothing else to do
			return AIFunc_BattleChaseStart( cs );
		}
	}
	// if we are obstructing someone else, move out the way
	if ( cs->obstructingTime > level.time ) {
		// setup a combatgoal in the obstructionYaw direction
		//cs->combatGoalTime = level.time + 10;
		//VectorCopy( cs->obstructingPos, cs->combatGoalOrigin );
		AICast_MoveToPos( cs, cs->obstructingPos, -1 );
		// if not crouching, walk instead of running
		cs->speedScale = cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED];
	}
	// if the enemy is really close, avoid them
	else if (   ( cs->obstructingTime < ( level.time - 500 + rand() % 300 ) ) &&
				( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 100 ) ) {
		if ( AICast_GetAvoid( cs, NULL, cs->obstructingPos, qtrue, cs->bs->enemy ) ) {
			cs->obstructingTime = level.time + 500;
		} else {
			cs->obstructingTime = level.time - 1;   // wait a bit before trying again
		}
	}
	//
	// setup for the fight
	//
	tfl = cs->travelflags;
	//if in lava or slime the bot should be able to get out
	if ( BotInLava( bs ) ) {
		tfl |= TFL_LAVA;
	}
	if ( BotInSlime( bs ) ) {
		tfl |= TFL_SLIME;
	}
	//
	/*
	moveresult = AICast_CombatMove(cs, tfl);
	//if the movement failed
	if (moveresult.failure) {
		//reset the avoid reach, otherwise bot is stuck in current area
		trap_BotResetAvoidReach(bs->ms);
		// reset the combatgoal
		cs->combatGoalTime = 0;
	} else if (cs->combatGoalTime > level.time && VectorLength(cs->bs->cur_ps.velocity)) {	// crouch if moving?
		if (cs->attributes[ATTACK_CROUCH] > 0.1) {
			AICast_RequestCrouchAttack( cs, cs->bs->origin, 0.5 );
		}
	}
	*/
	//
	AICast_Blocked( cs, &moveresult, qfalse, NULL );
	//
	// Retreat?
	if ( AICast_WantToRetreat( cs ) ) {
		if  ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) {
			// go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time
			cs->takeCoverTime = level.time + 2000 + rand() % 3000;
			return AIFunc_BattleTakeCoverStart( cs );
		}
	}
	//
	// Lob a Grenade?
	// if we haven't thrown a grenade in a bit, go into "grenade flush mode"
	if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) &&
		 ( cs->castScriptStatus.castScriptEventIndex < 0 ) &&
		 ( cs->startGrenadeFlushTime < level.time - 3000 ) &&
		 ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) &&
		 ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) &&
		 ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) &&
		 ( ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) &&
		 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) > 100 ) &&
		 ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 2000 ) ) {
		// try and flush them out with a grenade
		//G_Printf("pineapple?\n");
		return AIFunc_GrenadeFlushStart( cs );
	}
	//
	// Dodge enemy aim?
	if (    ( cs->attributes[AGGRESSION] < 1.0 ) &&
			( ent->client->ps.weapon ) &&
			( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD ) &&
			( !cs->lastRollMove || cs->lastRollMove < level.time - 4000 ) &&
			( cs->attributes[TACTICAL] > 0.5 ) && ( cs->aiFlags & AIFL_ROLL_ANIM ) &&
			( VectorLength( cs->bs->cur_ps.velocity ) < 1 ) ) {
		vec3_t aim, enemyVec, right;
		// are they aiming at us?
		AngleVectors( enemy->client->ps.viewangles, aim, right, NULL );
		VectorSubtract( cs->bs->origin, enemy->r.currentOrigin, enemyVec );
		VectorNormalize( enemyVec );
		// if they are looking at us, we should avoid them
		if ( DotProduct( aim, enemyVec ) > 0.97 ) {
			aicast_predictmove_t move;
			vec3_t dir;
			bot_input_t bi, bi_back;
			usercmd_t ucmd;
			float simTime = 0.8;

			cs->lastRollMove = level.time;

			trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi_back );
			trap_EA_ResetInput( cs->entityNum, NULL );
			if ( level.time % 200 < 100 ) {
				VectorNegate( right, dir );
			} else { VectorCopy( right, dir );}
			trap_EA_Move( cs->entityNum, dir, 400 );
			trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi );
			AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles );
			AICast_PredictMovement( cs, 4, simTime / 4, &move, &ucmd, bs->enemy );

			trap_EA_ResetInput( cs->entityNum, &bi_back );

			if ( move.groundEntityNum == ENTITYNUM_WORLD &&
				 VectorDistance( move.endpos, cs->bs->origin ) > simTime * cs->attributes[RUNNING_SPEED] * 0.8 ) {
				// good enough
				if ( AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, move.endpos, cs->bs->cur_ps.viewheight == cs->bs->cur_ps.crouchViewHeight, qfalse ) ) {
					return AIFunc_BattleRollStart( cs, dir );
				}
			}
		}
	}
	//
	// reload?
	if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( ammoTable[cs->bs->cur_ps.weapon].uses ) ) ) {
		if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) {
			trap_EA_Reload( cs->entityNum );
		} else {    // no ammo, switch?
			AICast_ChooseWeapon( cs, qfalse );
			if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) {
				// no ammo, get out of here
				return AIFunc_DefaultStart( cs );
			}
		}
	} else {
		//attack the enemy if possible
		AICast_ProcessAttack( cs );
	}
	//
	return NULL;
}

/*
============
AIFunc_BattleStart()
============
*/
char *AIFunc_BattleStart( cast_state_t *cs ) {
	char *rval;
	int lastweap;
	// make sure we don't avoid any areas when we start again
	trap_BotInitAvoidReach( cs->bs->ms );
	// wait some time before taking cover again
	cs->takeCoverTime = level.time + 300 + rand() % ( 2000 + (int)( 2000.0 * cs->attributes[AGGRESSION] ) );
	// wait some time before going to a combat spot
	cs->combatSpotDelayTime = level.time + 1500 + rand() % 2500;
	//
	// start a crouch attack?
	if ( ( random() * 3.0 + 1.0 < cs->attributes[ATTACK_CROUCH] )
		 && AICast_RequestCrouchAttack( cs, cs->bs->origin, 0.0 ) ) {
		cs->aiFlags |= AIFL_ATTACK_CROUCH;
	} else {
		cs->bs->attackcrouch_time = 0;
		cs->aiFlags &= ~AIFL_ATTACK_CROUCH;
	}
	//
	cs->lastEnemy = cs->bs->enemy;
	cs->startAttackCount++;
	cs->crouchHideFlag = qfalse;
	//
	// get out of talking state
	cs->aiFlags &= ~AIFL_TALKING;
	//
	//update the attack inventory values
	AICast_UpdateBattleInventory( cs, cs->bs->enemy );
	//
	// if we have a special attack, call the correct AI routine
recheck:
	rval = NULL;
	// ignore special attacks until we are facing our enemy
	if ( fabs( AngleDifference( cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW] ) ) < 10 ) {
		// select a weapon
		AICast_ChooseWeapon( cs, qtrue );
		//
		if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK1 ) && cs->aifuncAttack1 ) {
			if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) {
				rval = cs->aifuncAttack1( cs );
			} else {
				rval = AIFunc_BattleChaseStart( cs );
			}
		} else if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK2 ) && cs->aifuncAttack2 ) {
			if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) {
				rval = cs->aifuncAttack2( cs );
			} else {
				rval = AIFunc_BattleChaseStart( cs );
			}
		} else if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK3 ) && cs->aifuncAttack3 ) {
			if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) {
				rval = cs->aifuncAttack3( cs );
			} else {
				rval = AIFunc_BattleChaseStart( cs );
			}
		}
		//
		if ( !rval && cs->bs->weaponnum >= WP_MONSTER_ATTACK1 && cs->bs->weaponnum <= WP_MONSTER_ATTACK3 ) {
			// don't use this weapon again for a while
			cs->weaponFireTimes[cs->bs->weaponnum] = level.time;
			// select a different weapon
			lastweap = cs->bs->weaponnum;
			AICast_ChooseWeapon( cs, qfalse );  // qfalse so we don't choose a special weapon
			if ( cs->bs->weaponnum == lastweap ) {
				return NULL;
			}
			// try again
			goto recheck;
		}
	} else {    // normal weapons
		// select a weapon
		AICast_ChooseWeapon( cs, qfalse );
		rval = NULL;
	}
	//
	if ( !rval ) {    // use the generic battle routine for all "normal" weapons
		if ( cs->bs->weaponnum >= WP_MONSTER_ATTACK1 && cs->bs->weaponnum <= WP_MONSTER_ATTACK3 ) {
			// monster attacks are not allowed to go into the normal battle mode
			return NULL;
		} else {
			cs->aifunc = AIFunc_Battle;
			return "AIFunc_Battle";
		}
	}
	//
	// we decided to start a special monster attack
	return rval;
}

/*
============
AIFunc_DefaultStart()
============
*/
char *AIFunc_DefaultStart( cast_state_t *cs ) {
	qboolean first = qfalse;
	char    *rval = NULL;
	//
	if ( cs->aiFlags & AIFL_JUST_SPAWNED ) {
		first = qtrue;
		cs->aiFlags &= ~AIFL_JUST_SPAWNED;
	}
	//
	switch ( cs->aiCharacter ) {
	case AICHAR_FEMZOMBIE:
		if ( first ) {
			return AIFunc_FZombie_IdleStart( cs );
		}
		break;
	case AICHAR_ZOMBIE:
		// portal zombie, requires spawning effect
		if ( first && ( g_entities[cs->entityNum].spawnflags & 4 ) ) {
			return AIFunc_FlameZombie_PortalStart( cs );
		}
		break;
	case AICHAR_HELGA:
		if ( first ) {
			return AIFunc_Helga_IdleStart( cs );
		}
		break;
	}
	//
	// if they have an enemy, then pursue
	if ( cs->bs->enemy >= 0 ) {
		rval = AIFunc_BattleStart( cs );
	}
	//
	if ( !rval ) {
		return AIFunc_IdleStart( cs );
	}
	//
	return rval;
}
