// Created by Ivan_the_B
//

#include "../Game_local.h"

#include "AI_bot.h"
#include "Moveable.h"
#include "gamesys/SysCvar.h"
#include "script/Script_Thread.h"

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

	idAI_Bot

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

const idEventDef AI_Bot_SelectWeapon( "selectBotWeapon", "d", 'd' );
const idEventDef AI_Bot_IsWeaponEnabled( "isBotWeaponEnabled", "d", 'd' ); 
const idEventDef AI_Bot_AddWeaponInBalancing( "addWeaponInBalancing", "d" ); 
const idEventDef AI_Bot_GetBalancedWeapon( "getBalancedWeapon", NULL, 'd' ); 
const idEventDef AI_Bot_RegisterAttackForBalancing( "registerAttackForBalancing", "d" ); 
const idEventDef AI_Bot_SelectAnotherWeapon( "selectAnotherWeapon", "dd", 'd' );
//const idEventDef AI_Bot_SelectAnotherWeaponByNode( "selectAnotherWeaponByNode", "e", 'd' ); 
const idEventDef AI_Bot_GetNextSquadMate( "getNextSquadMate", "E", 'e' ); 
const idEventDef AI_Bot_GetCurrentWeapon( "getCurrentBotWeapon", NULL, 'd' );
const idEventDef AI_Bot_GetWeaponNumByName( "getBotWeaponNumByName", "s", 'd' );
const idEventDef AI_Bot_FireWeapon( "fireWeapon", NULL, 'e' );
const idEventDef AI_Bot_CanHitEnemyFromCurrentWeapon( "canHitEnemyFromCurrentWeapon", NULL, 'd' );
const idEventDef AI_Bot_CanFireToEnemyNoSelfDamage( "canFireToEnemyNoSelfDamage", "d", 'd' );
const idEventDef AI_Bot_CanHitEnemyFromFireAnim( "canHitEnemyFromFireAnim", "d", 'd' ); 
const idEventDef AI_Bot_GetIdleAnim( "getIdleAnim", NULL, 's' );
const idEventDef AI_Bot_GetReloadAnim( "getReloadAnim", NULL, 's' );
const idEventDef AI_Bot_GetFireAnim( "getFireAnim", NULL, 's' );
const idEventDef AI_Bot_ReloadWeapon( "reloadWeapon" );
const idEventDef AI_Bot_GetAmmoInClip( "getAmmoInClip", NULL, 'd' );
const idEventDef AI_Bot_GetClipSize( "getClipSize", NULL, 'd' );
const idEventDef AI_Bot_CheckReloadTolerance( "checkReloadTolerance", NULL, 'd' );
const idEventDef AI_Bot_GetRandomTargetTypePrefix( "getRandomTargetTypePrefix", "ss", 'e' );
const idEventDef AI_Bot_LostTimeMoreThan( "lostTimeMoreThan", "d", 'd' );
const idEventDef AI_Bot_WeaponChangedMoreThan( "weaponChangedMoreThan", "d", 'd' );
const idEventDef AI_Bot_PlayAnimOnWeapon( "playAnimOnWeapon", "s" );
//const idEventDef AI_Bot_ReleaseNode( "releaseCurrentNode" );
//const idEventDef AI_Bot_TryLockNode( "tryLockNode", "e", 'd' );
const idEventDef AI_Bot_FindEnemyAIorPL( "findEnemyAIorPL", "d", 'e' );
//const idEventDef AI_Bot_FindClosestBotNode( "findClosestBotNode", "E", 'e' );
const idEventDef AI_Bot_CanSpeak( "canSpeak", "f" );

/*
//redefined - no need to declare them again:
const idEventDef AI_CanHitEnemyFromAnim( "canHitEnemyFromAnim", "s", 'd' ); 
const idEventDef AI_CreateMissile( "createMissile", "s", 'e' ); 
const idEventDef AI_LaunchMissile( "launchMissile", "vv", 'e' ); 
const idEventDef AI_CanHitEnemyFromJoint( "canHitEnemyFromJoint", "s", 'd' ); 
*/

CLASS_DECLARATION( idAI, idAI_Bot ) 
	EVENT( AI_Bot_SelectWeapon,					idAI_Bot::Event_SelectWeapon )
	EVENT( AI_Bot_IsWeaponEnabled,				idAI_Bot::Event_IsWeaponEnabled ) 
	EVENT( AI_Bot_AddWeaponInBalancing,			idAI_Bot::Event_AddWeaponInBalancing ) 
	EVENT( AI_Bot_GetBalancedWeapon,			idAI_Bot::Event_GetBalancedWeapon ) 
	EVENT( AI_Bot_RegisterAttackForBalancing,	idAI_Bot::Event_RegisterAttackForBalancing ) 
	EVENT( AI_Bot_SelectAnotherWeapon,			idAI_Bot::Event_SelectAnotherWeapon )
	//EVENT( AI_Bot_SelectAnotherWeaponByNode,	idAI_Bot::Event_SelectAnotherWeaponByNode )
	EVENT( AI_Bot_GetNextSquadMate,				idAI_Bot::Event_GetNextSquadMate )
	EVENT( AI_Bot_GetCurrentWeapon,				idAI_Bot::Event_GetCurrentWeapon )
	EVENT( AI_Bot_GetWeaponNumByName,			idAI_Bot::Event_GetWeaponNumByName ) 
	EVENT( AI_Bot_FireWeapon,					idAI_Bot::Event_FireWeapon )  
	EVENT( AI_Bot_CanHitEnemyFromCurrentWeapon,	idAI_Bot::Event_CanHitEnemyFromCurrentWeapon ) 
	EVENT( AI_Bot_CanFireToEnemyNoSelfDamage,	idAI_Bot::Event_CanFireToEnemyNoSelfDamage )
	EVENT( AI_Bot_CanHitEnemyFromFireAnim,		idAI_Bot::Event_CanHitEnemyFromFireAnim )
	EVENT( AI_Bot_GetIdleAnim,					idAI_Bot::Event_GetIdleAnim )
	EVENT( AI_Bot_GetReloadAnim,				idAI_Bot::Event_GetReloadAnim )
	EVENT( AI_Bot_GetFireAnim,					idAI_Bot::Event_GetFireAnim )
	EVENT( AI_Bot_ReloadWeapon,					idAI_Bot::Event_ReloadWeapon )
	EVENT( AI_Bot_GetAmmoInClip,				idAI_Bot::Event_GetAmmoInClip )
	EVENT( AI_Bot_GetClipSize,					idAI_Bot::Event_GetClipSize )
	EVENT( AI_Bot_CheckReloadTolerance,			idAI_Bot::Event_CheckReloadTolerance ) 
	EVENT( AI_Bot_LostTimeMoreThan,				idAI_Bot::Event_LostTimeMoreThan )
	EVENT( AI_Bot_WeaponChangedMoreThan,		idAI_Bot::Event_WeaponChangedMoreThan )
	EVENT( AI_Bot_PlayAnimOnWeapon,				idAI_Bot::Event_PlayAnimOnWeapon )
	//EVENT( AI_Bot_ReleaseNode,					idAI_Bot::Event_ReleaseNode )
	//EVENT( AI_Bot_TryLockNode,					idAI_Bot::Event_TryLockNode )
	EVENT( AI_Bot_FindEnemyAIorPL,				idAI_Bot::Event_FindEnemyAIorPL )
	//EVENT( AI_Bot_FindClosestBotNode,			idAI_Bot::Event_FindClosestBotNode )
	EVENT( AI_Bot_CanSpeak,						idAI_Bot::Event_CanSpeak )
	EVENT( AI_CanHitEnemyFromAnim,				idAI_Bot::Event_CanHitEnemyFromAnim )	//redefined
	EVENT( AI_CreateMissile,					idAI_Bot::Event_CreateMissile )			//redefined
	EVENT( AI_LaunchMissile,					idAI_Bot::Event_LaunchMissile )			//redefined
	EVENT( AI_CanHitEnemyFromJoint,				idAI_Bot::Event_CanHitEnemyFromJoint )	//redefined
END_CLASS


int idAI_Bot::say_times[ AI_BOT_SAY_MAXVALUE ] = { 0 } ; //will not be saved/restored!
const int idAI_Bot::say_delays[ AI_BOT_SAY_MAXVALUE ] = { 8 * 1000, //generic min time
							20 * 1000,	//WATCHOUT
							20 * 1000,	//SIGHT
							20 * 1000,	//BACKTOIDLE
							20 * 1000,	//SUSPICIOUS
							20 * 1000,	//STARTMOVE
							20 * 1000,	//LOST
							20 * 1000,	//AFTERLOST
							20 * 1000,	//DAMAGED
							20 * 1000,	//FIRE
							10 * 1000,	//MATEDOWN
							8 * 1000};	//CHATTER - not the real chatter time: it's the minimum common 'distance'

const char* idAI_Bot::say_names_default[ AI_BOT_SAY_MAXVALUE ] = {"", 
							"snd_watchout",		//WATCHOUT
							"snd_sight",		//SIGHT
							"snd_backtoidle",	//BACKTOIDLE
							"snd_suspicious",	//SUSPICIOUS
							"snd_startmove",	//STARTMOVE
							"snd_lost",			//LOST
							"snd_afterlost",	//AFTERLOST
							"snd_damaged",		//DAMAGED
							"snd_fire",			//FIRE
							"snd_matedown",		//MATEDOWN
							""};	//CHATTER - not used. real snd will be set by SetChatSound.

const char* idAI_Bot::say_names_squad[ AI_BOT_SAY_MAXVALUE ] = {"", 
							"snd_watchout_squad",	//WATCHOUT
							"snd_sight_squad",		//SIGHT
							"snd_backtoidle_squad",	//BACKTOIDLE
							"snd_suspicious_squad",	//SUSPICIOUS
							"snd_startmove_squad",	//STARTMOVE
							"snd_lost_squad",		//LOST
							"snd_afterlost_squad",	//AFTERLOST
							"snd_damaged_squad",	//DAMAGED
							"snd_fire_squad",		//FIRE
							"snd_matedown_squad",	//MATEDOWN
							""};	//CHATTER - not used. real snd will be set by SetChatSound.

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

	Constructor and Destructor

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

/*
=====================
idAI_Bot::idAI_Bot
=====================
*/

idAI_Bot::idAI_Bot() {
	currentWeapon = -1; //nothing
	lastHitFCWCheckResult = false;
	lastHitFCWCheckTime = 0; 
	lastHitNSDCheckResult = false;
	lastHitNSDCheckTime = 0;
	lastVisibleEnemyTime = 0;
	lastWeaponChangedTime = 0;
	//attackNodesMaxDist = 0.0f;
	//currentNode = NULL;
	squad = NULL;
	weapons.SetGranularity( 1 );

	SetDefaultSayTimes(0); //need to reset it in case we load another map
}


/*
=====================
idAI_Bot::~idAI_Bot
=====================
*/

idAI_Bot::~idAI_Bot() {
	int i;
	idEntity *ent;

	DeconstructScriptObject();
	scriptObject.Free();
	
	// remove any attached entities
	for( i = 0; i < weapons.Num(); i++ ) {
		//weapon entity
		ent = weapons[ i ].ent.GetEntity();
		if ( ent ) {
			ent->PostEventMS( &EV_Remove, 0 );
		}

		//projectileClipModel
		delete weapons[ i ].projectileClipModel;
	}


	//we could be removed without being killed, so make sure we are removed from the squad
	if ( gameLocal.GameState() == GAMESTATE_ACTIVE ) {
		//gameLocal.Printf("idAI_Bot::~idAI_Bot in GAMESTATE_ACTIVE\n");
		ClearSquad(); //remove from current squad
	}
}

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

	Save and Restore

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

/*
=====================
idAI_Bot::Save
=====================
*/
void idAI_Bot::Save( idSaveGame *savefile ) const {
	int i;

	//currentNode.Save( savefile );
	squad.Save( savefile );

	savefile->WriteInt( currentWeapon );
	savefile->WriteInt( lastVisibleEnemyTime );
	savefile->WriteBool( lastHitFCWCheckResult );
	savefile->WriteInt( lastHitFCWCheckTime );
	savefile->WriteBool( lastHitNSDCheckResult );
	savefile->WriteInt( lastHitNSDCheckTime );
	savefile->WriteInt( lastWeaponChangedTime );
	//savefile->WriteFloat( attackNodesMaxDist );
	savefile->WriteBool( canSpeak );

	savefile->WriteInt( weapons.Num() );
	for( i = 0; i < weapons.Num(); i++ ) {
		//spawn settings
		weapons[i].ent.Save( savefile );
		savefile->WriteInt( weapons[i].channel );
		savefile->WriteJoint( weapons[i].fireJoint );
		savefile->WriteJoint( weapons[i].bindJoint );
		savefile->WriteJoint( weapons[i].flashJoint ); 
	
		//weapon
		savefile->WriteInt( weapons[i].priorityLevel );
		savefile->WriteBool( weapons[i].enabled );
		savefile->WriteString( weapons[i].weaponName );
		savefile->WriteFloat( weapons[i].minSelfDmgDistance );

		//projectile - const idDict * projectileDef
		idStr projectileName;
		idEntity *ent;
		ent = weapons[ i ].ent.GetEntity();
		ent->spawnArgs.GetString( "def_projectile", "", projectileName );
		savefile->WriteString( projectileName );

		//projectile - other stuff
		savefile->WriteFloat( weapons[i].projectileRadius );
		savefile->WriteFloat( weapons[i].projectileSpeed );
		savefile->WriteVec3( weapons[i].projectileVelocity );
		savefile->WriteVec3( weapons[i].projectileGravity );

		//mutable idClipModel *projectileClipModel; NOT SAVED

		//attack settings
		savefile->WriteFloat( weapons[i].attack_accuracy );
		savefile->WriteFloat( weapons[i].projectile_spread );
		savefile->WriteInt( weapons[i].num_projectiles );
		savefile->WriteInt( weapons[i].fireMode );
		
		//balancing info
		savefile->WriteInt( weapons[i].attackCounter );
		savefile->WriteInt( weapons[i].balancing_multiplier );
		savefile->WriteBool( weapons[i].requestedForBalancing );

		//clip
		savefile->WriteInt( weapons[i].clipSize );
		savefile->WriteInt( weapons[i].ammoInClip );
		savefile->WriteInt( weapons[i].reloadTolerance );

		//light
		savefile->WriteMaterial( weapons[i].shader );
		savefile->WriteVec3( weapons[i].flashColor );
		savefile->WriteFloat( weapons[i].flashRadius );
		savefile->WriteInt( weapons[i].flashTime );
	}
}

