//////////////////////////////////////////////////////////////////////////////
//
// AVP_ServerEntity_PlayerInfo.cpp
//
// Copyright (c) 2007 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Entity/PlayerInfo/AVP_ServerEntity_PlayerInfo.h"

// Engine independent.
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Timers.h"
#ifdef ASURA_USE_DEBUG_MESSAGE_TRIGGER
#include "Entity/Triggers/Asura_ServerEntity_DebugWaypointTrigger.h"
#endif //#ifdef ASURA_USE_DEBUG_MESSAGE_TRIGGER
#ifdef ASURA_SUPPORT_LOBBY
#include "Network/Online/Asura_Lobby.h"
#endif
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Debugging/Asura_DebugLineRender.h"
#endif

// Project...
#include "Actor/AVP_Actor_ObjectiveHandler.h"
#include "Actor/AVP_Actor_PickupHandler.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Core/AVP_Game_Core.h"
#include "Core/AVP_Settings.h"
#include "Entity/AVP_Entity_Classification.h"
#include "Entity/AVP_Messages.h"
#include "Entity/CheckPointNode/AVP_ServerEntity_CheckPointNode.h"
#include "Entity/Controller/AVP_ServerEntity_Squad_Marine.h"
#include "Entity/GamePlayVolume/AVP_ServerEntity_GamePlayVolume.h"
#include "Entity/Locations/LookAt/AVP_ServerEntity_LookAt.h"
#include "Entity/ObjectiveSystem/PlayerObjective/AVP_ServerEntity_PlayerObjective.h"
#include "Entity/Locations/Objective/Marine/AVP_ServerEntity_Objective_Marine.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"
#include "Entity/PhysicalObject/Pickup/AVP_ServerEntity_Pickup.h"
#include "Entity/PlayerInfo/AVP_PlayerInfo_Messages.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AlienQueen/AVP_AlienQueen_Messages.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Predator/AVP_ServerActor_Predator.h"
#include "Multiplayer/AVP_Multiplayer_GameCore.h"
#include "Multiplayer/AVP_Multiplayer_KillCounter.h"
#include "Multiplayer/AVP_Multiplayer_ScoreBoard.h"
#include "Network/AVP_VoiceChannels.h"

// Tools
#ifdef ASURA_TOOLS
#include "Load&Save/Tools_SaveAsura.h"
#endif

// Debug variables...

// Constants
const int iAVP_PLAYERINFO_CHUNK_VERSION = 7;

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

u_int AVP_ServerEntity_PlayerInfo::s_uCentroidCalcFrame = 0;
Asura_Vector_3 AVP_ServerEntity_PlayerInfo::s_xCentroid = Asura_Maths::ZeroVector3;
Asura_Hashed_List<AVP_ServerEntity_PlayerInfo> AVP_ServerEntity_PlayerInfo::s_xList(4);

AVP_ServerEntity_PlayerInfo::AVP_ServerEntity_PlayerInfo(Asura_Guid uGuid)
: Asura_Entity_Server(uGuid)
, m_uPuppetGuid(ASURA_GUID_UNREGISTERED)
, m_uCheckpointGuid(ASURA_GUID_UNREGISTERED)
, m_uPlayerGamePlayVolumeGuid(ASURA_GUID_UNREGISTERED)
, m_uPersecutorGuid(ASURA_GUID_UNREGISTERED)
, m_iIntelligentAmount(INTELLIGENT_AMOUNT_MEDIUM)
, m_eMultiplayerTeam( AVP_Settings::AVP_TEAM_NONE )
, m_eMultiplayerRole( AVP_Settings::AVP_ROLE_NORMAL )
, m_xPlayerScore( uGuid )
, m_fRouteUpdateTimer( 0.0f )
, m_xAIPositionOnRoute( Asura_Maths::ZeroVector3 )
, m_xPlayerPositionOnRoute( Asura_Maths::ZeroVector3 )
, m_xCurrentDirectionToObjective( Asura_Maths::ZeroVector3 )
, m_fCurrentAIRouteDistance( 0.0f )
, m_bNavDataEstablished( false )
, m_uLastAIObjectiveGuid( ASURA_GUID_UNREGISTERED )
, m_bWeHaveArrivedAtAIObjective( false )
, m_bLookingAtTaget( false )
, m_uLookAtTargetGuid( ASURA_GUID_UNREGISTERED )
, m_uLookAtTargetMarker( ASURA_HASH_ID_UNSET )
, m_iMissionTimeHours( 0 )
, m_iMissionTimeMinutes( 0 )
, m_iMissionTimeSeconds( 0 )
, m_fMissionTimer( 0.0f )
{
	Classification = AVP_ENTITYCLASS_PLAYERINFO;

    s_xList.AddToList(this);
}

AVP_ServerEntity_PlayerInfo::~AVP_ServerEntity_PlayerInfo()
{
	// Remove all the kill counters from the list
	DestroyKillCounterList();

	s_xList.RemoveFromList(this);
}

