/*
   Copyright (C) 1997-2001 Id Software, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

   See the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */

#include "../g_local.h"
#include "ai_local.h"
#include <math.h>

//ACE



//==========================================
// BOT_DMclass_Move
// DMClass is generic bot class
//==========================================
void BOT_DMclass_Move( edict_t *self, usercmd_t *ucmd )
{
#define INHIBIT_SPECIAL_RADIUS 48
	int current_node_flags = 0;
	int next_node_flags = 0;
	int i;
	qboolean inhibit_special = qfalse;
	qboolean accurate_angle = qfalse;
	vec3_t move_norm;

	current_node_flags = nodes[self->ai.current_node].flags;
	next_node_flags = nodes[self->ai.next_node].flags;


	if( self->ai.current_link_type == LINK_ROCKETJUMP )
	{

		Com_Printf( "Attempting RJ\n" );
		if( !self->ai.rj_triggered )
		{
			self->s.angles[PITCH] = 170;
			ucmd->forwardmove = 1;
			ucmd->upmove = 1;
			ucmd->buttons |= BUTTON_ATTACK;
			self->ai.rj_triggered = qtrue;

			return;
		}
	}
	else self->ai.rj_triggered = qfalse;

	// Platforms
	if( self->ai.current_link_type & LINK_PLATFORM )
	{
		vec3_t v1, v2;
		VectorCopy( self->s.origin, v1 );
		VectorCopy( nodes[self->ai.next_node].origin, v2 );
		v1[2] = v2[2] = 0;

		// Move to the center
		self->ai.move_vector[2] = 0; // kill z movement
		AI_ChangeAngle( self );

		ucmd->buttons |= BUTTON_WALK;
		if( DistanceFast( v1, v2 ) > 24 )
			ucmd->forwardmove = 1; // walk to center
		
		return; // No move, riding elevator
	}
	else if( next_node_flags & NODEFLAGS_PLATFORM )
	{
		// is lift down?
		for( i = 0; i < nav.num_ents; i++ )
		{
			if( nav.ents[i].node == self->ai.next_node )
			{
				//testing line
				//vec3_t	tPoint;
				//int		j;
				//for(j=0; j<3; j++)//center of the ent
				//	tPoint[j] = nav.ents[i].ent->s.origin[j] + 0.5*(nav.ents[i].ent->r.mins[j] + nav.ents[i].ent->r.maxs[j]);
				//tPoint[2] = nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->r.maxs[2];
				//tPoint[2] += 8;
				//AITools_DrawLine( self->s.origin, tPoint );

				//if not reachable, wait for it (only height matters)
				if( ( nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->r.maxs[2] )
				   > ( self->s.origin[2] + self->r.mins[2] + AI_JUMPABLE_HEIGHT ) &&
				   nav.ents[i].ent->moveinfo.state != STATE_BOTTOM ) //jabot092(2)
				{
					self->ai.blocked_timeout = level.time + 10000;
					return; //wait for elevator
				}
			}
		}
	}

	// Ladder movement
	if( self->is_ladder )
	{
		ucmd->forwardmove = 1;
		ucmd->upmove = 1;
		ucmd->sidemove = 0;
		return;
	}

	// Falling off ledge
	if( !self->groundentity && !self->is_step && !self->is_swim )
	{
		if( self->ai.current_link_type == LINK_JUMPPAD )
		{
			AI_ChangeAngle( self );
			ucmd->forwardmove = 1;
			return;
		}
		else if( self->ai.current_link_type == LINK_JUMP )
		{
			AI_ChangeAngle( self );
			self->velocity[0] = self->ai.move_vector[0] * 580;
			self->velocity[1] = self->ai.move_vector[1] * 580;
			return;
		}
	}

	// jumping over (keep fall before this)
	if( self->ai.current_link_type & LINK_JUMP && self->groundentity )
	{
		trace_t trace;
		vec3_t v1, v2;
		//check floor in front, if there's none... Jump!
		VectorCopy( self->s.origin, v1 );
		// XXX: ? VectorCopy( self->ai.move_vector, v2 );
		VectorNormalize2( self->ai.move_vector, v2 );
		VectorMA( v1, 18, v2, v1 );
		v1[2] += self->r.mins[2];
		VectorCopy( v1, v2 );
		v2[2] -= AI_JUMPABLE_HEIGHT;
		G_Trace( &trace, v1, vec3_origin, vec3_origin, v2, self, MASK_AISOLID );
		//G_Trace( &trace, v1, tv(-1, -1, -AI_JUMPABLE_HEIGHT), tv(1, 1, 0), v1, self, MASK_AISOLID );
		if( !trace.startsolid && trace.fraction == 1.0 )
		{
			//jump!
			ucmd->forwardmove = 1;
			//prevent double jumping on crates
			VectorCopy( self->s.origin, v1 );
			v1[2] += self->r.mins[2];
			G_Trace( &trace, v1, tv( -12, -12, -8 ), tv( 12, 12, 0 ), v1, self, MASK_AISOLID );
			if( trace.startsolid )
				ucmd->upmove = 1;
			return;
		}
	}

	if( self->ai.current_link_type & LINK_JUMP || self->ai.current_link_type & LINK_FALL )
		inhibit_special = qtrue;

	if( ( next_node_flags & NODEFLAGS_JUMPPAD ) || ( current_node_flags & NODEFLAGS_JUMPPAD ) )
		inhibit_special = qtrue;

	if( next_node_flags & NODEFLAGS_REACHATTOUCH )
		if( DistanceFast( nodes[self->ai.next_node].origin, self->s.origin ) < INHIBIT_SPECIAL_RADIUS )
			inhibit_special = qtrue;

	// if in tctf, if we are touching the button, just stand there
	if( ( game.gametype == GAMETYPE_CTF ) && G_CTF_TIMER )
	{
		if( !G_Gametype_CTF_HasFlag( self, self->s.team == TEAM_ALPHA ? TEAM_BETA : TEAM_ALPHA ) )
		{
			// find the flag base entity
			edict_t *flag = NULL;
			gitem_t *flagItem = G_Gametype_CTF_FlagItem( self->s.team == TEAM_ALPHA ? TEAM_BETA : TEAM_ALPHA );
			while( ( flag = G_Find( flag, FOFS( classname ), flagItem->classname ) ) != NULL )
			{
				// flag base without a flag
				if( flag->s.type == ET_FLAG_BASE && !( flag->s.effects & EF_ENEMY_FLAG ) )
				{
					// check if we are touching it
					vec3_t mins, maxs;
					VectorAdd( self->s.origin, self->r.mins, mins );
					VectorAdd( self->s.origin, self->r.maxs, maxs );
					if( BoundsIntersect( mins, maxs, flag->r.absmin, flag->r.absmax ) )
					{
						self->ai.blocked_timeout = level.time + 10000;
						ucmd->forwardmove = 0;
						return; // don't move
					}
				}
			}
		}
	}

	// Move To Short Range goal (not following paths)
	// plats, grapple, etc have higher priority than SR Goals, cause the bot will
	// drop from them and have to repeat the process from the beginning
	if( !inhibit_special && !self->ai.camp_item )
	{
//		if( AI_MoveToGoalEntity(self,ucmd) )
//			goto ;
		accurate_angle = AI_MoveToGoalEntity( self, ucmd );
	}

	// now calc move_normalized cause AI_MoveToGoalEntity can change it.
	VectorCopy( self->ai.move_vector, move_norm );
	move_norm[2] = 0.0;
	VectorNormalize( move_norm );

	// swimming
	if( self->is_swim )
	{
		// We need to be pointed up/down
		AI_ChangeAngle( self );

		if( !( G_PointContents( nodes[self->ai.next_node].origin ) & MASK_WATER ) )  // Exit water
			ucmd->upmove = 1;

		ucmd->forwardmove = 1;
		return;
	}

	// Check to see if stuck, and if so try to free us
	if( !self->ai.camp_item || !( DistanceFast( nodes[self->ai.next_node].origin, self->s.origin ) < 150 ) )
		if( VectorLengthFast( self->velocity ) < 37 )
		{
			// Keep a random factor just in case....
			if( random() > 0.1 && AI_SpecialMove( self, ucmd ) )  //jumps, crouches, turns...
				return;

			self->s.angles[YAW] += random() * 180 - 90;

			AI_ChangeAngle( self );

			ucmd->forwardmove = 1;

			return;
		}

	// calc whether we are close to the target node...

#if 0
	// is this necessary here ?
	// dont_jump handles this case very well.
	if( DistanceFast( nodes[self->ai.next_node].origin, self->s.origin ) < INHIBIT_SPECIAL_RADIUS )
	{
		vec3_t lookdir;
		AI_ChangeAngle( self );
		AngleVectors( self->s.angles, lookdir, NULL, NULL );
		VectorNormalize( lookdir );
		if( DotProduct( lookdir, move_norm ) > 0.7 )
		{
			if( !self->ai.camp_item )
				ucmd->forwardmove = 1;
		}
		return;
	}
#endif


	// Check for wall jumps.
	if( !inhibit_special )
	{
		vec3_t current_direction;
		trace_t trace;
		vec3_t vel, end;

		//VectorNormalize2( self->velocity, current_direction );
		VectorCopy( self->velocity, current_direction );
		current_direction[2] = 0.0f;
		VectorNormalize( current_direction );

		// wall-jump right now acts more against the bots than favoring them, 
		// cause they are chocking against walls all the time, and they are playing
		// quite well, so it's a shame to have it making them worse.
		// They aren't yet smart enough to perform true wall-jumps, it's better
		// to prevent them to try unless they do at non flow-breaking spots (having height)

		VectorCopy( self->s.origin, end );
		end[2] -= ( AI_JUMPABLE_HEIGHT + STEPSIZE );
		G_Trace( &trace, self->s.origin, self->r.mins, self->r.maxs, end, self, MASK_AISOLID );
		if( trace.ent == -1 )
		{
			if( self->ai.move_vector[2] > -20 && DotProduct( current_direction, move_norm ) < -0.2 )
			{
				VectorCopy( self->velocity, vel );
				vel[2] = 0;
				VectorMA( self->s.origin, 0.02, self->velocity, end );
				G_Trace( &trace, self->s.origin, self->r.mins, self->r.maxs, end, self, MASK_AISOLID );

				if( trace.fraction != 1.0f )
					ucmd->buttons |= BUTTON_SPECIAL;
			}
		}

//		if( !self->ai.dont_jump )
//			ucmd->sidemove = -AI_ChangeAngle( self );
//		else
//		{
			AI_ChangeAngle( self );
//			ucmd->upmove = 0;
//		}


		// easy bots dont jump
		if( self->ai.pers.skillLevel < 0.33f )
		    self->ai.dont_jump = qtrue;

		if( !self->ai.dont_jump )
		{
			if( DotProduct( current_direction, move_norm ) > 0.2 )
			{
				if( VectorLengthFast( self->velocity ) > 450.0 )
				{
					//if( self->velocity[2] >= 0.0 )
					if( self->groundentity )
						ucmd->upmove = 1;
				}
				else
				{
					if( self->groundentity )
					{
					    if( DotProduct( current_direction, move_norm ) > 0.7 )
					    {
						    // XXX: Check for dash against wall.
						    ucmd->buttons |= BUTTON_SPECIAL;
						    ucmd->sidemove = 0;
					    }
					}
				}
			}
		}
	}
	else
	{
		AI_ChangeAngle( self );
	}

	// Otherwise move as fast as we can...
	if( !self->ai.camp_item )
	{
		//ucmd->forwardmove = 1;

		vec3_t lookdir;
		float angle_accuracy = self->ai.dont_jump ? 0.7 : 0.4;

		if( accurate_angle )
		    angle_accuracy = 0.8f;

		AngleVectors( self->s.angles, lookdir, NULL, NULL );
		lookdir[2] = 0.0;
		VectorNormalize( lookdir );
		
		if( DotProduct( lookdir, move_norm ) > angle_accuracy )
			ucmd->forwardmove = 1;
		else
			ucmd->forwardmove = 0;
	}
	else
	{
		//if( g_instagib->integer ) // iTDM capture_area
		if( 1 ) // iTDM capture_area
		{
			if( DistanceFast( nodes[self->ai.next_node].origin, self->s.origin ) < 35 )
				ucmd->forwardmove = 0;
			else
			{
				vec3_t lookdir;
				AngleVectors( self->s.angles, lookdir, NULL, NULL );
				lookdir[2] = 0.0;
				VectorNormalize( lookdir );
				if( DotProduct( lookdir, move_norm ) > 0.5 )
					ucmd->forwardmove = 1;
				else
					ucmd->forwardmove = 0;
			}

		}
		else
			ucmd->forwardmove = 0;
	}
#undef INHIBIT_SPECIAL_RADIUS
}