/*
=====================
idAI_Bot::Restore
=====================
*/
void idAI_Bot::Restore( idRestoreGame *savefile ) {
	int	num;
	int i;
	
	//currentNode.Restore( savefile );
	squad.Restore( savefile );

	savefile->ReadInt( currentWeapon );
	savefile->ReadInt( lastVisibleEnemyTime );
	savefile->ReadBool( lastHitFCWCheckResult );
	savefile->ReadInt( lastHitFCWCheckTime );
	savefile->ReadBool( lastHitNSDCheckResult );
	savefile->ReadInt( lastHitNSDCheckTime );
	savefile->ReadInt( lastWeaponChangedTime );
	//savefile->ReadFloat( attackNodesMaxDist ); 
	savefile->ReadBool( canSpeak );

	savefile->ReadInt( num );
	weapons.SetGranularity( 1 );
	weapons.SetNum( num );

	for( i = 0; i < num; i++ ) {
		//spawn settings
		weapons[i].ent.Restore( savefile );
		savefile->ReadInt( weapons[i].channel );
		savefile->ReadJoint( weapons[i].fireJoint );
		savefile->ReadJoint( weapons[i].bindJoint );
		savefile->ReadJoint( weapons[i].flashJoint ); 
	
		//weapon
		savefile->ReadInt( weapons[i].priorityLevel );
		savefile->ReadBool( weapons[i].enabled );
		savefile->ReadString( weapons[i].weaponName );
		savefile->ReadFloat( weapons[i].minSelfDmgDistance );

		//projectile - const idDict * projectileDef
		idStr projectileName;
		savefile->ReadString( projectileName );
		if ( projectileName.Length() ) {
			weapons[ i ].projectileDef = gameLocal.FindEntityDefDict( projectileName );
		} else {
			weapons[ i ].projectileDef = NULL;
		}


		//projectile - other stuff
		savefile->ReadFloat( weapons[i].projectileRadius );
		savefile->ReadFloat( weapons[i].projectileSpeed );
		savefile->ReadVec3( weapons[i].projectileVelocity );
		savefile->ReadVec3( weapons[i].projectileGravity );

		//mutable idClipModel *projectileClipModel; NOT SAVED
		weapons[ i ].projectileClipModel = NULL;

		//attack settings
		savefile->ReadFloat( weapons[i].attack_accuracy );
		savefile->ReadFloat( weapons[i].projectile_spread );
		savefile->ReadInt( weapons[i].num_projectiles );
		savefile->ReadInt( weapons[i].fireMode );

		//balancing info
		savefile->ReadInt( weapons[i].attackCounter );
		savefile->ReadInt( weapons[i].balancing_multiplier );
		savefile->ReadBool( weapons[i].requestedForBalancing );

		//clip
		savefile->ReadInt( weapons[i].clipSize );
		savefile->ReadInt( weapons[i].ammoInClip );
		savefile->ReadInt( weapons[i].reloadTolerance );

		//light
		savefile->ReadMaterial( weapons[i].shader );
		savefile->ReadVec3( weapons[i].flashColor );
		savefile->ReadFloat( weapons[i].flashRadius );
		savefile->ReadInt( weapons[i].flashTime );	

		// Link the script variables back to the scriptobject
		LinkScriptVariables();

		//upd muzzle light
		setWeaponMuzzleFlash();
	}

	//don't know old values, so just average values...
	SetDefaultSayTimes( 0.4f );
}

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

	Spawn, killed, remove

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

/*
================
idAI_Bot::Show
================
*/
void idAI_Bot::Show( void ) {
	idAI::Show();
	ShowOnlyCurrentWeapon();
}

/*
=====================
idAI_Bot::Spawn
=====================
*/
void idAI_Bot::Spawn( void ) {
	//gameLocal.Printf("idAI_Bot::Spawn\n");

	LinkScriptVariables();
	CreateWeapons();
	SelectInitialWeapon();
	ShowOnlyCurrentWeapon();

	//spawnArgs.GetFloat("attackNodesMaxDist", "0", attackNodesMaxDist );
	spawnArgs.GetBool( "can_speak",				"0", canSpeak );
	
	/*
	//Warning: idAI_Bot::CalculateAttackOffsets is called by idAI::Spawn --> weapons are not available yet!
	//CalculateAttackOffsets(); 
	*/
}

/*
=====================
idAI_Bot::LinkScriptVariables
=====================
*/
void idAI_Bot::LinkScriptVariables( void ) {
	//idAI::LinkScriptVariables(); //useless because idAI::spawn() also calls idAI::LinkScriptVariables()
	AI_WEAPON_CHANGED.LinkTo( scriptObject, "AI_WEAPON_CHANGED" );
	AI_WEAPON_NEED_RELOAD.LinkTo( scriptObject, "AI_WEAPON_NEED_RELOAD" );
	AI_LEAVE_NODE_TO_FIGHT.LinkTo( scriptObject, "AI_LEAVE_NODE_TO_FIGHT" );
	AI_SAY_HINT.LinkTo( scriptObject, "AI_SAY_HINT" );
}

/*
================
idAI_Bot::CreateWeapons
================
*/
void idAI_Bot::CreateWeapons( void ) {
	idEntity *ent;
	idStr	weaponName;

	// spawn any weapons we might have
	const idKeyValue *kv = spawnArgs.MatchPrefix( "def_weapon_", NULL );
	while ( kv ) {
		idDict args;
		
		args.Set( "classname", kv->GetValue().c_str() ); //TODO: check it!?

		// make items non-touchable so the player can't take them out of the character's hands
		args.Set( "no_touch", "1" );

		// don't let them drop to the floor
		args.Set( "dropToFloor", "0" );

		gameLocal.SpawnEntityDef( args, &ent );
		if ( !ent ) {
			gameLocal.Error( "Couldn't spawn '%s' to attach to entity '%s'", kv->GetValue().c_str(), name.c_str() );
		} else {
			// get the weapon name
			weaponName = kv->GetKey();
			weaponName.Strip( "def_weapon_" );
			AttachWeapon( ent, weaponName );
		}
		kv = spawnArgs.MatchPrefix( "def_weapon_", kv );
	}
}


/*
================
idAI_Bot::AttachWeapon
================
*/
void idAI_Bot::AttachWeapon( idEntity *ent, idStr weaponName) { //TODO: spawn only the enabled ones
	idVec3			origin;
	idMat3			axis;
	jointHandle_t	bindJoint;
	//jointHandle_t	fireJoint;
	idStr			jointName;
	idBotWeapon	&attach = weapons.Alloc();
	idAngles		angleOffset;
	idVec3			originOffset;

	idStr			projectileName;
	idAnimator		*entAnimator;

	//weapon number
	currentWeapon = weapons.NumAllocated()-1; //needed to make CreateProjectile working properly

	//bindJoint
	jointName = ent->spawnArgs.GetString( "bindJoint" );
	bindJoint = animator.GetJointHandle( jointName );
	if ( bindJoint == INVALID_JOINT ) {
		gameLocal.Error( "Joint '%s' not found for attaching '%s' on '%s'", jointName.c_str(), ent->GetClassname(), name.c_str() );
	}
	attach.bindJoint = bindJoint;

	angleOffset = ent->spawnArgs.GetAngles( "angles" );
	originOffset = ent->spawnArgs.GetVector( "origin" );

	attach.channel = animator.GetChannelForJoint( bindJoint );
	GetJointWorldTransform( bindJoint, gameLocal.time, origin, axis );
	attach.ent = ent;

	ent->SetOrigin( origin + originOffset * renderEntity.axis );
	idMat3 rotate = angleOffset.ToMat3();
	idMat3 newAxis = rotate * axis;
	ent->SetAxis( newAxis );
	ent->BindToJoint( this, bindJoint, true );
	ent->cinematic = cinematic;
	
	//fire joint
	jointName = ent->spawnArgs.GetString( "fireJoint", "barrel"); //default is barrel
	entAnimator = ent->GetAnimator(); 
	if ( entAnimator && entAnimator->ModelHandle() ) { // check if the entity has an MD5 model
		attach.fireJoint = entAnimator->GetJointHandle( jointName ); //this could also be INVALID_JOINT!
		attach.flashJoint = entAnimator->GetJointHandle( "flash" ); //this could also be INVALID_JOINT!
	}else{
		attach.fireJoint = INVALID_JOINT;
		attach.flashJoint = INVALID_JOINT;
	}

	/*
	//test only
	if ( attach.fireJoint == INVALID_JOINT ) {  
		gameLocal.Printf("fireJoint == INVALID_JOINT\n");
	}else{
		gameLocal.Printf("fireJoint is valid: %d \n", attach.fireJoint);
	}*/
	
	//name 
	attach.weaponName = weaponName;

	//enabled 
	attach.enabled = spawnArgs.GetBool(attach.weaponName.c_str(),"0"); //default disabled

	//projectile
	if ( ent->spawnArgs.GetString( "def_projectile", "", projectileName ) && projectileName.Length() ) {
			//gameLocal.Printf("def_projectile found\n");
			attach.projectileDef = gameLocal.FindEntityDefDict( projectileName );
			CreateProjectile( vec3_origin, viewAxis[ 0 ] ); //this sets the variable projectile
			attach.projectileRadius	= projectile.GetEntity()->GetPhysics()->GetClipModel()->GetBounds().GetRadius();
			attach.projectileVelocity	= idProjectile::GetVelocity( attach.projectileDef );
			attach.projectileGravity	= idProjectile::GetGravity( attach.projectileDef );
			attach.projectileSpeed	= projectileVelocity.Length();
			delete projectile.GetEntity();
			projectile = NULL;

			attach.attack_accuracy = ent->spawnArgs.GetFloat( "attack_accuracy", "1" ); 
			attach.projectile_spread = ent->spawnArgs.GetFloat( "projectile_spread", "0" );
			attach.num_projectiles = ent->spawnArgs.GetInt( "num_projectiles", "1" );
			attach.fireMode = ent->spawnArgs.GetInt( "fireMode", "0" ); 
			attach.clipSize = ent->spawnArgs.GetInt( "clipSize", "0" );  //0 = unlimited
			if(attach.clipSize < 0){ attach.clipSize = 0; }
			attach.ammoInClip = attach.clipSize; //start with full ammo
			attach.reloadTolerance = ent->spawnArgs.GetInt( "reloadTolerance", "0" );
			attach.minSelfDmgDistance = ent->spawnArgs.GetFloat( "minSelfDmgDistance", "0" );

			//light
			attach.flashColor = ent->spawnArgs.GetVector( "flashColor", "0 0 0" );
			attach.flashRadius = ent->spawnArgs.GetFloat( "flashRadius" );	
			attach.flashTime = SEC2MS( ent->spawnArgs.GetFloat( "flashTime", "0.25" ) );

			const char *shader;
			spawnArgs.GetString( "mtr_flashShader", "muzzleflash", &shader );
			attach.shader = declManager->FindMaterial( shader, false );

	}else{
			//gameLocal.Printf("def_projectile not found\n");
			attach.projectileDef = NULL;
			attach.projectileRadius = 0.0f;
			attach.projectileSpeed =  0.0f;
			attach.projectileVelocity = vec3_origin;
			attach.projectileGravity = vec3_origin;

			attach.attack_accuracy = 0.0f;
			attach.projectile_spread = 0.0f;
			attach.num_projectiles = 0;
			attach.fireMode = 0;
			attach.clipSize = 0;
			attach.ammoInClip = 0;
			attach.reloadTolerance = 0;
			attach.minSelfDmgDistance = 0;

			attach.flashColor = vec3_origin; 
			attach.flashRadius = 0.0f;
			attach.flashTime = 0;
			attach.shader = NULL;
	}	

	attach.priorityLevel = ent->spawnArgs.GetInt( "priorityLevel", "0" );
	attach.projectileClipModel = NULL; //will be initialized later

	//balancing info
	attach.attackCounter = 0;
	attach.balancing_multiplier = ent->spawnArgs.GetInt( "balancing_multiplier", "0" );
	attach.requestedForBalancing = false;

	//gameLocal.Printf("fireJoint check: %d \n", weapons[ currentWeapon ].fireJoint);
}


/*
===================
idAI_Bot::CalculateAttackOffsets

calculate joint positions on attack frames so we can do proper "can hit" tests
===================
*/
void idAI_Bot::CalculateAttackOffsets( void ) { //Warning: this is called by idAI::Spawn --> weapons are no available yet!
	const idDeclModelDef	*modelDef;
	int						num;
	int						i;
	int						frame;
	const frameCommand_t	*command;
	idMat3					axis;
	const idAnim			*anim;
	jointHandle_t			joint;

	//gameLocal.Printf("idAI_Bot::CalculateAttackOffsets \n");

	modelDef = animator.ModelDef();
	if ( !modelDef ) {
		return;
	}
	num = modelDef->NumAnims();
	
	// needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim
	animator.RemoveOriginOffset( false );

	// anim number 0 is reserved for non-existant anims.  to avoid off by one issues, just allocate an extra spot for
	// launch offsets so that anim number can be used without subtracting 1.
	missileLaunchOffset.SetGranularity( 1 );
	missileLaunchOffset.SetNum( num + 1 );
	missileLaunchOffset[ 0 ].Zero();

	for( i = 1; i <= num; i++ ) {
		missileLaunchOffset[ i ].Zero();
		anim = modelDef->GetAnim( i );
		if ( anim ) {
			/*
			frame = anim->FindFrameForFrameCommand( FC_LAUNCHMISSILE, &command );
			if ( frame >= 0 ) {
				joint = animator.GetJointHandle( command->string->c_str() );
				if ( joint == INVALID_JOINT ) {
					gameLocal.Error( "Invalid joint '%s' on 'launch_missile' frame command on frame %d of model '%s'", command->string->c_str(), frame, modelDef->GetName() );
				}
				GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis );
			}*/

			//ivan - fix for new framecommand --> this allows CanHitEnemyFromAnim to work
			frame = anim->FindFrameForFrameCommand( FC_FIREWEAPON, &command );
			if ( frame >= 0 ) {
				//gameLocal.Printf("FC_FIREWEAPON trovato: '%s' \n", anim->Name());
				joint = animator.GetJointHandle( "PISTOL_ATTACHER" ); //TODO: fix this hack.
				if ( joint == INVALID_JOINT ) {
					gameLocal.Error( "Invalid joint on 'fire_weapon' frame command on frame %d of model '%s'", frame, modelDef->GetName() );
				}
				GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis );
			}else{
				//gameLocal.Printf("FC_FIREWEAPON non trovato: '%s' \n", anim->Name());
			}
			//ivan end
		}
	}

	animator.RemoveOriginOffset( true );
}