bool AVP_ServerEntity_PlayerInfo::HandleMessage(const Asura_Message* pxMessage)
{
	if (!pxMessage) { return false; }

	// Is this something we handle?
	switch (pxMessage->GetMsgID())
	{
		case AVP_MSGID_ALIENQUEEN_ADD_TO_TARGET_LIST:
		{
			if( GetPuppetSpecies() != AVP_Settings::AVP_SPECIES_ALIEN )
			{
				// if the player is not an alien or a sentry gun (need seperate for sentry gun as it is internally classified as human so smart gun doesnt target it)
				if( GetClassification() != AVP_ENTITYCLASS_SENTRYGUN )
				{
					const AVP_Msg_Add_Entity_To_Queen_Target_List *pxData = static_cast<const AVP_Msg_Add_Entity_To_Queen_Target_List*>( pxMessage->GetData() );

					if( pxData->GetQueenGuid() != ASURA_GUID_UNREGISTERED )
					{
						Asura_Network::Forward( pxMessage, pxData->GetQueenGuid(), GetGuid()  );
					}
				}
			}

			return true;
		}
		case AVP_MSGID_ALIENQUEEN_REMOVE_FROM_TARGET_LIST:
		{
			if( GetPuppetSpecies() != AVP_Settings::AVP_SPECIES_ALIEN )
			{
				// if the player is not an alien or a sentry gun forward it to queen (need seperate for sentry gun as it is internally classified as human so smart gun doesnt target it)
				if( GetClassification() != AVP_ENTITYCLASS_SENTRYGUN )
				{
					const AVP_Msg_Remove_Entity_From_Queen_Target_List *pxData = static_cast<const AVP_Msg_Remove_Entity_From_Queen_Target_List*>( pxMessage->GetData() );

					if( pxData->GetQueenGuid() != ASURA_GUID_UNREGISTERED )
					{
						Asura_Network::Forward( pxMessage, pxData->GetQueenGuid(), GetGuid()  );
					}
				}
			}

			return true;
		}
		case ASURA_MSG_DESTRUCT:
		{
			// Special case here - we need to process it both here and in our puppet.
			// TODO - it'd be really cute if we can activate a bot to take over the puppet, if the server allows. However, it may be better just to spawn a new one so as not to confuse people as to who is in control.
			PARENT::HandleMessage(pxMessage);
			AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(m_uPuppetGuid);
			if (pxActor)
			{
				pxActor->HandleMessage(pxMessage);
			}
		}
		return true;

		case ASURA_MSG_CLIENT_REQUESTCREATEMSG:
		{
			MakeCreateMessage(pxMessage->GetFromGuid());
		}
		return true;

		case AVP_MSGID_PICKUP_SET_INTELLIGENT_AMOUNT:
		{
			const AVP_Message_Pickup_SetIntelligentAmount* const pxData   = static_cast< const AVP_Message_Pickup_SetIntelligentAmount* const>( pxMessage->GetData() );
			if(pxData)
			{
				m_iIntelligentAmount = pxData->m_iIntelligentAmount;
			}
			return true;
		}

		case AVP_MSGID_ADD_PLAYER_OBJECTIVE:
		{
			// Grab the GUID of the objective and do a containment test on Update method
			// Send Objective GUID to client playerInfo so it draw the objective on the HUD
			const AVP_Message_AddPlayerObjective* pxData = static_cast<const AVP_Message_AddPlayerObjective*>(pxMessage->GetData());
			if (pxData)
			{
				AVP_ServerEntity_PlayerObjective* pxObjective = AVP_ServerEntity_PlayerObjective::FindEntity(pxData->m_uObjectiveGuid);
				if (pxObjective)
				{
					// Test if objective was already added; 
					if (m_xQuest.IsObjectiveInList(pxObjective->GetGuid()))
					{
						//Asura_Assert(false, "Objective already added to list!!!");
						return true;
					}
					m_xQuest.AddToObjectivesList(pxObjective->GetGuid());

					// Set this objective to be tracked on the objective handler
					// Will change after there is more info about what objectives are
					AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(m_uPuppetGuid);
					if (pxActor && pxActor->GetObjectiveHandler())
					{
						pxActor->GetObjectiveHandler()->SetObjectiveToTrack( pxObjective->GetGuid() );
					}			
				}
				else
				{
					Asura_Assert(false, "Where is the Objective!!!");
					return true;
				}
			}
			// Forward the message to the client who will display the objective text
			Asura_Network::Forward( pxMessage, ASURA_GUID_MY_CLIENT_ENTITIES, GetGuid() );
			return true;
		}

		case AVP_MSGID_SET_PLAYER_OBJECTIVE_COMPLETE:
		{
			const AVP_Message_PlayerObjectiveComplete* pxData = static_cast<const AVP_Message_PlayerObjectiveComplete*>(pxMessage->GetData());
			if (pxData)
			{
				// Set objective as completed
				AVP_ServerEntity_PlayerObjective* pxObjective = AVP_ServerEntity_PlayerObjective::FindEntity(pxData->m_uObjectiveGuid);
				if(pxObjective)
				{
					//Make sure the objective is of course already added, if not it is a design error!
					if(m_xQuest.IsObjectiveInList(pxObjective->GetGuid()))
					{
					    if ( pxObjective->IsCompleted())
						{
							//Asura_Assertf( false, ("Objective %d received completed message more than once!", pxObjective->GetGuid()) );
							return true;
						}
						pxObjective->SetAsCompleted(true);

						// Add to completed list
						m_xQuest.AddToCompletedObjectivesList( pxObjective->GetGuid() );

						// Set latest objective to track on the objective handler
						// Will change after there is more info about what objectives are
						AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(m_uPuppetGuid);
						if (pxActor && pxActor->GetObjectiveHandler())
						{
							pxActor->GetObjectiveHandler()->SetObjectiveToTrack( m_xQuest.GetLatestNonCompletedObjective() );
						}
						// Forward the message to the client who will hide the objective text
						Asura_Network::Forward( pxMessage, ASURA_GUID_MY_CLIENT_ENTITIES, GetGuid() );
					}
					else
					{
						//Asura_Assert(false, "Objective completed even if not in the list of objectives!");
						return true;
					}
				}
				else
				{
					Asura_Assert(false, "Where is the Objective!!!");
					return true;
				}
			}
			return true;
		}

		case AVP_MSGID_FUNCTIONALITY_MOTION_TRACKER_ON:
		{
			AVP_Settings::SetMotionTrackerFunc(true);
			return true;
		}

		case AVP_MSGID_FUNCTIONALITY_MOTION_TRACKER_OFF:
		{
			AVP_Settings::SetMotionTrackerFunc(false);
			return true;
		}

		case AVP_MSGID_FUNCTIONALITY_VISION_MODES_ON:
		{
			AVP_Settings::SetVisionModesFunc(true);
			return true;
		}

		case AVP_MSGID_FUNCTIONALITY_VISION_MODES_OFF:
		{
			AVP_Settings::SetVisionModesFunc(false);
			return true;
		}

		case AVP_MSGID_FUNCTIONALITY_TORCH_ON:
		{
			AVP_Settings::SetTorchFunc(true);
			return true;
		}

		case AVP_MSGID_FUNCTIONALITY_TORCH_OFF:
		{
			AVP_Settings::SetTorchFunc(false);
			return true;
		}

		case AVP_MSGID_REPLENISH_PREDATOR_ENERGY:
		{
			AVP_ServerActor_Predator* pxPredator = AVP_ServerActor_Predator::FindEntity(m_uPuppetGuid);
			if (pxPredator)
			{
				pxPredator->HandleMessage(pxMessage);
			}
			return true;
		}
		
		case AVP_MSGID_LOOK_AT_ENTITY:
		{
			const AVP_Message_LookAtEntity* pxData = static_cast<const AVP_Message_LookAtEntity*>(pxMessage->GetData());
			if (pxData)
			{
				m_bLookingAtTaget = true;
				m_uLookAtTargetGuid = pxData->GetLookAtGuid();
				m_uLookAtTargetMarker = pxData->GetLookAtMarker();
			}
			return true;
		}

		case AVP_MSGID_STOP_LOOKING_AT_ENTITY:
		{
			m_bLookingAtTaget = false;
			return true;
		}

		case AVP_MSGID_REQUEST_CLOAK_ON:
		{
			AVP_ServerActor_Predator* pxPredator = AVP_ServerActor_Predator::FindEntity(m_uPuppetGuid);
			if (pxPredator)
			{
				pxPredator->HandleMessage(pxMessage);
			}
			return true;
		}

		case AVP_MSGID_REQUEST_CLOAK_OFF:
		{
			AVP_ServerActor_Predator* pxPredator = AVP_ServerActor_Predator::FindEntity(m_uPuppetGuid);
			if (pxPredator)
			{
				pxPredator->HandleMessage(pxMessage);
			}
			return true;
		}

		case AVP_MSGID_ADD_PLAYER_PICKUP:
		{
			const AVP_Message_AddPlayerPickup* pxData = static_cast<const AVP_Message_AddPlayerPickup*>(pxMessage->GetData());
			if (pxData)
			{
				// Set objective as completed
				AVP_ServerEntity_Pickup* pxPickup = AVP_ServerEntity_Pickup::FindEntity(pxData->m_uPickupGuid);
				if(pxPickup)
				{
					AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(m_uPuppetGuid);
					if ( pxActor && pxActor->GetPickupHandler())
					{
						pxActor->GetPickupHandler()->OnPickup(pxActor, pxPickup, pxData->m_bSetAsCurrent);
					}
				}
			}
			return true;
		}

		default:
			break;
	}

	// See if our actor wants to handle the message.
	// TODO - is it better to be selective about which message IDs the actor can handle?
	AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(m_uPuppetGuid);
	if (pxActor && pxActor->HandleMessage(pxMessage))
	{
		return true;
	}

	// Finally, check our parent.
	return PARENT::HandleMessage(pxMessage);
}