//==========================================
// BOT_DMclass_Wander
// Wandering code (based on old ACE movement code)
//==========================================
void BOT_DMclass_Wander( edict_t *self, usercmd_t *ucmd )
{
	vec3_t temp;

	// Do not move
	if( self->ai.next_move_time > level.time )
		return;

	if( self->deadflag )
		return;

	// Special check for elevators, stand still until the ride comes to a complete stop.
	if( self->groundentity != NULL && self->groundentity->use == Use_Plat )
	{
		if( self->groundentity->moveinfo.state == STATE_UP ||
		    self->groundentity->moveinfo.state == STATE_DOWN )
		{
			self->velocity[0] = 0;
			self->velocity[1] = 0;
			self->velocity[2] = 0;
			self->ai.next_move_time = level.time + 500;
			return;
		}
	}

	// Move To Goal (Short Range Goal, not following paths)
	if( AI_MoveToGoalEntity( self, ucmd ) )
		return;

	// Swimming?
	VectorCopy( self->s.origin, temp );
	temp[2] += 24;

//	if(G_PointContents (temp) & MASK_WATER)
	if( G_PointContents( temp ) & MASK_WATER )
	{
		// If drowning and no node, move up
		if( self->r.client && self->r.client->next_drown_time > 0 ) //jalfixme: client references must pass into botStatus
		{
			ucmd->upmove = 1;
			self->s.angles[PITCH] = -45;
		}
		else
			ucmd->upmove = 1;

		ucmd->forwardmove = 1;
	}
	// else self->r.client->next_drown_time = 0; // probably shound not be messing with this, but


	// Lava?
	temp[2] -= 48;
	//if(G_PointContents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME))
	if( G_PointContents( temp ) & ( CONTENTS_LAVA|CONTENTS_SLIME ) )
	{
		self->s.angles[YAW] += random() * 360 - 180;
		ucmd->forwardmove = 1;
		if( self->groundentity )
			ucmd->upmove = 1;
		else
			ucmd->upmove = 0;
		return;
	}


	// Check for special movement
	if( VectorLengthFast( self->velocity ) < 37 )
	{
		if( random() > 0.1 && AI_SpecialMove( self, ucmd ) )  //jumps, crouches, turns...
			return;

		self->s.angles[YAW] += random() * 180 - 90;

		if( !self->is_step )  // if there is ground continue otherwise wait for next move
			ucmd->forwardmove = 0; //0
		else if( AI_CanMove( self, BOT_MOVE_FORWARD ) )
		{
			ucmd->forwardmove = 1;
			ucmd->buttons |= BUTTON_WALK;
		}

		return;
	}


	// Otherwise move slowly, walking wondering what's going on
	ucmd->buttons |= BUTTON_WALK;
	if( AI_CanMove( self, BOT_MOVE_FORWARD ) )
		ucmd->forwardmove = 1;
	else
		ucmd->forwardmove = -1;
}

