#include common_scripts\utility;
#include maps\mp\_utility;

//========================================================
//				agentFunc 
//========================================================
agentFunc( func_name )
{
	assert( IsAgent( self ) );
	assert( IsDefined(func_name) );
	assert( isDefined(self.agent_type) );
	assert( isDefined(level.agent_funcs[self.agent_type]) );
	assert( isDefined(level.agent_funcs[self.agent_type][func_name]) );
	
	return level.agent_funcs[self.agent_type][func_name];
}

//========================================================
//				set_agent_team 
//========================================================
set_agent_team( team, optional_owner )
{
	// since an agent entity has both a "sentient" and an "agent", we need both
	// these to understand the team the entity is on (much as client entities
	// have a "sentient" and a "client"). The "team" field sets the "sentient"
	// team and the "agentteam" field sets the "agent" team. 
	self.team 			= team;
	self.agentteam 		= team;
	self.pers["team"] 	= team;
	
	self.owner = optional_owner;
	self SetOtherEnt( optional_owner );
	self SetEntityOwner( optional_owner );
}


//=======================================================
//				initAgentScriptVariables
//=======================================================
initAgentScriptVariables()
{
	self.agent_type 			= "player"; // TODO: communicate this to code?
	self.pers 					= [];
	self.hasDied 				= false;
	self.isActive				= false;
	self.isAgent				= true;
	self.wasTI					= false;
	self.isSniper				= false;
	self.spawnTime				= 0;
	self.entity_number 			= self GetEntityNumber();	
	self.agent_teamParticipant 	= false;
	self.agent_gameParticipant 	= false;
	self.canPerformClientTraces = false;
	self.agentname 				= undefined;
	self.ignoreall				= false;
	self.ignoreme				= false;
	
	self DetachAll();

	self initPlayerScriptVariables( false );
}


//========================================================
//					initPlayerScriptVariables
//========================================================
initPlayerScriptVariables( asPlayer )
{
	if ( !asPlayer )
	{
		// Not as a player
		self.class							= undefined;
		self.lastClass						= undefined;
		self.moveSpeedScaler 				= undefined;
		self.avoidKillstreakOnSpawnTimer 	= undefined;
		self.guid 							= undefined;
		self.name							= undefined;
		self.saved_actionSlotData 			= undefined;		
		self.perks 							= undefined;
		self.weaponList 					= undefined;
		self.omaClassChanged 				= undefined;
		self.objectiveScaler 				= undefined;
		self.touchTriggers 					= undefined;
		self.carryObject 					= undefined;
		self.claimTrigger 					= undefined;
		self.canPickupObject 				= undefined;
		self.killedInUse 					= undefined;
		self.sessionteam					= undefined;
		self.sessionstate					= undefined;
		self.lastSpawnTime 					= undefined;
		self.lastspawnpoint 				= undefined;
		self.disabledWeapon					= undefined;
		self.disabledWeaponSwitch			= undefined;
		self.disabledOffhandWeapons			= undefined;
		self.disabledUsability				= undefined;
		self.shieldDamage 					= undefined;
		self.shieldBulletHits				= undefined;
	}
	else
	{
		// As a player
		self.moveSpeedScaler 				= level.basePlayerMoveScale;
		self.avoidKillstreakOnSpawnTimer 	= 5;
		self.guid 							= self getUniqueId();
		self.name							= self.guid;
		self.sessionteam 					= self.team;
		self.sessionstate					= "playing";
		self.shieldDamage 					= 0;
		self.shieldBulletHits				= 0;
		self.agent_gameParticipant			= true;		// If initialized as a player, always make agent a game participant
		
		self maps\mp\gametypes\_playerlogic::setupSavedActionSlots();
		self thread maps\mp\perks\_perks::onPlayerSpawned();
		
		if ( IsGameParticipant( self ) )
		{
			self.objectiveScaler = 1;
			self maps\mp\gametypes\_gameobjects::init_player_gameobjects();
			self.disabledWeapon = 0;
			self.disabledWeaponSwitch = 0;
			self.disabledOffhandWeapons = 0;
		}
	}
	
	self.disabledUsability 		= 1;
}

//===========================================
// 				getFreeAgent
//===========================================
getFreeAgent( agent_type )
{
	freeAgent = undefined;
	
	if( IsDefined( level.agentArray ) )
	{
		foreach( agent in level.agentArray )
		{
			if( ( !IsDefined( agent.isActive ) || !agent.isActive ) && ( !IsDefined( agent.isReserved ) || !agent.isReserved ) )
			{
				if ( IsDefined(agent.waitingToDeactivate) && agent.waitingToDeactivate )
					continue;
				
				if ( IsDefined(level.despawning_agents) && array_contains(level.despawning_agents,agent) )
					continue;
				
				freeAgent = agent;
				
				freeAgent initAgentScriptVariables();
				
				if ( IsDefined( agent_type ) ) 
					freeAgent.agent_type = agent_type; // TODO: communicate this to code?
					
				break;
			}
		}
	}
	
	return freeAgent;
}