AVP_Settings::Species AVP_ServerEntity_PlayerInfo::GetPuppetSpecies() const
{
	AVP_Settings::Species eSpecies = AVP_Settings::AVP_SPECIES_UNKNOWN; 
	const Asura_Guid uPuppetGuid = GetPuppetGuid(); 
	if ( uPuppetGuid != ASURA_GUID_UNREGISTERED )
	{
		AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(uPuppetGuid);
		if (pxActor)
		{
			// Get the player species from the puppet classification
			eSpecies = pxActor->GetSpeciesByClassification();  
		}
	}
	else
	{
		Asura_Assert(false, "Requesting puppet species thorugh the player info but the puppet itself hasn't been set"); 
	}
	return eSpecies; 
}

void AVP_ServerEntity_PlayerInfo::SetPuppetGuid(Asura_Guid uGuid)
{
	if (m_uPuppetGuid == uGuid) { return; }

	// We no longer attempt to return an actor we're leaving to an AI, as it was breaking in many entertaining ways and we don't actually have game mechanics that need it anyway.
	// We'll assume that if we're leaving an actor, it no longer has much need to do anything anyway.
	if (m_uPuppetGuid != ASURA_GUID_UNREGISTERED)
	{
		// Clean up what elements we can from the actor.
		AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(m_uPuppetGuid);
		if (pxActor)
		{
			Asura_Assert(pxActor->GetPlayerInfoGuid() == Guid, "Attached actor mismatch. Message ordering problem?");
			pxActor->DisconnectPlayer();
		}
	}

	m_uPuppetGuid = uGuid;

	if (m_uPuppetGuid != ASURA_GUID_UNREGISTERED)
	{
		// Set the new actor as a player.
		// TODO - need a bot version too?
		AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(m_uPuppetGuid);
		if (pxActor)
		{
			pxActor->MakePlayer(GetGuid());
		}
		else
		{
			Asura_Assert(false, "Setting attached actor to non-existent entity, should never happen on the server");
		}
	}

	// Tell the clients and the player of the change
	AVP_Message_SetPuppetGuid xMsgData;
	xMsgData.SetPuppetGuid(uGuid);
	Asura_Message* pxMessage = Asura_Network::CreateMessage(AVP_MSGID_SETPUPPETGUID, sizeof(AVP_Message_SetPuppetGuid), Classification, &xMsgData);
	if (pxMessage)
	{
		pxMessage->UseGuaranteedDelivery();
		// We need to send this message to two things, so don't delete it first time.
		pxMessage->SetFromGuid(Guid);
		pxMessage->SetToGuid(ASURA_GUID_MY_CLIENT_ENTITIES);

		Asura_Network::Send(pxMessage, false);

		pxMessage->SetToGuid(ASURA_GUID_PLAYER);

		Asura_Network::Send(pxMessage);
	}

#ifdef ASURA_USE_DEBUG_MESSAGE_TRIGGER
	// Let the debug waypoint triggers know about this, if we're player 1.
	if (Guid == ASURA_GUID_CLIENT_START)
	{
		Asura_ServerEntity_DebugWaypointTrigger::SetTriggeringGuid(m_uPuppetGuid);
	}
#endif //#ifdef ASURA_USE_DEBUG_MESSAGE_TRIGGER
}

void AVP_ServerEntity_PlayerInfo::SetMultiplayerTeam(AVP_Settings::Team eMultiplayerTeam)
{
	if (m_eMultiplayerTeam == eMultiplayerTeam) return; 

	// Change this player team
	m_eMultiplayerTeam = eMultiplayerTeam; 

#ifdef ASURA_SUPPORT_LOBBY
	// update lobby team record.
	if (Asura_Lobby_System::IsInitialised())
	{
		u_char ucTeam = eMultiplayerTeam == AVP_Settings::AVP_TEAM_NONE ? ASURA_LOBBY_INVALID_TEAM : static_cast<u_char>(eMultiplayerTeam - 1);
		Asura_Assert(ucTeam <= ASURA_LOBBY_MAX_TEAMS, "invalid team");
		Asura_Lobby_System::SetPlayerTeamByGuid(GetGuid(), ucTeam);
		
		// Only allow players to connect to their team voice channel in multiplayer games
		if (ucTeam != ASURA_LOBBY_INVALID_TEAM)
		{
			switch (ucTeam)
			{
				case 0: // TEAM 1
				{
					Asura_Lobby_System::SetPlayerVoiceChannelsByGuid(GetGuid(), uAVP_VOICE_CHANNELS_SETUP_TEAM1ONLY);
					break;
				}
				case 1: // TEAM 2
				{
					Asura_Lobby_System::SetPlayerVoiceChannelsByGuid(GetGuid(), uAVP_VOICE_CHANNELS_SETUP_TEAM2ONLY);
					break;
				}
				case 2: // TEAM 3
				{
					Asura_Lobby_System::SetPlayerVoiceChannelsByGuid(GetGuid(), uAVP_VOICE_CHANNELS_SETUP_TEAM3ONLY);
					break;
				}
				default:
				{
					Asura_Assert( false, "Invalid team index" ); 
					break;
				}
			}
		}
		else
		{
			Asura_Lobby_System::SetPlayerVoiceChannelsByGuid(GetGuid(), uAVP_VOICE_CHANNELS_SETUP_NONE);
		}
	}
#endif
}

void AVP_ServerEntity_PlayerInfo::MakeClientUpdateMessage()
{
	AVP_Message_PlayerInfo_ClientUpdate xMsgData;
	FillUpdateData(xMsgData);

	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_CLIENTUPDATE, sizeof(AVP_Message_PlayerInfo_ClientUpdate), Classification, &xMsgData);
	if (pxMessage)
	{
		Send(pxMessage, ASURA_GUID_MY_CLIENT_ENTITIES);
	}
}

void AVP_ServerEntity_PlayerInfo::InitialiseMissionTime( AVP_Settings::Species eSpecies )
{
	// Read in level blueprint to load mission time from
	AVP_Game_Core::CharacterType eCharType;
	if ( AVP_Settings::IsMultiPlayer() )
	{
		switch( eSpecies )
		{
			case AVP_Settings::AVP_SPECIES_ALIEN:		eCharType = AVP_Game_Core::MULTIPLAYER_ALIEN; break;
			default:	Asura_Assert( false, "Unable to determine species" ); // fall through to human
			case AVP_Settings::AVP_SPECIES_HUMAN:		eCharType = AVP_Game_Core::MULTIPLAYER_MARINE; break;
			case AVP_Settings::AVP_SPECIES_PREDATOR:	eCharType = AVP_Game_Core::MULTIPLAYER_PREDATOR; break;
		}
	}
	else
	{
		switch( eSpecies )
		{
			case AVP_Settings::AVP_SPECIES_ALIEN:		eCharType = AVP_Game_Core::SINGLE_PLAYER_ALIEN; break;
			default:	Asura_Assert( false, "Unable to determine species" ); // fall through to human
			case AVP_Settings::AVP_SPECIES_HUMAN:		eCharType = AVP_Game_Core::SINGLE_PLAYER_MARINE; break;
			case AVP_Settings::AVP_SPECIES_PREDATOR:	eCharType = AVP_Game_Core::SINGLE_PLAYER_PREDATOR; break;
		}
	}

	const char* szLevelName = 
#ifdef ASURA_TOOLS
			Tools_SaveAsura::IsEditingMission() ? Tools_SaveAsura::GetMissionFilenameWithoutExtension() : Tools_SaveAsura::GetMainFilename();
#else
			AVP_Settings::GetLevelName();
#endif

	const Asura_Blueprint* pxLevelBlueprint = AVP_Game_Core::GetLevelBlueprint( szLevelName, eCharType );
	if( pxLevelBlueprint )
	{
		m_iMissionTimeHours = pxLevelBlueprint->GetParameterAsInt( uAVP_BLUEPRINT_PARAM_MISSIONTIME_HOURS, m_iMissionTimeHours );
		m_iMissionTimeMinutes = pxLevelBlueprint->GetParameterAsInt( uAVP_BLUEPRINT_PARAM_MISSIONTIME_MINUTES, m_iMissionTimeMinutes );
		m_iMissionTimeSeconds = pxLevelBlueprint->GetParameterAsInt( uAVP_BLUEPRINT_PARAM_MISSIONTIME_SECONDS, m_iMissionTimeSeconds );
	}
	else
	{
		Asura_Assert( false, "Unable to retrieve level blueprint for mission time." );
	}
}