#define AI_ROCKET_DETECT_RADIUS 2000
#define AI_ROCKET_DANGER_RADIUS 200

static qboolean BOT_DMclass_FindRocket( edict_t *self, vec3_t away_from_rocket )
{

	edict_t *target = findradius( NULL, self->s.origin, AI_ROCKET_DETECT_RADIUS );
	float min_roxx_time = 1.0f;


	while( target )
	{
		// Missile detection code
		if( target->s.type == ET_ROCKET )
		{
			if( target->r.owner && target->r.owner != self )
			{
				//if player who shoot is an enemy

				vec3_t end;
				trace_t trace;

				VectorMA( target->s.origin, 2, target->velocity, end );
				G_Trace( &trace, target->s.origin, target->r.mins, target->r.maxs, end, target, MASK_SOLID );
				if( trace.fraction < min_roxx_time )
				{
					vec_t l;
					min_roxx_time = trace.fraction;
					VectorSubtract( trace.endpos, self->s.origin, end );
					// ok... end is where the impact will be.
					// trace.fraction is the time.

					if( ( l = VectorLengthFast( end ) ) < AI_ROCKET_DANGER_RADIUS )
					{
						RotatePointAroundVector( away_from_rocket, axis_identity[UP], end, -self->s.angles[YAW] );
						VectorNormalize( away_from_rocket );

						if( fabs( away_from_rocket[0] ) < 0.3 ) away_from_rocket[0] = 0;
						if( fabs( away_from_rocket[1] ) < 0.3 ) away_from_rocket[1] = 0;
						away_from_rocket[2] = 0;
						away_from_rocket[0] *= -1.0f;
						away_from_rocket[1] *= -1.0f;

						if( AIDevel.debugMode && bot_showcombat->integer > 2 )
							G_PrintMsg( AIDevel.chaseguy, "%s: ^1rocket dodge: ^2%f, %f d=%f^7\n", self->ai.pers.netname, away_from_rocket[0], away_from_rocket[1], l );


						//return qtrue;
					}
				}


			}
		}
		target = findradius( target, self->s.origin, AI_ROCKET_DETECT_RADIUS );
	}
	return min_roxx_time != 1.0f;
}



//==========================================
// BOT_DMclass_CombatMovement
//
// NOTE: Very simple for now, just a basic move about avoidance.
//       Change this routine for more advanced attack movement.
//==========================================
void BOT_DMclass_CombatMovement( edict_t *self, usercmd_t *ucmd )
{
	float c;
	float dist;
	qboolean rocket = qfalse;
	vec3_t away_from_rocket;

	if( !self->enemy || self->ai.rush_item )
	{
		self->ai.combatmovepush_timeout = level.time;
		if( AI_FollowPath( self ) )
		{
			BOT_DMclass_Move( self, ucmd );
		}
		return;
	}
	if( self->ai.pers.skillLevel >= 0.33f ) 
	    rocket = BOT_DMclass_FindRocket( self, away_from_rocket );

	dist = DistanceFast( self->s.origin, self->enemy->s.origin );
	c = random();

	if( level.time > self->ai.combatmovepush_timeout )
	{
		qboolean canMOVELEFT, canMOVERIGHT, canMOVEFRONT, canMOVEBACK;

		canMOVELEFT = AI_CanMove( self, BOT_MOVE_LEFT );
		canMOVERIGHT = AI_CanMove( self, BOT_MOVE_RIGHT );
		canMOVEFRONT = AI_CanMove( self, BOT_MOVE_FORWARD );
		canMOVEBACK = AI_CanMove( self, BOT_MOVE_BACK );

		self->ai.combatmovepush_timeout = level.time + AI_COMBATMOVE_TIMEOUT;
		VectorClear( self->ai.combatmovepushes );

		if( rocket )
		{
			//VectorScale(away_from_rocket,1,self->ai.combatmovepushes);
			if( away_from_rocket[0] )
			{
				if( ( away_from_rocket[0] < 0 ) && canMOVEBACK )
					self->ai.combatmovepushes[0] = -1;
				else if( ( away_from_rocket[0] > 0 ) && canMOVEFRONT )
					self->ai.combatmovepushes[0] = 1;
			}
			if( away_from_rocket[1] )
			{
				if( ( away_from_rocket[1] < 0 ) && canMOVELEFT )
					self->ai.combatmovepushes[1] = -1;
				else if( ( away_from_rocket[1] > 0 ) && canMOVERIGHT )
					self->ai.combatmovepushes[1] = 1;
			}

			ucmd->buttons |= BUTTON_SPECIAL;
		}
		else
		if( dist < 150 ) // range = AIWEAP_MELEE_RANGE;
		{
			if( self->s.weapon == WEAP_GUNBLADE ) // go into him!
			{
				ucmd->buttons &= ~BUTTON_ATTACK; // remove pressing fire
				if( canMOVEFRONT )  // move to your enemy
					self->ai.combatmovepushes[0] = 1;
				else if( c <= 0.5 && canMOVELEFT )
					self->ai.combatmovepushes[1] = -1;
				else if( canMOVERIGHT )
					self->ai.combatmovepushes[1] = 1;
			}
			else
			{
				//priorize sides
				if( canMOVELEFT || canMOVERIGHT )
				{
					if( canMOVELEFT && canMOVERIGHT )
					{
						self->ai.combatmovepushes[1] = c < 0.5 ? -1 : 1;
					}
					else if( canMOVELEFT )
					{
						self->ai.combatmovepushes[1] = -1;
					}
					else
					{
						self->ai.combatmovepushes[1] = 1;
					}
				}

				if( c < 0.3 && canMOVEBACK )
					self->ai.combatmovepushes[0] = -1;
			}

		}
		else if( dist < 500 ) //AIWEAP_SHORT_RANGE limit is Grenade Laucher range
		{
			if( canMOVELEFT || canMOVERIGHT )
			{
				if( canMOVELEFT && canMOVERIGHT )
				{
					self->ai.combatmovepushes[1] = c < 0.5 ? -1 : 1;
				}
				else if( canMOVELEFT )
				{
					self->ai.combatmovepushes[1] = -1;
				}
				else
				{
					self->ai.combatmovepushes[1] = 1;
				}
			}

			if( c < 0.3 && canMOVEFRONT )
			{
				self->ai.combatmovepushes[0] = 1;
			}

		}
		else if( dist < 900 )
		{
			if( canMOVELEFT || canMOVERIGHT )
			{
				if( canMOVELEFT && canMOVERIGHT )
				{
					self->ai.combatmovepushes[1] = c < 0.5 ? -1 : 1;
				}
				else if( canMOVELEFT )
				{
					self->ai.combatmovepushes[1] = -1;
				}
				else
				{
					self->ai.combatmovepushes[1] = 1;
				}
			}
		}
		else //range = AIWEAP_LONG_RANGE;
		{
			if( c < 0.75 && ( canMOVELEFT || canMOVERIGHT ) )
			{
				if( canMOVELEFT && canMOVERIGHT )
				{
					self->ai.combatmovepushes[1] = c < 0.5 ? -1 : 1;
				}
				else if( canMOVELEFT )
				{
					self->ai.combatmovepushes[1] = -1;
				}
				else
				{
					self->ai.combatmovepushes[1] = 1;
				}
			}
		}
	}

	if( !rocket && ( self->health < 25 || ( dist >= 500 && c < 0.2 ) || ( dist >= 1000 && c < 0.5 ) ) )
	{
		if( AI_FollowPath( self ) )
		{
			BOT_DMclass_Move( self, ucmd );
		}
	}

	if( !self->ai.camp_item )
	{
		ucmd->forwardmove = self->ai.combatmovepushes[0];
	}
	ucmd->sidemove = self->ai.combatmovepushes[1];
	ucmd->upmove = self->ai.combatmovepushes[2];
}