//=======================================================
//				activateAgent
//=======================================================
activateAgent()
{
/#
	if ( !self.isActive )
	{
		// Activating this agent, ensure that he has connected on the same frame
		AssertEx(self.connectTime == GetTime(), "Agent spawn took too long - there should be no waits in between connectNewAgent and spawning the agent");
	}
#/
	self.isActive = true;
}



//=======================================================
//				deactivateAgent
//=======================================================
deactivateAgent()
{
	self thread deactivateAgentDelayed();
}

//=======================================================
//				deactivateAgentDelayed
//=======================================================
deactivateAgentDelayed()
{
	self notify("deactivateAgentDelayed");
	self endon("deactivateAgentDelayed");
	
	if ( !IsDefined(level.despawning_agents) )
		level.despawning_agents = [];
	
	if ( !array_contains(level.despawning_agents,self) )
		level.despawning_agents = array_add(level.despawning_agents,self);
	
	// During the 0.05s wait in deactivateAgentDelayed, the agent's script variables are all cleared out
	// So we need to do this now while IsGameParticipant can still be checked
	if ( IsGameParticipant(self) )
		self maps\mp\gametypes\_spawnlogic::removeFromParticipantsArray();
	
	self maps\mp\gametypes\_spawnlogic::removeFromCharactersArray();
	
	//println("** " + GetTime() + " About to clear agent script vars (1) for agent " + self GetEntityNumber() + " of type " + self.agent_type );
	
	// Wait till next frame before we "disconnect"
	// That way things waiting on "death" but have endon("disconnect") will still function
	// e.g. maps\mp\killstreaks\_juggernaut::juggRemover()
	wait 0.05;

	self.isActive 	= false;
	self.hasDied 	= false;
	self.owner		= undefined;
	self.connectTime = undefined;
	self.waitingToDeactivate = undefined;
	
	// Clear this agent from any other character's attackers array	
	foreach ( character in level.characters )
	{
		if ( IsDefined( character.attackers ) )
		{
			foreach ( index, attacker in character.attackers )
			{
				if ( attacker == self )
					character.attackers[index] = undefined;
			}
		}
	}

	self.headModel = undefined;
	self DetachAll();
	
	self notify("disconnect");
	
	//println("** " + GetTime() + " About to clear agent script vars (2) for agent " + self GetEntityNumber() );
	self AgentClearScriptVars();	// This needs to be the last line, anything after this won't execute correctly because the entity has been cleared
	
	level.despawning_agents = array_remove(level.despawning_agents,self);
}


//===========================================
// 			getNumActiveAgents
//===========================================
getNumActiveAgents( type )
{
	if ( !IsDefined(type) )
		type = "all";
	
	agents = getActiveAgentsOfType(type);
	return agents.size;
}


//===========================================
// 			getActiveAgentsOfType
//===========================================
getActiveAgentsOfType( type )
{
	Assert(IsDefined(type));
	agents = [];
	
	if ( !IsDefined( level.agentArray ) )
		return agents;
		
	foreach ( agent in level.agentArray )
	{
		if ( IsDefined( agent.isActive ) && agent.isActive )
		{
			if ( type == "all" || agent.agent_type == type )
				agents[agents.size] = agent;
		}
	}
	
	return agents;
}


//===========================================
// 			getNumOwnedActiveAgents
//===========================================
getNumOwnedActiveAgents( player )
{
	return getNumOwnedActiveAgentsByType( player, "all" );
}

//===========================================
// 			getNumOwnedActiveAgentsByType
//===========================================
getNumOwnedActiveAgentsByType( player, type )
{
	Assert(IsDefined(type));
	numOwnedActiveAgents = 0;

	if( !IsDefined(level.agentArray) )
	{
		return numOwnedActiveAgents;
	}

	foreach( agent in level.agentArray )
	{
		if( IsDefined( agent.isActive ) && agent.isActive )
		{
			if ( IsDefined(agent.owner) && (agent.owner == player) )
			{
				if ( type == "all" || agent.agent_type == type )
					numOwnedActiveAgents++;
			}
		}
	}

	return numOwnedActiveAgents;
}