/*
=====================
idAI_Bot::DropWeapon
=====================
*/
void idAI_Bot::DropWeapon( int weaponNum ) {
	int ammoToAdd; 
	idVec3 velocity;
	idEntity *myweapon;
	idEntity *item;

	if(weaponNum < 0 || weaponNum >= weapons.Num() ){
		return;
	}

	myweapon = weapons[ weaponNum ].ent.GetEntity();
	item = NULL;

	const char *classname = myweapon->spawnArgs.GetString( "def_dropItem" );
	if ( !classname[0] ) {
		return;
	}

	velocity = 150.0f * idAngles( -60, current_yaw, 0 ).ToForward();

	item = idMoveableItem::DropItem( classname, myweapon->GetPhysics()->GetOrigin(), myweapon->GetPhysics()->GetAxis(), velocity , 200, AI_BOT_WEAPON_DROP_TIME );
	if ( !item ) {
		return;
	}

	if(weapons[ weaponNum ].clipSize <= 0){ //do nothing if clipSize is unlimited
		return;
	}
		
	const idKeyValue * keyval = item->spawnArgs.MatchPrefix( "inv_ammo_" );	
	if ( keyval ) {
		ammoToAdd = weapons[ weaponNum ].ammoInClip;
		if(ammoToAdd <= 0){ ammoToAdd = 1; } //at least 1
		//gameLocal.Printf("clipSize %d \n",ammoToAdd);
		item->spawnArgs.SetInt( keyval->GetKey(), ammoToAdd );
	}

}

/*
=====================
idAI_Bot::Killed
=====================
*/
void idAI_Bot::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
	idAI::Killed( inflictor, attacker, damage, dir, location );
	
	// drop the current weapon
	
//rev 2021 start. prevent bots from dropping weapons because we want them to drop ammo instead.
	//DropWeapon(currentWeapon);
//rev 2021 end

	//now we can remove all the weapons
	RemoveWeapons();

	//be sure...
	flashJointWorld = INVALID_JOINT; 

	/*
	//release the current node
	if ( currentNode.GetEntity() ) {
		currentNode.GetEntity()->Release();
		currentNode = NULL;
	}
	*/

	//leave the squad
	ClearSquad();
}
/*
================
idAI_Bot::RemoveWeapons
================
*/
void idAI_Bot::RemoveWeapons( void ) { 
	int i;
	idEntity *ent;

	// remove any attached weapons
	for( i = 0; i < weapons.Num(); i++ ) {
		//weapon entity
		ent = weapons[ i ].ent.GetEntity();
		if ( ent ) {
			ent->PostEventMS( &EV_Remove, 0 );
		}

		//projectileClipModel
		delete weapons[ i ].projectileClipModel;
	}

	weapons.Clear();
	currentWeapon = -1;
}


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

	Squad

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

/*
=====================
idAI_Bot::SetSquad
=====================
*/
void idAI_Bot::SetSquad( idBotSquad* newsquad ) {
	if( squad.GetEntity() ){
		gameLocal.Error( "%s (%s) already belongs to a squad", name.c_str(), GetEntityDefName() );
	}

	squad = newsquad;
}

/*
=====================
idAI_Bot::ClearSquad
=====================
*/
void idAI_Bot::ClearSquad( void ) {
	if( squad.GetEntity() ){
		squad.GetEntity()->RemoveMember( this );
		squad = NULL;
	}
}

/*
=====================
idAI_Bot::HandleSquadMsg
=====================
*/
void idAI_Bot::HandleSquadMsg( squadMsg_t type, int parm ) {

	switch(type) {					
		case AI_SQUAD_MEMBER_REMOVED:
			AI_SAY_HINT = AI_BOT_SAY_MATEDOWN;
			//gameLocal.Printf("farewell my friend\n");
			break;						
		case AI_SQUAD_MEMBER_ADDED:
			//TODO
			break;     
		default:           
			gameLocal.Warning("Unknown message");
	}
}

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

	Talk utils

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

/*
=====================
idAI_Bot::SetDefaultSayTimes
=====================
*/
void idAI_Bot::SetDefaultSayTimes( float mult ) {
	int i;
	for (i=0; i < AI_BOT_SAY_MAXVALUE; i++){
		say_times[i] = gameLocal.time + (int)(say_delays[i]*mult);
	}
}

/*
=====================
idAI_Bot::SetChatSound
=====================
*/
void idAI_Bot::SetChatSound( void ) {
	const char *snd;

	if ( IsHidden() ) {
		snd = NULL;
	} else if ( enemy.GetEntity() ) {
		snd = spawnArgs.GetString( "snd_chatter_combat", NULL );
		chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_combat_min", "5" ) );
		chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_combat_max", "10" ) );
	}else if ( idStr::Cmp( spawnArgs.GetString( "shownState", "" ), "Following" ) == 0  ) {
		snd = spawnArgs.GetString( "snd_chatter_following", NULL );
		chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_following_min", "5" ) );
		chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_following_max", "10" ) );
	}else if ( !spawnArgs.GetBool( "no_idle_chatter" ) ) {
		snd = spawnArgs.GetString( "snd_chatter", NULL );
		chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_min", "5" ) );
		chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_max", "10" ) );
	} else {
		snd = NULL;
	}

	if ( snd && *snd ) {
		//gameLocal.Printf("SetChatSound %s \n", snd );
		chat_snd = declManager->FindSound( snd );
		chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
	} else {
		//gameLocal.Printf("SetChatSound NULL \n" );
		chat_snd = NULL;
	}
}

/*
=====================
idAI_Bot::PlayChatter
=====================
*/
void idAI_Bot::PlayChatter( void ) {
	//--- say code --- 

	if( AI_DEAD || !canSpeak || (say_times[AI_BOT_SAY_GENERIC] > gameLocal.time ) ) return;

	int lenght = 0;

	//check flags
	if( AI_SAY_HINT != AI_BOT_SAY_GENERIC ){
		int type = (int) AI_SAY_HINT; 
		AI_SAY_HINT = AI_BOT_SAY_GENERIC; //reset it every time!

		if( type < 0 || type >= AI_BOT_SAY_MAXVALUE ){ //invalid values are used to upd chat snd
			SetChatSound();
			return;
		}

		if( say_times[type] < gameLocal.time ){

			if( squad.GetEntity() && (squad.GetEntity()->GetNumMembers() > 1)){
				if( StartSound( say_names_squad[type], SND_CHANNEL_VOICE, 0, false, &lenght ) ){
					//gameLocal.Printf("Say %s , flag %d, lenght %d \n", say_names_squad[type], type, lenght );
				}
			}
			
			if(lenght == 0){ //(alone) OR (no shader for squad defined) 
				if( StartSound( say_names_default[type], SND_CHANNEL_VOICE, 0, false, &lenght ) ){
					//gameLocal.Printf("Say %s , flag %d, lenght %d \n", say_names_default[type], type, lenght );
				}
			}
			
			say_times[type] = gameLocal.time + lenght + say_delays[type];
			say_times[AI_BOT_SAY_GENERIC] = gameLocal.time + lenght + say_delays[AI_BOT_SAY_GENERIC];
		}
		return; //don't check chatter after this
	}
	//--- say code end --- 

	//old chatter code:

	// check if it's time to play a chat sound
	if ( !chat_snd || ( chat_time > gameLocal.time ) || (say_times[AI_BOT_SAY_CHATTER] > gameLocal.time ) ) {
		return;
	}

	StartSoundShader( chat_snd, SND_CHANNEL_VOICE, 0, false, &lenght );
	//gameLocal.Printf("Say %s \n", chat_snd->GetName() );

	// set the next chat time
	chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
	say_times[AI_BOT_SAY_CHATTER] = gameLocal.time + lenght + say_delays[AI_BOT_SAY_CHATTER]; //shared
}

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

	Weapons

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

/*
================
idAI_Bot::SelectInitialWeapon
================
*/
void idAI_Bot::SelectInitialWeapon( void ) { 
	int weaponNum;
	idStr initialweaponValue;

	currentWeapon = -1; //start without weapons
	weaponNum = -1; //default is none

	//get the initialweapon key
	initialweaponValue = spawnArgs.GetString( "initialWeapon", "" );
	if( initialweaponValue.Length() ){
		weaponNum = GetWeaponNumByName( initialweaponValue );
		//gameLocal.Printf("The initial requested weapon is: %d \n" ,weaponNum);
		SelectWeapon(weaponNum); //could upd currentWeapon
	}	

	//still no weapon selected... try to select the higher one.
	if(currentWeapon < 0){ 
		for( weaponNum = weapons.Num()-1; weaponNum >= 0; weaponNum-- ) {
			if ( weapons[ weaponNum ].enabled) {
				SelectWeapon(weaponNum); //currentWeapon = weaponNum;
				break;
			}
		}
	}

	//gameLocal.Printf("The initial weapon is: %d \n" ,currentWeapon);
}


/*
================
idAI_Bot::GetWeaponNumByName
================
*/
int idAI_Bot::GetWeaponNumByName(idStr name) { 
	int i;
	for( i = 0; i < weapons.Num(); i++ ) {
		if ( weapons[ i ].weaponName == name) {
			return i;
		}
	}
	return -1; //no weapon
}

/*
================
idAI_Bot::SelectWeapon
================
*/
bool idAI_Bot::SelectWeapon( int weaponNum ) {
	//gameLocal.Printf("SelectWeapon requested: %d \n" ,weaponNum);
	
	if (weaponNum == currentWeapon){
		//gameLocal.Printf("The requested weapon:%d is already selected\n" ,weaponNum);
		return true;
	}
	if( weaponNum < -1 || weaponNum >= weapons.Num()){ //if 3 allocated, max allowed value is 2. Note: -1 is accepted because it means 'no weapon'
		gameLocal.Warning("The requested weapon: %d is out of range" ,weaponNum);
		return false;
	}

	if( (weaponNum >= 0)  && !weapons[ weaponNum ].enabled){
		gameLocal.Warning("The requested weapon: %d is disabled" ,weaponNum);
		return false;
	}

	//hide the current one
	if(currentWeapon >= 0){
		weapons[ currentWeapon ].ent.GetEntity()->Hide();
	}

	//show the new one
	if(weaponNum >= 0){
		weapons[ weaponNum ].ent.GetEntity()->Show();
		//ammo
		if(weapons[ weaponNum ].clipSize > 0){ //only if limited clip size
			if(weapons[ weaponNum ].ammoInClip <= 0){
				//AI_WEAPON_NEED_RELOAD = true;		
			//REV 2021 dHEWM 3 1.5.1 UPDATE start
				if(AI_WEAPON_NEED_RELOAD.IsLinked()) { // DG: otherwise this asserts at level start //REV 2021 dHEWM 3 1.5.1 UPDATE
					AI_WEAPON_NEED_RELOAD = true;	
			//REV 2021 dHEWM 3 1.5.1 UPDATE END
				}				
			}else{
				//AI_WEAPON_NEED_RELOAD = false;
			//REV 2021 dHEWM 3 1.5.1 UPDATE start	
				if(AI_WEAPON_NEED_RELOAD.IsLinked()) { // DG: otherwise this asserts at level start
					AI_WEAPON_NEED_RELOAD = false;
				}				
			//REV 2021 dHEWM 3 1.5.1 UPDATE END	
			}
		}
	}else{ //no weapon selected
		//AI_WEAPON_NEED_RELOAD = false;
	//REV 2021 dHEWM 3 1.5.1 UPDATE start		
		if(AI_WEAPON_NEED_RELOAD.IsLinked()) { // DG: otherwise this asserts at level start
			AI_WEAPON_NEED_RELOAD = false;
		}		
	//REV 2021 dHEWM 3 1.5.1 UPDATE END			
	}

	//remove the current projectileClipModel so that it'll be recreated the next time GetAimDir is called
	//DeleteProjectileClipModel();

	//upd the currentWeapon
	currentWeapon = weaponNum;
	//gameLocal.Printf("SelectWeapon done: %d \n" ,currentWeapon);

	//remember this moment 
	lastWeaponChangedTime = gameLocal.time;

	//flash joint
	if(currentWeapon >= 0){
		flashJointWorld = weapons[ currentWeapon ].flashJoint;
	}else{
		flashJointWorld = INVALID_JOINT;
	}

	//upd light
	setWeaponMuzzleFlash(); //ok because currentWeapon already updated

	//upd script
	//AI_WEAPON_CHANGED = true;
//REV 2021 DHEWM 3 1.5.1 UPDATES START	
	if(AI_WEAPON_CHANGED.IsLinked()) { // DG: otherwise the assignment will cause an assertion
		AI_WEAPON_CHANGED = true;
	}	
//REV 2021 DHEWM 3 1.5.1 UPDATES END	
	return true;
}


/*
================
idAI_Bot::ShowOnlyCurrentWeapon
================
*/
void idAI_Bot::ShowOnlyCurrentWeapon( void ) {
	int i;
	idEntity *ent;

	// remove any attached entities
	for( i = 0; i < weapons.Num(); i++ ) {
		ent = weapons[ i ].ent.GetEntity();
		if (i == currentWeapon ) {
			ent->Show();
		}else{
			ent->Hide();
		}
	}
}