void AVP_ServerEntity_PlayerInfo::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	int iVersion = iAVP_PLAYERINFO_CHUNK_VERSION;
	xStream << iVersion;

	PARENT::WriteToChunkStream(xStream);

	xStream << m_uPuppetGuid;
	xStream << m_uCheckpointGuid;

	m_xQuest.WriteToChunkStream( xStream );

	xStream << AVP_Settings::GetFunctionalityFlags();

	xStream << m_xAudioDiariesList.Size();

	Asura_LinkList_ConstIt< AVP_Player_AudioDiary > xLinkListIter( m_xAudioDiariesList );

	while ( !xLinkListIter.Done() )
	{
		xStream << (xLinkListIter.GetCurrent())->GetDialogBlueprintHashID();
		xStream << (xLinkListIter.GetCurrent())->GetRead();
		xLinkListIter.Next();
	}

	xStream << m_iMissionTimeHours;
	xStream << m_iMissionTimeMinutes;
	xStream << m_iMissionTimeSeconds;
}

void AVP_ServerEntity_PlayerInfo::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion;
	xStream >> iVersion;
	Asura_Assert(iVersion <= iAVP_PLAYERINFO_CHUNK_VERSION, "Invalid player info chunk version.");

	if ( iVersion == iAVP_PLAYERINFO_CHUNK_VERSION )
	{
		PARENT::ReadFromChunkStream(xStream);

		xStream >> m_uPuppetGuid;
		xStream >> m_uCheckpointGuid;

		m_xQuest.ReadFromChunkStream( xStream );

		u_int uFuncFlags;
		xStream >> uFuncFlags, AVP_Settings::SetFunctionalityFlags(uFuncFlags);

		u_int uNumDiaries = 0;
		xStream >> uNumDiaries;

		AVP_Player_AudioDiary xAudioDiary;
		for ( u_int u = 0; u < uNumDiaries; ++u )
		{
			Asura_Hash_ID uBlueprintHash;
			xStream >> uBlueprintHash;
			bool       bRead;
			xStream >> bRead;
			xAudioDiary.SetDialogBlueprintHashID( uBlueprintHash );
			xAudioDiary.SetRead( bRead );
			m_xAudioDiariesList.Push_Back( xAudioDiary );
		}

		xStream >> m_iMissionTimeHours;
		xStream >> m_iMissionTimeMinutes;
		xStream >> m_iMissionTimeSeconds;
	}
	else if ( iVersion == 6 )
	{
		PARENT::ReadFromChunkStream(xStream);

		xStream >> m_uPuppetGuid;
		xStream >> m_uCheckpointGuid;

		m_xQuest.ReadFromChunkStream( xStream );

		u_int uFuncFlags;
		xStream >> uFuncFlags, AVP_Settings::SetFunctionalityFlags(uFuncFlags);

		u_int uNumDiaries = 0;
		xStream >> uNumDiaries;

		AVP_Player_AudioDiary xAudioDiary;
		for ( u_int u = 0; u < uNumDiaries; ++u )
		{
			Asura_Hash_ID uBlueprintHash;
			xStream >> uBlueprintHash;
			bool       bRead;
			xStream >> bRead;
			xAudioDiary.SetDialogBlueprintHashID( uBlueprintHash );
			xAudioDiary.SetRead( bRead );
			m_xAudioDiariesList.Push_Back( xAudioDiary );
		}
	}
	else if ( iVersion == 5 )
	{
		PARENT::ReadFromChunkStream(xStream);

		xStream >> m_uPuppetGuid;
		xStream >> m_uCheckpointGuid;

		m_xQuest.ReadFromChunkStream( xStream );

		u_int uFuncFlags;
		xStream >> uFuncFlags, AVP_Settings::SetFunctionalityFlags(uFuncFlags);
	}
	else if ( iVersion == 4 )
	{
		PARENT::ReadFromChunkStream(xStream);

		xStream >> m_uPuppetGuid;
		xStream >> m_uCheckpointGuid;

		m_xQuest.ReadFromChunkStream( xStream );
	}
	else if (iVersion == 3)
	{
		PARENT::ReadFromChunkStream(xStream);

		xStream >> m_uPuppetGuid;
		xStream >> m_uCheckpointGuid;

		u_int uPrimaryObjectivesCount;
		xStream >> uPrimaryObjectivesCount;

		Asura_Guid uGuid;
		for(u_int i = 0; i < uPrimaryObjectivesCount; ++i )
		{
			xStream >> uGuid;
            m_xQuest.SetObjective(i, uGuid);
		}

		u_int uSecondaryObjectivesCount;
		xStream >> uSecondaryObjectivesCount;

		m_xQuest.SetObjectivesCount(uSecondaryObjectivesCount + uPrimaryObjectivesCount);

		for(u_int i = uPrimaryObjectivesCount; i < uPrimaryObjectivesCount + uSecondaryObjectivesCount; ++i )
		{
			xStream >> uGuid;
			m_xQuest.SetObjective(i, uGuid);
		}
	}
	else if (iVersion == 2)
	{
		PARENT::ReadFromChunkStream(xStream);

		xStream >> m_uPuppetGuid;

		u_int uPrimaryObjectivesCount;
		xStream >> uPrimaryObjectivesCount;

		Asura_Guid uGuid;
		for(u_int i = 0; i < uPrimaryObjectivesCount; ++i )
		{
			xStream >> uGuid;
            m_xQuest.SetObjective(i, uGuid);
		}

		u_int uSecondaryObjectivesCount;
		xStream >> uSecondaryObjectivesCount;

		m_xQuest.SetObjectivesCount(uSecondaryObjectivesCount + uPrimaryObjectivesCount);

		for(u_int i = uPrimaryObjectivesCount; i < uPrimaryObjectivesCount + uSecondaryObjectivesCount; ++i )
		{
			xStream >> uGuid;
			m_xQuest.SetObjective(i, uGuid);
		}
	}
	else if (iVersion == 1)
	{
		PARENT::ReadFromChunkStream(xStream);

		xStream >> m_uPuppetGuid;

		int iDummy;  // Was objective Guid
		xStream >> iDummy;
	}
	else if (iVersion == 0)
	{
		PARENT::ReadFromChunkStream(xStream);

		xStream >> m_uPuppetGuid;
	}
}