//=======================================================
//				getValidSpawnPathNodeNearPlayer
//=======================================================
getValidSpawnPathNodeNearPlayer( bDoPhysicsTraceToPlayer, bDoPhysicsTraceToValidateNode ) // self = player
{
	assert( isPlayer( self ) );
	
	nodeArray 	= GetNodesInRadius( self.origin, 350, 64, 128, "Path" );
	
	if( !IsDefined(nodeArray) || (nodeArray.size == 0) )
	{
		return undefined;
	}
	
	if ( IsDefined(level.waterDeleteZ) && IsDefined(level.trigUnderWater) )
	{
		// Ignore any nodes where the agent would die immediately upon spawning	
		nodeArrayOld = nodeArray;
		nodeArray = [];
		foreach( node in nodeArrayOld )
		{
			if ( node.origin[ 2 ] > level.waterDeleteZ || !IsPointInVolume( node.origin, level.trigUnderWater ) )
				nodeArray[nodeArray.size] = node;
		}
	}
	
	playerDirection = AnglesToForward( self.angles );
	bestDot = -10;
	
	playerHeight = maps\mp\gametypes\_spawnlogic::getPlayerTraceHeight( self );
	zOffset = ( 0, 0, playerHeight );
	
	if ( !IsDefined(bDoPhysicsTraceToPlayer) )
		bDoPhysicsTraceToPlayer = false;
	
	if ( !IsDefined(bDoPhysicsTraceToValidateNode) )
		bDoPhysicsTraceToValidateNode = false;
	
	pathNodeSortedByDot = [];
	pathNodeDotValues = [];
	foreach( pathNode in nodeArray )
	{
		if ( !pathNode DoesNodeAllowStance("stand") )
			continue;
		
		directionToNode = VectorNormalize( pathNode.origin - self.origin );
		dot = VectorDot( playerDirection, directionToNode );
		
		i = 0;
		for ( ; i < pathNodeDotValues.size; i++ )
		{
			if ( dot > pathNodeDotValues[i] )
			{
				for ( j = pathNodeDotValues.size; j > i; j-- )
				{
					pathNodeDotValues[j] = pathNodeDotValues[j-1];
					pathNodeSortedByDot[j] = pathNodeSortedByDot[j-1];
				}
				break;
			}
		}
		pathNodeSortedByDot[i] = pathNode;
		pathNodeDotValues[i] = dot;
	}
	
	// pick a path node in the player's view
	for ( i = 0; i < pathNodeSortedByDot.size; i++ )
	{
		pathNode = pathNodeSortedByDot[i];
		
		traceStart = self.origin + zOffset;
		traceEnd = pathNode.origin + zOffset;
		
		if ( i > 0 )
			wait(0.05);	// Spread out the traces across multiple frames
		
		// prevent selecting a node that the player cannot see
		if( !SightTracePassed( traceStart, traceEnd, false, self ) )
		{
			continue;
		}
		
		if ( bDoPhysicsTraceToValidateNode )
		{
			if ( i > 0 )
				wait(0.05);	// Spread out the traces across multiple frames
			
			hitPos = PlayerPhysicsTrace( pathNode.origin + zOffset, pathNode.origin );
			if ( DistanceSquared( hitPos, pathNode.origin ) > 1 )
				continue;
		}

		if ( bDoPhysicsTraceToPlayer )
		{
			if ( i > 0 )
				wait(0.05);	// Spread out the traces across multiple frames
			
			hitPos = PhysicsTrace( traceStart, traceEnd );
			if ( DistanceSquared( hitPos, traceEnd ) > 1 )
				continue;
		}
		
		return pathNode;
	}
	
	// always return a node for safeguard
	if( (pathNodeSortedByDot.size > 0) && IsDefined(level.isHorde) )
		return pathNodeSortedByDot[0];
}


//=======================================================
//					killAgent
//=======================================================
killAgent( agent ) 
{
	// do enough damage to kill the agent regardless of any damage mitigation  
 	agent DoDamage( agent.health + 500000, agent.origin );
}


//=======================================================
//					killDog
//=======================================================
killDog() // self == dog
{
	self [[ self agentFunc( "on_damaged" ) ]](
		level, // eInflictor The entity that causes the damage.(e.g. a turret)
		undefined, // eAttacker The entity that is attacking.
		self.health + 1, // iDamage Integer specifying the amount of damage done
		0, // iDFlags Integer specifying flags that are to be applied to the damage
		"MOD_CRUSH", // sMeansOfDeath Integer specifying the method of death
		"none", // sWeapon The weapon number of the weapon used to inflict the damage
		( 0, 0, 0 ), // vPoint The point the damage is from?
		(0, 0, 0), // vDir The direction of the damage
		"none", // sHitLoc The location of the hit
		0 // psOffsetTime The time offset for the damage
	);
}