//==========================================
// BOT_DMclass_FindEnemy
// Scan for enemy (simplifed for now to just pick any visible enemy)
//==========================================
void BOT_DMclass_FindEnemy( edict_t *self )
{
	int i;

	edict_t	*bestenemy = NULL;
	float bestweight = 99999;
	float weight;

	// we also latch NULL enemies, so the bot can loose them
	if( self->ai.enemyReactionDelay > 0 )
	{
		self->ai.enemyReactionDelay -= game.frametime;
		return;
	}

	self->enemy = self->ai.latched_enemy;

	// if in a jump or a jumppad fly, don't accept new enemies
	if( ( !self->groundentity && ( self->ai.current_link_type & LINK_JUMP ) )
	   || ( !self->groundentity && ( self->ai.current_link_type & LINK_JUMPPAD ) ) )
	{
		AI_NewEnemyInView( self, NULL );
		return;
	}

	// start finding new enemies again
	for( i = 0; i < num_AIEnemies; i++ )
	{
		if( AIEnemies[i] == NULL || AIEnemies[i] == self
		    || AIEnemies[i]->r.solid == SOLID_NOT )
			continue;

		//Ignore players with 0 weight (was set at botstatus)
		if( self->ai.status.playersWeights[i] == 0 )
			continue;

		if( AIEnemies[i]->ai.notarget || AIEnemies[i]->deadflag )
			continue;

		if( !AIEnemies[i]->deadflag && G_Visible( self, AIEnemies[i] ) &&
		   trap_inPVS( self->s.origin, AIEnemies[i]->s.origin ) )
		{
			//(weight enemies from fusionbot) Is enemy visible, or is it too close to ignore
			weight = DistanceFast( self->s.origin, AIEnemies[i]->s.origin );

			// if weight is lesser than 0.1 only react to enemies very, very close to you
			if( self->ai.status.playersWeights[i] < 0.1f && weight > 300 )
				continue;

			//modify weight based on precomputed player weights
			weight *= ( 1.0 - self->ai.status.playersWeights[i] );

			if( G_InFront( self, AIEnemies[i] ) ||
			   ( weight < 300 ) )
			{
				// Check if best target, or better than current target
				if( weight < bestweight )
				{
					bestweight = weight;
					bestenemy = AIEnemies[i];
				}
			}
		}
	}

	AI_NewEnemyInView( self, bestenemy );
}

//==========================================
// BOT_DMClass_ChangeWeapon
//==========================================
static qboolean BOT_DMClass_ChangeWeapon( edict_t *ent, gitem_t *item )
{
	int ammocount, weakammocount;

	// see if we're already using it
	if( !item || item->tag == ent->s.weapon )
		return qtrue;

	// Has not picked up weapon yet
	if( !ent->r.client->inventory[item->tag] )
		return qfalse;

	// Do we have ammo for it?
	if( item->ammo_tag )
		ammocount = ent->r.client->inventory[item->ammo_tag];
	else
		ammocount = 0;

	if( item->weakammo_tag )
		weakammocount = ent->r.client->inventory[item->weakammo_tag];
	else
		weakammocount = 0;

	if( !ammocount && !weakammocount )
		return qfalse;

	// Change to this weapon
	ent->r.client->latched_weapon = item->tag;
	ent->ai.changeweapon_timeout = level.time + 2000;

	ChangeWeapon( ent );

	return qtrue;
}

//==========================================
// BOT_DMclass_ChooseWeapon
// Choose weapon based on range & weights
//==========================================
static float BOT_DMclass_ChooseWeapon( edict_t *self )
{
	float dist;
	int i;
	float best_weight = 0.0;
	gitem_t	*best_weapon = NULL;
	int weapon_range = 0;

	// if no enemy, then what are we doing here?
	if( !self->enemy )
	{
		weapon_range = AIWEAP_MEDIUM_RANGE;
		if( self->s.weapon == WEAP_GUNBLADE )
		{
			self->ai.changeweapon_timeout = level.time;
		}
	}
	else // Base weapon selection on distance:
	{
		dist = DistanceFast( self->s.origin, self->enemy->s.origin );

		if( dist < 150 )
			weapon_range = AIWEAP_MELEE_RANGE;

		else if( dist < 500 )  //Medium range limit is Grenade Laucher range
			weapon_range = AIWEAP_SHORT_RANGE;

		else if( dist < 900 )
			weapon_range = AIWEAP_MEDIUM_RANGE;

		else
			weapon_range = AIWEAP_LONG_RANGE;
	}

	if( self->ai.changeweapon_timeout > level.time )
		return AIWeapons[self->s.weapon].RangeWeight[weapon_range]; //return current

	for( i = 0; i < WEAP_TOTAL; i++ )
	{
		float rangeWeight;

		if( !AIWeapons[i].weaponItem )
			continue;

		//ignore those we don't have
		if( !self->r.client->inventory[AIWeapons[i].weaponItem->tag] )
			continue;

		//ignore those we don't have ammo for
		if( AIWeapons[i].ammoItem != NULL //excepting for those not using ammo
		    && !self->r.client->inventory[AIWeapons[i].ammoItem->tag]
		    && !self->r.client->inventory[AIWeapons[i].ammoWeakItem->tag] )
			continue;

		rangeWeight = AIWeapons[i].RangeWeight[weapon_range];

		if( i && self->r.client->inventory[AIWeapons[i].ammoItem->tag] )
		{
			// We have Strong Ammo (weigh up)
			rangeWeight *= 2;
		}

		//compare range weights
		if( rangeWeight > best_weight )
		{
			best_weight = rangeWeight;
			best_weapon = AIWeapons[i].weaponItem;
		}
		//jal: enable randomnes later
		//else if (AIWeapons[i].RangeWeight[weapon_range] == best_weight && random() > 0.2) {	//allow some random for equal weights
		//	best_weight = AIWeapons[i].RangeWeight[weapon_range];
		//	best_weapon = AIWeapons[i].weaponItem;
		//}
	}

	//do the change (same weapon, or null best_weapon is covered at ChangeWeapon)
	BOT_DMClass_ChangeWeapon( self, best_weapon );

	return AIWeapons[self->s.weapon].RangeWeight[weapon_range]; //return current
}

//==========================================
// BOT_DMclass_CheckShot
// Checks if shot is blocked (doesn't verify it would hit)
//==========================================
static qboolean BOT_DMclass_CheckShot( edict_t *ent, vec3_t point )
{
	trace_t tr;
	vec3_t start, forward, right, offset;

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );

	VectorSet( offset, 0, 0, ent->viewheight );
	G_ProjectSource( ent->s.origin, offset, forward, right, start );

	//blocked, don't shoot
	//tr = gi.trace( start, vec3_origin, vec3_origin, point, ent, MASK_AISOLID);
	G_Trace( &tr, start, vec3_origin, vec3_origin, point, ent, MASK_AISOLID );
	if( tr.fraction < 0.8f )
	{
		if( tr.ent < 1 || !game.edicts[tr.ent].takedamage || game.edicts[tr.ent].movetype == MOVETYPE_PUSH )
			return qfalse;

		// check if the player we found is at our team
		if( game.edicts[tr.ent].s.team == ent->s.team && GS_Gametype_IsTeamBased( game.gametype ) )
			return qfalse;
	}

	return qtrue;
}