void AVP_ServerEntity_PlayerInfo::PostLoadInitialise()
{
	PARENT::PostLoadInitialise();

	// Force an update of puppet guids
	Asura_Guid uGuid = m_uPuppetGuid;
	m_uPuppetGuid = ASURA_GUID_UNREGISTERED;
	SetPuppetGuid(uGuid);

	if (m_uCheckpointGuid != ASURA_GUID_UNREGISTERED)
	{
		AVP_ServerEntity_Actor* pxPuppet = AVP_ServerEntity_Actor::FindEntity( m_uPuppetGuid );
		const AVP_ServerEntity_CheckPointNode* pxCheckpoint = AVP_ServerEntity_CheckPointNode::FindEntity(m_uCheckpointGuid);
		if (pxPuppet && pxCheckpoint)
		{
			Asura_Matrix_3x3 xOri;
			pxCheckpoint->GetOrientation().ToMatrix(xOri);
			pxPuppet->TeleportTo(pxCheckpoint->GetPosition(), xOri, false);
			m_uCheckpointGuid = ASURA_GUID_UNREGISTERED;
		}
	}

	// Set latest objective to track on the objective handler
    Asura_Guid uObjectiveGuid = m_xQuest.GetLatestNonCompletedObjective();
	if ( uObjectiveGuid != ASURA_GUID_UNREGISTERED )
	{
		AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(m_uPuppetGuid);
		if (pxActor && pxActor->GetObjectiveHandler())
		{
			pxActor->GetObjectiveHandler()->SetObjectiveToTrack( uObjectiveGuid );
		}
	}
}

void AVP_ServerEntity_PlayerInfo::Update()
{
	const AVP_ServerEntity_Actor* pxPuppet = AVP_ServerEntity_Actor::FindEntity( m_uPuppetGuid );
	if ( pxPuppet )
	{
		if ( m_bNavDataEstablished && pxPuppet->GetClassification() == AVP_ENTITYCLASS_MARINE )
		{
			m_bNavDataEstablished = true;
			EstablishNavData();
		}

		if ( pxPuppet->IsRagdoll() )
		{
			// Leave the character on ragdolling.
			SetPuppetGuid( ASURA_GUID_UNREGISTERED );
		}
		else
		{
			// Update the player's gameplay volume guid.
			const AVP_ServerEntity_GamePlayVolume* pxGamePlayVolume =
				AVP_ServerEntity_GamePlayVolume::GetVolumeThatContainsPosition( pxPuppet->GetPosition(), m_uPlayerGamePlayVolumeGuid );
			m_uPlayerGamePlayVolumeGuid = pxGamePlayVolume ? pxGamePlayVolume->GetGuid() : ASURA_GUID_UNREGISTERED;

			// marine only!
			if ( pxPuppet->GetClassification() == AVP_ENTITYCLASS_MARINE )
			{
				if ( !m_bNavDataEstablished )
				{
					m_bNavDataEstablished = true;
					EstablishNavData();
				}

				// Update the players route
				UpdateAIObjectiveRoute();
				
				// JG - 03 February 2009 - Removed as it was part of the old player objective system we are not using anymore
				//UpdatePlayerObjectiveRoute();
			}
		}
	}

	UpdateMissionTime();
}

void AVP_ServerEntity_PlayerInfo::EstablishNavData()
{
	AVP_ServerEntity_Actor*				pxPlayerActor		= AVP_ServerEntity_Actor::FindEntity( m_uPuppetGuid );

	if ( pxPlayerActor )
	{
		// Set the human path finding flags
		m_xAIObjectiveNavData.m_uPathfinderFlags			= AVP_Navigation::AVP_AI_PATHFINDER_FLAG_HUMANOID;
		m_xAIObjectiveNavData.m_eMovementMode				= AVP_Navigation::MM_MEDIUM;
		m_xPlayerObjectiveNavData.m_uPathfinderFlags		= m_xAIObjectiveNavData.m_uPathfinderFlags;
		m_xPlayerObjectiveNavData.m_eMovementMode			= m_xAIObjectiveNavData.m_eMovementMode;

		// Establish our current waypoint ID.
		AVP_Navigation::TestParams xTestParams;
		xTestParams.m_uPathfinderFlags		= m_xAIObjectiveNavData.m_uPathfinderFlags;
		xTestParams.m_bRejectNonalignedWPs	= true;
		xTestParams.m_xNormal				= -pxPlayerActor->GetCurrentGravityDirection();

		const bool bGotWaypointID = AVP_Navigation::GetNearestWaypointID(pxPlayerActor->GetPosition(), m_xAIObjectiveNavData.m_usCurrentWPID, xTestParams);
		m_xPlayerObjectiveNavData.m_usCurrentWPID = m_xAIObjectiveNavData.m_usCurrentWPID;
		
		if ( bGotWaypointID )
		{
			// Establish our current zone.
			m_xAIObjectiveNavData.m_usCurrentZone		= Asura_AI_Navigation_System::GetWaypoint(m_xAIObjectiveNavData.m_usCurrentWPID)->m_usZone;
			m_xPlayerObjectiveNavData.m_usCurrentZone	= m_xAIObjectiveNavData.m_usCurrentZone;
		}

		m_xAIObjectiveNavData.m_fCharacterHeight		= 2.0f;
		m_xAIObjectiveNavData.m_fCharacterWidth			= 1.0f;
		m_xPlayerObjectiveNavData.m_fCharacterHeight	= m_xAIObjectiveNavData.m_fCharacterHeight;
		m_xPlayerObjectiveNavData.m_fCharacterWidth		= m_xAIObjectiveNavData.m_fCharacterWidth;
	}
}

