/*
	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 represents all humanly controlled enemys of the game.
//=====================================================================================================================

#include "Precompiled.h"
#include "GameObjects/GameEnemy.h"
#include "GameObjects/GameMap.h"
#include "GameObjects/GamePlayer.h"
#include "Game/GameCore.h"
#include "Graphics/Camera3D.h"
#include "Graphics/SpriteAnimation.h"
#include "Doom/DoomLevel.h"
#include "Doom/DoomCollision.h"
#include "AI/AIController.h"

using namespace Game;

//=====================================================================================================================
// Statics
//=====================================================================================================================

/// Width of the enemy's bounding ellipse

const float GameEnemy::COLLISION_WIDTH = 36.0f;

/// Half height of the enemy's bounding ellipse

const float GameEnemy::COLLISION_HEIGHT = 50.0f;

/// Maximum health for a enemy

const float GameEnemy::MAX_HEALTH = 50.0f;

/// Starting health at spawn

const float GameEnemy::START_HEALTH = 50;

/// Movement speed of the enemy 

const float GameEnemy::MOVE_SPEED = 200.0f;

/// Base running animation speed

const float GameEnemy::RUNNING_ANIM_SPEED = 0.030f;

/// Amount of friction to apply to built up force if on the ground

const float GameEnemy::FORCE_FRICTION = 0.92f;

/// Time the enemy takes to die fully

const float GameEnemy::DEATH_TIME = 12.0f;

/// Speed that the death animation plays at

const float GameEnemy::DEATH_ANIM_SPEED = 0.10f;

//=====================================================================================================================
/// @brief		Constructor. Creates the game player.
/// @param		position - position of the player
/// @param		y_rot - rotation about the y axis, or yaw
/// @param		net_id - id identifying the object across the network. This should not be specified on the server.
///				The NetObject base class will decide this itself on the server.
/// @pre		GameCore must exist
/// @pre		GameCore must have a sprite bank
//=====================================================================================================================

GameEnemy::GameEnemy( const Vec3f & position , float y_rot , unsigned net_id ) 
: 
GameCharacter(position,GameCharacter::OWNER_ID_NONE,48,80,GameSpriteBank::SPRITE_ENEMY,net_id)
{
	// Make sure ok

	ENSURE( GameCore::GetSingleton()					, "GameCore must exist"					)
	ENSURE( GameCore::GetSingleton()->GetSpriteBank()	, "GameCore must have a sprite bank"	)

	// Set our position and orientation
	
	SetPosition(position);
	SetRotationX(0);
	SetRotationY(y_rot);
	
	// Save previous position:
	
	m_previous_pos = position;
	
	// Reset these:
	
	m_in_scope_previously	= true;
	m_previously_dead		= false;
	m_death_time_left		= DEATH_TIME;
	
	// Initialise these:

	m_health = START_HEALTH;
	
	// Correct our orientation to that of the spawn point
	
	CorrectSpawnOrientation();

	// Server only:

	if ( IsServer() )
	{
		m_ai_controller = new AIController(*this);
	}
	else
	{
		m_ai_controller = 0;
	}
}

//=====================================================================================================================
/// @brief		Destructor.
//=====================================================================================================================

GameEnemy::~GameEnemy(){}

//=====================================================================================================================
/// @brief		Server only: Alerts the AI of noise from the given player
/// @param		player - player that made the noise
/// @pre		Must be called on the server
/// @pre		Given player cannot be null
//=====================================================================================================================

void GameEnemy::NoiseAlert( GamePlayer * player )
{
	// Make sure ok

	ENSURE( player != 0 , "Given player cannot be null"		)
	ENSURE( IsServer()	, "Must be called on the server"	)

	// Alert the ai

	m_ai_controller->NoiseAlert(*player);
}

//=====================================================================================================================
/// @brief		Server only: Alerts the AI of damage from the given player
/// @param		player - player that made the damage
/// @pre		Must be called on the server
/// @pre		Given player cannot be null
//=====================================================================================================================

void GameEnemy::DamageAlert( GamePlayer * player )
{
	// Make sure ok

	ENSURE( player != 0 , "Given player cannot be null"		)
	ENSURE( IsServer()	, "Must be called on the server"	)

	// Alert the ai

	m_ai_controller->DamageAlert(*player);
}

//=====================================================================================================================
/// @brief		Called when the enemy is removed from the game
//=====================================================================================================================

void GameEnemy::OnGameRemoved()
{
	if ( m_ai_controller ) delete m_ai_controller;
}

//=====================================================================================================================
/// @brief		Update function. called once for every frame. 
/// @param		time_delta - amount of time that has passed between this frame and the last one
/// @pre		This object should not be marked as being controlled by a client
//=====================================================================================================================

void GameEnemy::OnUpdate( float time_delta )
{
	// Debug only

	#ifdef DEBUG

		if ( IsClient() ) 
		{
			ENSURE( IsRemoteObject() , "This object should not be marked as being controlled by a client" )
		}

	#endif

	// See if we are on the client or the server

	if ( IsClient() ) UpdateRemote(time_delta); else if ( IsServer() ) UpdateServer(time_delta);
}

//=====================================================================================================================
/// @brief		Does an update on the client for this object that is controlled remotely
/// @param		time_delta - amount of time elapsed between this frame and the last
/// @pre		This can only be called on the client
/// @pre		This can only be called if the object is controlled locally
//=====================================================================================================================

void GameEnemy::UpdateRemote( float time_delta )
{
	// Check:
	
	ENSURE(	IsClient()			, "This can only be called on the client"							)
	ENSURE( IsRemoteObject()	, "This can only be called if the object is controlled remotely"	)
	
	//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
	// For remotely controlled objects we simply interpolate/extrapolate using the velocity. This is calculated
	// from packets that we receive from the server.
	//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
	
	// If dead then reduce the death time left
	
	if ( GetHealth() <= 0 )
	{
		m_death_time_left -= time_delta;
	}
	else
	{
		m_death_time_left = DEATH_TIME;
	}
	
	// Only do this however if we are in scope:
	
	if ( HasScope() )
	{
		// Perform interpolation
			
		SetPosition( GetPosition() + m_velocity_force * time_delta );
		
		// Collide with the level:
		//
		// REMOVED THIS BECAUSE ITS ACTUALLY MORE CONSISTENT WITHOUT THIS AND LESS JITTERS!
		
		// SetPosition( GameMap::GetSingleton().GetLevel().GetCollision().Collide(GetPosition(),COLLISION_WIDTH,COLLISION_HEIGHT) );
		
		// Do animation:
		
		Animate(time_delta);
	}
	
	// Save whether we were in scope previously:
	
	m_in_scope_previously = HasScope();	
}

//=====================================================================================================================
/// @brief		Animates the enemy's sprite:
/// @param		time_delta - amount of time between current and last frames
//=====================================================================================================================

void GameEnemy::Animate( float time_delta )
{
	// See if dead first however
	
	if ( GetHealth() <= 0 )
	{		
		// Use the previously dead var on the server to change animation to death:
		
		if ( IsServer() &&  m_previously_dead == false )
		{
			m_sprite->SetSequence(1,true);
		}
	
		// Dead: play the death animation at a constant rate and stop at the end
		
		m_sprite->SetFrameTime( DEATH_ANIM_SPEED );
		m_sprite->Animate(time_delta,true);
		
		// No flipping:
		
		m_sprite->SetFlippingDisabled(true);
	}
	else
	{
		// If we are on the server and were previously dead then put back normal animation:
		
		if ( IsServer() && m_previously_dead )
		{
			m_sprite->SetSequence(0,true);
		}
		
		// Use previous position if on the server to figure out velocity vector:
		
		if ( IsServer() )
		{
			// Get the velocity vector:
			
			Vec3f v = GetPosition() - m_previous_pos;
			
			// Figure out velocity:
			
			v /= time_delta;
			
			// Not dead: set animation speed based on movement speed
	
			m_sprite->SetFrameTime( 1.0f / RUNNING_ANIM_SPEED );		
			m_sprite->Animate( time_delta * Vec2f(v.x,v.z).Length() );	
		}
		else
		{
			// Not dead: set animation speed based on movement speed
	
			m_sprite->SetFrameTime( 1.0f / RUNNING_ANIM_SPEED );		
			m_sprite->Animate( time_delta * Vec2f(m_velocity_force.x,m_velocity_force.z).Length() );
		}
		
		// Allow flipping:
		
		m_sprite->SetFlippingDisabled(false);
	}
}

//=====================================================================================================================
/// @brief		Does an update on the server for this object
/// @param		time_delta - amount of time elapsed between this frame and the last
/// @pre		This can only be called on the server
//=====================================================================================================================

void GameEnemy::UpdateServer( float time_delta )
{
	// Check:
	
	ENSURE( IsServer() , "This can only be called on the server" )

	//-----------------------------------------------------------------------------------------------------------------
	// AI
	//------------------------------------------------------------------------------------------------------------------
	
	// Save previous position:
	
	m_previous_pos = GetPosition();
	
	// Only if not dead:

	if ( GetHealth() > 0 )  m_ai_controller->Update(time_delta); 		
	
	//-----------------------------------------------------------------------------------------------------------------
	// Do animation:
	//-----------------------------------------------------------------------------------------------------------------
	
	Animate(time_delta);
	
	// Save the previous death state:
	
	if ( GetHealth() > 0 )
	{
		m_previously_dead = false;
	}
	else
	{
		m_previously_dead = true;
	}

	//-----------------------------------------------------------------------------------------------------------------
	// Apply gravity and collision dettection
	//-----------------------------------------------------------------------------------------------------------------

	// Apply gravity and move:
	
	m_velocity_force.y -= GRAVITY * time_delta; SetPosition( GetPosition() + m_velocity_force * time_delta );
	
	// Do collision detection:

	SetPosition( GameMap::GetSingleton().GetLevel().GetCollision().Collide(GetPosition(),COLLISION_WIDTH,COLLISION_HEIGHT) );
	
	// If we are on the floor then put the gravity force back to zero:
	
	const DoomGLSubsectorV2 * subsector = GameMap::GetSingleton().GetLevel().GetSubsector(GetPosition());
	
	// See if in a subsector:
	
	if ( subsector )
	{
		// Get the sector we are in
		
		const DoomSector & sector = GameMap::GetSingleton().GetLevel().GetSector(*subsector);
		
		// See if we are at floor height:
		
		if ( GetPosition().y - sector.m_floor_y <= COLLISION_HEIGHT + 0.001f ) 
		{
			// Reset y velocity:
		
			m_velocity_force.y = 0;
			
			// Apply friction to ground velocity:
			
			m_velocity_force.x *= FORCE_FRICTION;
			m_velocity_force.z *= FORCE_FRICTION;
		}
	}

	//-----------------------------------------------------------------------------------------------------------------
	// Respawn
	//-----------------------------------------------------------------------------------------------------------------
	
	// If dead then reduce the death time left
	
	if ( GetHealth() <= 0 )
	{
		// Dead: reduce this
	
		m_death_time_left -= time_delta;
		
		// See if time to respawn:
		
		if ( m_death_time_left <= 0 )
		{
			// Reset this:
			
			m_death_time_left = DEATH_TIME;
			
			// Reset health and ammo:
			
			SetHealth(START_HEALTH);
			
			// Pick a spawn point from the map
			
			SetPosition( GameMap::GetSingleton().PickEnemyStartPosition() );
			
			// Reset forces acting on us
			
			m_velocity_force = Vec3f::Zero();
			
			// Correct our orientation to that of the spawn point
			
			CorrectSpawnOrientation();	
			
			// Reset AI state:
			
			m_ai_controller->ResetState();
		}
	}
	else
	{
		// Not dead: reset this
		
		m_death_time_left = DEATH_TIME;
	}	
}

//=====================================================================================================================
/// @brief		Network send function. Called on each net object by the GameSession object as required. 
/// @param		packet - packet to send
/// @param		context - context under which the packet is being sent
/// @returns	True to indicate the packet was sent#
/// @pre		Send context must always be from the server to the client for a remote object
//=====================================================================================================================

#pragma warning(disable:4100)

bool GameEnemy::NetSend( GamePacket & packet , SendContext context )
{
	// Make sure ok

	ENSURE
	(
		context == SEND_CONTEXT_REMOTE_TO_CLIENT ,
		"Send context must always be from the server to the client for a remote object"
	);

	// Server is sending moves for this remotely controlled object to the client
	
	NetSendRemote(packet);

	// We sent our update

	return true;
}

#pragma warning(default:4100)

//=====================================================================================================================
/// @brief		Network receive function. Called on each net object by the GameSession object as required. 
/// @param		packet - packet that is being received
/// @param		context - context under which the packet is being received
/// @returns	True if the packet was read sucessfully
/// @pre		The receive context always must be for a remotely controlled object, from server to client
//=====================================================================================================================

#pragma warning(disable:4100)

bool GameEnemy::NetReceive( GamePacket & packet , ReceiveContext context )
{
	// Make sure ok:

	ENSURE
	( 
		context == SEND_CONTEXT_REMOTE_TO_CLIENT	,
		"The receive context always must be for a remotely controlled object, from server to client"
	)

	// Receiving data for a remotely controlled object on the client
	
	return NetReceiveRemote(packet);
}

#pragma warning(default:4100)

//=====================================================================================================================
/// @brief		Does network sending for an object on the server to the client, where the client has no control 
///				over the object.
/// @param		packet - packet to write to
/// @pre		This can only be called on the server
//=====================================================================================================================

void GameEnemy::NetSendRemote( GamePacket & packet )
{
	// Make sure ok
	
	ENSURE( IsServer() , "This can only be called on the server" )
	
	// Get the constant packet generation interval:
	
	unsigned cpg_interval = GameSession::GetSingleton().GetSettings().m_server_cpg_interval;
	
	// Make sure it is not less than the frame time:
	
	if ( cpg_interval < GameFrameTimer::FRAME_INTERVAL_MS )
	{
		// Can't update faster than minimum frame interval!
	
		cpg_interval = GameFrameTimer::FRAME_INTERVAL_MS;
	}
	
	// Write the number of milliseconds passed since the last update
	
	packet.WritePackedUint( cpg_interval );
	
	// Write the position of the object:
	
	packet.Write(short(GetPosition().x));
	packet.Write(short(GetPosition().y));
	packet.Write(short(GetPosition().z));
	
	// Write the angle of the object:
	
	packet.WriteAngle12( m_y_rot );
	
	// Send a bit saying if we're dead:
	
	if ( GetHealth() <= 0 ) packet.WriteFlag(true); else packet.WriteFlag(false);
}

//=====================================================================================================================
/// @brief		Does network receiving for a remotely controlled object on the client.
/// @param		packet - packet being received
/// @returns	True if the packet was read successfully
/// @pre		This can only be called for a remotely controlled object on the client
//=====================================================================================================================

bool GameEnemy::NetReceiveRemote( GamePacket & packet )
{
	// Make sure ok
	
	ENSURE( IsClient()			, "This can only be called for a remotely controlled object on the client" )
	ENSURE( IsRemoteObject()	, "This can only be called for a remotely controlled object on the client" )
	
	// Read all of the data for the object:
	
	bool		success			= true;	
	unsigned	cpg_interval	= 0;
	short		pos_x			= 0;
	short		pos_y			= 0;
	short		pos_z			= 0;
	float		angle			= 0;
	bool		dead			= false;
	
	// Read the number of milliseconds passed since the last update: this is the constant packet generation interval
	
	success = success && packet.ReadPackedUint( cpg_interval );
	
	// Read the position of the object:
	
	success = success && packet.Read(pos_x);
	success = success && packet.Read(pos_y);
	success = success && packet.Read(pos_z);
	
	// Read the angle of the object:
	
	success = success && packet.ReadAngle12(angle);
	
	// Read if we are dead
	
	success = success && packet.ReadFlag(dead);
	
	// See if all successful:
	
	if ( success )
	{
		// Make sure the angle is wrapped:
		
		angle = Angle::Wrap(angle);
	
		// Make sure the constant packet generation is not below the frame time: if so then clamp it
		
		if ( cpg_interval < GameFrameTimer::FRAME_INTERVAL_MS )
		{
			// Clamp:
			
			cpg_interval = GameFrameTimer::FRAME_INTERVAL_MS;
		}
		
		//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
		// If we were not in scope the last time then our position could be vastly different: just set our position 
		// in this case. Also do this if we were dead previously and are not dead anymore.
		//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
		
		if ( m_in_scope_previously == false || ( GetHealth() <= 0 && dead == false ) )
		{
			// Set our position explicitly
		
			m_velocity_force = Vec3f::Zero(); SetPosition( Vec3f(pos_x,pos_y,pos_z) );
			
			// Save the angle of the object
			
			SetRotationY(angle);			
		}
		else
		{
			//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
			// Calculate the velocity of the remote object from the difference in positions and time taken: 
			// this will be used to move the object and interpolate / extrapolate past received position.
			//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
			
			m_velocity_force  = Vec3f(pos_x,pos_y,pos_z); 
			m_velocity_force -= GetPosition();
			m_velocity_force /= float(cpg_interval) * 0.001f;
			
			// Save the angle of the object
			
			SetRotationY(angle);
		}
		
		//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
		// See if there is a change in our dead/alive status:
		//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
		
		if ( dead != ( GetHealth() <= 0 ) )
		{
			// See if we are now dead:
			
			if ( dead == false )
			{
				// Not dead anymore:
				
				SetHealth(MAX_HEALTH);
				
				// Back to normal animation:
				
				m_sprite->SetSequence(0);
			}
			else
			{
				// Now dead:
				
				SetHealth(0);
				
				// Play the death sequence:
				
				m_sprite->SetSequence(1);
			}
		}
	}
	
	// Return whether the read was successful
	
	return success;
}

//=====================================================================================================================
/// @brief		Draws the character
/// @param		c - camera viewpoint
/// @pre		GameCore must exist
/// @pre		GameCore must have a sprite renderer
//=====================================================================================================================

void GameEnemy::OnDraw( Camera3D * c )
{
	// Make sure ok

	ENSURE( GameCore::GetSingleton()						, "GameCore must exist"						)
	ENSURE( GameCore::GetSingleton()->GetSpriteRenderer()	, "GameCore must have a sprite renderer"	)

	// If dead then fade us out:
	
	if ( GetHealth() <= 0 )
	{
		// Figure out how much to fade out by:
		
		float t = ( m_death_time_left - DEATH_TIME * 0.5f ) / (DEATH_TIME*0.5f);
		
		// Clamp
		
		if ( t < 0 ) t = 0;
		
		// Do the fade:
		
		Vec4f col = m_sprite->GetColor();
		
		// Set alpha:
		
		col.w = t;
		
		// Set:
		
		m_sprite->SetColor(col);
	}
	else
	{
		// Not dead: draw at full alpha
		
		Vec4f col = m_sprite->GetColor();
		
		// Set alpha to full
		
		col.w = 1;
		
		// Set:
		
		m_sprite->SetColor(col);
	}

	// Draw our character sprite:

	GameCharacter::OnDraw(c);
}

//=====================================================================================================================
/// @brief		Sets the amount of health the player has
/// @param		health - new amount of healt
//=====================================================================================================================

inline void GameEnemy::SetHealth( float health )
{
	m_health = health;
	
	// Keep in range:
	
	m_health = Float::Clamp( m_health , 0 , MAX_HEALTH );
}