/*
================
idAI_Bot::TriggerWeaponEffects
================
*/
void idAI_Bot::TriggerWeaponEffects( const idVec3 &muzzle ) {
	idVec3 org;
	idMat3 axis;
	idEntity *myWeaponEnt; 

	if ( !g_muzzleFlash.GetBool() ) {
		return;
	}

	myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity();	
	if(myWeaponEnt->GetRenderEntity()){
		myWeaponEnt->GetRenderEntity()->shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
		myWeaponEnt->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat();
	}

	if ( flashJointWorld != INVALID_JOINT ) { //flashJointWorld has been updated on weapon selection
		//was: GetJointWorldTransform( flashJointWorld, gameLocal.time, org, axis );
	
		//get the reference to the weapon animator
		idAnimator *myWeaponAnimator;
		myWeaponAnimator = myWeaponEnt->GetAnimator(); 

		//do the equivalent of GetJointWorldTransform even without being casted to idAnimatedEntity
		myWeaponAnimator->GetJointTransform( flashJointWorld, gameLocal.time, org, axis );
		myWeaponEnt->ConvertLocalToWorldTransform( org, axis );

		if ( worldMuzzleFlash.lightRadius.x > 0.0f ) {
			worldMuzzleFlash.axis = axis;
			worldMuzzleFlash.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
			if ( worldMuzzleFlashHandle != - 1 ) {
				gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
			} else {
				worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash );
			}
			muzzleFlashEnd = gameLocal.time + flashTime;
			UpdateVisuals();
		}
	}
}


/*
================
idAI_Bot::UpdateMuzzleFlash
================
*/
void idAI_Bot::UpdateMuzzleFlash( void ) {
	if ( worldMuzzleFlashHandle != -1 ) { 
		if ( gameLocal.time >= muzzleFlashEnd ) {
			gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
			worldMuzzleFlashHandle = -1;
		} else {
			if ( flashJointWorld != INVALID_JOINT ) { //flashJointWorld has been updated on weapon selection
				idVec3 muzzle;
				idEntity *myWeaponEnt;
				idAnimator *myWeaponAnimator;

				//get the reference to the weapon and its animator
				myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity();
				myWeaponAnimator = myWeaponEnt->GetAnimator(); 

				//do the equivalent of GetJointWorldTransform even without being casted to idAnimatedEntity
				myWeaponAnimator->GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
				myWeaponEnt->ConvertLocalToWorldTransform( muzzle, worldMuzzleFlash.axis );

				worldMuzzleFlash.origin = muzzle;
				gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
			}
		}
	}
}

/*
===================
idAI_Bot::InitMuzzleFlash
===================
*/
void idAI_Bot::InitMuzzleFlash( void ) { //called on spawn and on relaod. NOTE: in both cases weapons are not created yet --> just set default values.
	
	//gameLocal.Printf("idAI_Bot::InitMuzzleFlash\n");

	flashTime = 0;

	memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );

	worldMuzzleFlash.pointLight = true;
	worldMuzzleFlash.shader = NULL;
	worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = 0.0f;
	worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = 0.0f;
	worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = 0.0f;
	worldMuzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
	worldMuzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
	worldMuzzleFlash.lightRadius[0] = 0.0f;
	worldMuzzleFlash.lightRadius[1]	= 0.0f;
	worldMuzzleFlash.lightRadius[2]	= 0.0f;
	worldMuzzleFlash.noShadows = spawnArgs.GetBool( "flashNoShadows" );

	worldMuzzleFlashHandle = -1;
}


/*
================
idAI_Bot::setWeaponMuzzleFlash
================
*/
void idAI_Bot::setWeaponMuzzleFlash( void ) {
	const idMaterial *	shader;	
	idVec3				flashColor;
	float				flashRadius;

	if(currentWeapon < 0 || currentWeapon >= weapons.Num()){
		shader = NULL;
		flashColor = vec3_origin;
		flashRadius = 0.0f;
		flashTime = 0; //global
	}else{
		shader = weapons[ currentWeapon ].shader;
		flashColor = weapons[ currentWeapon ].flashColor;
		flashRadius = weapons[ currentWeapon ].flashRadius;
		flashTime = weapons[ currentWeapon ].flashTime; //global
	}
	
	worldMuzzleFlash.shader = shader;
	worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0];
	worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1];
	worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2];
	worldMuzzleFlash.lightRadius[0] = flashRadius;
	worldMuzzleFlash.lightRadius[1]	= flashRadius;
	worldMuzzleFlash.lightRadius[2]	= flashRadius;
}

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

	Projectiles 

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


/*
=====================
idAI_Bot::DeleteProjectileClipModel 
=====================

void idAI_Bot::DeleteProjectileClipModel( void ) const { 
	//remove the current projectileClipModel so that it'll be recreated the next time GetAimDir or other functions are called
	delete projectileClipModel;
	projectileClipModel = NULL;
}
*/

/*
=====================
idAI_Bot::CreateProjectileClipModel 
=====================
*/
void idAI_Bot::CreateProjectileClipModel( void ) const { //warning! a weapon needs to be selected when you call this!
	if ( weapons[ currentWeapon ].projectileClipModel == NULL ) {
		idBounds projectileBounds( vec3_origin );
		projectileBounds.ExpandSelf( weapons[ currentWeapon ].projectileRadius );
		weapons[ currentWeapon ].projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) );
	}
}

/*
=====================
idAI_Bot::CreateProjectileClipModel 
=====================
*/
void idAI_Bot::CreateProjectileClipModel( int requestedWeapon ) const { //warning! make sure the requested weapon exists when you call this!
	if ( weapons[ requestedWeapon ].projectileClipModel == NULL ) {
		idBounds projectileBounds( vec3_origin );
		projectileBounds.ExpandSelf( weapons[ requestedWeapon ].projectileRadius );
		weapons[ requestedWeapon ].projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) );
	}
}


/*
=====================
idAI_Bot::CreateProjectile
=====================
*/
idProjectile *idAI_Bot::CreateProjectile( const idVec3 &pos, const idVec3 &dir ) { //NOTE: make sure a weapon is selected and it has a weapon before calling this
	idEntity *ent;
	const char *clsname;
	const idDict *	currentProjectileDef;

	if ( !projectile.GetEntity() ) {
		currentProjectileDef = weapons[ currentWeapon ].projectileDef; 
		gameLocal.SpawnEntityDef( *currentProjectileDef, &ent, false );

		if ( !ent ) {
			clsname = currentProjectileDef->GetString( "classname" );
			gameLocal.Error( "Could not spawn entityDef '%s'", clsname );
		}
		
		if ( !ent->IsType( idProjectile::Type ) ) {
			clsname = ent->GetClassname();
			gameLocal.Error( "'%s' is not an idProjectile", clsname );
		}
		projectile = ( idProjectile * )ent;
	}
	projectile.GetEntity()->Create( this, pos, dir );
	return projectile.GetEntity();
}



/*
=====================
idAI_Bot::LaunchProjectile
=====================
*/
idProjectile *idAI_Bot::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ) {
	idVec3				muzzle;
	idVec3				dir;
	idVec3				start;
	trace_t				tr;
	idBounds			projBounds;
	float				distance;
	const idClipModel	*projClip;
	float				attack_accuracy;
	float				attack_cone;
	float				projectile_spread;
	float				diff;
	float				angle = 0.0f;
	float				spin;
	idAngles			ang;
	int					num_projectiles;
	int					fireMode;
	int					i;
	idMat3				axis;
	idVec3				tmp;
	idProjectile		*lastProjectile;
	int 				noAim; //rev 2019
	
	if ( currentWeapon < 0){
		gameLocal.Warning( "%s (%s) is trying to fire but doesn't have a weapon selected", name.c_str(), GetEntityDefName() );
		return NULL;
	}

	if ( !weapons[ currentWeapon ].projectileDef ) {
		gameLocal.Warning( "%s (%s) : the current weapon doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
		return NULL;
	}

	attack_accuracy = weapons[ currentWeapon ].attack_accuracy;
	attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
	projectile_spread = weapons[ currentWeapon ].projectile_spread;
	num_projectiles = weapons[ currentWeapon ].num_projectiles;
	fireMode = weapons[ currentWeapon ].fireMode;

	GetMuzzle( jointname, muzzle, axis );

	if ( !projectile.GetEntity() ) {
		CreateProjectile( muzzle, axis[ 0 ] );
	}

	lastProjectile = projectile.GetEntity();

//rev 2019 start.  make the ai shoot only shoot straight ahead when the noaim key is set to 1 in their def file	
/*	
	if ( target != NULL ) {
		tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
		tmp.Normalize();
		axis = tmp.ToMat3();
	} else {
		axis = viewAxis;
	}
*/

	noAim  = spawnArgs.GetInt( "noaim" );	
	if ( noAim > 0 ) {
		// FIXME: DG: the following line is not good
		if ( target = NULL ) {  //rev 202 reverted back to = NULL.  != NULL broke noaim from working.  i am sure this is the wrong way to do this. :p
			tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
			tmp.Normalize();
			axis = tmp.ToMat3();
		} else {
			axis = viewAxis;
		}
	}
	if ( noAim < 1 ) {
		if ( target != NULL ) {
			tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
			tmp.Normalize();
			axis = tmp.ToMat3();
		} else {
			axis = viewAxis;
		}
	}
		
//rev 2019 noaim end

	// rotate it because the cone points up by default
	tmp = axis[2];
	axis[2] = axis[0];
	axis[0] = -tmp;

	// make sure the projectile starts inside the monster bounding box
	const idBounds &ownerBounds = physicsObj.GetAbsBounds();
	projClip = lastProjectile->GetPhysics()->GetClipModel();
	projBounds = projClip->GetBounds().Rotate( axis );

	// check if the owner bounds is bigger than the projectile bounds
	if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
		( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
		( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
		if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
			start = muzzle + distance * viewAxis[ 0 ];
		} else {
			start = ownerBounds.GetCenter();
		}
	} else {
		// projectile bounds bigger than the owner bounds, so just start it from the center
		start = ownerBounds.GetCenter();
	}

	gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
	muzzle = tr.endpos;

	// set aiming direction
	GetAimDirByWeapon( muzzle, target, this, dir, currentWeapon);
	ang = dir.ToAngles();

	// adjust his aim so it's not perfect.  uses sine based movement so the tracers appear less random in their spread.
	float t = MS2SEC( gameLocal.time + entityNumber * 497 );
	ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
	ang.yaw	+= idMath::Sin16( t * 6.7 ) * attack_accuracy;

	if ( clampToAttackCone ) {
		// clamp the attack direction to be within monster's attack cone so he doesn't do
		// things like throw the missile backwards if you're behind him
		diff = idMath::AngleDelta( ang.yaw, current_yaw );
		if ( diff > attack_cone ) {
			ang.yaw = current_yaw + attack_cone;
		} else if ( diff < -attack_cone ) {
			ang.yaw = current_yaw - attack_cone;
		}
	}

	axis = ang.ToMat3();

	//ivan start - fire modes - setup
	int firemodeCounter = 0;
	int firemodeCounterPos = 0;
	idVec3 updown_offset;
	updown_offset.Zero();

	if( fireMode == AI_FIREMODE_2D_STEP_SPREAD){ 
		if(num_projectiles > 1){								//Example: spread = 90, num projs = 5
			angle = 2*projectile_spread/(num_projectiles-1);	//Spread step: 2* 90 /(5-1) = 45 degrees
			angle = angle/180.0f;								//normalized from 0 to 1: 45/180 = 0.5 --> % of 180 degrees to use: 0, 0.25, -0.25, 0.5, -0.5
		}
	} 
	//ivan end

	float spreadRad = DEG2RAD( projectile_spread );
	for( i = 0; i < num_projectiles; i++ ) {		
		//ivan start - fire modes - direction
		if( fireMode == AI_FIREMODE_DEFAULT){
			// spread the projectiles out
			angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
			spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
			dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
		}else if( fireMode == AI_FIREMODE_2D_STEP_SPREAD){
			// FIXME: DG: angle might not be properly initialized (if num_projectiles == 1); I added the angle = 0 to the variable definition
			dir = (axis[ 0 ] * (1-angle*firemodeCounterPos) ) + ( axis[ 2 ] * angle*firemodeCounter );
			//upd counter
			if(firemodeCounter >= 0){
				firemodeCounter++;
				firemodeCounterPos = firemodeCounter;
			}
			firemodeCounter = - firemodeCounter;
		}else if( fireMode == AI_FIREMODE_2D_PARALLEL_SPREAD){ 
			dir = axis[ 0 ];
			updown_offset = axis[ 2 ]*projectile_spread*firemodeCounter; 
			//upd counter
			if(firemodeCounter >= 0){
				firemodeCounter++;
			}
			firemodeCounter = - firemodeCounter;
		}else if( fireMode == AI_FIREMODE_2D_RANDOM_SPREAD){
			// spread the projectiles out
			angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
			spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
			dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) );
		}else{
			gameLocal.Error("Unknown AI fire mode: %d", fireMode );
		}
		//ivan end
		
		dir.Normalize();

		// launch the projectile
		if ( !projectile.GetEntity() ) {
			CreateProjectile( muzzle, dir );
		}
		lastProjectile = projectile.GetEntity();

		//ivan start - fire modes - postion offset
		//lastProjectile->Launch( muzzle, dir, vec3_origin );
		lastProjectile->Launch( muzzle + updown_offset, dir, vec3_origin );
		//ivan test end

		projectile = NULL;
	}

	//weapon fxs
	TriggerWeaponEffects( muzzle );

	//ammo
	if(weapons[ currentWeapon ].clipSize > 0){ //only if limited clip size
		weapons[ currentWeapon ].ammoInClip--;
		if(weapons[ currentWeapon ].ammoInClip <= 0){
			AI_WEAPON_NEED_RELOAD = true;
		}
	}

	lastAttackTime = gameLocal.time;

	return lastProjectile;
}


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

	Aim

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

/*
=====================
idAI_Bot::GetAimDir
=====================
*/
bool idAI_Bot::GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const {
	return GetAimDirByWeapon( firePos, aimAtEnt, ignore, aimDir, currentWeapon);
}