void AVP_ServerEntity_PlayerInfo::UpdatePlayerObjectiveRoute()
{
	// AJK 16-03-2009; this function is no longer used for the objective system

//	// Find the players squad;
//	Asura_Guid uObjectiveGuid		= m_xQuest.GetCurrentPrimaryObjective();
//
//	if ( uObjectiveGuid != ASURA_GUID_UNREGISTERED )
//	{
//		AVP_ServerEntity_PlayerObjective*	pxObjective			= AVP_ServerEntity_PlayerObjective::FindEntity( uObjectiveGuid );
//		AVP_ServerEntity_Actor*				pxPlayerActor		= AVP_ServerEntity_Actor::FindEntity( m_uPuppetGuid );
//
//		if ( pxObjective && pxPlayerActor )
//		{
//			const Asura_Vector_3& xPlayerPos = pxPlayerActor->GetPosition();
//			AVP_Navigation::TestParams	xTestParams;
//			bool bUpdateRoute = false;
//
//			// Fill the test against parameters with the route finding needed information
//			xTestParams.m_uPathfinderFlags		= m_xPlayerObjectiveNavData.m_uPathfinderFlags;
//			xTestParams.m_usZone				= m_xPlayerObjectiveNavData.m_usCurrentZone;
//			xTestParams.m_xNormal				= -pxPlayerActor->GetCurrentGravityDirection();
//
//			AVP_Navigation::UpdateCurrentWaypointID( xPlayerPos, m_xPlayerObjectiveNavData.m_usCurrentWPID, xTestParams);
//
//			if ( m_xPlayerObjectiveNavData.m_usCurrentWPID != ASURA_INVALID_WAYPOINT_ID )
//			{
//				m_xAIObjectiveNavData.m_usCurrentZone = Asura_AI_Navigation_System::GetWaypoint(m_xAIObjectiveNavData.m_usCurrentWPID)->m_usZone;
//
//				if ( m_xPlayerObjectiveNavData.m_pusRoute )
//				{
//					m_xPlayerObjectiveNavData.m_usRouteSegmentIndex = 0;
//
//					AVP_Navigation::FindNearestPointOnRoute(m_xPlayerObjectiveNavData, xPlayerPos, 5.0f, m_xPlayerPositionOnRoute, m_xPlayerObjectiveNavData.m_usRouteSegmentIndex );
//
//					Asura_Vector_3 xTempDir( m_xPlayerPositionOnRoute - xPlayerPos );
//					float fDistanceToRoute = xTempDir.Normalise();
//					m_xCurrentDirectionToObjective.Lerp( xTempDir, 0.5f );
//
//					if ( fDistanceToRoute > 5.0f )
//					{
//						bUpdateRoute = true;
//					}
//
//					float fDistanceToObjectiveSq = ( xPlayerPos - pxObjective->GetPosition() ).MagnitudeSquared();
//
//					if ( fDistanceToObjectiveSq < 25.0f )
//					{
//						m_xPlayerObjectiveNavData.ResetRoute();
//						m_xCurrentDirectionToObjective = Asura_Maths::ZeroVector3;
//						bUpdateRoute = false;
//					}
//				}
//				else
//				{
//					float fDistanceToObjectiveSq = ( xPlayerPos - pxObjective->GetPosition() ).MagnitudeSquared();
//
//					if ( fDistanceToObjectiveSq > 25.0f )
//					{
//						bUpdateRoute = true;
//					}
//					else
//					{
//						bUpdateRoute = false;
//					}
//				}
//
//				if ( bUpdateRoute )
//				{
//					bool bSuccess = false;
//
//					// Fill The navigation data with our desired route information
//					m_xPlayerObjectiveNavData.m_xStartPosition			= xPlayerPos;
//					m_xPlayerObjectiveNavData.m_xDestinationPosition	= pxObjective->GetPosition();
//
//					AVP_Navigation::ResolveToValidPosition( m_xPlayerObjectiveNavData.m_xDestinationPosition, xTestParams );
//
//					// Find the waypoint id closest to the destination position
//					if ( AVP_Navigation::GetNearestWaypointID( m_xPlayerObjectiveNavData.m_xDestinationPosition, m_xPlayerObjectiveNavData.m_usDestinationWPID, xTestParams ) && 
//						Asura_AI_Navigation_System::IsPositionInWaypoint( m_xPlayerObjectiveNavData.m_xDestinationPosition, m_xPlayerObjectiveNavData.m_usDestinationWPID ) )
//					{
//						// Clear out the old route and find the new one.
//						m_xPlayerObjectiveNavData.ResetRoute();
//						bSuccess = AVP_Navigation::FindRoute( m_xPlayerObjectiveNavData );
//					}
//				}
//			}
//		}
//		else
//		{
//			m_xCurrentDirectionToObjective = Asura_Maths::ZeroVector3;
//		}
//	}
//#ifndef ASURA_NO_DEBUG_OUTPUT
//// Debugging
//// 	if ( m_xCurrentDirectionToObjective != Asura_Maths::ZeroVector3 )
//// 	{
//// 		AVP_ServerEntity_Actor*				pxPlayerActor		= AVP_ServerEntity_Actor::FindEntity( m_uPuppetGuid );
//// 
//// 		if ( pxPlayerActor )
//// 		{
//// 			Asura_Vector_3 xPos( pxPlayerActor->GetPosition() );
//// 			const Asura_Matrix_3x3& xOri	= pxPlayerActor->GetOrientation();
//// 			const Asura_Vector_3 xFwd( xOri.e13, xOri.e23, xOri.e33 );
//// 			xPos.y -= 1.5f;
//// 			xPos += xFwd * 2.0f;
//// 			Asura_DebugLineRender::AddArrow( xPos, xPos + ( m_xCurrentDirectionToObjective * 2.0f ) );
//// 		}
//// 	}
//#endif
}

void AVP_ServerEntity_PlayerInfo::UpdateAIObjectiveRoute()
{
	// Find the players squad;
	AVP_ServerEntity_Squad_Marine* pxPlayerSquad = AVP_ServerEntity_Squad_Marine::GetPlayersSquad();

	if ( pxPlayerSquad && pxPlayerSquad->GetCurrentObjective() )
	{
		Asura_Guid							uObjectiveGuid		= pxPlayerSquad->GetCurrentObjectiveGuid();
		AVP_ServerNode_Objective_Marine*	pxObjective			= AVP_ServerNode_Objective_Marine::FindEntity( uObjectiveGuid );
		AVP_ServerEntity_Actor*				pxPlayerActor		= AVP_ServerEntity_Actor::FindEntity( m_uPuppetGuid );

		if ( pxObjective && pxPlayerActor )
		{
			const Asura_Vector_3& xPlayerPos = pxPlayerActor->GetPosition();

			AVP_Navigation::TestParams	xTestParams;
			bool bUpdateRoute = false;

			// Fill the test against parameters with the route finding needed information
			xTestParams.m_uPathfinderFlags		= m_xAIObjectiveNavData.m_uPathfinderFlags;
			xTestParams.m_usZone				= m_xAIObjectiveNavData.m_usCurrentZone;
			xTestParams.m_xNormal				= -pxPlayerActor->GetCurrentGravityDirection();

			AVP_Navigation::UpdateCurrentWaypointID(xPlayerPos, m_xAIObjectiveNavData.m_usCurrentWPID, xTestParams);

			if ( m_xPlayerObjectiveNavData.m_usCurrentWPID != ASURA_INVALID_WAYPOINT_ID )
			{
				if ( m_uLastAIObjectiveGuid == pxObjective->GetGuid() && m_bWeHaveArrivedAtAIObjective == false )
				{
					if ( pxObjective->Contains(pxPlayerActor->GetPosition() ) )
					{
						m_bWeHaveArrivedAtAIObjective = true;
					}
				}

				if ( m_xAIObjectiveNavData.m_pusRoute && m_xAIObjectiveNavData.m_usDestinationWPID != ASURA_INVALID_WAYPOINT_ID )
				{
					m_xAIObjectiveNavData.m_usRouteSegmentIndex = 0;

					AVP_Navigation::FindNearestPointOnRoute(m_xAIObjectiveNavData, xPlayerPos, 0.5f, m_xAIPositionOnRoute, m_xAIObjectiveNavData.m_usRouteSegmentIndex );
					m_fCurrentAIRouteDistance = AVP_Navigation::CalculateRouteDistance(m_xAIObjectiveNavData, m_xAIPositionOnRoute, m_xAIObjectiveNavData.m_usRouteSegmentIndex + 1 );
					float fDistanceToRouteSq = ( m_xAIPositionOnRoute - xPlayerPos ).MagnitudeSquared();;

					if ( fDistanceToRouteSq > 25.0f )
					{
						bUpdateRoute = true;
					}
				}
				else
				{
					bUpdateRoute = true;
				}

				if ( bUpdateRoute )
				{
					// Fill The navigation data with our desired route information
					m_xAIObjectiveNavData.m_xStartPosition			= xPlayerPos;
					m_xAIObjectiveNavData.m_xDestinationPosition	= pxObjective->GetPosition();

					AVP_Navigation::ResolveToValidPosition( m_xAIObjectiveNavData.m_xDestinationPosition, xTestParams );

					// Find the waypoint id closest to the destination position
					if ( AVP_Navigation::GetNearestWaypointID( m_xAIObjectiveNavData.m_xDestinationPosition, m_xAIObjectiveNavData.m_usDestinationWPID, xTestParams ) && 
						Asura_AI_Navigation_System::IsPositionInWaypoint( m_xAIObjectiveNavData.m_xDestinationPosition, m_xAIObjectiveNavData.m_usDestinationWPID ) )
					{
						if ( m_uLastAIObjectiveGuid != pxObjective->GetGuid() )
						{
							m_uLastAIObjectiveGuid = pxObjective->GetGuid();
							m_bWeHaveArrivedAtAIObjective = false;
						}

						// Clear out the old route and find the new one.
						m_xAIObjectiveNavData.ResetRoute();
						AVP_Navigation::FindRoute( m_xAIObjectiveNavData );
					}
				}
			}
		}
		else
		{
			m_fCurrentAIRouteDistance = 0.0f;
		}



#ifndef ASURA_NO_DEBUG_OUTPUT
// 		Asura_Vector_2		xPos			= Asura_Vector_2( 300.0f, 30.0f );
// 		u_int				uFlags			= ASURA_TEXTFORMAT_LEFTJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING;
// 		Asura_Font_System::Print(xPos.x, xPos.y, 1.5f, 0xffffffff,	uFlags,	"Route Len = %0.2f", m_fCurrentAIRouteDistance );
// 		AVP_Navigation::RenderRoute( m_xAIObjectiveNavData );
// 		Asura_DebugLineRender::AddSphere( m_xAIPositionOnRoute, 0.5f, 0xffffff00, 4, 2 );
// 		Asura_Vector_2		xPos			= Asura_Vector_2(300.0f, 50.0f);
// 		u_int				uFlags			= ASURA_TEXTFORMAT_LEFTJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING;
// 		Asura_Font_System::Print( xPos.x, xPos.y, 1.5f, 0xffffffff,	uFlags,	"Route Length Remaining = %0.2f", m_fCurrentAIRouteDistance );
// 		xPos.y += 25.0f;
// 		Asura_Font_System::Print( xPos.x, xPos.y, 1.5f, 0xffffffff,	uFlags,	"Current Route Segment = %d", m_xNavData.m_usRouteSegmentIndex );
// 		xPos.y += 25.0f;
// 		Asura_Font_System::Print( xPos.x, xPos.y, 1.5f, 0xffffffff,	uFlags,	"Current Waypoint = %d", m_xNavData.m_pusRoute[ m_xNavData.m_usRouteSegmentIndex ] );
// 		xPos.y += 25.0f;
// 		Asura_Font_System::Print( xPos.x, xPos.y, 1.5f, 0xffffffff,	uFlags,	"Next Waypoint = %d", m_xNavData.m_pusRoute[ m_xNavData.m_usRouteSegmentIndex + 1 ] );
#endif

	}
}

