/*
	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 is the base class for any character (player or enemy) in the game.
//=====================================================================================================================

#include "Precompiled.h"
#include "GameObjects/GameCharacter.h"
#include "GameObjects/GameMap.h"
#include "Game/GameCore.h"
#include "Game/GameSpriteBank.h"
#include "Graphics/Camera3D.h"
#include "Graphics/SpriteRenderer.h"
#include "Graphics/SpriteAnimation.h"
#include "Doom/DoomLevel.h"
#include "Doom/DoomSector.h"
#include "Doom/DoomGLSubsectorV2.h"

using namespace Game;

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

/// This is the amount of downwards gravity velocity per second to apply to the player

const float GameCharacter::GRAVITY = 500.0f;

//=====================================================================================================================
/// @brief		Constructor. Creates the character
/// @param		owner_net_id - unique network identifier for the owner this character
/// @param		net_id - this id intefies the object accross the network
/// @param		width - half width of the character. this is for the texture only.
/// @param		height - half height of the character. this is for the texture only.
/// @pre		Width must be > 0
/// @pre		Height must be > 0
/// @pre		GameCore must exist
/// @pre		GameCore must have a sprite bank
//=====================================================================================================================

GameCharacter::GameCharacter
( 
	const Vec3f &			position		,
	unsigned				owner_net_id	, 
	float					width			, 
	float					height			,
	GameSpriteBank::Sprite	sprite			,
	unsigned				net_id
) 
: PositionalObject(position,owner_net_id,net_id)
{
	// Make sure ok
	
	ENSURE( width > 0									, "Width must be > 0"					)
	ENSURE( height > 0									, "Height must be > 0"					)
	ENSURE( GameCore::GetSingleton()					, "GameCore must exist"					)
	ENSURE( GameCore::GetSingleton()->GetSpriteBank()	, "GameCore must have a sprite bank"	)
	
	// Save all:
	
	m_width		= width;
	m_height	= height;
	
	// Load our sprite
	
	m_sprite = new SpriteAnimation( GameCore::GetSingleton()->GetSpriteBank()->GetSprite(sprite) );
	
	// This is zero intially
	
	m_next_move_id = 0;
}

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

GameCharacter::~GameCharacter()
{
	// Destroy our sprite
	
	delete m_sprite;
}

//=====================================================================================================================
/// @brief		Draws the character
/// @param		c - camera to draw from
/// @pre		Given camera cannot be null
/// @pre		GameCore cannot be null
/// @pre		GameCore must have a sprite renderer
//=====================================================================================================================

void GameCharacter::OnDraw( Camera3D * c )
{
	// Make sure ok
	
	ENSURE( c != 0											, "Given camera cannot be null"				)
	ENSURE( GameCore::GetSingleton()						, "GameCore cannot be null"					)
	ENSURE( GameCore::GetSingleton()->GetSpriteRenderer()	, "GameCore must have a sprite renderer"	)
	
	// Bail if we belong to the current peer: we will be in first person for this object
	
	if ( IsClient() ) 
	{
		if ( IsLocalObject() )
		{
			return;
		}
		else
		{
			// Also bail if we are not in scope:
			
			if ( HasScope() == false ) return;
		}	
	}
	
	// Get what sector we are in
	
	const DoomSector & sector = GameMap::GetSingleton().GetLevel().GetSector 
	(
		* GameMap::GetSingleton().GetLevel().GetSubsector(GetPosition())
	);
	
	// Light our sprite according to the light level of the sector:
	
	float intensity = float( sector.m_light_level ) / float(255);
	
	m_sprite->SetColor( Vec4f(intensity,intensity,intensity,m_sprite->GetColor().w) );
	
	// Set the position and orientation of the sprite
	
	m_sprite->SetPosition(GetPosition());
	m_sprite->SetDirection(c->GetPosition(),GetPosition(),m_y_rot);
	
	// Draw the sprite:
	
	m_sprite->Draw(c,GameCore::GetSingleton()->GetSpriteRenderer());
}

//=====================================================================================================================
/// @brief		Returns the flattened (on the y axis) forward vector for the character
/// @returns	The flattened forward vector for the character
//=====================================================================================================================

Vec3f GameCharacter::GetFlatForward() const
{
	return Vec3f
	(
		- Float::Sin( m_y_rot ),
		  0,
		- Float::Cos( m_y_rot )
	);
}

//=====================================================================================================================
/// @brief		Returns the flattened (on the y axis) right vector for the character
/// @returns	The flattened right vector for the character
//=====================================================================================================================

Vec3f GameCharacter::GetFlatRight() const
{
	return Vec3f
	(
		+ Float::Cos( m_y_rot ),		
		0,
		- Float::Sin( m_y_rot )		
	);
}

//=====================================================================================================================
/// @brief		Returns the forward vector for the character
/// @returns	The forward vector for the character
//=====================================================================================================================

Vec3f GameCharacter::GetForward() const
{
	// Create a rotation matrix for our forward vector

	Matrix3f x = Matrix3f::RotationX(m_x_rot);
	Matrix3f y = Matrix3f::RotationY(m_y_rot);

	// Combine:

	Matrix3f c = x * y;

	// Transform forward:

	Vec3f fwd = c.Transform( - Vec3f::UnitZ() );

	// Return

	return fwd;
}

//=====================================================================================================================
/// @brief		Returns the up vector for the character
/// @returns	The up vector for the character
//=====================================================================================================================

Vec3f GameCharacter::GetUp() const
{
	// Create a rotation matrix for our forward vector

	Matrix3f x = Matrix3f::RotationX(m_x_rot);
	Matrix3f y = Matrix3f::RotationY(m_y_rot);

	// Combine:

	Matrix3f c = x * y;

	// Transform up:

	Vec3f up = c.Transform( Vec3f::UnitY() );

	// Return

	return up;
}

//=====================================================================================================================
/// @brief		Tells if the given predicted move with the given id is in the list of predicted moves we made
/// @param		id - id of the move to look for
//=====================================================================================================================

bool GameCharacter::HavePredictedMove( unsigned id ) const
{
	// Run through the list

	std::list<PredictedMove>::const_iterator i = m_predicted_moves.begin();
	std::list<PredictedMove>::const_iterator e = m_predicted_moves.end();

	while ( i != e )
	{
		// Get this predicted move:

		const PredictedMove & p = * i; i++;

		// See if it is the move we are looking for

		if ( p.m_move_id == id ) return true;
	}

	// Couldn't find it:

	return false;
}

//=====================================================================================================================
/// @brief		Gets the predicted move with the given id
/// @param		id - id of the move to look for
/// @pre		The predicted move must exist in the list of predicted moves
//=====================================================================================================================

GameCharacter::PredictedMove GameCharacter::GetPredictedMove( unsigned id ) const
{
	// Make sure ok:

	ENSURE( HavePredictedMove(id) , "HavePredictedMove" )

	// Run through the list

	std::list<PredictedMove>::const_iterator i = m_predicted_moves.begin();
	std::list<PredictedMove>::const_iterator e = m_predicted_moves.end();

	while ( i != e )
	{
		// Get this predicted move:

		const PredictedMove & p = * i; i++;

		// See if it is the move we are looking for

		if ( p.m_move_id == id ) return p;
	}

	// This is here to keep the compiler happy

	return PredictedMove();
}

//=====================================================================================================================
/// @brief		Removes all predicted moves, including and up to moves with the given id
/// @param		id - id of the move to clear the moves list up to and including
//=====================================================================================================================

void GameCharacter::CleanPredictedMoveList( unsigned id )
{
	// Run through the predicted move list

	std::list<PredictedMove>::const_iterator i = m_predicted_moves.begin();
	std::list<PredictedMove>::const_iterator e = m_predicted_moves.end();

	while ( i != e )
	{
		// Get this predicted move:

		const PredictedMove & p = * i;

		// See if it is the move we are looking for

		if ( p.m_move_id <= id )
		{
			// Remove this move from the list

			i = m_predicted_moves.erase(i);
		}
		else
		{
			// Do not delete this move

			i++;
		}
	}
}

//=====================================================================================================================
/// @brief		Server only: Apply a force to the player
/// @param		force - force to apply to the player
/// @pre		This can only be called on the server
//=====================================================================================================================

void GameCharacter::ApplyForce( Vec3f force )
{
	// Make sure ok
	
	ENSURE( IsServer() , "This can only be called on the server" )
	
	// Apply the force:
	
	m_velocity_force += force;
}

//=====================================================================================================================
/// @brief		Corrects spawn orientation for the object both on the client and server
//=====================================================================================================================

void GameCharacter::CorrectSpawnOrientation()
{
	// Get the doom level:
	
	const DoomLevel & level = GameMap::GetSingleton().GetLevel();
	
	// Run through all the things in the level: make a list of spawn positions
	
	std::list<const DoomThing *> spawn_points;
	
	for ( unsigned i = 0 ; i < level.GetThingCount() ; ++i )
	{
		// Get this thing:
		
		const DoomThing & thing = level.GetThing(i);
		
		// See if it is a spawn point:
		
		if 
		( 
			(DoomThing::ThingType)(thing.m_type) == DoomThing::TT_PLAYER1_START ||
			(DoomThing::ThingType)(thing.m_type) == DoomThing::TT_PLAYER2_START	||
			(DoomThing::ThingType)(thing.m_type) == DoomThing::TT_PLAYER3_START ||
			(DoomThing::ThingType)(thing.m_type) == DoomThing::TT_PLAYER4_START
		)
		{
			// Spawn point: put into the list:
			
			spawn_points.push_back(&thing);
		}
	}
	
	// See if there are any spawn points:
	
	if ( spawn_points.size() > 0 )
	{
		// Get our xz position:
		
		Vec2f pos_xz = Vec2f( GetPosition().x , GetPosition().z );
		
		// Current position in the list
		
		std::list<const DoomThing *>::iterator i = spawn_points.begin();
		
		// Get the distance to the first spawn point
		
		float nearest_dist = pos_xz.DistanceTo
		( 
			Vec2f( (*i)->m_x , (*i)->m_z )
		);
		
		// Move on in the list
		
		i++;
		
		// Set as the closest spawn point
		
		std::list<const DoomThing *>::iterator closest = spawn_points.begin();
		
		// Get the end iterator:
		
		std::list<const DoomThing *>::iterator e = spawn_points.end();
		
		// Run through all the other spawn points and see if any are closer:
		
		while ( i != e )
		{
			// Get the distance to this spawn point
			
			float dist = pos_xz.DistanceTo
			( 
				Vec2f( (*i)->m_x , (*i)->m_z )
			);
			
			// See if closer than the closest spawn point:
			
			if ( dist < nearest_dist )
			{
				// Closer spawn point
				
				nearest_dist = dist; closest = i;
			}
			
			// Next spawn point
			
			i++;
		}
		
		// Now get the closest spawn point
		
		const DoomThing & spawn_pt = ** closest;
		
		// Set our y rotation from the angle:
		
		m_y_rot = - Angle::ToRadians( float(spawn_pt.m_angle) - 90 ) + Float::PI();
	}

	// No x rotation when spawning
	
	m_x_rot = 0;
}