//==========================================
// BOT_DMclass_PredictProjectileShot
// predict target movement
//==========================================
static void BOT_DMclass_PredictProjectileShot( edict_t *self, vec3_t fire_origin, float projectile_speed, vec3_t target, vec3_t target_velocity )
{
	vec3_t predictedTarget;
	vec3_t targetMovedir;
	float targetSpeed;
	float predictionTime;
	float distance;
	trace_t	trace;
	int contents;

	if( projectile_speed <= 0.0f )
		return;

	targetSpeed = VectorNormalize2( target_velocity, targetMovedir );

	// ok, this is not going to be 100% precise, since we will find the
	// time our projectile will take to travel to enemy's CURRENT position,
	// and them find enemy's position given his CURRENT velocity and his CURRENT dir
	// after prediction time. The result will be much better if the player
	// is moving to the sides (relative to us) than in depth (relative to us).
	// And, of course, when the player moves in a curve upwards it will totally miss (ie, jumping).

	distance = DistanceFast( fire_origin, target );
	predictionTime = distance/projectile_speed;
	VectorMA( target, predictionTime*targetSpeed, targetMovedir, predictedTarget );

	// if this position is inside solid, try finding a position at half of the prediction time
	contents = G_PointContents( predictedTarget );
	if( contents & CONTENTS_SOLID && !( contents & CONTENTS_PLAYERCLIP ) )
	{
		VectorMA( target, ( predictionTime * 0.5f )*targetSpeed, targetMovedir, predictedTarget );
		contents = G_PointContents( predictedTarget );
		if( contents & CONTENTS_SOLID && !( contents & CONTENTS_PLAYERCLIP ) )
			return; // INVALID
	}

	// if we can see this point, we use it, otherwise we keep the current position
	G_Trace( &trace, fire_origin, vec3_origin, vec3_origin, predictedTarget, self, MASK_SHOT );
	if( trace.fraction == 1.0f || ( trace.ent && game.edicts[trace.ent].takedamage ) )
		VectorCopy( predictedTarget, target );
}

//==========================================
// BOT_DMclass_FireWeapon
// Fire if needed
//==========================================
static qboolean BOT_DMclass_FireWeapon( edict_t *self, usercmd_t *ucmd )
{
#define WFAC_GENERIC_PROJECTILE 300.0
#define WFAC_GENERIC_INSTANT 150.0
	float firedelay;
	vec3_t target;
	vec3_t angles;
	int weapon;
	float wfac;
	vec3_t fire_origin;
	vec3_t dir;
	trace_t	trace;
	firedef_t *firedef = Player_GetCurrentWeaponFiredef( self );

	if( !self->enemy )
		return qfalse;

	weapon = self->s.weapon;
	if( weapon < 0 || weapon >= WEAP_TOTAL )
		weapon = 0;

	if( !firedef )
		return qfalse;

	// Aim
	VectorCopy( self->enemy->s.origin, target );
	fire_origin[0] = self->s.origin[0];
	fire_origin[1] = self->s.origin[1];
	fire_origin[2] = self->s.origin[2] + self->viewheight;

	if( !BOT_DMclass_CheckShot( self, target ) )
		return qfalse;

	// find out our weapon AIM style
	if( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION_EXPLOSIVE )
	{
		// in the lowest skill level, don't predict projectiles
		if( self->ai.pers.skillLevel >= 0.33f )
			BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity );

		wfac = WFAC_GENERIC_PROJECTILE * 1.3;

		//aim to the feets when enemy isn't higher
		if( fire_origin[2] > ( target[2] + ( self->enemy->r.mins[2] * 0.8 ) ) )
		{
			vec3_t checktarget;
			VectorSet( checktarget,
			           self->enemy->s.origin[0],
			           self->enemy->s.origin[1],
			           self->enemy->s.origin[2] + self->enemy->r.mins[2] + 4 );

			G_Trace( &trace, fire_origin, vec3_origin, vec3_origin, checktarget, self, MASK_SHOT );
			if( trace.fraction == 1.0f || ( trace.ent > 0 && game.edicts[trace.ent].takedamage ) )
				VectorCopy( checktarget, target );
		}
		else if( !AI_IsStep( self->enemy ) )
			wfac *= 2.5; // more imprecise for air rockets
	}
	else if( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION )
	{
		if( self->s.weapon == WEAP_PLASMAGUN )
			wfac = WFAC_GENERIC_PROJECTILE * 0.5;
		else
			wfac = WFAC_GENERIC_PROJECTILE;

		// in the lowest skill level, don't predict projectiles
		if( self->ai.pers.skillLevel >= 0.33f )
			BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity );
	}
	else if( AIWeapons[weapon].aimType == AI_AIMSTYLE_DROP )
	{
		//jalToDo
		wfac = WFAC_GENERIC_PROJECTILE;
		// in the lowest skill level, don't predict projectiles
		if( self->ai.pers.skillLevel >= 0.33f )
			BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity );

	}
	else // AI_AIMSTYLE_INSTANTHIT
	{
		if( self->s.weapon == WEAP_ELECTROBOLT )
			wfac = WFAC_GENERIC_INSTANT;
		else if( self->s.weapon == WEAP_LASERGUN )
			wfac = WFAC_GENERIC_INSTANT * 1.5;
		else
			wfac = WFAC_GENERIC_INSTANT;
	}

	wfac = 25 + wfac * ( 1.0f - self->ai.pers.skillLevel );

	// look to target
	VectorSubtract( target, fire_origin, self->ai.move_vector );

	if( self->r.client->weaponstate.status == WEAPON_READY )
	{
		// in continuous fire weapons don't add delays
		if( self->s.weapon == WEAP_LASERGUN || self->s.weapon == WEAP_PLASMAGUN )
			firedelay = 1.0f;
		else
			firedelay = ( 1.0f - self->ai.pers.skillLevel ) - ( random()-0.25f );

		if( firedelay > 0.0f )
		{
			ucmd->buttons = BUTTON_ATTACK;; // could fire, but wants to?
			// mess up angles only in the attacking frames
			if( self->r.client->weaponstate.status == WEAPON_READY )
			{
				if( (self->s.weapon == WEAP_LASERGUN) || (self->s.weapon == WEAP_PLASMAGUN) ) {
				    target[0] += sinf( (float)level.time/100.0) * wfac;
				    target[1] += cosf( (float)level.time/100.0) * wfac;
				}
				else
				{
				    target[0] += ( random()-0.5f ) * wfac;
				    target[1] += ( random()-0.5f ) * wfac;
				}
			}
		}
	}

	//update angles
	VectorSubtract( target, fire_origin, dir );
	VecToAngles( dir, angles );
	VectorCopy( angles, self->s.angles );
	VectorCopy( angles, self->r.client->ps.viewangles );

	if( AIDevel.debugChased && bot_showcombat->integer )
		G_PrintMsg( AIDevel.chaseguy, "%s: attacking %s\n", self->ai.pers.netname, self->enemy->r.client ? self->enemy->r.client->pers.netname : self->classname );

	return qtrue;
}