void AVP_ServerEntity_PlayerInfo::UpdateMissionTime( void )
{
	m_fMissionTimer += Asura_Timers::GetGameTimeSlice();

	// If we are in a multiplayer match this is a countdown instead of a missiontime
	if( AVP_Settings::IsMultiPlayer() )
	{
		int iRemainingMatchTime = Asura_Maths::FloatToInt( AVP_Multiplayer_GameCore::GetMatchTimeLeft() );
		
		m_iMissionTimeHours = iRemainingMatchTime / 3600;
		iRemainingMatchTime -= m_iMissionTimeHours * 3600;

		m_iMissionTimeMinutes = iRemainingMatchTime / 60;
		iRemainingMatchTime -= m_iMissionTimeMinutes * 60;

		m_iMissionTimeSeconds = iRemainingMatchTime;
	}
	else
	{
		if( m_fMissionTimer >= 1.0f )
		{
			m_fMissionTimer -= 1.0f;

			m_iMissionTimeSeconds += 1;
		}
		if( m_iMissionTimeSeconds > 59 )
		{
			m_iMissionTimeSeconds = 0;

			m_iMissionTimeMinutes += 1;
		}
		if( m_iMissionTimeMinutes > 59 )
		{
			m_iMissionTimeMinutes = 0;

			m_iMissionTimeHours += 1;
		}
		if( m_iMissionTimeHours > 23 )
		{
			m_iMissionTimeHours = 0;
			m_iMissionTimeMinutes = 0;
			m_iMissionTimeSeconds = 0;
		}
	}	
}


AVP_ServerEntity_PlayerInfo* AVP_ServerEntity_PlayerInfo::Create(Asura_Guid uGuid)
{
	return new AVP_ServerEntity_PlayerInfo(uGuid);
}

void AVP_ServerEntity_PlayerInfo::CreateFromChunkStream(Asura_Guid uGuid, Asura_Chunk_Stream& xStream)
{
	AVP_ServerEntity_PlayerInfo* pxEntity = new AVP_ServerEntity_PlayerInfo(uGuid);
	if (pxEntity)
	{
		pxEntity->ReadFromChunkStream(xStream);
	}
}

const Asura_Vector_3& AVP_ServerEntity_PlayerInfo::GetPlayerPosition() const
{
	const AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity( GetPuppetGuid() );
	if ( pxActor )
	{
		return pxActor->GetPosition();
	}
	else
	{
		return Asura_Maths::ZeroVector3;
	}
}

const Asura_Matrix_3x3& AVP_ServerEntity_PlayerInfo::GetPlayerOrientation() const
{
	const AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity( GetPuppetGuid() );
	if ( pxActor )
	{
		return pxActor->GetOrientation();
	}
	else
	{
		return Asura_Maths::IdentityMatrix3x3;
	}
}

void AVP_ServerEntity_PlayerInfo::MakeCreateMessage(Asura_Guid uRequestingClient, bool bSendGuaranteed/* = false*/)
{
	AVP_Message_PlayerInfo_ClientCreate xMsgData;
	FillCreateData(xMsgData);

	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_CLIENTCREATE, sizeof(AVP_Message_PlayerInfo_ClientCreate), Classification, &xMsgData);
	if (pxMessage)
	{
		pxMessage->SetToGuid(ASURA_GUID_MY_CLIENT_ENTITIES);
		pxMessage->SetFromGuid(GetGuid());
		if (bSendGuaranteed)
		{
			pxMessage->UseGuaranteedDelivery();
		}
		Asura_Network::SendToSpecificMachine(pxMessage, uRequestingClient);
	}
}

bool AVP_ServerEntity_PlayerInfo::CalculatePlayersCentroid( Asura_Vector_3& xCentroid )
{
	// ( s_uCentroidCalcFrame == 0 ) denotes that the stored value is invalid.

	const u_int uFrame = Asura_Timers::GetFrameNumber();
	if ( s_uCentroidCalcFrame != 0 && uFrame == s_uCentroidCalcFrame )
	{
		xCentroid = s_xCentroid;
		return true;
	} 
	else
	{
		u_int uPlayerCount = 0;
		xCentroid = Asura_Maths::ZeroVector3;
		for ( Asura_Hashed_List_ConstIt< AVP_ServerEntity_PlayerInfo > xPlayerIter( AVP_ServerEntity_PlayerInfo::GetList() );
			!xPlayerIter.Done();
			xPlayerIter.Next() )
		{
			const AVP_ServerEntity_PlayerInfo* pxPlayerInfo = xPlayerIter.GetCurrent();
			const AVP_ServerEntity_Actor*	   pxActor = AVP_ServerEntity_Actor::FindEntity( pxPlayerInfo->GetPuppetGuid() );
			if ( !pxActor || pxActor->IsDead() || !pxActor->IsEntityActive() )
			{
				continue;
			}

			++uPlayerCount;
			xCentroid += pxActor->GetPosition();
		}
		if ( uPlayerCount > 1 )
		{
			xCentroid /= static_cast< float >( uPlayerCount );
		}

		if ( uPlayerCount > 0 )
		{
			s_uCentroidCalcFrame = uFrame;
			return true;
		} 
		else
		{
			s_uCentroidCalcFrame = 0;
			return false;
		}
	}
}

