/*
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"

//ACE



//==========================================
// BOT_DMclass_Move
// DMClass is generic bot class
//==========================================
void BOT_DMclass_Move(edict_t *self, usercmd_t *ucmd)
{
	int current_node_flags = 0;
	int next_node_flags = 0;
	int	current_link_type = 0;
	int i;

	current_node_flags = nodes[self->ai.current_node].flags;
	next_node_flags = nodes[self->ai.next_node].flags;
	if( AI_PlinkExists( self->ai.current_node, self->ai.next_node ))
	{
		current_link_type = AI_PlinkMoveType( self->ai.current_node, self->ai.next_node );
		//Com_Printf("%s\n", AI_LinkString( current_link_type ));
	}

	// Platforms
	if( current_link_type == LINK_PLATFORM )
	{
		// Move to the center
		self->ai.move_vector[2] = 0; // kill z movement
		if(VectorLengthFast(self->ai.move_vector) > 10)
			ucmd->forwardmove = 200; // walk to center

		AI_ChangeAngle(self);

		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)
					return; //wait for elevator
			}
		}
	}

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

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

	// jumping over (keep fall before this)
	if( 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 );
		VectorCopy( self->ai.move_vector, v2 );
		VectorNormalize( v2 );
		VectorMA( v1, 16, v2, v1 );
		v1[2] += self->r.mins[2];
		//trap_Trace( &trace, v1, tv(-2, -2, -AI_JUMPABLE_HEIGHT), tv(2, 2, 0), v1, self, MASK_AISOLID );
		trap_Trace( &trace, v1, tv(-2, -2, -AI_JUMPABLE_HEIGHT), tv(2, 2, 0), v1, self, MASK_AISOLID );
		if( !trace.startsolid && trace.fraction == 1.0 )
		{
			//jump!
			ucmd->forwardmove = 400;
			//prevent double jumping on crates
			VectorCopy( self->s.origin, v1 );
			v1[2] += self->r.mins[2];
			trap_Trace( &trace, v1, tv(-12, -12, -8), tv(12, 12, 0), v1, self, MASK_AISOLID );
			if( trace.startsolid )
				ucmd->upmove = 400;
			return;
		}
	}

	// 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 (AI_MoveToGoalEntity(self,ucmd))
		return;

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

		if( !(trap_PointContents(nodes[self->ai.next_node].origin) & MASK_WATER) ) // Exit water
			ucmd->upmove = 400;
		
		ucmd->forwardmove = 300;
		return;
	}

	// Check to see if stuck, and if so try to free us
 	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 = 400;

		return;
	}


	AI_ChangeAngle(self);

	// Otherwise move as fast as we can... 
	ucmd->forwardmove = 400;
}


//==========================================
// 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 + 0.5;
			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(trap_PointContents (temp) & MASK_WATER)
	if( trap_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 = 100;
			self->s.angles[PITCH] = -45;
		}
		else
			ucmd->upmove = 15;

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


	// Lava?
	temp[2]-=48;
	//if(trap_PointContents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME))
	if( trap_PointContents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME) )
	{
		self->s.angles[YAW] += random() * 360 - 180;
		ucmd->forwardmove = 400;
		if(self->groundentity)
			ucmd->upmove = 400;
		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 = 100;

		return;
	}


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


//==========================================
// 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;

	if(!self->enemy) {
		//do whatever (tmp move wander)
		if( AI_FollowPath(self) ) {
			BOT_DMclass_Move(self, ucmd);
		}
		return;
	}

	c = random();
	dist = DistanceFast( self->s.origin, self->enemy->s.origin );
	if( dist < 150 ) {
		//range = AIWEAP_MELEE_RANGE;
		if( self->s.weapon == WEAP_GUNBLADE ) { // go into him!
			ucmd->buttons &= ~BUTTON_ATTACK; // remove pressing fire 
			if( AI_CanMove(self,BOT_MOVE_FORWARD) ) // move to your enemy
				ucmd->forwardmove += 400;
			else if( c <= 0.5 && AI_CanMove(self,BOT_MOVE_LEFT) )
				ucmd->sidemove -= 400;
			else if( AI_CanMove(self,BOT_MOVE_RIGHT) )
				ucmd->sidemove += 400;
		} else {
			//priorize sides
			if( c < 0.4 && AI_CanMove(self,BOT_MOVE_LEFT) )
				ucmd->sidemove -= 400;
			else if( c < 0.8 && AI_CanMove(self,BOT_MOVE_RIGHT) )
				ucmd->sidemove += 400;
			else if( game.gametype != GAMETYPE_MIDAIR
				&& AI_CanMove(self,BOT_MOVE_BACK) )
				ucmd->forwardmove -= 400;
			else if( game.gametype == GAMETYPE_MIDAIR && (dist > 75)
				&& AI_CanMove(self,BOT_MOVE_FORWARD) ) // move to your enemy
				ucmd->forwardmove += 400;
		}

	} else if( dist < 500 ) {	//Medium range limit is Grenade Laucher range
		//range = AIWEAP_SHORT_RANGE;
		// Priorize forward for going into MELEE
		c = random();

		if( c < 0.30 && AI_CanMove(self,BOT_MOVE_LEFT) )
			ucmd->sidemove -= 400;
		else if( c < 0.60 && AI_CanMove(self,BOT_MOVE_RIGHT) )
			ucmd->sidemove += 400;
		else if( (game.gametype == GAMETYPE_MIDAIR || dist > 250) 
			&& AI_CanMove(self,BOT_MOVE_FORWARD) ) // move to your enemy
			ucmd->forwardmove += 400;
		else if( AI_CanMove(self,BOT_MOVE_FORWARD) )
			ucmd->forwardmove += 100;

	} else if(dist < 900) { // all dirs
		//range = AIWEAP_MEDIUM_RANGE;
		if( c < 0.2 && AI_CanMove(self,BOT_MOVE_LEFT) )
			ucmd->sidemove -= 400;
		else if( c < 0.4 && AI_CanMove(self,BOT_MOVE_RIGHT) )
			ucmd->sidemove += 400;
		//else if( c < 0.6 && AI_CanMove(self,BOT_MOVE_BACK) )
		//	ucmd->forwardmove -= 400;
		else if( c < 0.8 && AI_CanMove(self,BOT_MOVE_FORWARD) )
			ucmd->forwardmove += 400;

	} else {
		//range = AIWEAP_LONG_RANGE;
		// only move to the sides, and not always
		if( c < 0.2 && AI_CanMove(self,BOT_MOVE_LEFT) )
			ucmd->sidemove -= 400;
		else if( c < 0.4 && AI_CanMove(self,BOT_MOVE_RIGHT) )
			ucmd->sidemove += 400;
	}
}

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

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

	// we already set up an enemy this frame (reacting to attacks)
	if(self->enemy != NULL)
		return self->enemy;

	// Find Enemy
	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]->deadflag && G_Visible(self, AIEnemies[i]) &&
			trap_inPVS (self->s.origin, AIEnemies[i]->s.origin))
			//gi.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];
				}
			}
		}
	}

	// If best enemy, set up
	if(bestenemy)
	{
//		if (AIDevel.debugChased && bot_showcombat->value && bestenemy->ai.is_bot)
//			G_PrintMsg ( AIDevel.chaseguy, "%s: selected %s as enemy.\n",
//			self->ai.pers.netname,
//			bestenemy->ai.pers.netname );

		self->enemy = bestenemy;
		return self->enemy;
	}

	return NULL;	// NO enemy
}

//==========================================
// BOT_DMClass_ChangeWeapon
//==========================================
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 + 6.0;

	//if the bot has no weapon, don't wait for put down
	if( !ent->s.weapon )
		ChangeWeapon(ent);

	return qtrue;
}

//==========================================
// BOT_DMclass_ChooseWeapon
// Choose weapon based on range & weights
//==========================================
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)
		return qfalse;

	// 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++ )
	{
		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;
		
		//compare range weights
		if (AIWeapons[i].RangeWeight[weapon_range] > best_weight) {
			best_weight = AIWeapons[i].RangeWeight[weapon_range];
			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)
//==========================================
qboolean BOT_DMclass_CheckShot( edict_t *ent, vec3_t point )
{
	trace_t tr;
	vec3_t	start, forward, right, offset;

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

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

	//bloqued, don't shoot
	//tr = gi.trace( start, vec3_origin, vec3_origin, point, ent, MASK_AISOLID);
	trap_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;
}

firedef_t *Player_GetCurrentWeaponFiredef( edict_t *ent );
//==========================================
// BOT_DMclass_PredictProjectileShot
// predict target movement
//==========================================
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 = trap_PointContents( predictedTarget );
	if( contents & CONTENTS_SOLID && !(contents & CONTENTS_PLAYERCLIP) ) {
		VectorMA( target, (predictionTime * 0.5f)*targetSpeed, targetMovedir, predictedTarget );
		contents = trap_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
	trap_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
//==========================================
qboolean BOT_DMclass_FireWeapon( edict_t *self, usercmd_t *ucmd )
{
	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 )
	{
		BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity );

		if( game.gametype == GAMETYPE_MIDAIR )
			wfac = 40.0f; // highest precission in midair
		else
			wfac = 200.0f;

		//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 );

			trap_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( game.gametype != GAMETYPE_MIDAIR && !AI_IsStep(self->enemy) )
				wfac = 260.0f; // more imprecise for air rockets unless it's midair
	}
	else if ( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION )
	{
		wfac = 180.0f;
		BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity );
	}
	else if ( AIWeapons[weapon].aimType == AI_AIMSTYLE_DROP )
	{
		//jalToDo
		wfac = 160.0f;
		BOT_DMclass_PredictProjectileShot( self, fire_origin, firedef->speed, target, self->enemy->velocity );

	} else { // AI_AIMSTYLE_INSTANTHIT
		if( self->s.weapon == WEAP_ELECTROBOLT )
			wfac = 250.0f;
		else if( self->s.weapon == WEAP_LASERGUN )
			wfac = 200.0f;
		else
			wfac = 160.0f;
	}

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

	// modify attack angles based on accuracy
	//target[0] += (random()-0.5f) * wfac;
	//target[1] += (random()-0.5f) * wfac;

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

	// Set the attack (fix this ramdomness)
	firedelay = ( 100.0f * (random()-0.25f) ) + ( 10.0f * self->ai.pers.skillLevel );
	if( firedelay > 0.0f ) {
		ucmd->buttons = BUTTON_ATTACK;; // could fire, but wants to?
		// mess up angles
		{
			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->v_angle );

	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
//==========================================
void BOT_DMclass_WeightPlayers( edict_t *self )
{
	int i, team;

	//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]->r.svflags & SVF_NOCLIENT || AIEnemies[i]->deadflag ) {
			self->ai.status.playersWeights[i] = 0.0f;
			continue;
		}

		//if not team based give some weight to every one
		if( !GS_Gametype_IsTeamBased(game.gametype) ) {
			self->ai.status.playersWeights[i] = 0.3f;
			continue;
		}
		
		//team based. Ignore team mates
		if( AIEnemies[i]->s.team == self->s.team )
			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_RED; team < TEAM_RED + 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;
			}
			
			continue;
		}

		//else is some other team based gametype
		self->ai.status.playersWeights[i] = 0.3f;
	}
}


//==========================================
// BOT_DMclass_WantedFlag
// find needed flag
//==========================================
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 viceversa

	//see if we have any others flag
	for( team = TEAM_RED; team < TEAM_RED + 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_RED; team < TEAM_RED + 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.svflags & SVF_NOCLIENT ) //flag is not at base
					break;
				else
					return flag;
			}
		}	
	}

	return NULL;
}


//==========================================
// BOT_DMclass_WeightInventory
// weight items up or down based on bot needs
//==========================================
void BOT_DMclass_WeightInventory( edict_t *self )
{
	float		LowNeedFactor = 0.5;
	gclient_t	*client;
	int			i;

	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; i < WEAP_TOTAL; i++ ) {
		if( AIWeapons[i].weaponItem && client->inventory[AIWeapons[i].weaponItem->tag] )
			self->ai.status.inventoryWeights[AIWeapons[i].weaponItem->tag] *= LowNeedFactor;
	}

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

	//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 persistant inventory. Remove weight from the unwanted one/s.
		for( team = TEAM_RED; team < TEAM_RED + g_maxteams->integer; team++ ) {
			otherflag = G_Gametype_CTF_FlagItem(team);
			if( otherflag && otherflag != wantedFlag )
				self->ai.status.inventoryWeights[otherflag->tag] = 0.0;
		}
	}
}


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

	//if the bot is at spectator team, try to join
	if( self->s.team == TEAM_SPECTATOR ) 
	{
		//extend the suicide timout
		self->ai.bloqued_timeout = level.time + 20.0;
		
		//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.timemsec + 1000 + (int)(6000 * random());
			return;
		} else {
			self->timestamp = level.time + 1 + (8 * random());
		}

	} 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->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
}

#ifdef VSAYS
qboolean G_BOTvsay_f( edict_t *ent, char *msg, qboolean team );
void BOT_DMclass_VSAYmessages( edict_t *self ) {
	gitem_t *itemgoal = NULL;

	if( match.state != MATCH_STATE_PLAYTIME )
		return;

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

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

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

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

	if( GS_Gametype_IsTeamBased(game.gametype) ) {

		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;
		}
	} 
	else if( random() > 1.0f / game.numBots ) { // when not team based we only have these options, so reduce the chances of being played
		return;
	}

	if( random() > 0.8 ) {
		G_BOTvsay_f( self, "yeehaa", GS_Gametype_IsTeamBased(game.gametype) );
		return;
	}

	if( random() > 0.8 ) {
		G_BOTvsay_f( self, "attack", GS_Gametype_IsTeamBased(game.gametype) );
		return;
	}

	if( random() > 0.8 ) {
		G_BOTvsay_f( self, "noproblem", GS_Gametype_IsTeamBased(game.gametype) );
		return;
	}
}
#endif // VSAYS


//==========================================
// BOT_DMClass_BloquedTimeout
// the bot has been bloqued for too long
//==========================================
void BOT_DMClass_BloquedTimeout( edict_t *self )
{
	self->health = 0;
	self->ai.bloqued_timeout = level.time + 15.0;
	self->die( self, self, self, 100000, vec3_origin );
	self->nextthink = level.timemsec + game.framemsec;
}


//==========================================
// BOT_DMclass_DeadFrame
// ent is dead = run this think func
//==========================================
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.timemsec > self->deathtimestamp + 3000 ) {
		self->r.client->buttons = self->r.client->latched_buttons = 0;
		ucmd.buttons = BUTTON_ATTACK;
	}

	ClientThink( self, &ucmd );
	self->nextthink = level.timemsec + game.framemsec;
}


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

#ifndef WSW_RELEASE
	if( !bot_dummy->integer )
	{
#endif
		// Look for enemies
		self->enemy = BOT_DMclass_FindEnemy(self);
		if( self->enemy && BOT_DMclass_ChooseWeapon(self) >= 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 + 1.0;
			}

		} 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;
		}

		// 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]);

		// press button walk
		if( abs(ucmd.forwardmove) <= 200 && abs(ucmd.sidemove) <= 200 )
			ucmd.buttons |= BUTTON_WALK;
#ifndef WSW_RELEASE
	}
#endif

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

	// send command through id's code
	ClientThink( self, &ucmd );
	self->nextthink = level.timemsec + game.framemsec;

	// vsay 
#ifdef VSAYS
	BOT_DMclass_VSAYmessages( self );
#endif
}


//==========================================
// 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.bloquedTimeout = BOT_DMClass_BloquedTimeout;
	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);

	//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] = 0.9f;
	self->ai.pers.inventoryWeights[ARMOR_YA] = 0.8f;
	self->ai.pers.inventoryWeights[ARMOR_GA] = 0.5f;
	self->ai.pers.inventoryWeights[ARMOR_SHARD] = 0.2f;

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

	if( game.gametype == GAMETYPE_CTF ) {
		self->ai.pers.inventoryWeights[FLAG_BLUE] = 4.0f;
		self->ai.pers.inventoryWeights[FLAG_RED] = 4.0f;
		self->ai.pers.inventoryWeights[FLAG_YELLOW] = 4.0f;
		self->ai.pers.inventoryWeights[FLAG_GREEN] = 4.0f;
	}
}