//==========================================
// BOT_DMclass_WeightPlayers
// weight players based on game state
//==========================================
static void BOT_DMclass_WeightPlayers( edict_t *self )
{
	int i, team;
	qboolean rage_mode = qfalse;

	if( game.gametype != GAMETYPE_CTF )
	{
		if( self->r.client->quad_timeout > level.time )
			rage_mode = qtrue;
		if( self->r.client->shell_timeout > level.time )
			rage_mode = qtrue;
	}

	//clear
	memset( self->ai.status.playersWeights, 0, sizeof( self->ai.status.playersWeights ) );

	for( i = 0; i < num_AIEnemies; i++ )
	{
		if( !AIEnemies[i] )
			continue;

		if( AIEnemies[i] == self )
			continue;

		//ignore spectators and dead players
		if( AIEnemies[i]->s.solid == SOLID_NOT || G_IsDead( AIEnemies[i] ) || AIEnemies[i]->ai.notarget )
		{
			continue;
		}

		//if not team based give some weight to every one
		if( !GS_Gametype_IsTeamBased( game.gametype ) )
		{
			self->ai.status.playersWeights[i] = rage_mode ? 8.0f : 0.3f;
			continue;
		}

		//team based. Ignore team mates
		if( AIEnemies[i]->s.team == self->s.team )
			continue;

		// dont fight with quad.
		if( AIEnemies[i]->r.client->quad_timeout > level.time )
		{
			self->ai.status.playersWeights[i] = 0.01f;
			continue;
		}

		// dont fight with shell neither.
		if( AIEnemies[i]->r.client->shell_timeout > level.time )
		{
			self->ai.status.playersWeights[i] = 0.01f;
			continue;
		}

		if( game.gametype == GAMETYPE_CTF )
		{
			qboolean has_enemy_flag = qfalse;

			//if the bot has a enemy flag we don't want it going after enemies
			for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
			{
				if( team == self->s.team ) continue;
				if( G_Gametype_CTF_HasFlag( self, team ) )
				{
					has_enemy_flag = qtrue;
					break;
				}
			}

			if( has_enemy_flag )
			{
				//everyone has ultra-low weight
				self->ai.status.playersWeights[i] = 0.01f;
			}
			else
			{
				//being at enemy team gives a small weight
				self->ai.status.playersWeights[i] = 0.2f;

				//if enemy has our flag, big weight on enemy
				if( G_Gametype_CTF_HasFlag( AIEnemies[i], self->s.team ) )
					self->ai.status.playersWeights[i] = 0.9f;
			}
		}
		else
		{
			// is some other team based gametype
			self->ai.status.playersWeights[i] = rage_mode ? 8.0f : 0.3f;
		}
	}
}


//==========================================
// BOT_DMclass_WantedFlag
// find needed flag
//==========================================
static gitem_t *BOT_DMclass_WantedFlag( edict_t *self )
{
	qboolean has_enemy_flag = qfalse;
	edict_t	*ent;
	int team;

	if( !self->r.client )
		return NULL;

	if( !self->s.team )
		G_Printf( "ERROR: BOT_DMclass_WantedFlag: Player without a defined team\n" );

	//basically, if we have any other's team flag, we want ours, and vice versa

	//see if we have any others flag
	for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
	{
		if( team == self->s.team ) continue;
		if( G_Gametype_CTF_HasFlag( self, team ) )
		{
			has_enemy_flag = qtrue;
			break;
		}
	}

	//we have it, so we want ours
	if( has_enemy_flag )
		return G_Gametype_CTF_FlagItem( self->s.team );

	//TO DO: right now it just returns the first it finds, we should set up all them
	for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
	{
		gitem_t	*flag = NULL;
		if( team == self->s.team ) continue;
		flag = G_Gametype_CTF_FlagItem( team );
		if( !flag )
			continue;

		ent = NULL;
		while( ( ent = G_Find( ent, FOFS( classname ), flag->classname ) ) != NULL )
		{
			if( !( ent->spawnflags & DROPPED_ITEM ) )
			{
				if( ent->r.solid == SOLID_NOT )  //flag is not available
					break;
				else
					return flag;
			}
		}
	}

	return NULL;
}




//==========================================
// BOT_DMclass_WantedFlag
// check a timed item whether it will be there
//==========================================

static void BOT_DMclass_WeighTimedItem( edict_t *self, char *cname, int tag )
{

	edict_t *it = G_Find( NULL, FOFS( classname ), cname );

	if( !it )
	{
		self->ai.status.inventoryWeights[tag] = 0.0f;
		return;
	}

	if( ( it->nextthink == 0 ) || ( it->nextthink < ( level.time + AI_TIMEDITEM_CAMPTIME ) && ( it->think == DoRespawn ) ) )
	{
		//printf( "%s seems to be there. nextthink %d. level.time %d\n", cname, it->nextthink, level.time );
		return;

	}
	else
	{
		self->ai.status.inventoryWeights[tag] = 0.0f;
	}
}

#ifndef WSW_RELEASE
static void BOT_DMclass_WeighCaptureArea( edict_t *self, char *cname, int tag )
{

	edict_t *it = G_Find( NULL, FOFS( classname ), cname );

	if( !it )
	{
		self->ai.status.inventoryWeights[tag] = 0.0f;
		return;
	}

	if( ( it->team == self->team ) )
	{
		self->ai.status.inventoryWeights[tag] = 0.0f;
		return;

	}
}
#endif
//==========================================
// BOT_DMclass_WeightInventory
// weight items up or down based on bot needs
//==========================================
static void BOT_DMclass_WeightInventory( edict_t *self )
{
	float LowNeedFactor = 0.5;
	gclient_t *client;
	int i;
	qboolean onlyGotGB = qtrue;

	client = self->r.client;

	//reset with persistant values
	memcpy( self->ai.status.inventoryWeights, self->ai.pers.inventoryWeights, sizeof( self->ai.pers.inventoryWeights ) );

	//AMMO:

	for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ )
	{
		//find out if it's packed up
		if( !AI_CanPick_Ammo( self, AIWeapons[i].ammoItem ) )
			self->ai.status.inventoryWeights[AIWeapons[i].ammoItem->tag] = 0.0;
		//find out if it has a weapon for this amno
		else if( !client->inventory[AIWeapons[i].weaponItem->tag] )
			self->ai.status.inventoryWeights[AIWeapons[i].ammoItem->tag] *= LowNeedFactor;
	}

	//WEAPONS


	//weight weapon down if bot already has it
	for( i = WEAP_GUNBLADE+1; i < WEAP_TOTAL; i++ )
	{
		if( AIWeapons[i].weaponItem && client->inventory[AIWeapons[i].weaponItem->tag] )
		{
			self->ai.status.inventoryWeights[AIWeapons[i].weaponItem->tag] *= LowNeedFactor;
			onlyGotGB = qfalse;
		}
	}

	// if only GB weigh weapons up. (get one real weapon at least)
	if( onlyGotGB )
	{
		for( i = WEAP_GUNBLADE+1; i < WEAP_TOTAL; i++ )
		{
			if( AIWeapons[i].weaponItem && client->inventory[AIWeapons[i].weaponItem->tag] )
			{
				self->ai.status.inventoryWeights[AIWeapons[i].weaponItem->tag] = 3.0f;
			}
		}
	}

	// ARMOR
	for( i = ARMOR_GA; i <= ARMOR_SHARD; i++ )
	{
		self->ai.status.inventoryWeights[i] = self->ai.pers.inventoryWeights[i] * AI_CanUseArmor( game.items[i], self );
	}

	for( i = POWERUP_QUAD; i <= POWERUP_SHELL; i++ )
	{
		self->ai.status.inventoryWeights[i] = self->ai.pers.inventoryWeights[i];
	}

	if( (game.gametype == GAMETYPE_TDM) || game.gametype == GAMETYPE_DUEL )
	{
		BOT_DMclass_WeighTimedItem( self, "item_armor_ra", ARMOR_RA );
		BOT_DMclass_WeighTimedItem( self, "item_health_mega", HEALTH_MEGA );
		BOT_DMclass_WeighTimedItem( self, "item_quad", POWERUP_QUAD );
		BOT_DMclass_WeighTimedItem( self, "item_warshell", POWERUP_SHELL );
	}



	//CTF:

	if( game.gametype == GAMETYPE_CTF )
	{
		gitem_t	*wantedFlag, *otherflag;
		int team;

		wantedFlag = BOT_DMclass_WantedFlag( self ); //Returns the flag gitem_t

		// all flags have weights defined inside persistent inventory. Remove weight from the unwanted one/s.
		for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
		{
			otherflag = G_Gametype_CTF_FlagItem( team );
			if( otherflag && otherflag != wantedFlag )
				self->ai.status.inventoryWeights[otherflag->tag] = 0.0;
			else if( wantedFlag == G_Gametype_CTF_FlagItem( self->s.team ) )
				self->ai.status.inventoryWeights[wantedFlag->tag] = self->ai.pers.inventoryWeights[wantedFlag->tag] * 2;
		}
	}
	else if( ( game.gametype == GAMETYPE_TDM ) && g_instagib->integer )
	{

		//BOT_DMclass_WeighCaptureArea( self, "capture_area_indicator", CAPTURE_AREA_MARKER );

	}
}