bool AVP_ServerEntity_PlayerInfo::IsVisibleByAnyPlayer( const Asura_Vector_3& xPosition )
{
	for ( Asura_Hashed_List_ConstIt< AVP_ServerEntity_PlayerInfo > xPlayerInfoIt( GetList() );
		!xPlayerInfoIt.Done();
		xPlayerInfoIt.Next() )
	{
		const AVP_ServerEntity_PlayerInfo* pxPlayerInfo = xPlayerInfoIt.GetCurrent();
		const AVP_ServerEntity_Actor*      pxPlayer     = AVP_ServerEntity_Actor::FindEntity( pxPlayerInfo->GetPuppetGuid() );
		if ( !pxPlayer || !pxPlayer->IsEntityActive() || pxPlayer->IsDead() )
		{
			continue;
		}

		Asura_Vector_3 xPlayerHeadPosition = pxPlayer->GetPosition();
		pxPlayer->GetHeadPosition( xPlayerHeadPosition );

		Asura_Vector_3 xFromPlayer = xPosition - xPlayerHeadPosition;
		const float    fFromPlayer = xFromPlayer.Normalise();

		const Asura_Vector_3& xPlayerAimDir = pxPlayer->GetAimDirection();

		// FOV test first.
		if ( xFromPlayer * xPlayerAimDir > 0.0f )
		{
			// LOS test next.
			Asura_LOS_InputData xInputData( xPlayerHeadPosition, xFromPlayer, pxPlayer->GetGuid(), fFromPlayer, true );
			const Asura_LOS_OutputData* pxOutputData = Asura_LOS::TestAgainstEverything( &xInputData );
			// We add a fudge factor on to the los hit distance as we may be placed on the ground
			// and so the LOS test may hit the ground just before getting to us.
			if ( !pxOutputData->m_bHitEnvironment || pxOutputData->m_fDistanceToIntersection > fFromPlayer - 0.1f )
			{
				return true;
			}
		}
	}

	return false;
}

void AVP_ServerEntity_PlayerInfo::ResetMultiplayerInfoForAllPlayers()
{
	Asura_Hashed_List_It<AVP_ServerEntity_PlayerInfo> xPlayerInfoIt( GetList() ); 
	while ( !xPlayerInfoIt.Done() )
	{
		AVP_ServerEntity_PlayerInfo* pxPlayerInfo = xPlayerInfoIt.GetCurrent(); 
		if (pxPlayerInfo)
		{
			// Reset this player score
			AVP_Multiplayer_PlayerScore& xPlayerScore = pxPlayerInfo->GetPlayerScore(); 
			xPlayerScore.Reset(); 

			// Also clear the kill counter list if needed
			pxPlayerInfo->DestroyKillCounterList(); 

			// Stop all persecutor / persecuted relationships
			pxPlayerInfo->SetPersecutorGuid(ASURA_GUID_UNREGISTERED);
		}
		else
		{
			Asura_Assert( pxPlayerInfo, "Invalid player info pointer when resetting the scores" ); 
		}
		xPlayerInfoIt.Next(); 
	}
}

void AVP_ServerEntity_PlayerInfo::DestroyKillCounterList(void)
{
	Asura_Hashed_List_Deleter_It<AVP_Multiplayer_KillCounter> xKillCounterIt( &m_xKillCounterList ); 
	while ( !xKillCounterIt.Done() )
	{
		AVP_Multiplayer_KillCounter* pxCurrentKillCounter = xKillCounterIt.GetCurrent(); 
		xKillCounterIt.DeleteCurrent(); 
		delete pxCurrentKillCounter; 
	}
}

void AVP_ServerEntity_PlayerInfo::FillUpdateData(AVP_Message_PlayerInfo_ClientUpdate& xData) const
{
	xData.SetPuppetGuid(GetPuppetGuid());
	xData.SetPersecutorGuid(GetPersecutorGuid());
	xData.SetMultiplayerTeam(GetMultiplayerTeam()); 
	xData.SetMultiplayerRole(GetMultiplayerRole()); 
	xData.SetIndividualScore(m_xPlayerScore.GetIndividualScore()); 
	xData.SetTeamOneScore(m_xPlayerScore.GetTeamOneScore()); 
	xData.SetTeamTwoScore(m_xPlayerScore.GetTeamTwoScore()); 
	xData.SetTeamThreeScore(m_xPlayerScore.GetTeamThreeScore()); 
	xData.SetNumberOfKillsAsAlien(m_xPlayerScore.GetNumberOfKillsAsAlien()); 
	xData.SetNumberOfKillsAsMarine(m_xPlayerScore.GetNumberOfKillsAsMarine()); 
	xData.SetNumberOfKillsAsPredator(m_xPlayerScore.GetNumberOfKillsAsPredator()); 
	xData.SetKillStreak(m_xPlayerScore.GetKillStreak()); 
	xData.SetNumberOfKillAssists(m_xPlayerScore.GetNumberOfKillAssists()); 
	xData.SetNumberOfTimesKilled(m_xPlayerScore.GetNumberOfTimesKilled()); 
	xData.SetNumberOfHeadshots(m_xPlayerScore.GetNumberOfHeadshots()); 
	xData.SetNumberOfTrophies(m_xPlayerScore.GetNumberOfTrophies()); 
	xData.SetNumberOfHeadbites(m_xPlayerScore.GetNumberOfHeadbites());
	xData.SetLookingAtTarget(m_bLookingAtTaget);
	
	xData.SetLookAtTargetPos( Asura_Maths::ZeroVector3 );
	xData.SetLookAtTargetGuid( ASURA_GUID_UNREGISTERED );
	xData.SetLookAtTargetMP( ASURA_HASH_ID_UNSET );

	if ( m_bLookingAtTaget )
	{
		AVP_ServerEntity_LookAt* pxLookAtEntity = AVP_ServerEntity_LookAt::FindEntity( m_uLookAtTargetGuid );
		if ( pxLookAtEntity )
		{
			// These don't exist on the client, but luckily they don't move so we don't have to worry about smoothing/interpolating the position.
			xData.SetLookAtTargetPos( pxLookAtEntity->GetPosition() );
		}
		else
		{
			xData.SetLookAtTargetGuid( m_uLookAtTargetGuid );
			xData.SetLookAtTargetMP( m_uLookAtTargetMarker );
		}
	}

	int iMissionTime = (m_iMissionTimeHours * 3600) + (m_iMissionTimeMinutes * 60) + m_iMissionTimeSeconds;
	xData.SetMissionTime( iMissionTime );
}

void AVP_ServerEntity_PlayerInfo::FillCreateData(AVP_Message_PlayerInfo_ClientCreate& xData) const
{
	FillUpdateData(xData);
	xData.Fill(this);
}

AVP_Player_AudioDiary* AVP_ServerEntity_PlayerInfo::GetAudioDiary( u_int uIndex )
{
	Asura_LinkList_It< AVP_Player_AudioDiary > xLinkListIter( m_xAudioDiariesList );

	u_int uElement = 0;
	while ( !xLinkListIter.Done() )
	{
		if ( uIndex == uElement)
		{
			return (xLinkListIter.GetCurrent());
		}
		++uElement;
		xLinkListIter.Next();
	}
	return NULL;
}


/// End of file. ///
