/*
	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.
*/

/*
	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 players of the game.
//=====================================================================================================================

#include "Precompiled.h"
#include "GameObjects/GamePlayer.h"
#include "GameObjects/GameMap.h"
#include "GameObjects/GameRocket.h"
#include "GameObjects/GameTeleportCloud.h"
#include "Game/GameFrameTimer.h"
#include "Doom/DoomLevel.h"
#include "Doom/DoomCollision.h"
#include "Graphics/Camera3D.h"
#include "Graphics/SpriteAnimation.h"
#include "Game/GameCore.h"
#include "Windows/Window.h"

using namespace Game;

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

/// Width of the player's bounding ellipse

const float GamePlayer::COLLISION_WIDTH = 36.0f;

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

const float GamePlayer::COLLISION_HEIGHT = 50.0f;

/// Amount to offset the camera position on the y axis

const float GamePlayer::CAMERA_Y_OFFSET = 16.0f;

/// Maximum health for a player

const float GamePlayer::MAX_HEALTH = 100.0f;

/// Maximum ammo for a player

const unsigned GamePlayer::MAX_AMMO = 100;

/// Starting health at spawn

const float GamePlayer::START_HEALTH = 100;

/// Starting ammo at spawn

const unsigned GamePlayer::START_AMMO = 10;

/// X turn sensitivity with the mouse

const float GamePlayer::X_TURN_SPEED = 0.004f;			

/// Y turn sensitivity with the mouse

const float GamePlayer::Y_TURN_SPEED = 0.004f;

/// Movement speed of the player 

const float GamePlayer::MOVE_SPEED = 400.0f;

/// Amount to smooth the movement of the player

const float GamePlayer::MOVE_SMOOTH = 0.75f;

/// Amount to smooth the turning of the player

const float GamePlayer::TURN_SMOOTH = 0.85f;

/// The amount to smooth out prediction error on the client

const float GamePlayer::ERROR_SMOOTH = 0.85f;

/// Base running animation speed

const float GamePlayer::RUNNING_ANIM_SPEED = 0.030f;

/// Minimum time in between firing

const float GamePlayer::FIRE_INTERVAL = 0.8f;

/// Speed for the rocket launcher fire animation

const float GamePlayer::ROCKET_LAUNCHER_ANIM_SPEED = 0.1f;

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

const float GamePlayer::FORCE_FRICTION = 0.92f;

/// Time the player takes to die fully

const float GamePlayer::DEATH_TIME = 2.0f;

/// Amount to reduce camera height by during death on the client

const float GamePlayer::DEATH_Y_FALL = 56.0f;

/// Speed that the death animation plays at

const float GamePlayer::DEATH_ANIM_SPEED = 0.08f;

//=====================================================================================================================
/// @brief		Constructor. Creates the game player.
/// @param		owner_net_id - id of the peer tha owns the object
/// @param		position - position of the player
/// @param		x_rot - rotation about the x axis, or pitch
/// @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
//=====================================================================================================================

GamePlayer::GamePlayer( unsigned owner_net_id , const Vec3f & position , float x_rot , float y_rot , unsigned net_id ) 
: 
GameCharacter(position,owner_net_id,48,80,GameSpriteBank::SPRITE_PLAYER,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(x_rot);
	SetRotationY(y_rot);

	// Need to initialise this

	m_last_received_move_id = 0xffffffff;
	
	// Save these:
	
	m_previous_pos = GetPosition();
	m_previously_dead = false;

	// Initialise the error value:

	m_client_pos_error = Vec2f::Zero();
	
	// Reset these:
	
	m_server_received_new_move	= false;
	m_in_scope_previously		= true;
	m_death_time_left			= DEATH_TIME;
	
	// Can fire now:
	
	m_time_till_fire = 0;
	m_play_fire_anim = false;	
	
	// Initialise these:
	
	m_ammo		= START_AMMO;
	m_health	= START_HEALTH;
	
	// On server make sure this is false initially:
	
	m_client_firing = false;

	// Load the animation if is a client controlled object on the client

	if ( IsClient() && IsLocalObject() )
	{
		m_rocket_animation = new SpriteAnimation
		( 
			GameCore::GetSingleton()->GetSpriteBank()->GetSprite( GameSpriteBank::SPRITE_ROCKET_LAUNCHER )
		);
		
		// Set the speed for the animation:
		
		m_rocket_animation->SetFrameTime(ROCKET_LAUNCHER_ANIM_SPEED);
	}
	else
	{
		m_rocket_animation = 0;
	}
	
	// Correct our orientation to that of the spawn point
	
	CorrectSpawnOrientation();	
}

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

GamePlayer::~GamePlayer()
{
	if ( m_rocket_animation ) delete m_rocket_animation;
}

//=====================================================================================================================
/// @brief		Update function. called once for every frame. 
/// @param		time_delta - amount of time that has passed between this frame and the last one
//=====================================================================================================================

void GamePlayer::OnUpdate( float time_delta )
{
	// See if this object is a client and a locally controlled object:

	if ( IsClient() && IsLocalObject() )
	{
		UpdateClient(time_delta);
	}
	else if ( IsClient() )
	{
		UpdateRemote(time_delta);
	}
	else if ( IsServer() )
	{
		UpdateServer(time_delta);
	}
}

//=====================================================================================================================
/// @brief		Does an update on the client for this object that is controlled by the client
/// @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
/// @pre		GameCore must exist
/// @pre		GameCore must have a window
//=====================================================================================================================

void GamePlayer::UpdateClient( float time_delta )
{
	// Check:
	
	ENSURE(	IsClient()		, "This can only be called on the client"						)
	ENSURE( IsLocalObject()	, "This can only be called if the object is controlled locally"	)
	
	// Make sure ok
	
	ENSURE( GameCore::GetSingleton()				, "GameCore must exist"				)
	ENSURE( GameCore::GetSingleton()->GetWindow()	, "GameCore must have a window"		)	
	
	// Get the camera:

	Camera3D * camera = GameCore::GetSingleton()->GetCamera();
	
	// Get the game window:
	
	Window * window = GameCore::GetSingleton()->GetWindow();
	
	//-----------------------------------------------------------------------------------------------------------------
	// Camera 
	//-----------------------------------------------------------------------------------------------------------------
	
	// Set it's position: if we are dead then make the camera fall to the floor
	
	if ( GetHealth() > 0 )
	{	
		camera->SetPosition( GetPosition() + m_client_pos_error + Vec3f::UnitY() * CAMERA_Y_OFFSET );
	}
	else
	{
		// Dying: lower camera
		
		Vec3f p = GetPosition() + m_client_pos_error + Vec3f::UnitY() * CAMERA_Y_OFFSET;
		
		// Make the camera fall in half the death time
		
		float t = ( m_death_time_left - DEATH_TIME * 0.5f ) / ( DEATH_TIME * 0.5f );
		
		// Make sure not less than zero:
		
		if ( t < 0 ) t = 0;
		
		// Make it fall gradually:
		
		p -= Vec3f::UnitY() * ( 1.0f - t ) * DEATH_Y_FALL;
		
		// Set position:
		
		camera->SetPosition(p);
	}
	
	// Calcaulte the orientation matrix
	
	Matrix4f orientation = Matrix4f::RotationX( m_x_rot ); orientation *= Matrix4f::RotationY( m_y_rot );
	
	// Set it:
	
	camera->SetOrientation( orientation );
	
	//-----------------------------------------------------------------------------------------------------------------
	// Movement
	//-----------------------------------------------------------------------------------------------------------------	

	// Safety: if our buffer of sent off moves is getting full then begin to cull it:
	
	while ( m_predicted_moves.size() > 128 ) m_predicted_moves.pop_front();
	
	// Make up our predicted move:
	
	PredictedMove move;	
	
	// Only allow movement if we are not dead:
	
	if ( GetHealth() > 0 )
	{
		// The move is smoothed with the previous: 
		
		move.m_x_mov = m_last_move.m_x_mov * MOVE_SMOOTH;
		move.m_z_mov = m_last_move.m_z_mov * MOVE_SMOOTH;
		move.m_x_rot = m_last_move.m_x_rot * TURN_SMOOTH;
		move.m_y_rot = m_last_move.m_y_rot * TURN_SMOOTH;	
		
		// Zero the move if movement amount is lower than this
		
		const float MOVE_EPSILON = 0.001f;
		const float TURN_EPSILON = 0.001f;
		
		// Only move if the window is in focus:

		if ( window->HasFocus() && window->IsMinimized() == false )
		{
			// Check for move foward and back:
			
			if ( Keyboard::IsKeyPressed( Keyboard::KB_W ) && Keyboard::IsKeyReleased( Keyboard::KB_S ) ) 
			{
				move.m_z_mov += ( 1.0f - MOVE_SMOOTH );
			}

			if ( Keyboard::IsKeyPressed( Keyboard::KB_S ) && Keyboard::IsKeyReleased( Keyboard::KB_W ) ) 
			{
				move.m_z_mov -= ( 1.0f - MOVE_SMOOTH );
			}
			
			// Check for move left and right:
			
			if ( Keyboard::IsKeyPressed( Keyboard::KB_D ) && Keyboard::IsKeyReleased( Keyboard::KB_A ) ) 
			{
				move.m_x_mov += ( 1.0f - MOVE_SMOOTH );
			}
			
			if ( Keyboard::IsKeyPressed( Keyboard::KB_A ) && Keyboard::IsKeyReleased( Keyboard::KB_D ) ) 
			{
				move.m_x_mov -= ( 1.0f - MOVE_SMOOTH );
			}
			
			// Get the current position the mouse on the screen:
			
			Vec2i mouse_pos; 
			
			Mouse::GetClientPosition
			( 
				GameCore::GetSingleton()->GetWindow(), mouse_pos.x, mouse_pos.y 
			);
			
			// Get the center of the window on the screen
			
			Vec2i win_center = GameCore::GetSingleton()->GetWindow()->GetClientSize() / 2;
			
			// Get the amount of movement:
			
			Vec2f move_amount = mouse_pos - win_center;
			
			move_amount.x *= X_TURN_SPEED;
			move_amount.y *= Y_TURN_SPEED;
			
			// Interpolate with previous movement:
			
			move_amount *= 1.0f - TURN_SMOOTH; move_amount += m_last_turning * TURN_SMOOTH;
			
			// Save the last turn amount:
			
			m_last_turning = move_amount;
			
			// Save the movement
			
			move.m_x_rot = move_amount.y;
			move.m_y_rot = move_amount.x;
		
			// Center the mouse again
			
			Mouse::CenterInWindow( GameCore::GetSingleton()->GetWindow() );
		}
		
		// Zero these if very low:
		
		if ( Float::Abs(move.m_x_mov) < MOVE_EPSILON ) move.m_x_mov	= 0;
		if ( Float::Abs(move.m_z_mov) < MOVE_EPSILON ) move.m_z_mov	= 0;
		if ( Float::Abs(move.m_x_rot) < TURN_EPSILON ) move.m_x_rot	= 0;
		if ( Float::Abs(move.m_y_rot) < TURN_EPSILON ) move.m_y_rot	= 0;
		
		// Decrement amount of time till next fire
		
		m_time_till_fire -= GameFrameTimer::FRAME_INTERVAL;
		
		// If less than zero then clamp:
		
		if ( m_time_till_fire < 0 ) m_time_till_fire = 0;
		
		// See if we are firing:
		
		if ( m_time_till_fire <= 0 && Mouse::IsButtonPressed(Mouse::BTN_LEFT) && GetAmmo() > 0 )
		{
			// Only if the window is in focus and is not minimized:
			
			if (  window->HasFocus() && window->IsMinimized() == false )
			{		
				// Fire:
				
				move.m_fire = true;
				
				// Reset amount of time till we can next fire:
				
				m_time_till_fire = FIRE_INTERVAL; 
				
				// We can re-play the rocket animation:
				
				m_play_fire_anim = true; m_rocket_animation->SetFrame(0);
			}
		}
		
	}	// end if ( GetHealth() > 0 )
	
	// Apply the move:

	ApplyMove(move,time_delta);

	// Save the current move as the previous:
	
	m_last_move = move;
			
	// Save all these results of the move and assign the move id:
	
	move.m_move_id			= m_next_move_id; m_next_move_id++;
	move.m_result_x_rot		= m_x_rot;
	move.m_result_y_rot		= m_y_rot;
	move.m_result_position	= GetPosition();
	move.m_sent_to_server	= false;

	// Add to the list of predicted moves:

	m_predicted_moves.push_back(move);
	
	//-----------------------------------------------------------------------------------------------------------------
	// Gun animation
	//-----------------------------------------------------------------------------------------------------------------
	
	// See if we are to play the fire animation:
	
	if ( m_play_fire_anim )
	{
		// Play and stop at the end
	
		m_rocket_animation->Animate(time_delta,true);
		
		// See if we are at the end:
		
		if ( m_rocket_animation->IsSequenceFinished() )
		{
			m_play_fire_anim = false;
		}
	}
	else
	{
		// Put back to the start:
		
		m_rocket_animation->SetFrame(0);
	}	

	//-----------------------------------------------------------------------------------------------------------------
	// Client prediction error
	//-----------------------------------------------------------------------------------------------------------------	
	
	// Reduce the amount of client error:
	
	m_client_pos_error *= GameSession::GetSingleton().GetSettings().m_client_smooth;
	
	//-----------------------------------------------------------------------------------------------------------------
	// Death:
	//-----------------------------------------------------------------------------------------------------------------
	
	if ( GetHealth() > 0 )
	{
		m_death_time_left = DEATH_TIME;
	}
	else
	{
		// Dying: reduce the death time left
	
		m_death_time_left -= time_delta;
		
		// Clamp to zero:
		
		if ( m_death_time_left < 0 ) m_death_time_left = 0;
	}
}

//=====================================================================================================================
/// @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 GamePlayer::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) );
		
		// Animate:
		
		Animate(time_delta);
	}
	
	// Save whether we were in scope previously:
	
	m_in_scope_previously = HasScope();	
}

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

void GamePlayer::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 GamePlayer::UpdateServer( float time_delta )
{
	// Check:
	
	ENSURE( IsServer() , "This can only be called on the server" )
	
	//-----------------------------------------------------------------------------------------------------------------
	// Animation:
	//-----------------------------------------------------------------------------------------------------------------
	
	Animate(time_delta);
	
	// Save these variables as we will need them for server side animation:
	
	m_previous_pos = GetPosition();
		
	if ( GetHealth() > 0 )
	{
		m_previously_dead = false;
	}
	else
	{
		m_previously_dead = true;
	}
	
	//-----------------------------------------------------------------------------------------------------------------
	// Firing
	//-----------------------------------------------------------------------------------------------------------------
	
	// Decrement amount of time till next fire
	
	m_time_till_fire -= time_delta;
	
	// If less than zero then clamp:
	
	if ( m_time_till_fire < 0 ) m_time_till_fire = 0;
	
	// See if the client is trying to fire:
	
	if ( m_client_firing && m_time_till_fire <= 0 && GetAmmo() > 0 )
	{
		// Get our forward vector
		
		Vec3f fwd = GetForward();
	
		// Make a rocket: 
		
		GameRocket * rocket = new GameRocket(GetPosition(),fwd,NetObject::NET_ID_INVALID,this);
		
		// Add to the game and remove the reference:
		
		GetManager()->AddObject(rocket); rocket->RemoveReference();
		
		// One less rocket:
		
		SetAmmo( GetAmmo() - 1 );
	}
	
	// Reset this:
	
	m_client_firing = false;		
	
	//-----------------------------------------------------------------------------------------------------------------
	// 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);
			SetAmmo(START_AMMO);
			
			// Pick a spawn point from the map
			
			SetPosition( GameMap::GetSingleton().PickStartPosition() );
			
			// Reset forces acting on us
			
			m_velocity_force = Vec3f::Zero();
			
			// Correct our orientation to that of the spawn point
			
			CorrectSpawnOrientation();					
		}
	}
	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
//=====================================================================================================================

bool GamePlayer::NetSend( GamePacket & packet , SendContext context )
{
	// See what context we are sending the packet under:

	if ( context == NetObject::SEND_CONTEXT_CLIENT_TO_SERVER )
	{
		// Client sending moves to the server
		
		NetSendLocal(packet);
	}
	else if ( context == NetObject::SEND_CONTEXT_REMOTE_TO_CLIENT )
	{
		// Server is sending moves for this remotely controlled object to the client
		
		NetSendRemote(packet);
	}
	else 
	{
		// This must be true
		
		ENSURE( context == NetObject::SEND_CONTEXT_SERVER_TO_CLIENT , "Unknown send context" )
		
		// Send a server update to the client:
		
		NetSendServer(packet);
	}

	// We sent our update

	return true;
}

//=====================================================================================================================
/// @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
//=====================================================================================================================

bool GamePlayer::NetReceive( GamePacket & packet , ReceiveContext context )
{
	// See what context we are sending the packet under:

	if ( context == NetObject::RECV_CONTEXT_SERVER_TO_CLIENT )
	{
		// Receiving the result of moves from the server
		
		return NetReceiveLocal(packet);
	}
	else if ( context == NetObject::RECV_CONTEXT_REMOTE_TO_CLIENT )
	{
		// Receiving data for a remotely controlled object on the client
		
		return NetReceiveRemote(packet);
	}
	else 
	{
		// This must be true
		
		ENSURE( context == NetObject::RECV_CONTEXT_CLIENT_TO_SERVER , "Unknown send context" )
		
		// Server: receiving from the client
		
		return NetReceiveServer(packet);
	}
}

//=====================================================================================================================
/// @brief		Does network sending for a locally controlled object on the client
/// @param		packet - packet to write to
/// @pre		This can only be called on the client
//=====================================================================================================================

void GamePlayer::NetSendLocal( GamePacket & packet )
{
	// Make sure ok
	
	ENSURE( IsClient() , "This can only be called on the client" )

	// See if we have any updates to send off: start with the oldest ones first

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

	// We need to keep track of which predicted move we are on

	unsigned list_entry_num = 0;

	// Find the oldest move that is unsent in the list:

	while ( i != e )
	{
		if ( (*i).m_sent_to_server == false ){ break; } else { i++; list_entry_num++; }
	}

	// See if we found moves to be sent off

	if ( i != e )
	{
		// Write the number of moves we are sending:

		packet.WritePackedUint( m_predicted_moves.size() - list_entry_num );

		// Write all the moves:

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

			PredictedMove & move = * i;

			// Needs to be sent off to the server: Decide what will be written

			bool mx	= false; if ( Float::Abs(move.m_x_mov) > 0 ) mx = true;
			bool mz	= false; if ( Float::Abs(move.m_z_mov) > 0 ) mz = true;
			bool tx = false; if ( Float::Abs(move.m_x_rot) > 0 ) tx = true;
			bool ty = false; if ( Float::Abs(move.m_y_rot) > 0 ) ty = true;

			// Write the id of the move:

			packet.Write( move.m_move_id );
			
			// Makeup the control bits for what will written: we need to send this as well
			
			unsigned cb = 0;
			
			if ( mx ) cb |= 0x01;
			if ( mz ) cb |= 0x02;
			if ( tx ) cb |= 0x04;
			if ( ty ) cb |= 0x08;
			
			// Write the control bits:
			
			packet.WritePrecisionUint(cb,4);
			
			// Write all the data if it is needed to:
			
			if ( mx ) packet.WriteNormalisedSignedFloat16(move.m_x_mov);
			if ( mz ) packet.WriteNormalisedSignedFloat16(move.m_z_mov);
			if ( tx ) packet.WriteAngle16(move.m_result_x_rot);
			if ( ty ) packet.WriteAngle16(move.m_result_y_rot);	
			
			// Write if trying to fire
			
			packet.WriteFlag(move.m_fire);
			
			// This move has been sent to the server
			
			move.m_sent_to_server = true;

			// Next move:

			i++;
		}
	}
	else
	{
		// No moves to send:
		
		packet.WritePackedUint(0);
	}	
}

//=====================================================================================================================
/// @brief		Called when the object is added to the game world
//=====================================================================================================================

void GamePlayer::OnGameAdded()
{
	// Call the base function first
	
	GameObjectBase::OnGameAdded();
	
	// Make sure ok
	
	ENSURE( GetManager() , "Must have a manager" )
	
	// Create a teleport cloud where we are:
	
	GameTeleportCloud * cloud = new GameTeleportCloud(GetPosition());
	
	// Add to the game world
	
	GetManager()->AddObject(cloud);
	
	// Done with it:
	
	cloud->RemoveReference();
}

//=====================================================================================================================
/// @brief		Called when the object is removed from the game world
//=====================================================================================================================

void GamePlayer::OnGameRemoved()
{
	// Call the base function first
	
	GameObjectBase::OnGameRemoved();
	
	// Make sure ok
	
	ENSURE( GetManager() , "Must have a manager" )
	
	// Create a teleport cloud where we are:
	
	GameTeleportCloud * cloud = new GameTeleportCloud(GetPosition());
	
	// Add to the game world
	
	GetManager()->AddObject(cloud);
	
	// Done with it:
	
	cloud->RemoveReference();
}

//=====================================================================================================================
/// @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 GamePlayer::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 sending for an object on the server to the client, where the client controls this object
/// @param		packet - packet we are writing to
/// @pre		This can only be called on the server
//=====================================================================================================================

void GamePlayer::NetSendServer( GamePacket & packet )
{
	// Make sure ok
	
	ENSURE( IsServer() , "This can only be called on the server" )

	// See if we have got a new move from the client:
	
	if ( m_server_received_new_move )
	{
		// Got a new move from the client: write 1 to indicate there is data to be sent
		
		packet.WritePrecisionUint(1,1);
		
		// Write the id of the move we are responding to:

		packet.Write(m_last_received_move_id);
		
		// Write the position of the player:

		packet.Write( GetPosition().x );
		packet.Write( GetPosition().y );
		packet.Write( GetPosition().z );
		
		// Write the velocity of the player:
		
		packet.Write( Vec3s(m_velocity_force) );
		
		// Write ammo
		
		packet.WritePrecisionUint( GetAmmo() , 7 );
		
		// Write health:
		
		float health = GetHealth();
		
			// Round:
			
			if ( health < 1 )
			{
				if ( health != 0 )
				{
					health = 1;
				}
			}
			else 
			{
				health = Float::Round(health,0);
			}
			
			// Make sure in range:
			
			health = Float::Clamp(health,0,MAX_HEALTH);
			
		// Write health:
		
		packet.WritePrecisionUint(unsigned(health),7);		
		
		// Haven't received a new move after this:
		
		m_server_received_new_move = false;				
	}
	else
	{
		// Didn't get a new move: write 0 to indicate no further data applies
		
		packet.WritePrecisionUint(0,1);
	}
}

//=====================================================================================================================
/// @brief		Does network receiving for a locally controlled object on the client.
/// @param		packet - packet we are reading from
/// @returns	True if reading succeeded
/// @pre		This can only be called for a locally controlled object on the client
//=====================================================================================================================

bool GamePlayer::NetReceiveLocal( GamePacket & packet )
{
	// Make sure ok
	
	ENSURE( IsClient()		, "This can only be called for a locally controlled object on the client" )
	ENSURE( IsLocalObject() , "This can only be called for a locally controlled object on the client" )

	// See if there was a move:
	
	unsigned was_move = 0; 
	
	// Read this:
	
	if ( packet.ReadPrecisionUint(was_move,1) )
	{
		// See if a move was given to us:
		
		if ( was_move == 1 )
		{
			// Read all the move data:
			
			bool		success			= true;
			unsigned	move_id			= 0;
			Vec3f		move_position	= Vec3f::Zero();
			Vec3s		move_velocity	= Vec3s::Zero();
			unsigned	health			= 0;
			unsigned	ammo			= 0;
			
			success = success && packet.Read				( move_id			);
			success = success && packet.Read				( move_position.x	);
			success = success && packet.Read				( move_position.y	);
			success = success && packet.Read				( move_position.z	);
			success = success && packet.Read				( move_velocity		);
			success = success && packet.ReadPrecisionUint	( ammo , 7			);
			success = success && packet.ReadPrecisionUint	( health , 7		);
			
			// See if it went ok:
			
			if ( success )
			{							
				// Save our predicted position so that we can calculate error amount

				Vec3f wrong_pos	= GetPosition();			
			
				// Only if it is a new move:
				
				if ( move_id > m_last_received_move_id || m_last_received_move_id == 0xffffffff )
				{
					// Got a new move. Get the move we predicted:

					PredictedMove prediction = GetPredictedMove(move_id);

					// Need to rewind our position and re-apply our moves: only do however if we were not previously dead
					
					if ( GetHealth() > 0 )
					{
						SetPosition(move_position);
						
						m_x_rot				= prediction.m_result_x_rot;
						m_y_rot				= prediction.m_result_y_rot;
						m_velocity_force	= move_velocity;

						// Need to reapply all moves after this move: older moves are first in the list

						std::list<PredictedMove>::iterator i = m_predicted_moves.begin();
						std::list<PredictedMove>::iterator e = m_predicted_moves.end();
						
						// Go through the list:

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

							PredictedMove & move = * i; i++;

							// Apply it: if after our current move:

							if ( move.m_move_id > move_id ) ApplyMove(move,GameFrameTimer::FRAME_INTERVAL);
						}
						
						// Set the id of the last received move:
						
						m_last_received_move_id = move_id;
					}
					else
					{
						// Snap to server position if we were previously dead
						
						SetPosition(move_position);
						
						wrong_pos			= GetPosition(); 
						m_client_pos_error	= Vec3f::Zero();
						m_velocity_force	= Vec3f::Zero();
						
						// If we are now re-alive then correct our orientation to that of the spawn point
						
						if ( health > 0 )
						{						
							// Correct our orientation to that of the spawn point
							
							CorrectSpawnOrientation();
						}
					}
					
					// Set our health and ammo directly:
					
					SetHealth(float(health));
					SetAmmo(ammo);
				}
				
				// Clear any previously predicted moves with this id or lower. We got our response.

				CleanPredictedMoveList(move_id);
				
				// Add to the current amount of prediction error:

				m_client_pos_error += wrong_pos - GetPosition();			
			}
			
			// Read the move ok:
			
			return true;
		}
		else
		{
			// Failed to read the move result:
			
			return false;
		}
	}
	else
	{
		// Failed reading
		
		return 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 GamePlayer::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);
				
				// Make a spawn teleport at our current position:
	
				GameTeleportCloud * cloud = new GameTeleportCloud(GetPosition());
				
				// Add to the game world
				
				GetManager()->AddObject(cloud);
				
				// Done with it:
				
				cloud->RemoveReference();
			}
			else
			{
				// Now dead:
				
				SetHealth(0);
				
				// Play the death sequence:
				
				m_sprite->SetSequence(1);
			}
		}
	}
	
	// Return whether the read was successful
	
	return success;
}

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

bool GamePlayer::NetReceiveServer( GamePacket & packet )
{
	// Make sure ok
	
	ENSURE( IsServer() , "This can only be called on the server" )

	// Read the number of moves that the client has sent us:
	
	unsigned num_moves = 0; 
	
	// Read:
	
	if ( packet.ReadPackedUint(num_moves) )
	{
		// Read the number of moves: continue reading all client moves
		
		while ( num_moves != 0 )
		{
			// Read the move id:

			unsigned move_id = 0; 
			
			// Do the reading
			
			if ( packet.Read(move_id) )
			{
				// Store if the read went ok:
				
				bool read_success = true;
				
				// Makeup a predicted move so we can apply it:
				
				PredictedMove move;

				// Initialise the move: we may not read some of these fields so they need to be set to defaults

				move.m_move_id			= move_id;
				move.m_x_rot			= 0;
				move.m_y_rot			= 0;
				move.m_x_mov			= 0;
				move.m_z_mov			= 0;
				move.m_result_position	= GetPosition();
				move.m_result_x_rot		= GetRotationX();
				move.m_result_y_rot		= GetRotationY();
				move.m_fire				= false;	
			
				// Read the control bits to see what was read
			
				unsigned cb = 0; read_success = read_success && packet.ReadPrecisionUint(cb,4);

				// See what was written:
				
				if ( ( cb & 0x01 ) != 0 ) read_success = read_success && packet.ReadNormalisedSignedFloat16( move.m_x_mov );
				if ( ( cb & 0x02 ) != 0 ) read_success = read_success && packet.ReadNormalisedSignedFloat16( move.m_z_mov );
				if ( ( cb & 0x04 ) != 0 ) read_success = read_success && packet.ReadAngle16( move.m_result_x_rot );
				if ( ( cb & 0x08 ) != 0 ) read_success = read_success && packet.ReadAngle16( move.m_result_y_rot );
				
				read_success = read_success && packet.ReadFlag(move.m_fire);
				
				// Only if this packet was read ok:
				
				if ( read_success )
				{
					// Only apply the move if it is a newer move:
					
					if ( move.m_move_id > m_last_received_move_id || m_last_received_move_id == 0xffffffff )
					{
						// Save the last received move id
						
						m_last_received_move_id = move.m_move_id;
						
						// Figure out how much turning was applied:
						
						move.m_x_rot = Angle::Difference( move.m_result_x_rot , GetRotationX() );
						move.m_y_rot = Angle::Difference( move.m_result_y_rot , GetRotationY() );
						
						// Apply the move:
						
						ApplyMove(move,GameFrameTimer::FRAME_INTERVAL);
						
						// Try to fire if the client requests:
						
						m_client_firing = m_client_firing || move.m_fire;
						
						// Server: need to send an update to the client
						
						m_server_received_new_move = true;
					}
				}
				else
				{
					// Failed to read the packet:
					
					return false;
				}
			}
			else
			{
				// Failed to read the packet:
				
				return false;
			}
		
			// One less move to read
			
			num_moves--;
		}
		
		// If we got to here then we read everything successfully: return true
		
		return true;
	}
	else
	{
		// Failed to read the packet: return false for failure
		
		return false;
	}
}

//=====================================================================================================================
/// @brief		Applys a move to the client or server: does not save the result of this move
/// @param		move - move to apply
/// @param		time_delta - amount of time in between frames
//=====================================================================================================================

void GamePlayer::ApplyMove( const PredictedMove & move , float time_delta )
{
	// Do not allow any moves from a dead player:
	
	if ( GetHealth() <= 0 ) return;

	// Calculate the final result of the turning:
	
	m_x_rot -= move.m_x_rot;
	m_y_rot -= move.m_y_rot;
	
	// Clamp y rotation
	
	if ( m_x_rot > +Float::TwoPI() * 0.25f ) m_x_rot = +Float::TwoPI() * 0.25f;
	if ( m_x_rot < -Float::TwoPI() * 0.25f ) m_x_rot = -Float::TwoPI() * 0.25f;

	// Calculate the final result of the movement:
	
	Vec3f pos = GetPosition();
	
	pos += GetFlatRight()	* time_delta *  MOVE_SPEED * move.m_x_mov;
	pos += GetFlatForward()	* time_delta *  MOVE_SPEED * move.m_z_mov; 
	
	// Apply gravity:
	
	m_velocity_force.y -= GRAVITY * time_delta; pos += m_velocity_force * time_delta;

	// Do collision detection:
	
	pos = GameMap::GetSingleton().GetLevel().GetCollision().Collide
	( 
		pos , 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(pos);
	
	// 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 ( pos.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;
		}
	}

	// Save
	
	SetPosition(pos);
}

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

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

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

	// Draw our character sprite:

	GameCharacter::OnDraw(c);

	// Only if client controlled object:

	if ( IsClient() && IsLocalObject() )
	{
		// Only if we are alive draw our rocket launcher:
		
		if ( GetHealth() > 0 )
		{
			// Set the position of our rocket sprite:

			Vec3f pos = c->GetPosition() - (c->GetViewMatrix().GetCol(2).ToVec3() * c->GetNearDist() * 1.1f );

			// Need to move up a little

			pos -= c->GetViewMatrix().GetCol(1).ToVec3() * 0.35f;

			// Draw our rocket:

			m_rocket_animation->SetPosition(pos);
			m_rocket_animation->Draw(c,GameCore::GetSingleton()->GetSpriteRenderer());
		}
	}
	
	//-----------------------------------------------------------------------------------------------------------------
	// Death fading on the client for remote objects and on the server:
	//-----------------------------------------------------------------------------------------------------------------
	
	if ( ( IsClient() && IsRemoteObject() ) || IsServer() )
	{
		// 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);
		}
	}
}

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

void GamePlayer::SetAmmo( unsigned amount )
{ 
	// Set

	m_ammo = amount;
	
	// Make sure not over the max
	
	if ( m_ammo > MAX_AMMO ) m_ammo = MAX_AMMO;
	
}

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

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