/*
=====================
idAI_Bot::GetAimDirByWeapon
=====================
*/
bool idAI_Bot::GetAimDirByWeapon( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir, int weaponToTest) const {
	idVec3	targetPos1;
	idVec3	targetPos2;
	idVec3	delta;
	float	max_height;
	bool	result;

	//if no weapon is selected
	if ( weaponToTest < 0 || weaponToTest >= weapons.Num()){ 
		gameLocal.Warning( "idAI_Bot::GetAimDirByWeapon: the weapon to test is out of range" );
		return false;
	}

	// if no aimAtEnt or projectile set
	if ( !aimAtEnt || !weapons[ weaponToTest ].projectileDef ) {
		aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis();
		return false;
	}

	if ( weapons[ weaponToTest ].projectileClipModel == NULL ) {
		CreateProjectileClipModel( weaponToTest );
	}

	if ( aimAtEnt == enemy.GetEntity() ) {
		static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( lastVisibleEnemyPos, targetPos1, targetPos2 );
	} else if ( aimAtEnt->IsType( idActor::Type ) ) {
		static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( aimAtEnt->GetPhysics()->GetOrigin(), targetPos1, targetPos2 );
	} else {
		targetPos1 = aimAtEnt->GetPhysics()->GetAbsBounds().GetCenter();
		targetPos2 = targetPos1;
	}

	// try aiming for chest
	delta = firePos - targetPos2;
	max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
	result = PredictTrajectory( firePos, targetPos2, weapons[ weaponToTest ].projectileSpeed, weapons[ weaponToTest ].projectileGravity, weapons[ weaponToTest ].projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
	if ( result || !aimAtEnt->IsType( idActor::Type ) ) {
		return result;
	}

	// try aiming for head
	delta = firePos - targetPos1;
	max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
	result = PredictTrajectory( firePos, targetPos1, weapons[ weaponToTest ].projectileSpeed, weapons[ weaponToTest ].projectileGravity, weapons[ weaponToTest ].projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );

	return result;
}

/*
================
idAI_Bot::GetMuzzle
================
*/
void idAI_Bot::GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ) { 
	jointHandle_t joint;

	if ( !jointname || !jointname[ 0 ] ) { //new behaviour: is nothing is passed, use the current weapon settings
		if(currentWeapon >= 0){
			//WEAPON CASE 1: weapon fireJoint!
			if(weapons[ currentWeapon ].fireJoint != INVALID_JOINT){ //if fireJoint is valid, we are sure the entity has an MD5 model
				idEntity *myWeaponEnt;
				idAnimator *myWeaponAnimator;

				//get the reference to the weapon and its animator
				myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity();
				myWeaponAnimator = myWeaponEnt->GetAnimator(); 

				//use the fireJoint!
				joint = weapons[ currentWeapon ].fireJoint; //myWeaponAnimator->GetJointHandle( "barrel" ); //

				//do the equivalent of GetJointWorldTransform even without being casted to idAnimatedEntity
				myWeaponAnimator->GetJointTransform( joint, gameLocal.time, muzzle, axis );
				myWeaponEnt->ConvertLocalToWorldTransform( muzzle, axis );
				
				//gameLocal.Printf("GetMuzzle che usa fireJoint\n");
			}
			//WEAPON CASE 2: my bindJoint !
			else{ 
				joint = weapons[ currentWeapon ].bindJoint; //bindJoint is always valid
				GetJointWorldTransform( joint, gameLocal.time, muzzle, axis );
				//gameLocal.Printf("GetMuzzle using bindJoint\n");
			}
		//NO-WEAPON CASE 1: my origin
		}else{ 
			muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14;
			muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f;
		}
	//NO-WEAPON CASE 2: my specified joint
	} else {
		joint = animator.GetJointHandle( jointname );
		if ( joint == INVALID_JOINT ) {
			gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
		}
		GetJointWorldTransform( joint, gameLocal.time, muzzle, axis );
	}
}

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

	Weapon events

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


/*
================
idAI_Bot::Event_SelectWeapon
================
*/
void idAI_Bot::Event_SelectWeapon( int weaponNum ){
	idThread::ReturnInt( SelectWeapon( weaponNum ) );
}

/*
================
idAI_Bot::Event_IsWeaponEnabled
================
*/
void idAI_Bot::Event_IsWeaponEnabled( int weaponNum ){
	if( weaponNum == -1 ){ //no weapon... is always enabled
		idThread::ReturnInt( true );
		return;
	}

	if( weaponNum < 0 || weaponNum >= weapons.Num()){ 
		gameLocal.Warning("The weapon: %d is out of range" ,weaponNum);
		idThread::ReturnInt( false );
		return;
	}else{
		idThread::ReturnInt( weapons[ weaponNum ].enabled );
		return;
	}
}

/*
================
idAI_Bot::Event_AddWeaponInBalancing
================
*/
void idAI_Bot::Event_AddWeaponInBalancing( int weaponNum ){
	if( weaponNum < 0 || weaponNum >= weapons.Num()){ //-1 is meaningless here
		gameLocal.Warning("The weapon: %d is out of range" ,weaponNum);
		return;
	}

	weapons[ weaponNum ].requestedForBalancing = true;
}

/*
================
idAI_Bot::Event_GetBalancedWeapon
================
*/
void idAI_Bot::Event_GetBalancedWeapon(){
	int bestWeapon = -1;	
	int bestValue = 0;	
	int i;	
	for( i = 0; i < weapons.Num(); i++ ) { //need to search the one with the lower attackCounter
		if( weapons[ i ].requestedForBalancing ){
			/*
			gameLocal.Printf("Checking Weapon: %d " ,i);
			gameLocal.Printf(" - attackCounter: %d \n" ,weapons[ i ].attackCounter);
			*/
			weapons[ i ].requestedForBalancing = false; //remove the flag!
			if( (weapons[ i ].attackCounter < bestValue) || (bestValue == 0)){
				bestValue = weapons[ i ].attackCounter;
				bestWeapon = i;
			}
		}
	}

	idThread::ReturnInt( bestWeapon ); //can be -1
}

/*
================
idAI_Bot::Event_RegisterAttackForBalancing
================
*/
void idAI_Bot::Event_RegisterAttackForBalancing( int weaponNum ){
	if( weaponNum < 0 || weaponNum >= weapons.Num()){ //-1 is meaningless here
		gameLocal.Warning("The weapon: %d is out of range" ,weaponNum);
		return;
	}

	weapons[ weaponNum ].attackCounter += weapons[ weaponNum ].balancing_multiplier;
}

/*
================
idAI_Bot::Event_SelectAnotherWeapon
================
*/
void idAI_Bot::Event_SelectAnotherWeapon( int idealLevel, int minLevel ){ 
	int testWeapon;
	int nextWeapon;
	int wCounter;
	int wNum;

	wNum = weapons.Num();

	//ideal loop
	testWeapon = currentWeapon;
	nextWeapon = currentWeapon;

	for( wCounter = 0; wCounter < wNum; wCounter++ ) { //do exactly a complete loop (also test the current weapon so that it's ok even if we start from -1)
		
		//find the next one
		testWeapon++;
		if(testWeapon >= wNum){testWeapon = 0;}

		//test it
		if ( (weapons[ testWeapon ].enabled) && (weapons[ testWeapon ].priorityLevel >= idealLevel) ) {
			nextWeapon = testWeapon;
			break;
		}
	}

	if(nextWeapon != currentWeapon){ //found!
		idThread::ReturnInt( SelectWeapon( nextWeapon ) ); //it's enabled and on range, so no way this could be false.
		return;
	}

	//minlevel loop
	testWeapon = currentWeapon;
	nextWeapon = currentWeapon;

	for( wCounter = 0; wCounter < wNum; wCounter++ ) { //do exactly a complete loop (also test the current weapon so that it's ok even if we start from -1)
		
		//find the next one
		testWeapon++;
		if(testWeapon >= wNum){testWeapon = 0;}

		//test it
		if ( (weapons[ testWeapon ].enabled) && (weapons[ testWeapon ].priorityLevel >= minLevel) ) {
			nextWeapon = testWeapon;
			break;
		}
	}

	if(nextWeapon != currentWeapon){ //found!
		idThread::ReturnInt( SelectWeapon( nextWeapon ) ); //it's enabled and on range, so no way this can be false.
		return;
	}

	//nothing can satisfy the conditions
	idThread::ReturnInt( false );

}

/*
================
idAI_Bot::Event_ReloadWeapon
================
*/
void idAI_Bot::Event_ReloadWeapon( void ){
	AI_WEAPON_NEED_RELOAD = false;
	if( currentWeapon < 0) return;
	weapons[ currentWeapon ].ammoInClip = weapons[ currentWeapon ].clipSize;
}

/*
================
idAI_Bot::Event_CheckReloadTolerance
================
*/
void idAI_Bot::Event_CheckReloadTolerance( void ){ //returns true if we should reload
	if( currentWeapon < 0){ //no weapon selected
		idThread::ReturnInt( false );
		return;
	}

	if( weapons[ currentWeapon ].clipSize == 0){ //unlimited clip
		idThread::ReturnInt( false );
		return;
	}

	if(	weapons[ currentWeapon ].ammoInClip > weapons[ currentWeapon ].reloadTolerance ){ //still enough ammo
		idThread::ReturnInt( false );
		return;
	}
	
	//reloading is a good idea at this point...
	idThread::ReturnInt( true );
	return;
}


/*
================
idAI_Bot::Event_GetAmmoInClip
================
*/
void idAI_Bot::Event_GetAmmoInClip( void ){
	if( currentWeapon < 0){
		idThread::ReturnInt( 0 );
		return;
	}

	idThread::ReturnInt( weapons[ currentWeapon ].ammoInClip );
}

/*
================
idAI_Bot::Event_GetClipSize
================
*/
void idAI_Bot::Event_GetClipSize( void ){
	if( currentWeapon < 0){
		idThread::ReturnInt( 0 );
		return;
	}

	idThread::ReturnInt( weapons[ currentWeapon ].clipSize );
}


/*
================
idAI_Bot::Event_GetCurrentWeapon
================
*/
void idAI_Bot::Event_GetCurrentWeapon( void ){
	idThread::ReturnInt( currentWeapon );
}

/*
================
idAI_Bot::Event_GetWeaponNumByName
================
*/
void idAI_Bot::Event_GetWeaponNumByName( const char *weaponName ){
	int weaponNum;
	weaponNum = GetWeaponNumByName( idStr( weaponName ) );
	idThread::ReturnInt( weaponNum );
}


/*
=====================
idAI::Event_AllowMovement
=====================
*/
void idAI_Bot::Event_CanSpeak( float can ) {
	canSpeak = ( can != 0.0f );
	if(canSpeak){
		SetDefaultSayTimes(0.5f);
	}
}

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

	Anims events

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

/*
=====================
idAI_Bot::Event_GetIdleAnim
=====================
*/

void idAI_Bot::Event_GetIdleAnim( void ) {
	const char *animname;
	if(currentWeapon < 0){
		animname = "idle";
	}else{
		animname = va( "%s_idle", weapons[ currentWeapon ].weaponName.c_str() ); 
	}
	idThread::ReturnString( animname );
}

/*
=====================
idAI_Bot::Event_GetReloadAnim
=====================
*/

void idAI_Bot::Event_GetReloadAnim( void ) {
	const char *animname;
	if(currentWeapon < 0){
		animname = "idle"; //in case someone tries to reload but no weapon is selected :)
	}else{
		animname = va( "%s_reload", weapons[ currentWeapon ].weaponName.c_str() ); 
	}
	idThread::ReturnString( animname );
}

/*
=====================
idAI_Bot::Event_GetReloadAnim
=====================
*/

void idAI_Bot::Event_GetFireAnim( void ) {
	const char *animname;
	if(currentWeapon < 0){
		animname = "idle"; //in case someone tries to fire but no weapon is selected :)
	}else{
		animname = va( "%s_fire", weapons[ currentWeapon ].weaponName.c_str() ); 
	}
	idThread::ReturnString( animname );
}

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

	Fire events

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

/*
=====================
idAI_Bot::Event_FireWeapon
=====================
*/
void idAI_Bot::Event_FireWeapon( void ) {
	idProjectile *proj;

	proj = LaunchProjectile( "", enemy.GetEntity(), true ); //"" will force the use of weapon specific settings
	idThread::ReturnEntity( proj );
}

/*
=====================
idAI_Bot::Event_CreateMissile
=====================
*/
void idAI_Bot::Event_CreateMissile( const char *jointname ) {
	idVec3 muzzle;
	idMat3 axis;

	//gameLocal.Printf("idAI_Bot::Event_CreateMissile \n");

	//ivan start
	if ( currentWeapon < 0){
		gameLocal.Warning( "idAI_Bot::Event_CreateMissile: %s (%s) is trying to fire but doesn't have a weapon selected", name.c_str(), GetEntityDefName() );
		return idThread::ReturnEntity( NULL );
	}

	if ( !weapons[ currentWeapon ].projectileDef ) {
		gameLocal.Warning( "idAI_Bot::Event_CreateMissile: %s (%s) : the current weapon doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
		return idThread::ReturnEntity( NULL );
	}
	//ivan end

	GetMuzzle( jointname, muzzle, axis );
	CreateProjectile( muzzle, viewAxis[ 0 ] * physicsObj.GetGravityAxis() );
	if ( projectile.GetEntity() ) {
		if ( !jointname || !jointname[ 0 ] ) {
			projectile.GetEntity()->Bind( this, true );
		} else {
			projectile.GetEntity()->BindToJoint( this, jointname, true );
		}
	}
	idThread::ReturnEntity( projectile.GetEntity() );
}


/*
=====================
idAI_Bot::Event_LaunchMissile
=====================
*/
void idAI_Bot::Event_LaunchMissile( const idVec3 &org, const idAngles &ang ) {
	idVec3		start;
	trace_t		tr;
	idBounds	projBounds;
	const idClipModel *projClip;
	idMat3		axis;
	float		distance;

	//gameLocal.Printf("idAI_Bot::Event_LaunchMissile \n");

	//ivan start
	if ( currentWeapon < 0){
		gameLocal.Warning( "idAI_Bot::Event_LaunchMissile: %s (%s) is trying to fire but doesn't have a weapon selected", name.c_str(), GetEntityDefName() );
		idThread::ReturnEntity( NULL );
		return;
	}

	if ( !weapons[ currentWeapon ].projectileDef ) {
		gameLocal.Warning( "idAI_Bot::Event_LaunchMissile: %s (%s) : the current weapon doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
		idThread::ReturnEntity( NULL );
		return;
	}
	//ivan end

	axis = ang.ToMat3();
	if ( !projectile.GetEntity() ) {
		CreateProjectile( org, axis[ 0 ] );
	}

	// make sure the projectile starts inside the monster bounding box
	const idBounds &ownerBounds = physicsObj.GetAbsBounds();
	projClip = projectile.GetEntity()->GetPhysics()->GetClipModel();
	projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() );

	// check if the owner bounds is bigger than the projectile bounds
	if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
		( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
		( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
		if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) {
			start = org + distance * viewAxis[ 0 ];
		} else {
			start = ownerBounds.GetCenter();
		}
	} else {
		// projectile bounds bigger than the owner bounds, so just start it from the center
		start = ownerBounds.GetCenter();
	}

	gameLocal.clip.Translation( tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this );

	// launch the projectile
	idThread::ReturnEntity( projectile.GetEntity() );
	projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin );
	projectile = NULL;

	TriggerWeaponEffects( tr.endpos );

	lastAttackTime = gameLocal.time;
}
 
