/*
	Lan Wars.
	Copyright (c) 2009, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//=====================================================================================================================
/// @brief		This class is responsible for controlling and moving an AI. It maintains the current state of the 
///				AI and it's current goals and objectives.
//=====================================================================================================================

#include "Precompiled.h"
#include "AI/AIController.h"
#include "AI/AIMover.h"
#include "AI/PathNetwork.h"
#include "GameObjects/GameEnemy.h"
#include "GameObjects/GameMap.h"
#include "GameObjects/GamePlayer.h"
#include "GameObjects/GameFireball.h"
#include "Doom/DoomLevel.h"
#include "Doom/DoomCollision.h"

using namespace Game;

//=====================================================================================================================
// Constants
//=====================================================================================================================

/// Amount of time it takes for an enemy to 'notice' the player

const float AIController::PLAYER_NOTICE_TIME = 0.25f;

/// If the enemy is within this radius of a destination point (patrol point etc) then the AI is considered 'at' the destination.

const float AIController::DESTINATION_RADIUS = 32.0f;

/// Amount of randomness to the chosen ambush point's position to add

const float AIController::AMBUSH_POSITION_RANDOMNESS = 128.0f;

/// Amount of randomness to the chosen ambush point's direction to add

const float AIController::AMBUSH_DIRECTION_RANDOMNESS = 0.25f;

/// How much to smooth the position of the point we are moving towards

const float AIController::MOVE_POINT_SMOOTHING = 0.85f;

/// How far the AI attempts to get to the player when attacking before stopping

const float AIController::ATTACK_DISTANCE = 80.0f;

/// This is the amount of time it takes for the AI to 'forget' and give up chasing the player it is attacking

const float AIController::ATTACK_GIVE_UP_TIME = 5.0f;

/// Chance that the AI will fire at the player if it has sighting. 0-1.

const float AIController::ATTACK_FIRE_CHANCE = 0.015f;

/// Time it takes for the AI to ready itself to fire

const float AIController::ATTACK_FIRE_TIME = 0.35f;

//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
/// Cost during pathfinding of a monster being close to a pathfinding node. This cost is added to
/// the costs of traversal for going to nodes where monsters are in already. This extra cost makes the 
/// AI fan out and produces team-like behaviour.
//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

const float AIController::AI_AT_NODE_G_COST = 1500.0f;

//=====================================================================================================================	
/// @brief		Creates the AIController module
/// @param		enemy - enemy that the controller controls
/// @pre		Given enemy cannot be null
//=====================================================================================================================

AIController::AIController( GameEnemy & enemy )
{
	// Make sure ok
	
	ENSURE( &enemy != 0 , "Given enemy cannot be null" )

	// Save the enemy we are controlling
	
	m_character = &enemy;
	
	// Save the doom level:
	
	m_level = &GameMap::GetSingleton().GetLevel();
	
	// Save a reference to the map:
	
	m_map = &GameMap::GetSingleton(); m_map->AddReference();
	
	// Get the map:
	
	GameMap & map = GameMap::GetSingleton();
	
	// Create our mover module
	
	m_mover = new AIMover( map.GetLevel() );
	
	// Get the pathfinding network
	
	m_path_network = &map.GetPathNetwork();
	
	// No target currently
	
	m_target					= 0;
	m_last_patrol_point_index	= 0xffffffff;
	m_destination_direction		= 0;
	m_last_move_point			= m_character->GetPosition();
		
	// Reset the state of the AI
	
	ResetState();

	// Add our cost to the cost of going through our current pathfinding node:

	m_path_network->AddPathCost( m_character->GetPosition() , AI_AT_NODE_G_COST );
}

//=====================================================================================================================
/// @brief		Destroys the AI controller.
//=====================================================================================================================

AIController::~AIController()
{
	// Remove our cost to the cost of going through our current pathfinding node:

	m_path_network->RemovePathCost( m_character->GetPosition() , AI_AT_NODE_G_COST );

	// Done with this

	delete m_mover;
	
	// Done with the map:
	
	m_map->RemoveReference();
	
	// Remove current target:
	
	SetTarget(0);

	// Clear our list of players

	for ( unsigned i = 0 ; i < m_players.size() ; ++i )
	{
		// Done with this:

		m_players[i].m_player->RemoveReference();
	}
}

//=====================================================================================================================
/// @brief		Sets the current target of the AI
/// @param		target - new target of the AI
//=====================================================================================================================

void AIController::SetTarget( GamePlayer * target )
{
	// Add a reference to the new target
	
	if ( target ) target->AddReference();

	// Remove reference to old target:
	
	if ( m_target ) m_target->RemoveReference();
	
	// Save:
	
	m_target = target;	
}

//=====================================================================================================================
/// @brief		Updates the AI and performs all AI actions
/// @param		time_delta - amount of time elapsed
/// @pre		The state of the AI must not be invalid
//=====================================================================================================================

void AIController::Update( float time_delta )
{
	// Remove our cost to the cost of going through our current pathfinding node:

	m_path_network->RemovePathCost( m_character->GetPosition() , AI_AT_NODE_G_COST );

	// Make sure ok
	
	ENSURE( unsigned(m_state) < AIS_INVALID , "The state of the AI must not be invalid" )

	// Update the list of players in the game
	
	UpdatePlayerList(time_delta);

	// See what our current goal is:
	
	switch ( m_state )
	{
		case AIS_PURSUIT:		Goal_Pursuit(time_delta);		break;
		case AIS_ATTACK:		Goal_Attack(time_delta);		break;
		case AIS_PATROL:		Goal_Patrol(time_delta);		break;
		case AIS_AMBUSH:		Goal_Ambush(time_delta);		break;
		case AIS_ATTACK_FIRE:	Goal_Attack_Fire(time_delta);	break;
	}

	// Add our cost to the cost of going through our current pathfinding node:

	m_path_network->AddPathCost( m_character->GetPosition() , AI_AT_NODE_G_COST );
}

//=====================================================================================================================
/// @brief		Alerts the AI of noise from the given player
/// @param		player - player to alert the AI of
/// @pre		Given player cannot be null
//=====================================================================================================================

void AIController::NoiseAlert( GamePlayer & player )
{
	// Make sure ok

	ENSURE( &player != 0 , "Given player cannot be null" )

	// If we have no target already then attack this player: otherwise ignore the noise

	if ( m_target == 0 )
	{
		// New target:

		SetTarget(&player);

		// Attack:

		m_state = AIS_ATTACK;
	}
}

//=====================================================================================================================
/// @brief		Alerts the AI of damage from the given player
/// @param		player - player to alert the AI of
/// @pre		Given player cannot be null
//=====================================================================================================================

void AIController::DamageAlert( GamePlayer & player )
{
	// Make sure ok

	ENSURE( &player != 0 , "Given player cannot be null" )

	// Make sure ok

	ENSURE( &player != 0 , "Given player cannot be null" )

	// New target:

	SetTarget(&player);

	// Attack:

	m_state = AIS_ATTACK;
}

//=====================================================================================================================
/// @brief		Updates the current list of players in the game
/// @param		time_delta - amount of time elapsed
/// @pre		Our character must still be in the game
//=====================================================================================================================

void AIController::UpdatePlayerList( float time_delta )
{	
	// Get the manager of our character:
	
	GameObjectManager * mgr = m_character->GetManager();
	
	// Make sure ok
	
	ENSURE( mgr , "Our character must still be in the game" )
	
	// Get a list of players
	
	std::vector<GameObjectBase*> current_players; mgr->FindByType<GamePlayer>("GamePlayer",current_players);
	
	// Make a new array of information for all the current players:
	
	std::vector<Player> current_players_info(current_players.size());
	
	// Run through each:
	
	for ( unsigned i = 0 ; i < current_players.size() ; ++i ) 
	{
		// Add a reference to this player:
		
		current_players[i]->AddReference();
	
		// See if we already have this player in our previous list:
		
		bool previous_player = false;
		
		// Run through the previous list and look for this player:
		
		for ( unsigned j = 0 ; j < m_players.size() ; ++j )
		{		
			// See if this is the player:
			
			if ( m_players[j].m_player == current_players[i] )
			{
				// This is a current player: save all it's information into the new array
				
				current_players_info[i] = m_players[j];
				
				// Now need to update it's info:
				
				current_players_info[i].m_time_since_attack += time_delta;
				
				// See if this player is visible to us:
				
				if ( InLineOfSight(current_players_info[i].m_player) )				
				{
					current_players_info[i].m_time_in_sight		+= time_delta;
					current_players_info[i].m_time_since_sight	 = 0;
				}
				else
				{
					current_players_info[i].m_time_in_sight		 = 0;
					current_players_info[i].m_time_since_sight	+= time_delta;
				}
				
				// Previously had this player in the players list
				
				previous_player = true; break;
			}
		}
		
		// If this is a new player then we need to initialise all of it's info:
		
		if ( previous_player == false )
		{
			current_players_info[i].m_player			= (GamePlayer*) current_players[i];
			current_players_info[i].m_time_in_sight		= 0;
			current_players_info[i].m_time_since_sight	= 0;
			current_players_info[i].m_time_since_attack	= 0;
		}
	}
	
	// Remove references to all current players:
	
	for ( unsigned i = 0 ; i < m_players.size() ; ++i ) m_players[i].m_player->RemoveReference();
	
	// Save the new list of players
	
	m_players = current_players_info;
}

//=====================================================================================================================
/// @brief		Picks a random state for the AI to be in. Picks one of the non-combat states.
//=====================================================================================================================

void AIController::PickRandomState()
{
	// No target currently:
	
	SetTarget(0);

	// Pick one of these:
	
	unsigned i = m_random.NextUnsigned() % 3;
	
	// Go into these states:
	
	switch ( i )
	{
		//=============================================================================================================
		// Go into pursuit mode:
		//=============================================================================================================
	
		case 0:
		{
			// Only if the there are players to pursue:
			
			if ( m_players.size() > 0 )
			{
				// Pick a random player to pursue:
				
				int player = m_random.NextUnsigned() % m_players.size();
				
				// Set it as the target:
				
				SetTarget(m_players[player].m_player);
				
				// Go into pursuit mode:
				
				m_state = AIS_PURSUIT;
			}
			else
			{
				// Otherwise go into patrol mode:
				
				m_state = AIS_PATROL;
				
				// Pick a patrol point to head towards
				
				PickPatrolPoint();				
			}
		
		}	break;
		
		//=============================================================================================================
		// Go into patrol mode:
		//=============================================================================================================
		
		case 1:
		{
			// Set the state of the AI:
		
			m_state = AIS_PATROL;
			
			// Pick a patrol point to head towards
			
			PickPatrolPoint();
		
		}	break;
		
		//=============================================================================================================
		// Go into ambush mode:
		//=============================================================================================================
		
		case 2:
		{
			// Set the state of the AI
		
			m_state = AIS_AMBUSH;
			
			// Pick an ambush point:
				
			PickAmbushPoint();			
		
		}	break;	
	}
}

//=====================================================================================================================
/// @brief		Picks a patrol point for the AI to head towards
//=====================================================================================================================

void AIController::PickPatrolPoint()
{
	// Pick a patrol point to head to:
	
	if ( m_map->GetNumPatrolPoints() > 1 )
	{
		// Keep picking until the patrol point is different to last picked point:
		
		unsigned i = m_random.NextUnsigned() % m_map->GetNumPatrolPoints();
		
		// See if different:
		
		while ( i == m_last_patrol_point_index )
		{
			// Pick again:
			
			i = m_random.NextUnsigned() % m_map->GetNumPatrolPoints();
		}
		
		// Save the index of the last patrol point we went to
		
		m_last_patrol_point_index = i;
		
		// Save the point we must head towards:
		
		m_destination_point = m_map->GetPatrolPoint(i);
	}
	else
	{
		// Just pick a player spawn position as a patrol point:
		
		m_destination_point = m_map->PickStartPosition();
	}	
}

//=====================================================================================================================
/// @brief		Picks an ambush point for the AI to head towards
//=====================================================================================================================

void AIController::PickAmbushPoint()
{
	// Pick an ambush point to head towards
	
	if ( m_map->GetNumAmbushPoints() > 1 )
	{
		// Pick an ambush point to head towards
		
		unsigned i = m_random.NextUnsigned() % m_map->GetNumAmbushPoints();
		
		// Set the position we must go to
		
		m_destination_point = m_map->GetAmbushPointPosition(i);
		
		// Set the direction we must face at the ambush point:
		
		m_destination_direction = m_map->GetAmbushPointDirection(i);
		
		// Add some randomness to the direction:
		
		m_destination_direction += ( m_random.NextFloat() - 0.5f ) * AMBUSH_DIRECTION_RANDOMNESS * 2.0f;
		
		// Now add a bit of randomness to the destination point:
		
		Vec3f random_offset = Vec3f::Zero();
		
		random_offset.x += ( m_random.NextFloat() - 0.5f ) * AMBUSH_POSITION_RANDOMNESS * 2.0f;
		random_offset.z += ( m_random.NextFloat() - 0.5f ) * AMBUSH_POSITION_RANDOMNESS * 2.0f;
		
		// Do a raycast from the original point to the new point:
		
		DoomCollision::RaycastResult result = m_level->GetCollision().Raycast
		(
			m_destination_point	, m_destination_point + random_offset
		);
		
		// See if there was a hit:
		
		if ( result.m_hit )
		{
			// Save the new destination position:
			
			m_destination_point = ( result.m_hit_point - m_destination_point ) * 0.5f + m_destination_point;
		}
		else
		{
			// Save the new destination position:
			
			m_destination_point = m_destination_point + random_offset;	
		}
	}
	else
	{
		// Just pick a player spawn position as the ambush point:
		
		m_destination_point = m_map->PickStartPosition();
		
		// Pick a random direction to face:
		
		m_destination_direction = ( m_random.NextFloat() - 0.5f ) * 2.0f * Float::PI();
	}
}

//=====================================================================================================================
/// @brief		Picks a random state for the AI. State can either be AIS_PATROL, AIS_PURSUIT, or
///				AIS_AMBUSH. Also resets other state information. This is done after a respawn.
//=====================================================================================================================

void AIController::ResetState()
{
	// Need to clear the list of players:
	
	for ( unsigned i = 0 ; i < m_players.size() ; ++i )
	{
		m_players[i].m_player->RemoveReference();
	}	
	
	m_players.clear();
	
	// Clear all of these fields
	
	m_last_patrol_point_index	= 0xffffffff;
	m_destination_direction		= 0;
	m_destination_point			= Vec3f::Zero();
	m_last_move_point			= m_character->GetPosition();
	m_time_since_firing_began	= 0;
	m_target_fire_position		= Vec3f::Zero();
	
	// Pick a random new state:
	
	PickRandomState();
}

//=====================================================================================================================
/// @brief		Performs a line of sight check between the AI and the given player
/// @param		player - player to perform the line of sight check with
/// @pre		Given player cannot be null
//=====================================================================================================================

bool AIController::InLineOfSight( GamePlayer * player )
{
	// Make sure ok
	
	ENSURE( player != 0 , "Given player cannot be null" )
	
	// Get the direction our character is facing:
	
	float dir = m_character->GetRotationY();
	
	// Get the angle for 75 degrees:
	
	const float DEGREES_75 = Float::PI() * 0.41666666f;
	
	// Build a 2d view frustrum for the AI: get the two view frustrum directions first
	
		// Left view plane direction
	
		Vec2f vp_dir_l = - Vec2f( Float::Sin( dir - DEGREES_75 ) , Float::Cos( dir - DEGREES_75 ) );
		
		// Right view plane direction
		
		Vec2f vp_dir_r = - Vec2f( Float::Sin( dir + DEGREES_75 ) , Float::Cos( dir + DEGREES_75 ) );
		
		// Left and right plane normals:
		
		Vec2f vp_normal_l =   vp_dir_l.Perpendicular();
		Vec2f vp_normal_r = - vp_dir_r.Perpendicular();
	
	// Get the position of the player relative to the AI on the xz plane:
	
	Vec2f rel = Vec2f
	(
		player->GetPosition().x - m_character->GetPosition().x ,
		player->GetPosition().z - m_character->GetPosition().z
	);
	
	// Now see if the given player is within our view frustrum:
	
	if ( rel.Dot(vp_normal_l) >= 0 && rel.Dot(vp_normal_r) >= 0 )
	{	
		// Get the subsector the player is in, and the subsector we are in:
		
		const DoomGLSubsectorV2 * e_subsec = m_level->GetSubsector(m_character->GetPosition());
		const DoomGLSubsectorV2 * p_subsec = m_level->GetSubsector(player->GetPosition());
		
		// Use the reject map to determine if these two subsectors are visible to each other:
		
		if ( e_subsec != 0 && p_subsec != 0 )
		{
			// Use the reject map to check if the player's position is potentially visible. Early out:
			
			if ( m_level->GetCollision().IsPotentiallyVisible(m_level->GetSector(*e_subsec),m_level->GetSector(*p_subsec)) )
			{
				// Potentially visible. Do the line of sight check: raycast just above the AI's head
				
				DoomCollision::RaycastResult result = m_level->GetCollision().Raycast
				(
					m_character->GetPosition() + Vec3f::UnitY() * GameEnemy::COLLISION_HEIGHT , player->GetPosition()
				);
			
				// Return if in sight:
				
				return result.m_hit == false;
			}
			else
			{
				// Not potentially visible: no need to do a full raycast
				
				return false;
			}
		}
		else
		{	
			// Invalid subsectors. Do the line of sight check: raycast just above the AI's head
			
			DoomCollision::RaycastResult result = m_level->GetCollision().Raycast
			(
				m_character->GetPosition() + Vec3f::UnitY() * GameEnemy::COLLISION_HEIGHT , player->GetPosition()
			);
		
			// Return if in sight:
			
			return result.m_hit == false;
		}
	}
	else
	{		
		// Character is not within our view plane frustrum: therefore not in sight
		
		return false;
	}
}

//=====================================================================================================================
/// @brief		Performs the patrol goal
/// @param		time_delta - amount of time elapsed
//=====================================================================================================================

void AIController::Goal_Patrol( float time_delta )
{
	// Check to see if we can see any player's currently if so then we will go into attack mode
	
	if ( CheckPlayerSight() ) 
	{
		Goal_Attack(time_delta); return;
	}
	
	// See if we are at our destination	
	
	if ( m_destination_point.DistanceTo( m_character->GetPosition() ) <= DESTINATION_RADIUS )
	{
		// At our destination: pick a new patrol point
		
		PickPatrolPoint();
	}
	
	// Move towards the patrol point
	
	MoveToPoint( m_destination_point , time_delta );
}

//=====================================================================================================================
/// @brief		Performs the attack goal
/// @param		time_delta - amount of time elapsed
/// @pre		We must have a target
//=====================================================================================================================

void AIController::Goal_Attack( float time_delta )
{
	// Make sure ok:
	
	ENSURE( m_target , "We must have a target" )
	
	//-----------------------------------------------------------------------------------------------------------------
	// Move towards target
	//-----------------------------------------------------------------------------------------------------------------
	
	// See if we are within range of our target:
	
	Vec2f tar_xz = Vec2f( m_target->GetPosition().x		, m_target->GetPosition().z		);
	Vec2f our_xz = Vec2f( m_character->GetPosition().x	, m_character->GetPosition().z	);
	
	// See if we are close enough to the target:
	
	if ( tar_xz.DistanceTo(our_xz) > ATTACK_DISTANCE )
	{
		// Not close enough: move towards the target
		
		MoveToPoint( m_target->GetPosition() , time_delta );
	}
	
	//-----------------------------------------------------------------------------------------------------------------
	// See if we have lost our target, or if we can fire at it:
	//-----------------------------------------------------------------------------------------------------------------
	
	// Run through the current list of players:
	
	for ( unsigned i = 0 ; i < m_players.size() ; ++i )
	{
		// See if this is our target:
		
		if ( m_players[i].m_player == m_target )
		{
			// See if we have lost them for long enough:
			
			if ( m_players[i].m_time_since_sight >= ATTACK_GIVE_UP_TIME ) 
			{
				// Give up: we have lost track of this player
				
				PickRandomState(); return;
			}
			else 
			{
				// See if we can fire at the target:
				
				if ( m_players[i].m_time_since_sight == 0 )
				{
					// Visible: randomly decide whether to fire or not
					
					if ( m_random.NextFloat() <= ATTACK_FIRE_CHANCE )
					{
						// Get ready to fire:
						
						m_time_since_firing_began = 0; m_state = AIS_ATTACK_FIRE;
						
						// Set the position that we will fire at:
						
						m_target_fire_position = m_target->GetPosition();
					}
				}
			}
		}
	}
}

//=====================================================================================================================
/// @brief		Performs the attack fire goal
/// @pre		We must have a target
//=====================================================================================================================

void AIController::Goal_Attack_Fire( float time_delta )
{
	// Make sure ok:
	
	ENSURE( m_target , "We must have a target" )
	
	// Increase the amount of time in this state:
	
	m_time_since_firing_began += time_delta;
	
	// Get our position in 2d and the position of the point we are firing at:
	
	Vec2f cur_pos_2d = Vec2f( m_character->GetPosition().x	, m_character->GetPosition().z	);
	Vec2f tar_pos_2d = Vec2f( m_target_fire_position.x		, m_target_fire_position.z		);
	
	// Smooth our orientation to face the position we are firing at:
	
	float cur_ang = m_character->GetRotationY();
	float tar_ang = ( tar_pos_2d - cur_pos_2d ).AngleBetween( - Vec2f::UnitY() );
	
	// This is the amount to smooth:
	
	const float SMOOTH = 0.65f;
	
	// Do it:
	
	cur_ang *= SMOOTH; cur_ang += ( 1.0f - SMOOTH ) * tar_ang;
	
	// Set our new orientation:
	
	m_character->SetRotationY(cur_ang);
	
	// See if time to fire:
	
	if ( m_time_since_firing_began >= ATTACK_FIRE_TIME )
	{
		// Figure out the direction we will fire it in
		
		Vec3f dir = m_target_fire_position - m_character->GetPosition();
		
		// Normalise
		
		dir.Normalize();
	
		// Fire:
		
		GameFireball * fireball = new GameFireball
		(
			m_character->GetPosition() , dir , NetObject::NET_ID_INVALID
		);
		
		// Add to the world:
		
		m_character->GetManager()->AddObject(fireball);
		
		// Done with this:
		
		fireball->RemoveReference();
		
		// Go back to the attacking state:
		
		m_state = AIS_ATTACK;

		// Make a list of all the currently visible players:

		std::list<GamePlayer*> visible_players;

		for ( unsigned i = 0 ; i < m_players.size() ; ++i )
		{
			// See if this player has been visible to us lately

			if ( m_players[i].m_time_since_sight <= PLAYER_NOTICE_TIME )
			{
				// Add this player to the list

				visible_players.push_back(m_players[i].m_player);
			}
		}

		// See if any players are visible:

		if ( visible_players.size() > 0 )
		{
			// Pick a random player to attack next:

			unsigned i = m_random.NextUnsigned() % visible_players.size();

			// Get this player:

			std::list<GamePlayer*>::iterator iter = visible_players.begin();

			// Get:

			for ( unsigned n = 0 ; n < i ; ++n ) iter++;

			// Make the current target:

			SetTarget(*iter);
		}
	}
}

//=====================================================================================================================
/// @brief		Performs the ambush goal
/// @param		time_delta - amount of time elapsed
//=====================================================================================================================

void AIController::Goal_Ambush( float time_delta )
{
	// Check to see if we can see any player's currently if so then we will go into attack mode
	
	if ( CheckPlayerSight() ) 
	{
		Goal_Attack(time_delta); return;
	}
	
	// See if we are at our ambush point:
	
	if ( m_character->GetPosition().DistanceTo( m_destination_point ) <= DESTINATION_RADIUS )
	{
		// At the ambush point: turn towards the direction we are supposed to turn to
		
		float ang = m_character->GetRotationY();
		
		// Amount to smooth the turning
		
		const float SMOOTH = 0.9f;
		
		// Smoothly turn towards the direction:
		
		ang *= SMOOTH; ang += m_destination_direction * ( 1.0f - SMOOTH );
		
		// Set the new direction:
		
		m_character->SetRotationY(ang);
	}
	else
	{
		// Not at the ambush point: move towards it
		
		MoveToPoint(m_destination_point,time_delta);
	}
}

//=====================================================================================================================
/// @brief		Performs the pursuit goal
/// @param		time_delta - amount of time elapsed
/// @pre		We must have a target
//=====================================================================================================================

void AIController::Goal_Pursuit( float time_delta )
{
	// Make sure ok:
	
	ENSURE( m_target , "We must have a target" )
	
	// If our target is no longer in the game then pick another goal:
	
	if ( m_target->HasManager() == false )
	{
		// Pick another goal and finish up:
		
		SetTarget(0); PickRandomState(); return;
	}
	
	// Check to see if we can see any player's currently if so then we will go into attack mode
	
	if ( CheckPlayerSight() ) 
	{
		Goal_Attack(time_delta); return;
	}
	
	// Move towards target
	
	MoveToPoint( m_target->GetPosition() , time_delta );
}

//=====================================================================================================================
/// @brief		Returns the height of the given path node in the level
/// @param		node - node to get the height of
/// @returns	Height of the node in the level
//=====================================================================================================================

float AIController::GetNodeHeight( const PathNetwork::Node & node )
{
	// Save the height here:
	
	float height = 0;
	
	// Get the subsector the node is in:
	
	const DoomGLSubsectorV2 * subsector = m_level->GetSubsector(node.m_position);
	
	// See if there:
	
	if ( subsector )
	{
		// Get the sector it's in:
		
		const DoomSector & sector = m_level->GetSector(*subsector);
		
		// Save the height of the node: which is our height plus the height of the sector
		
		height = GameEnemy::COLLISION_HEIGHT + sector.m_floor_y;
	}
	
	// Return the height:
	
	return height;
}

//=====================================================================================================================
/// @brief		Moves the enemy character towards a given point
/// @param		point - point to move towards
/// @param		time_delta - amount of time that has elapsed
//=====================================================================================================================

void AIController::MoveToPoint( const Vec3f & point , float time_delta )
{
	// Set the current position and angle of the mover

	m_mover->SetPosition	( m_character->GetPosition()	);
	m_mover->SetAngle		( m_character->GetRotationY()	);
	
	// Build a path from us to the target position:
	
	m_path_network->GeneratePath
	( 
		Vec2f
		(
			m_character->GetPosition().x,
			m_character->GetPosition().z
		), 
		
		Vec2f
		(
			point.x,
			point.z
		)
	);
	
	// See if there is only one node in the network: if so then we can use direct following:
	
	if ( m_path_network->GetGeneratedPathSize() <= 1 )
	{
		// Smooth the position of the point:
		
		m_last_move_point *= MOVE_POINT_SMOOTHING; m_last_move_point += point * ( 1.0f - MOVE_POINT_SMOOTHING );
	
		// Directly move towards the point:
		
		m_mover->SetGoalPosition( m_last_move_point );
	}
	else
	{
		// Get the two nodes in the path:
		
		const PathNetwork::Node node1 = m_path_network->GetGeneratedPathNode(0);
		const PathNetwork::Node node2 = m_path_network->GetGeneratedPathNode(1);
		
		// See if the second node is visible to us:
		
		if ( m_level->GetCollision().Raycast2D( Vec3f(node2.m_position.x,0,node2.m_position.y) , m_character->GetPosition() ).m_hit == false )
		{
			// Go after the second path node: smooth the position we are moving to first however
			
			m_last_move_point *= MOVE_POINT_SMOOTHING; 
			m_last_move_point += Vec3f(node2.m_position.x,0,node2.m_position.y) * ( 1.0f - MOVE_POINT_SMOOTHING );
		}
		else
		{
			// Go after the first path node
			
			m_last_move_point *= MOVE_POINT_SMOOTHING; 
			m_last_move_point += Vec3f(node1.m_position.x,0,node1.m_position.y) * ( 1.0f - MOVE_POINT_SMOOTHING );
		}
		
		// Move to the goal point:
		
		m_mover->SetGoalPosition(m_last_move_point);
	}
	
	// Update the mover:
	
	m_mover->Update();
	
	// Now move the character
	
	m_character->SetRotationY( m_mover->GetAngle() );
	
	m_character->SetPosition
	( 
		m_character->GetPosition() + m_character->GetFlatForward() * GameEnemy::MOVE_SPEED * time_delta
	);	
}

//=====================================================================================================================
/// @brief		Checks if the monster can see any players and sets it into attack mode if it can
/// @returns	True if we can see any players
//=====================================================================================================================

bool AIController::CheckPlayerSight()
{
	// Run through all the players:
	
	for ( unsigned i = 0 ; i < m_players.size() ; ++i )
	{
		// See if we have sighted this player long enough to register with the AI:
		
		if ( m_players[i].m_time_in_sight >= PLAYER_NOTICE_TIME )
		{
			// Player in sight: attack this player
			
			SetTarget(m_players[i].m_player);
			
			// Switch to the attack behaviour
			
			m_state = AIS_ATTACK;
			
			// Return true: we sighted a player
			
			return true;
		}
	}
	
	// Haven't sighted any players:
	
	return false;
}