//==========================================
// BOT_DMclass_UpdateStatus
// update ai.status values based on bot state,
// so ai can decide based on these settings
//==========================================
static void BOT_DMclass_UpdateStatus( edict_t *self )
{
	self->movetarget = NULL;

	//if the bot is at spectator team, try to join
	if( self->s.team == TEAM_SPECTATOR )
	{
		// wait a few seconds after map restarts
		if( level.spawnedTimeStamp + 2000 > game.realtime )
		{
			self->ai.blocked_timeout = level.time + 20000;
			self->nextthink = level.time + 2000 + (int)( 6000 * random() );
			return;
		}

		if( AIDevel.debugMode && bot_showcombat->integer > 2 )
			G_PrintMsg( AIDevel.chaseguy, "%s: teamspec\n", self->ai.pers.netname );
		//extend the suicide timout
		self->ai.blocked_timeout = level.time + 20000;

		//try to join
		if( !self->r.client->pers.queueTimeStamp )
			G_Teams_JoinAnyTeam( self, qfalse );
		if( self->s.team == TEAM_SPECTATOR ) //couldn't join, delay the next think
		{
			self->nextthink = level.time + 1000 + (int)( 6000 * random() );
			if( AIDevel.debugMode && bot_showcombat->integer > 2 )
				G_PrintMsg( AIDevel.chaseguy, "%s: teamspec after join\n", self->ai.pers.netname );
			return;
		}
		else
		{
			self->timeStamp = level.time + 1000 + ( 8000 * random() );
			self->nextthink = level.time + 1000 + ( 8000 * random() );
			if( AIDevel.debugMode && bot_showcombat->integer > 2 )
				G_PrintMsg( AIDevel.chaseguy, "%s: setting timestamp\n", self->ai.pers.netname );
		}

	}
	else //get ready if in the game
	{
		if( level.time > self->timeStamp )
			G_Match_Ready( self );
	}

	// Set up for new client movement: jalfixme
	VectorCopy( self->r.client->ps.viewangles, self->s.angles );
	VectorSet( self->r.client->ps.pmove.delta_angles, 0, 0, 0 );

	if( self->r.client->ps.pmove.pm_flags & PMF_JUMPPAD_TIME )
		self->ai.status.jumpadReached = qtrue; //jumpad time from client to botStatus
	else
		self->ai.status.jumpadReached = qfalse;

	if( self->r.client->ps.pmove.pm_flags & PMF_TIME_TELEPORT )
		self->ai.status.TeleportReached = qtrue;
	else
		self->ai.status.TeleportReached = qfalse;

	//set up AI status for the upcoming AI_frame
	BOT_DMclass_WeightInventory( self ); //weight items
	BOT_DMclass_WeightPlayers( self );  //weight players
}

//==========================================
// BOT_DMclass_VSAYmessages
//==========================================
static void BOT_DMclass_VSAYmessages( edict_t *self )
{
	gitem_t *itemgoal = NULL;

	if( match.state != MATCH_STATE_PLAYTIME )
		return;

	if( self->snap.damageteam_given > 25 )
	{
		if( rand() & 1 )
		{
			if( rand() & 1 )
			{
				G_BOTvsay_f( self, "oops", qtrue );
			}
			else
			{
				G_BOTvsay_f( self, "sorry", qtrue );
			}
		}
		return;
	}

	if( self->ai.vsay_timeout > level.time )
		return;

	if( match.endtime && level.time + 4000 > match.endtime )
	{
		self->ai.vsay_timeout = level.time + ( 1000 + match.endtime - level.time );
		if( rand() & 1 )
			G_BOTvsay_f( self, "goodgame", qfalse );
		return;
	}

	self->ai.vsay_timeout = level.time + ( ( 8+random()*12 ) * 1000 );

	// the more bots, the less vsays to play
	if( random() > 0.1 + 1.0f / game.numBots )
		return;

	if( GS_Gametype_IsTeamBased( game.gametype ) && ( game.gametype != GAMETYPE_DUEL ) )
	{
		if( self->ai.vsay_goalent && self->ai.vsay_goalent->item )
		{
			itemgoal = self->ai.vsay_goalent->item;

			if( game.gametype == GAMETYPE_CTF )
			{
				gitem_t *wantedFlag = BOT_DMclass_WantedFlag( self );
				gitem_t *myFlag = G_Gametype_CTF_FlagItem( self->s.team );
				if( wantedFlag == myFlag && itemgoal == wantedFlag && random() > 0.7 )
				{
					G_BOTvsay_f( self, "needbackup", qtrue );
					return;
				}
				if( itemgoal == wantedFlag && random() > 0.7 )
				{
					G_BOTvsay_f( self, "onoffense", qtrue );
					return;
				}
			}
		}

		if( self->health < 20 && random() > 0.3 )
		{
			G_BOTvsay_f( self, "needhealth", qtrue );
			return;
		}

		if( ( self->s.weapon == 0 || self->s.weapon == 1 ) && random() > 0.7 )
		{
			G_BOTvsay_f( self, "needweapon", qtrue );
			return;
		}

		if( self->r.client->armor < 10 && random() > 0.8 )
		{
			G_BOTvsay_f( self, "needarmor", qtrue );
			return;
		}
	}

	// NOT team based here

	if( random() > 0.2 )
		return;

	switch( (int)brandom( 1, 8 ) )
	{
	default:
		break;
	case 1:
		G_BOTvsay_f( self, "roger", qfalse );
		break;
	case 2:
		G_BOTvsay_f( self, "noproblem", qfalse );
		break;
	case 3:
		G_BOTvsay_f( self, "yeehaa", qfalse );
		break;
	case 4:
		G_BOTvsay_f( self, "yes", qfalse );
		break;
	case 5:
		G_BOTvsay_f( self, "no", qfalse );
		break;
	case 6:
		G_BOTvsay_f( self, "booo", qfalse );
		break;
	case 7:
		G_BOTvsay_f( self, "attack", qfalse );
		break;
	case 8:
		G_BOTvsay_f( self, "ok", qfalse );
		break;
	}
}


//==========================================
// BOT_DMClass_BlockedTimeout
// the bot has been blocked for too long
//==========================================
static void BOT_DMClass_BlockedTimeout( edict_t *self )
{
	self->health = 0;
	self->ai.blocked_timeout = level.time + 15000;
	self->die( self, self, self, 100000, vec3_origin );
	self->nextthink = level.time + 1;
}

//==========================================
// BOT_DMclass_DeadFrame
// ent is dead = run this think func
//==========================================
static void BOT_DMclass_DeadFrame( edict_t *self )
{
	usercmd_t ucmd;

	ucmd.buttons = 0;

	// set approximate ping and show values
	ucmd.msec = 75 + floor( random() * 25 ) + 1;
//	self->r.client->r.ping = ucmd.msec;

	// ask for respawn if the minimum bot respawning time passed
	if( level.time > self->deathTimeStamp + 3000 )
	{
		ucmd.buttons = BUTTON_ATTACK;
	}

	ClientThink( self, &ucmd, 0 );
	self->nextthink = level.time + 1;
}