/*
===============================================================================

	CanHit events

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

/*
=====================
idAI_Bot::Event_CanHitEnemyFromCurrentWeapon
=====================
*/
void idAI_Bot::Event_CanHitEnemyFromCurrentWeapon( void ) {
	trace_t	tr;
	idVec3	muzzle;
	idMat3	axis;
	idVec3	start;
	//float	distance;

	idActor *enemyEnt = enemy.GetEntity();
	if ( !AI_ENEMY_VISIBLE || !enemyEnt ) {
		idThread::ReturnInt( false );
		return;
	}

	//ivan start
	if( currentWeapon < 0 ){
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromCurrentWeapon: no weapon selected!");
		idThread::ReturnInt( false );
		return;
	}

	if( !weapons[ currentWeapon ].projectileDef ){
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromCurrentWeapon: no projectile defined!");
		idThread::ReturnInt( false );
		return;
	}
	//ivan end

	
	// don't check twice per frame - makes sense because no parameters can be passed
	if ( gameLocal.time == lastHitFCWCheckTime ) {
		idThread::ReturnInt( lastHitFCWCheckResult );
		return;
	}

	lastHitFCWCheckTime = gameLocal.time;

	//get muzzle and destination positions
	GetMuzzle( "", muzzle, axis ); //"" will force use the weapon fire position
	idVec3 toPos = enemyEnt->GetEyePosition();
	
	if ( weapons[ currentWeapon ].projectileClipModel == NULL ) {
		CreateProjectileClipModel();
	}

	gameLocal.clip.Translation( tr, muzzle, toPos, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this );
	if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) {
		//gameLocal.Printf("OK FromCurrentWeapon!\n");
		lastHitFCWCheckResult = true;
	} else {
		//gameLocal.Printf("NO FromCurrentWeapon!\n");
		lastHitFCWCheckResult = false;
	}

	idThread::ReturnInt( lastHitFCWCheckResult );
}

/*
=====================
idAI_Bot::Event_CanFireToEnemyNoSelfDamage
=====================
*/
void idAI_Bot::Event_CanFireToEnemyNoSelfDamage( int requireVisible ) {
	trace_t	tr;
	idVec3	muzzle;
	idMat3	axis;
	idVec3	start;
	//float	distance;

	idActor *enemyEnt = enemy.GetEntity();
	if ( (!AI_ENEMY_VISIBLE && requireVisible != 0 )  || !enemyEnt ) {
		idThread::ReturnInt( false );
		return;
	}

	//ivan start
	if( currentWeapon < 0 ){
		gameLocal.Warning("idAI_Bot::Event_CanFireToEnemyNoSelfDamage: no weapon selected!");
		idThread::ReturnInt( false );
		return;
	}

	if( !weapons[ currentWeapon ].projectileDef ){
		gameLocal.Warning("idAI_Bot::Event_CanFireToEnemyNoSelfDamage: no projectile defined!");
		idThread::ReturnInt( false );
		return;
	}
	//ivan end

	
	// don't check twice per frame - makes sense because if (requireVisible = true && !AI_ENEMY_VISIBLE) the timer is not updated, so in any case if we are here we are doing the same thing again  
	if ( gameLocal.time == lastHitNSDCheckTime ) {
		idThread::ReturnInt( lastHitNSDCheckResult );
		return;
	}
	lastHitNSDCheckTime = gameLocal.time;

	//get muzzle and destination positions
	GetMuzzle( "", muzzle, axis ); //"" will force to use the weapon fire position
	idVec3 toPos = enemyEnt->GetEyePosition();

	if ( weapons[ currentWeapon ].projectileClipModel == NULL ) {
		CreateProjectileClipModel();
	}

	gameLocal.clip.Translation( tr, muzzle, toPos, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this );
	
	//fix crash
	if ( tr.fraction >= 1.0f ){
		//gameLocal.Printf("NoSelfDamage OK: As far as we know, there's nothing between us.\n");
		lastHitNSDCheckResult = true;
		idThread::ReturnInt( lastHitNSDCheckResult );
		return;
	}

	if ( tr.fraction == 0.0f ){
		//gameLocal.Printf("NoSelfDamage NO: something is completely blocking our aim.\n");
		lastHitNSDCheckResult = false;
		idThread::ReturnInt( lastHitNSDCheckResult );
		return;
	}
	//fix end


	float dist = ( muzzle - tr.endpos ).LengthFast(); //crashato qui 1 volta
	
	if ( tr.c.material->GetSurfaceType() == SURFTYPE_GLASS ) { //TODO: fix this in another way... he could see me through a glass but hit another material type.
		//gameLocal.Printf("NoSelfDamage NO: Never fire against glasses!\n");
		lastHitNSDCheckResult = false;
	}else if ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) { //was: ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) {
		//gameLocal.Printf("NoSelfDamage OK: I can hit him!\n");
		lastHitNSDCheckResult = true;
	} else if (dist > weapons[ currentWeapon ].minSelfDmgDistance ){ //60.0f
		//gameLocal.Printf("NoSelfDamage OK: I cannot hit him but is distant enough %f!\n", dist);
		lastHitNSDCheckResult = true;
	} else {
		//gameLocal.Printf("NoSelfDamage NO: I would hit myself!\n");
		lastHitNSDCheckResult = false;
	}

	idThread::ReturnInt( lastHitNSDCheckResult );
}


/*
=====================
idAI_Bot::Event_CanFireNoSelfDamageFromAnim
=====================

void idAI_Bot::Event_CanFireNoSelfDamageFromAnim( const char *animname, int requireVisible ) {
	
}
*/

/*
=====================
idAI_Bot::Event_CanHitEnemyFromFireAnim
=====================
*/
void idAI_Bot::Event_CanHitEnemyFromFireAnim( int weaponToTest ) { //note: it uses the proj of the requested weapon
	int		anim;
	idVec3	dir;
	idVec3	local_dir;
	idVec3	fromPos;
	idMat3	axis;
	idVec3	start;
	trace_t	tr;
	float	distance;
	const char *animname; 

	//gameLocal.Printf("idAI_Bot::Event_CanHitEnemyFromFireAnim called! \n" );

	idActor *enemyEnt = enemy.GetEntity();
	if ( !AI_ENEMY_VISIBLE || !enemyEnt ) {
		idThread::ReturnInt( false );
		return;
	}	

	//ivan start
	if(weaponToTest < 0 || weaponToTest >= weapons.Num()){
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromFireAnim: requested weapon is out of range!");
		idThread::ReturnInt( false );
		return;
	}

	if( !weapons[ weaponToTest ].projectileDef ){
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromFireAnim: requested weapon has no projectile defined!");
		idThread::ReturnInt( false );
		return;
	}
	//ivan end

	animname = va( "%s_fire", weapons[ weaponToTest ].weaponName.c_str() ); 
	anim = GetAnim( ANIMCHANNEL_LEGS, animname );
	if ( !anim ) {
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromFireAnim: requested weapon has no fire anim defined!");
		idThread::ReturnInt( false );
		return;
	}

	// just do a ray test if close enough
	if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) {
		Event_CanHitEnemy();
		return;
	}

	// calculate the world transform of the launch position
	
	const idVec3 &org = physicsObj.GetOrigin();
	dir = lastVisibleEnemyPos - org;
	physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
	local_dir.z = 0.0f;
	local_dir.ToVec2().Normalize();
	axis = local_dir.ToMat3();
	fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis;
	
	
	//ivan:
	//fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * viewAxis * physicsObj.GetGravityAxis();


	if ( weapons[ weaponToTest ].projectileClipModel == NULL ) { //TODO: use the requested one!!!
		CreateProjectileClipModel(weaponToTest);
	}

	// check if the owner bounds is bigger than the projectile bounds
	const idBounds &ownerBounds = physicsObj.GetAbsBounds();
	const idBounds &projBounds = weapons[ weaponToTest ].projectileClipModel->GetBounds();
	if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
		( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
		( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
		if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) {
			start = org + distance * viewAxis[ 0 ];
		} else {
			start = ownerBounds.GetCenter();
		}
	} else {
		// projectile bounds bigger than the owner bounds, so just start it from the center
		start = ownerBounds.GetCenter();
	}	

	gameLocal.clip.Translation( tr, start, fromPos, weapons[ weaponToTest ].projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this );
	fromPos = tr.endpos;

	if ( GetAimDirByWeapon( fromPos, enemy.GetEntity(), this, dir, weaponToTest) ) {
		//gameLocal.Printf("idAI_Bot::Event_CanHitEnemyFromAnim true!\n");
		idThread::ReturnInt( true );
	} else {
		//gameLocal.Printf("idAI_Bot::Event_CanHitEnemyFromAnim false!\n");
		idThread::ReturnInt( false );
	}
}


/*
=====================
idAI_Bot::Event_CanHitEnemyFromAnim
=====================
*/
void idAI_Bot::Event_CanHitEnemyFromAnim( const char *animname ) { //deprecated: it uses the current projectile!
	int		anim;
	idVec3	dir;
	idVec3	local_dir;
	idVec3	fromPos;
	idMat3	axis;
	idVec3	start;
	trace_t	tr;
	float	distance;

	gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromAnim is deprecated: it uses the current projectile!");

	idActor *enemyEnt = enemy.GetEntity();
	if ( !AI_ENEMY_VISIBLE || !enemyEnt ) {
		idThread::ReturnInt( false );
		return;
	}

	//ivan start
	if( currentWeapon < 0 ){
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromAnim: no weapon selected!");
		idThread::ReturnInt( false );
		return;
	}

	if( !weapons[ currentWeapon ].projectileDef ){
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromAnim: no projectile defined!");
		idThread::ReturnInt( false );
		return;
	}
	//ivan end

	anim = GetAnim( ANIMCHANNEL_LEGS, animname );
	if ( !anim ) {
		idThread::ReturnInt( false );
		return;
	}

	// just do a ray test if close enough
	if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) {
		Event_CanHitEnemy();
		return;
	}

	// calculate the world transform of the launch position
	const idVec3 &org = physicsObj.GetOrigin();
	dir = lastVisibleEnemyPos - org;
	physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
	local_dir.z = 0.0f;
	local_dir.ToVec2().Normalize();
	axis = local_dir.ToMat3();
	fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis;

	if ( weapons[ currentWeapon ].projectileClipModel == NULL ) {
		CreateProjectileClipModel();
	}

	// check if the owner bounds is bigger than the projectile bounds
	const idBounds &ownerBounds = physicsObj.GetAbsBounds();
	const idBounds &projBounds = weapons[ currentWeapon ].projectileClipModel->GetBounds();
	if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
		( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
		( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
		if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) {
			start = org + distance * viewAxis[ 0 ];
		} else {
			start = ownerBounds.GetCenter();
		}
	} else {
		// projectile bounds bigger than the owner bounds, so just start it from the center
		start = ownerBounds.GetCenter();
	}

	gameLocal.clip.Translation( tr, start, fromPos, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this );
	fromPos = tr.endpos;

	if ( GetAimDirByWeapon( fromPos, enemy.GetEntity(), this, dir, currentWeapon ) ) {
		idThread::ReturnInt( true );
	} else {
		idThread::ReturnInt( false );
	}
}


/*
=====================
idAI_Bot::Event_CanHitEnemyFromJoint
=====================
*/
void idAI_Bot::Event_CanHitEnemyFromJoint( const char *jointname ) { //deprecated: it uses the current projectile!
	trace_t	tr;
	idVec3	muzzle;
	idMat3	axis;
	idVec3	start;
	float	distance;

	gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromJoint is deprecated: it uses the current projectile!");

	idActor *enemyEnt = enemy.GetEntity();
	if ( !AI_ENEMY_VISIBLE || !enemyEnt ) {
		idThread::ReturnInt( false );
		return;
	}

	//ivan start
	if( currentWeapon < 0 ){
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromJoint: no weapon selected!");
		idThread::ReturnInt( false );
		return;
	}

	if( !weapons[ currentWeapon ].projectileDef ){
		gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromJoint: no projectile defined!");
		idThread::ReturnInt( false );
		return;
	}
	//ivan end

	// don't check twice per frame - we cannot check more than 1 joint per frame!
	if ( gameLocal.time == lastHitCheckTime ) {
		idThread::ReturnInt( lastHitCheckResult );
		return;
	}

	lastHitCheckTime = gameLocal.time;

	const idVec3 &org = physicsObj.GetOrigin();
	idVec3 toPos = enemyEnt->GetEyePosition();
	jointHandle_t joint = animator.GetJointHandle( jointname );
	if ( joint == INVALID_JOINT ) {
		gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
	}
	animator.GetJointTransform( joint, gameLocal.time, muzzle, axis );
	muzzle = org + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis();

	if ( weapons[ currentWeapon ].projectileClipModel == NULL ) {
		CreateProjectileClipModel();
	}

	// check if the owner bounds is bigger than the projectile bounds
	const idBounds &ownerBounds = physicsObj.GetAbsBounds();
	const idBounds &projBounds = weapons[ currentWeapon ].projectileClipModel->GetBounds();
	if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
		( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
		( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
		if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) {
			start = org + distance * viewAxis[ 0 ];
		} else {
			start = ownerBounds.GetCenter();
		}
	} else {
		// projectile bounds bigger than the owner bounds, so just start it from the center
		start = ownerBounds.GetCenter();
	}

	gameLocal.clip.Translation( tr, start, muzzle, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this );
	muzzle = tr.endpos;

	gameLocal.clip.Translation( tr, muzzle, toPos, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this );
	if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) {
		lastHitCheckResult = true;
	} else {
		lastHitCheckResult = false;
	}

	idThread::ReturnInt( lastHitCheckResult );
}