//==========================================
// BOT_DMclass_RunFrame
// States Machine & call client movement
//==========================================
static void BOT_DMclass_RunFrame( edict_t *self )
{
	usercmd_t ucmd;
	float weapon_quality;
	memset( &ucmd, 0, sizeof( ucmd ) );


	if( !bot_dummy->integer )
	{

		// Look for enemies
		BOT_DMclass_FindEnemy( self );
		weapon_quality = BOT_DMclass_ChooseWeapon( self );
		//	if( self->ai.rush_item )
		//	{
		//		self->ai.state = BOT_STATE_MOVE;
		//		self->ai.combatmovepush_timeout = level.time;
		//	}
		//	else
		if( self->enemy && weapon_quality >= 0.3 )    // don't fight with bad weapons
		{
			if( BOT_DMclass_FireWeapon( self, &ucmd ) )
			{
				self->ai.state = BOT_STATE_ATTACK;
				self->ai.state_combat_timeout = level.time + 500;
			}
		}
		else if( self->ai.state == BOT_STATE_ATTACK &&
			level.time > self->ai.state_combat_timeout )
		{
			//Jalfixme: change to: AI_SetUpStateMove(self);
			self->ai.state = BOT_STATE_MOVE;
			self->ai.combatmovepush_timeout = level.time;
		}

		// Execute the move, or wander
		if( self->ai.state == BOT_STATE_MOVE )
			BOT_DMclass_Move( self, &ucmd );

		else if( self->ai.state == BOT_STATE_ATTACK )
			BOT_DMclass_CombatMovement( self, &ucmd );

		else if( self->ai.state == BOT_STATE_WANDER )
			BOT_DMclass_Wander( self, &ucmd );

		//set up for pmove
		ucmd.angles[PITCH] = ANGLE2SHORT( self->s.angles[PITCH] );
		ucmd.angles[YAW] = ANGLE2SHORT( self->s.angles[YAW] );
		ucmd.angles[ROLL] = ANGLE2SHORT( self->s.angles[ROLL] );

	}

	// set approximate ping and show values
	ucmd.msec = game.frametime;

	ucmd.forwardfrac = ucmd.forwardmove;
	clamp( ucmd.forwardfrac, -1, 1 );
	ucmd.sidefrac = ucmd.sidemove;
	clamp( ucmd.sidefrac, -1, 1 );
	ucmd.upfrac = ucmd.upmove;
	clamp( ucmd.upfrac, -1, 1 );

	//	self->r.client->r.ping = ucmd.msec;

	// send command through id's code
	ClientThink( self, &ucmd, 0 );
	self->nextthink = level.time + 1;

	// vsay
	BOT_DMclass_VSAYmessages( self );
}


//==========================================
// BOT_DMclass_InitPersistant
// Persistant after respawns.
//==========================================
void BOT_DMclass_InitPersistant( edict_t *self )
{
	self->classname = "dmbot";

	//copy name
	if( self->r.client->pers.netname )
		self->ai.pers.netname = self->r.client->pers.netname;
	else
		self->ai.pers.netname = "dmBot";

	//set 'class' functions
	self->ai.pers.RunFrame = BOT_DMclass_RunFrame;
	self->ai.pers.UpdateStatus = BOT_DMclass_UpdateStatus;
	self->ai.pers.blockedTimeout = BOT_DMClass_BlockedTimeout;
	self->ai.pers.deadFrame = BOT_DMclass_DeadFrame;

	//available moveTypes for this class
	self->ai.pers.moveTypesMask = ( LINK_MOVE|LINK_STAIRS|LINK_FALL|LINK_WATER|LINK_WATERJUMP|LINK_JUMPPAD|LINK_PLATFORM|LINK_TELEPORT|LINK_LADDER|LINK_JUMP|LINK_CROUCH | LINK_ROCKETJUMP );

	//Persistant Inventory Weights (0 = can not pick)
	memset( self->ai.pers.inventoryWeights, 0, sizeof( self->ai.pers.inventoryWeights ) );

	// weapons
	self->ai.pers.inventoryWeights[WEAP_GUNBLADE] = 0.0f;
	self->ai.pers.inventoryWeights[WEAP_SHOCKWAVE] = 0.0f;
	self->ai.pers.inventoryWeights[WEAP_RIOTGUN] = 0.5f;
	self->ai.pers.inventoryWeights[WEAP_GRENADELAUNCHER] = 0.6f;
	self->ai.pers.inventoryWeights[WEAP_ROCKETLAUNCHER] = 0.8f;
	self->ai.pers.inventoryWeights[WEAP_PLASMAGUN] = 0.7f;
	self->ai.pers.inventoryWeights[WEAP_ELECTROBOLT] = 0.8f;
	self->ai.pers.inventoryWeights[WEAP_LASERGUN] = 0.8f;

	//ammo
	//self->ai.pers.inventoryWeights[AMMO_WEAK_GUNBLADE] = 0.4f;
	self->ai.pers.inventoryWeights[AMMO_WAVES] = 0.1f;
	self->ai.pers.inventoryWeights[AMMO_SHELLS] = 0.5f;
	self->ai.pers.inventoryWeights[AMMO_GRENADES] = 0.5f;
	self->ai.pers.inventoryWeights[AMMO_ROCKETS] = 0.6f;
	self->ai.pers.inventoryWeights[AMMO_PLASMA] = 0.5f;
	self->ai.pers.inventoryWeights[AMMO_BOLTS] = 0.6f;
	self->ai.pers.inventoryWeights[AMMO_LASERS] = 0.6f;

	//armor
	self->ai.pers.inventoryWeights[ARMOR_RA] = 2.0f;
	self->ai.pers.inventoryWeights[ARMOR_YA] = 1.0f;
	self->ai.pers.inventoryWeights[ARMOR_GA] = 0.5f;
	self->ai.pers.inventoryWeights[ARMOR_SHARD] = 0.2f;

	//health
	self->ai.pers.inventoryWeights[HEALTH_MEGA] = 0.9f;
	self->ai.pers.inventoryWeights[HEALTH_LARGE] = 0.3f;
	self->ai.pers.inventoryWeights[HEALTH_MEDIUM] = 0.2f;
	self->ai.pers.inventoryWeights[HEALTH_SMALL] = 0.1f;

	//backpack
	self->ai.pers.inventoryWeights[AMMO_PACK] = 0.4f;

	self->ai.pers.inventoryWeights[POWERUP_QUAD] = 6.0f;
	self->ai.pers.inventoryWeights[POWERUP_SHELL] = 6.0f;

	if( game.gametype == GAMETYPE_CTF )
	{
		self->ai.pers.inventoryWeights[FLAG_ALPHA] = 8.0f;
		self->ai.pers.inventoryWeights[FLAG_BETA] = 8.0f;
		self->ai.pers.inventoryWeights[FLAG_GAMMA] = 8.0f;
		self->ai.pers.inventoryWeights[FLAG_DELTA] = 8.0f;
	}
	if( ( game.gametype == GAMETYPE_TDM ) && g_instagib->integer )
	{
		self->ai.pers.inventoryWeights[CAPTURE_AREA_INDICATOR_A] = 4.0f;
		self->ai.pers.inventoryWeights[CAPTURE_AREA_INDICATOR_B] = 4.0f;
		self->ai.pers.inventoryWeights[CAPTURE_AREA_INDICATOR_C] = 4.0f;
		self->ai.pers.inventoryWeights[CAPTURE_AREA_INDICATOR_D] = 4.0f;
	}
}