/*
=====================
idAI_Bot::Event_FindEnemyAIorPL
=====================
*/
void idAI_Bot::Event_FindEnemyAIorPL( int useFOV ) {
	idEntity	*ent;
	idActor		*actor;
	idActor		*bestEnemy;
	float		bestDist;
	float		dist;
	idVec3		delta;
	pvsHandle_t pvs;
	int			i;

	pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() );

	bestDist = idMath::INFINITY;
	bestEnemy = NULL;
	for ( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
		if ( ent->fl.hidden || ent->fl.isDormant || !ent->IsType( idActor::Type ) ) {
			continue;
		}

		actor = static_cast<idActor *>( ent );
		if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) {
			continue;
		}

		if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) {
			continue;
		}

		delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin();
		dist = delta.LengthSqr();
		if ( ( dist < bestDist ) && CanSee( actor, useFOV != 0 ) ) {
			bestDist = dist;
			bestEnemy = actor;
		}
	}

	gameLocal.pvs.FreeCurrentPVS( pvs );

	if(bestEnemy != NULL){
		idThread::ReturnEntity( bestEnemy );
		return;
	}

	//else player!

	if ( gameLocal.InPlayerPVS( this ) ) {
		for ( i = 0; i < gameLocal.numClients ; i++ ) {
			ent = gameLocal.entities[ i ];

			if ( !ent || !ent->IsType( idActor::Type ) ) {
				continue;
			}

			actor = static_cast<idActor *>( ent );
			if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) {
				continue;
			}

			if ( CanSee( actor, useFOV != 0 ) ) {
				idThread::ReturnEntity( actor );
				return;
			}
		}
	}

	idThread::ReturnEntity( NULL );
}

/*
=====================
idAI_Bot::Event_FindClosestBotNode
=====================

void idAI_Bot::Event_FindClosestBotNode( idEntity *ignoreNode ) {
	idEntity	*ent;
	idBotNode	*botNode;
	idBotNode	*bestBotNode;
	float		bestDist;
	float		dist;
	idVec3		delta;
	//pvsHandle_t pvs;

	//pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() );

	bestDist = idMath::INFINITY;
	bestBotNode = NULL;
	for ( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
		if ( !ent->IsType( idBotNode::Type ) ) { //ent->fl.isDormant ||  <-- set bot nodes never dormant to cycle entities faster (TODO?)
			//gameLocal.Printf("%s is not an idBotNode \n", ent->GetName());
			continue;
		}

		botNode = static_cast<idBotNode *>( ent );
		if ( ( botNode->IsDisabled() ) || botNode->IsInUse() ) { //skip at least the ones disabled or already in use
			//gameLocal.Printf("%s IsDisabled || IsInUse \n", botNode->GetName());
			continue;
		}

		if( (ignoreNode) && (botNode == ignoreNode) ){
			//gameLocal.Printf("%s == ignoreNode \n", botNode->GetName());
			continue;
		}

		delta = physicsObj.GetOrigin() - botNode->GetPhysics()->GetOrigin();
		dist = delta.LengthSqr();
		if ( dist < bestDist ) { //TODO: add can reach test?
			bestDist = dist;
			bestBotNode = botNode;
		}
	}

	//gameLocal.pvs.FreeCurrentPVS( pvs );
	bestDist = bestDist * idMath::RSqrt( bestDist ); //see idVec3::LengthFast()
	if(bestDist < attackNodesMaxDist){
		idThread::ReturnEntity( bestBotNode );
	}else{
		//gameLocal.Printf("TOO FAR: %f\n",bestDist);
		idThread::ReturnEntity( NULL );
	}
}
*/

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

	Misc Events

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

/*
=====================
idAI_Bot::GetEnemyRange
=====================

float idAI_Bot::GetEnemyRange( void ) {
	float dist;
	idActor *enemyEnt = enemy.GetEntity();

	if ( enemyEnt ) {
		dist = ( enemyEnt->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length();
	} else {
		// Just some really high number
		dist = idMath::INFINITY;
	}

	return dist;
}
*/

/*
=====================
idAI_Bot::SetEnemyPosition
=====================
*/
void idAI_Bot::SetEnemyPosition( void ) {
	idAI::SetEnemyPosition();
	lastVisibleEnemyTime = gameLocal.time;
}

/*
=====================
idAI_Bot::Event_LostTimeMoreThan
=====================
*/
void idAI_Bot::Event_LostTimeMoreThan( int time ) {
	if(lastVisibleEnemyTime + SEC2MS( time ) < gameLocal.time){
		//gameLocal.Printf("Event_LostTimeMoreThan true !\n" );
		idThread::ReturnInt( true ); 
	}else{
		//gameLocal.Printf("Event_LostTimeMoreThan false !\n" );
		idThread::ReturnInt( false ); 
	}
}

/*
=====================
idAI_Bot::Event_WeaponChangedMoreThan
=====================
*/
void idAI_Bot::Event_WeaponChangedMoreThan( int time ) {
	if(lastWeaponChangedTime + SEC2MS( time ) < gameLocal.time){
		//gameLocal.Printf("Event_WeaponChangedMoreThan true !\n" );
		idThread::ReturnInt( true ); 
	}else{
		//gameLocal.Printf("Event_WeaponChangedMoreThan false !\n" );
		idThread::ReturnInt( false ); 
	}
}

/*
=====================
idAI_Bot::Event_PlayAnimOnWeapon
=====================
*/
void idAI_Bot::Event_PlayAnimOnWeapon( const char *animname ) {
	if(currentWeapon < 0 ){
		return;
	}
	
	int anim;
	idEntity *myWeaponEnt;
	idAnimator *myWeaponAnimator;
	
	//get the reference to the weapon and its animator
	myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity();	
	myWeaponAnimator = myWeaponEnt->GetAnimator(); 

	// only if the entity has an MD5 model
	if ( myWeaponAnimator && myWeaponAnimator->ModelHandle() ) { 
		anim = myWeaponAnimator->GetAnim( animname );
		if ( !anim ) {
			gameLocal.Warning( "missing '%s' animation on current weapon!", animname );
			myWeaponAnimator->Clear( 0, gameLocal.time, 0 );
		} else {
			myWeaponAnimator->PlayAnim( 0, anim, gameLocal.time, 0 );
		}
	}
}



/*
=====================
idAI_Bot::idAI_Bot
=====================


void idAI_Bot::Event_ReleaseNode( void ) {
	if ( currentNode.GetEntity() ) {
		currentNode.GetEntity()->Release();
		currentNode = NULL;
	}
}
*/

/*
=====================
idAI_Bot::Event_TryLockNode
=====================


void idAI_Bot::Event_TryLockNode( idEntity *node ) {
	bool resultOK;

	if( !node || !node->IsType( idBotNode::Type )){
		gameLocal.Warning("trylock failed: the entity is not an idBotNode!" );
		idThread::ReturnInt( false ); return;
	}

	//cast it
	idBotNode * castedNode;
	castedNode = static_cast<idBotNode *>( node );

	//try
	resultOK = castedNode->TryLock(this);

	if(resultOK){
		//release the current node if test ok
		if( currentNode.GetEntity()){
			currentNode.GetEntity()->Release(); 
		}

		//upd the current node
		currentNode = castedNode;
	}

	idThread::ReturnInt( resultOK );
}
*/


/*
================
idAI_Bot::Event_SelectAnotherWeaponByNode
================

void idAI_Bot::Event_SelectAnotherWeaponByNode( idEntity *node ){ 
	int			testWeapon;
	int			nextWeapon;
	const char *testWeaponName;
	int			wCounter;
	int			wNum;

	assert( node );

	wNum = weapons.Num();

	//weapon loop
	testWeapon = currentWeapon;
	nextWeapon = currentWeapon;

	for( wCounter = 0; wCounter < wNum; wCounter++ ) { //do exactly a complete loop (also test the current weapon so that it's ok even if we start from -1)
		
		//find the next one
		testWeapon++;
		if(testWeapon >= wNum){testWeapon = 0;}

		//test it
		if ( (weapons[ testWeapon ].enabled) ){
			//check if the weapon is enabled on the node by its name
			testWeaponName = weapons[ testWeapon ].weaponName.c_str();
			if ( node->spawnArgs.GetBool(testWeaponName,"1") ){ //default: everything enabled
				nextWeapon = testWeapon;
				break;
			}
		}
	}

	if(nextWeapon != currentWeapon){ //found!
		//gameLocal.Printf("weapon '%s' selected!\n", testWeaponName );
		idThread::ReturnInt( SelectWeapon( nextWeapon ) ); //it's enabled and on range, so no way this could be false.
		return;
	}

	//nothing can satisfy the conditions
	idThread::ReturnInt( false );
}
*/


/*
================
idAI_Bot::Event_GetNextSquadMate
================
*/
void idAI_Bot::Event_GetNextSquadMate( idEntity *lastMatch ) {
	idAI_Bot *temp_bot;

	if(!squad.GetEntity()){
		idThread::ReturnEntity( NULL );
		return;
	}
	
	if ( lastMatch && lastMatch->IsType( idAI_Bot::Type ) ) {
		temp_bot = static_cast<idAI_Bot *>( lastMatch );
	}else{
		temp_bot = NULL;
	}

	idThread::ReturnEntity( squad.GetEntity()->GetNextMember( temp_bot, this ) );
}


/***********************************************************************

idBotNode

***********************************************************************/

/*
const idEventDef EV_BotNode_EvaluateConditions( "evaluateConditions", NULL, 'd' );
const idEventDef EV_BotNode_GetNextUsableNode( "getNextUsableNode", "e", 'e' );
const idEventDef EV_BotNode_CanBeUsedBy( "canBeUsedBy", "e", 'd' );
const idEventDef EV_BotNode_Reached( "reached" );

CLASS_DECLARATION( idEntity, idBotNode )
	EVENT( EV_BotNode_EvaluateConditions,		idBotNode::Event_EvaluateConditions )
	EVENT( EV_BotNode_GetNextUsableNode,		idBotNode::Event_GetNextUsableNode ) 
	EVENT( EV_BotNode_CanBeUsedBy,				idBotNode::Event_CanBeUsedBy ) 
	EVENT( EV_BotNode_Reached,					idBotNode::Event_Reached )
END_CLASS
*/

/*
=====================
idBotNode::idBotNode
=====================

idBotNode::idBotNode( void ) {
	fight_distance_atnode = 0.0f;
	end_distance_min = 0.0f; 
	end_distance_max = 0.0f;
	node_distance_max = 0.0f;
	end_time = 0;
	end_time_max = 0;
	end_time_min = 0;
	end_health_delta_atnode = 0;
	fight_health_delta_reaching = 0;
	intial_health = 0;
	disabled = false;
	inuse = false;
	reached = false;
	ownerBot = NULL;
}
*/

/*
=====================
idBotNode::Save
=====================

void idBotNode::Save( idSaveGame *savefile ) const {
	savefile->WriteFloat( fight_distance_atnode );
	savefile->WriteFloat( end_distance_min );
	savefile->WriteFloat( end_distance_max );
	savefile->WriteFloat( node_distance_max ); 

	savefile->WriteInt( end_time_max );
	savefile->WriteInt( end_time_min );
	savefile->WriteInt( end_time );

	savefile->WriteInt( end_health_delta_atnode );
	savefile->WriteInt( fight_health_delta_reaching ); 
	savefile->WriteInt( intial_health );

	savefile->WriteBool( disabled );
	savefile->WriteBool( inuse );
	savefile->WriteBool( reached );

	ownerBot.Save( savefile );
}
*/
/*
=====================
idBotNode::Restore
=====================

void idBotNode::Restore( idRestoreGame *savefile ) {
	savefile->ReadFloat( fight_distance_atnode );
	savefile->ReadFloat( end_distance_min );
	savefile->ReadFloat( end_distance_max );
	savefile->ReadFloat( node_distance_max ); 

	savefile->ReadInt( end_time_max );
	savefile->ReadInt( end_time_min );
	savefile->ReadInt( end_time );

	savefile->ReadInt( end_health_delta_atnode );
	savefile->ReadInt( fight_health_delta_reaching ); 
	savefile->ReadInt( intial_health );

	savefile->ReadBool( disabled );
	savefile->ReadBool( inuse );
	savefile->ReadBool( reached );

	ownerBot.Restore( savefile );
}
*/

/*
=====================
idBotNode::Spawn
=====================

void idBotNode::Spawn( void ) {
	fight_distance_atnode = spawnArgs.GetFloat( "fight_distance_atnode" ,"0");
	end_distance_min = spawnArgs.GetFloat( "end_distance_min","0" );
	end_distance_max = spawnArgs.GetFloat( "end_distance_max","0" );
	if( end_distance_min > 0 && (end_distance_min <= fight_distance_atnode)){
		gameLocal.Warning("idBotNode '%s' Spawn: end_distance_min <= fight_distance_atnode! --> end_distance_min will never be used", GetName());
		gameLocal.Warning("FIX: end_distance_min changed to fight_distance_atnode*2");
		end_distance_min = fight_distance_atnode*2; //fix
	}

	end_time_max = spawnArgs.GetInt( "end_time_max","0" );
	end_time_min = spawnArgs.GetInt( "end_time_min","0" );	
	if( end_time_min < 0){end_time_min = 0;}
	if( end_time_max < end_time_min ){end_time_max = end_time_min;}

	end_health_delta_atnode = spawnArgs.GetInt( "end_health_delta_atnode","0" );
	fight_health_delta_reaching = spawnArgs.GetInt( "fight_health_delta_reaching","0" );

	disabled = spawnArgs.GetBool( "start_off" );
	node_distance_max = spawnArgs.GetFloat( "node_distance_max","0" );

	BecomeActive( TH_THINK ); //just to be in the list of the active entitites in order to be easily found  //TODO: create my own list for nodes
}
*/

/*
=====================
idBotNode::TryLock
=====================

bool idBotNode::TryLock( idAI_Bot *activator ) {
	if( !activator ){
		gameLocal.Warning("trylock failed on '%s' : activator not valid!", GetName() );
		return false;
	}
	
	if( disabled || inuse ){
		gameLocal.Warning("trylock failed on '%s' : disabled or inuse!", GetName() );
		return false;
	}

	//time
	if( end_time_min > 0){
		end_time = gameLocal.time + SEC2MS( end_time_min + gameLocal.random.RandomInt(end_time_max - end_time_min) );
	}

	//health
	intial_health = activator->GetHealth();

	//new owner
	ownerBot = activator;
	inuse = true;
	reached = false;
	//gameLocal.Warning("trylock ok on '%s' !", GetName() );
	return true; 
}
*/

/*
=====================
idBotNode::Release
=====================

void idBotNode::Release( void ) {
	if(inuse){
		if ( spawnArgs.GetBool( "use_once" ) ) {
			disabled = true;
		}

		inuse = false;
		reached = false;
		ownerBot = NULL;
		//gameLocal.Printf("node '%s' released!\n", GetName() );
	}
}
*/

/*
=====================
idBotNode::Event_Reached
=====================

void idBotNode::Event_Reached( void ) {
	if(inuse && !reached){
		reached = true;
		intial_health = ownerBot.GetEntity()->GetHealth();
	}
}
*/

/*
=====================
idBotNode::Event_CanBeUsedBy
=====================

void idBotNode::Event_CanBeUsedBy( idEntity *botEntity ) {
	if(disabled || inuse || !botEntity || !botEntity->IsType( idAI_Bot::Type )){
		//gameLocal.Printf("cannot use the node '%s'!\n", GetName() );
		idThread::ReturnInt( false ); return;
	}
	//gameLocal.Printf("can use the node '%s' !\n", GetName() );
	idThread::ReturnInt( true ); 

}
*/

/*
=====================
idBotNode::Event_GetNextNode
=====================

void idBotNode::Event_GetNextUsableNode( idEntity *botEntity ) {
	if( !botEntity || !botEntity->IsType( idAI_Bot::Type )){
		idThread::ReturnEntity( NULL ); return;
	}

	idThread::ReturnEntity( GetNextUsableNode( static_cast<idAI_Bot *>( botEntity ) , 2 ) );
}
*/

/*
=====================
idBotNode::AllNextNodesUsed
=====================

bool idBotNode::AllNextNodesUsed( void ){
	int	i;
	idEntity *ent;
	idBotNode * myNode;
	bool allUsed = true;

	//gameLocal.Printf("idBotNode::AllNextNodesUsed called!\n");

	for( i = 0; i < targets.Num(); i++ ) {
		ent = targets[ i ].GetEntity();		
		if( ent ){
			if ( ent->IsType( idBotNode::Type ) ) {		
				myNode = static_cast<idBotNode *>( ent );
				if( !myNode->IsInUse() ){
					allUsed = false;
					//gameLocal.Printf("uno libero trovato: '%s'\n",myNode->GetName() );
					break;
				}
			}
		}
	}

	return allUsed ;
}
*/

/*
=====================
idBotNode::GetNextUsableNode
=====================

idEntity * idBotNode::GetNextUsableNode( idAI_Bot *botEntity, int lookAheadMaxLevel ) {
	int	i;
	int	numBotNodes;
	int	numOkEnts;
	int which;
	bool addItToList;
	idBotNode * myNode;

	//gameLocal.Printf("idBotNode::GetNextUsableNode!\n");

	idEntity *ent;
	idEntity *ents[ MAX_GENTITIES ];
	idBotNode *botNodeEnts[ MAX_GENTITIES ];

	numBotNodes = 0;
	
	//create a list containing the next idBotNodes
	for( i = 0; i < targets.Num(); i++ ) {
		ent = targets[ i ].GetEntity();		
		if( ent ){
			if ( ent->IsType( idBotNode::Type ) ) {		
				myNode = static_cast<idBotNode *>( ent );

				botNodeEnts[ numBotNodes++ ] = myNode;
				if ( numBotNodes >= MAX_GENTITIES ) {
					break;
				}
			}
		}
	}

	if ( !numBotNodes ) { //no nodes next
		return NULL;
	}

	numOkEnts = 0;

	//create a sub list containing the next VALID idBotNodes
	for( i = 0; i < numBotNodes; i++ ) {
		myNode = botNodeEnts[ i ];	
		addItToList = true;

		if( myNode->IsInUse() || ( myNode->DistanceContidionsTrue( botEntity ) < DISTANCE_COND_NOENEMY )){
			addItToList = false;
		}

		if (idStr::Cmp( myNode->GetEntityDefName(), "ai_bot_attack_path_corner" ) == 0) {
			if( myNode->AllNextNodesUsed() ){ addItToList = false; } //if it leads to used targets
		}

		if(addItToList){
			ents[ numOkEnts++ ] = myNode;
			if ( numOkEnts >= MAX_GENTITIES ) {
				break;
			}
		}

	}

	if ( numOkEnts ) {
		which = gameLocal.random.RandomInt( numOkEnts );
		//gameLocal.Printf("preso uno random dei %d nodi trovati: '%s' \n", numOkEnts, ents[ which ]->GetName() ); 
		return ents[ which ] ;
	}

	//nothing? Try looking ahead!
	lookAheadMaxLevel--;
	if(lookAheadMaxLevel > 0){
		for( i = 0; i < numBotNodes; i++ ) {
			ent = botNodeEnts[ i ]->GetNextUsableNode( botEntity, lookAheadMaxLevel );
			if(ent){ 
				//gameLocal.Printf("trovato uno con ricorsione: '%s' \n", ent->GetName() );
				return ent; 
			}
		}
	}

	//nothing
	//gameLocal.Printf("nothing found :( \n");
	return NULL;
}
*/

/*
=====================
idBotNode::DistanceContidionsTrue
=====================

int idBotNode::DistanceContidionsTrue( idAI_Bot *testEntity ) {
	idEntity * testEnemy; 
	float entityRange;
	float enemyRange;

	//entity stuff	
	if( node_distance_max > 0.0f ){ 
		entityRange = ( testEntity->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthFast();
		if( entityRange > node_distance_max ){
			//gameLocal.Printf("The node is too far\n" );
			return DISTANCE_COND_TOOFAR;
		}
	}
	
	//enemy stuff
	testEnemy = testEntity->GetEnemy();

	if(!testEnemy){
		//gameLocal.Printf("DistanceContidions !testEnemy...\n" );
		return DISTANCE_COND_NOENEMY; 
	}

	enemyRange = ( testEnemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthFast();

	if( fight_distance_atnode > 0.0f ){ //this is the most important one and should be the first.
		if( enemyRange < fight_distance_atnode ){
			//gameLocal.Printf("DistanceContidions fight_distance_atnode no\n" );
			return DISTANCE_COND_FIGHT_FAILED;
		}
	}

	if( end_distance_min > 0.0f){ // this check is enabled
		if( enemyRange < end_distance_min ){
			//gameLocal.Printf("DistanceContidions end_distance_min no\n" );
			return DISTANCE_COND_MIN_FAILED;
		}
	}

	if( end_distance_max > 0.0f ){ //this check is enabled
		if( enemyRange > end_distance_max ){
			//gameLocal.Printf("DistanceContidions end_distance_max no\n" );
			return DISTANCE_COND_MAX_FAILED;
		}
	}
	
	//gameLocal.Printf("DistanceContidions all ok\n" );
	return DISTANCE_COND_OK;
}
*/

/*
=====================
idBotNode::Event_EvaluateConditions
=====================

void idBotNode::Event_EvaluateConditions( void ) { //true if one of them is verified!
	idAI_Bot* currentOwner;
	int result;
	
	if ( !inuse ) {
		//gameLocal.Printf("Event_EvaluateConditions: node not in use!\n" );
		idThread::ReturnInt( true ); return;
	}

	assert( ownerBot );
	currentOwner = ownerBot.GetEntity();


	//time
	if( end_time_min > 0){ //not unlimited
		if(end_time < gameLocal.time){ 
			//gameLocal.Printf("time cond!\n" );
			idThread::ReturnInt( true ); return;
		}
	}

	//health
	int deltaToUse;
	if(reached){
		deltaToUse = end_health_delta_atnode;
	}else{
		deltaToUse = fight_health_delta_reaching;
	}
	if( deltaToUse > 0){
		if( deltaToUse < (intial_health - currentOwner->GetHealth())){
			//gameLocal.Printf("health cond!\n" );
			//currentOwner->AI_SAY_DAMAGED = true;
			currentOwner->AI_SAY_HINT = AI_BOT_SAY_DAMAGED;
			if(!reached){ currentOwner->AI_LEAVE_NODE_TO_FIGHT = true; } 
			idThread::ReturnInt( true ); return;
		}
	}
	

	//distance
	result = DistanceContidionsTrue( currentOwner );
	if( result == DISTANCE_COND_FIGHT_FAILED ){ 	
		currentOwner->AI_LEAVE_NODE_TO_FIGHT = true;
	}
	if( result != DISTANCE_COND_OK && result != DISTANCE_COND_NOENEMY ){
		//gameLocal.Printf("distance cond!\n" );
		idThread::ReturnInt( true ); return;
	}

	//else
	//gameLocal.Printf("Event_EvaluateConditions all false...go on!\n" );
	idThread::ReturnInt( false );
}
*/

/***********************************************************************

idBotSquad

***********************************************************************/

const idEventDef EV_FindMembers( "<findMembers>", NULL );

CLASS_DECLARATION( idEntity, idBotSquad )
	EVENT( EV_FindMembers,			idBotSquad::Event_FindMembers )
	EVENT( EV_Activate,				idBotSquad::Event_Activate )
END_CLASS

/*
=====================
idBotSquad::idBotSquad
=====================
*/
idBotSquad::idBotSquad( void ) {
	members.SetGranularity( 1 );
}

/*
=====================
idBotSquad::Save
=====================
*/
void idBotSquad::Save( idSaveGame *savefile ) const {
	int i;
	savefile->WriteInt( members.Num() );
	for( i = 0; i < members.Num(); i++ ) {
		members[ i ].Save( savefile ); //save the pointer
	}
}

/*
=====================
idBotSquad::Restore
=====================
*/
void idBotSquad::Restore( idRestoreGame *savefile ) {
	int num, i;

	members.Clear();
	savefile->ReadInt( num );
	members.SetNum( num );
	for( i = 0; i < num; i++ ) {
		members[ i ].Restore( savefile );
	}
}

/*
=====================
idBotSquad::FindMembers
=====================
*/
void idBotSquad::FindMembers( void ) {
	idEntity *ent;
	idAI_Bot *bot;
	int i;

	for( i = 0; i < targets.Num(); i++ ) { 
		ent = targets[ i ].GetEntity();
		if ( ent && ent->IsType( idAI_Bot::Type ) ) {
			bot = static_cast<idAI_Bot *>( ent );
			
			//Add Member 
			idEntityPtr<idAI_Bot> &entityPtr = members.Alloc();
			entityPtr = bot;

			bot->SetSquad( this ); 
		}
	}

	gameLocal.Printf("idBotSquad::FindMembers - result: %d\n", members.Num());
}

/*
===============
idBotSquad::Event_FindMembers
===============
*/
void idBotSquad::Event_FindMembers( void ) {
	FindMembers();
}

/*
=====================
idAI::Event_Activate
=====================
*/
void idBotSquad::Event_Activate( idEntity *activator ) {
	ActivateTargets( (!activator) ? this : activator );
}


/*
=====================
idBotSquad::Spawn
=====================
*/
void idBotSquad::Spawn( void ) {	
	if ( spawnArgs.MatchPrefix( "target" ) ) {
		if ( gameLocal.GameState() == GAMESTATE_STARTUP ) {
			PostEventMS( &EV_FindMembers, 0 );
		} else {
			// not during spawn, so it's ok to get the targets
			FindMembers();
		}
	}
}


/*
=====================
idBotSquad::RemoveMember
=====================
*/
void idBotSquad::RemoveMember( idAI_Bot *bot ){
	int i;
	idAI_Bot *temp_bot;
	
	//gameLocal.Printf("idBotSquad::RemoveMember...\n");

	for( i = members.Num() - 1; i >= 0; i-- ) {
		if ( members[ i ].GetEntity() == bot) {
			break;
		}
	}

	if(i >= 0){ //then it has been removed
		members.RemoveIndex( i );
		//gameLocal.Printf("...Member removed\n");

		for( i = members.Num() - 1; i >= 0; i-- ) {
			temp_bot = members[ i ].GetEntity();
			if ( temp_bot ) {
				temp_bot->HandleSquadMsg( AI_SQUAD_MEMBER_REMOVED, members.Num() );
			}
		}
	}
}

/*
=====================
idBotSquad::GetNextMember
=====================
*/
idAI_Bot* idBotSquad::GetNextMember( idAI_Bot *lastMatch, idAI_Bot *ignore ) {
	int i;
	idAI_Bot *temp_bot;
	bool lastMatch_found;

	//gameLocal.Printf("idBotSquad::GetNextMember\n");

	if(lastMatch){
		lastMatch_found = false;
	}else{
		lastMatch_found = true; //no need to search it
	}

	for( i = members.Num() - 1; i >= 0; i-- ) {
		temp_bot = members[ i ].GetEntity();

		if(!temp_bot){
			continue;
		}

		if (lastMatch_found && ( temp_bot != ignore ) ){
			return temp_bot;
		}

		if( !lastMatch_found && ( temp_bot == lastMatch ) ){
			lastMatch_found = true;
		}
	}

	return NULL;
}
