//===========================================================================//
// File:	MWPlayer.cpp																	     //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 09/09/98  JSE  Inital base class based off of Shadowrun/Adept                                //
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive										 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "MW4Headers.hpp"

#include "MWPlayer.hpp"
#include "MWMover.hpp"
#include "Vehicle.hpp"
#include "VehicleInterface.hpp"
#include "MWMission.hpp"
#include "MWApplication.hpp"
#include "playerai.hpp"
#include "AI_Groups.hpp"
#include "Team.hpp"

#include <Adept\EntityClassData.hpp>
#include <Adept\EntityManager.hpp>
#include <Adept\Interface.hpp>
#include <Adept\Application.hpp>
#include <Adept\Map.hpp>
#include <adept\collisionvolume.hpp>
#include <Adept\CollisionGrid.hpp>
#include <Adept\DamageObject.hpp>

using namespace MW4AI;

//#############################################################################
//###############################    MWPlayer    ##############################
//#############################################################################

MWPlayer::ClassData*
	MWPlayer::DefaultData = NULL;


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MWPlayer::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			MWPlayerClassID,
			"MechWarrior4::MWPlayer",
			Player::DefaultData,
			0, NULL,
			(Entity::Factory)Make,
			(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
			ExecutionStateEngine::DefaultData,
			(Entity::GameModel::Factory)GameModel::ConstructGameModel,
			NULL,
			(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
			(Entity::GameModel::ModelWrite)GameModel::WriteToText,
			(Entity::GameModel::ModelSave)GameModel::SaveGameModel
		);
	Register_Object(DefaultData);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MWPlayer::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MWPlayer*
	MWPlayer::Make(
		CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(message);
	gos_PushCurrentHeap(Heap);
	MWPlayer *new_entity =
		new MWPlayer(DefaultData, message, base_id, NULL);
	gos_PopCurrentHeap();
	Check_Object(new_entity);
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ObjectID MWPlayer::FindDropZone(int team)
{
	if (team != MWApplication::No_Team)
	{
		NameTable *table = NameTable::GetInstance();

		int size(table->nameTableArray[NameTable::DropZoneArray].GetLength());

		{for (int i = 0;
			  i < size;
			  ++i)
		{
			NameTableEntry *data;

			data = &table->nameTableArray [NameTable::DropZoneArray][i];
			Adept::Entity* ent = data->dataPointer->GetCurrent ();

			if (ent != 0)
			{
				int team_number = MWApplication::NameToTeamNumber(ent->instanceName);

				if (team_number == team)
				{
					return (ent->objectID);
				}
			}
		}}
	}

	ObjectID obj_id = (NameTable::GetInstance()->FindID("PlayerStart"));

	if (obj_id == NameTable::NullObjectID)
	{
		STOP(("No valid dropzone"));
	}

	return obj_id;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MWPlayer::MWPlayer(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Player(class_data, message, base_id, element),
	m_Team(MWApplication::No_Team)
{
	Check_Pointer(this);
	Check_Object(message);

	isDropped = false;
	

	Mission::GetInstance()->AddChild(this);

	//
	//----------------------
	// Create the interface
	//----------------------
	//
	playerInterface = NULL;
	interfaceResourceID = ResourceID::Null;
	if (GetReplicatorMode() == Replicator::ClientMasterMode || GetReplicatorMode() == Replicator::MasterMode)
	{
		Resource stream(message->interfaceResourceID);
		Verify(stream.DoesResourceExist());
		//ReplicatorID id = owningConnection->GetNextReplicatorID();
		ReplicatorID interface_id = Connection::Hermit->GetNextReplicatorID();
		playerInterface = Cast_Object(Interface *, CreateEntity(&stream, &interface_id));
		Check_Object(playerInterface);
		interfaceResourceID = message->interfaceResourceID;
	}

	


	//
	//-------------------
	// Create the vehicle
	//-------------------
	//
	
	Resource mech_instance(message->vehicleResourceID);
	mech_instance.LoadData();
	Verify(mech_instance.DoesResourceExist());

	void *new_data = new BYTE[mech_instance.GetSize()];
	Mem_Copy(new_data, mech_instance.GetPointer(), mech_instance.GetSize(), mech_instance.GetSize());

	MWObject::CreateMessage *create_message =
		Cast_Pointer(MWObject::CreateMessage*, new_data);
	Check_Object(create_message);

	create_message->pilotDecal = message->pilotDecal;
	create_message->teamDecal = message->teamDecal;


	Team* team = 0;
	if (message->teamNumber != MWApplication::No_Team)
	{
		team = MWApplication::GetInstance()->teams[message->teamNumber]->GetCurrent();
		if (team)
		{
			create_message->skinPrefix = team->GetTeamPrefix();
		}
	}
	else if (MWApplication::GetInstance()->networkingFlag)
	{
		create_message->skinPrefix = message->skinPrefix;
	}
// for campaign skin - jcem
	else {
		extern bool g_bCOOP;
		if (g_bCOOP) { // m_isCampaign == (serverFlag && !networkingFlag)
			create_message->skinPrefix = message->skinPrefix;
		}
	}
// for campaign skin - jcem




	MemoryStream mech_stream(create_message, create_message->messageLength);

	//Insure that we start with a bad id and don't get our stuff set since we are going to do it later
	Mech::CreateMessage *mech_message = Cast_Pointer(Mech::CreateMessage *, mech_stream.GetPointer());
	mech_message->nameID = NameTableEntry::BuildObjectID(NameTable::TableSize, 0);
	if(mech_message->initialAge == 0.0f)
	{
		Stuff::LinearMatrix4D drop_location = Stuff::LinearMatrix4D::Identity;
		drop_location.BuildTranslation(Point3D::HellPoint);

		mech_message->localToParent = drop_location;
	}
	vehicle = CreateEntity(&mech_stream, base_id);
	Check_Object(vehicle);


	//SPEW(("jerryeds","CREATE MECH %d:%d", vehicle->GetReplicatorID().connectionID, vehicle->GetReplicatorID().localID));


	delete new_data;



	
	if (team)
	{
		((Vehicle*)vehicle)->SetTeam(message->teamNumber);
		team->AddMemberToTeam((Vehicle*)vehicle);
		m_Team = message->teamNumber;
	}
	
	vehicle->SetPropType(PlayerPropType);
	Check_Object(Map::GetInstance());
	Map::GetInstance()->AddChild(vehicle);


	EntityManager::GetInstance()->RemoveMover((Mover*)vehicle);

	ReplicatorID mech_id = vehicle->GetReplicatorID();
	vehicle->instanceName =
		Network::GetInstance()->GetConnection(mech_id.connectionID)->GetConnectionName();

	if(!vehicle->instanceName || !vehicle->instanceName.GetLength())
	{
		char buffer[10];
		sprintf(buffer, "%d::", owningConnection->GetID());
		MString new_name;
		new_name = buffer + vehicle->instanceName;
		vehicle->instanceName = new_name;
	}

	Check_Object(NameTable::GetInstance());
	vehicle->objectID = NameTable::GetInstance()->FindID(vehicle->instanceName);
	
	if(vehicle->objectID == NameTable::NullObjectID)
		NameTable::GetInstance()->AddEntry(vehicle, NameTable::VehicleArray);
	else
		NameTable::GetInstance()->SetData(vehicle);
	if (owningConnection != Connection::Local)
	{
		vehicle->SetAlignment(Entity::Enemy);
	}

	Check_Object(EntityManager::GetInstance());
	EntityManager::GetInstance()->AddMover((Mover*)vehicle);


	// connect to the drop zone

	if (message->initialAge != 0.0f)
	{
	
		ElementRenderer::Element *element = vehicle->GetElement();
		Check_Object(element);
		ReceiveDropZone(element->GetNewLocalToParent());


	}
	else
	{
		dropZone = FindDropZone(m_Team);

		Stuff::LinearMatrix4D drop_location = Stuff::LinearMatrix4D::Identity;
		drop_location.BuildTranslation(Point3D::HellPoint);

		vehicle->SetNewLocalToParent(drop_location);
		vehicle->SyncMatrices(true);
		Map::GetInstance()->UpdateZone(vehicle);
	}


	//
	//----------------------------
	//Store the Vehicle ResourceID
	//----------------------------
	//
	vehicleResourceID = message->vehicleResourceID;

	m_LockedRects.reserve (50);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

void
	MWPlayer::Respawn(CreateMessage *message)
{

	Check_Pointer(this);
	Check_Object(message);

	isDropped = false;
	

	//
	//----------------------
	// Respawn the interface
	//----------------------
	//

	Entity::CreateMessage *interface_respawn_message = NULL;

	
	interfaceResourceID = ResourceID::Null;
	if (GetReplicatorMode() == Replicator::ClientMasterMode || GetReplicatorMode() == Replicator::MasterMode)
	{
		Resource stream(message->interfaceResourceID);
		Verify(stream.DoesResourceExist());
		//ReplicatorID id = owningConnection->GetNextReplicatorID();
		//ReplicatorID interface_id = Connection::Hermit->GetNextReplicatorID();

		interface_respawn_message =
			Cast_Pointer(Entity::CreateMessage*, stream.GetPointer());

		//playerInterface->Respawn(new_message);
		//Check_Object(playerInterface);
		
	}
	
	//
	//-------------------
	// Create the vehicle
	//-------------------
	//
	
	Resource mech_instance(message->vehicleResourceID);
	Verify(mech_instance.DoesResourceExist());

	void *new_data = new BYTE[mech_instance.GetSize()];
	Mem_Copy(new_data, mech_instance.GetPointer(), mech_instance.GetSize(), mech_instance.GetSize());

	MWObject::CreateMessage *create_message =
		Cast_Pointer(MWObject::CreateMessage*, new_data);
	Check_Object(create_message);

	create_message->pilotDecal = message->pilotDecal;
	create_message->teamDecal = message->teamDecal;
	//create_message->skinPrefix = message->skinPrefix;

	MemoryStream mech_stream(create_message, create_message->messageLength);

	
	vehicle->Respawn(create_message);
	Check_Object(vehicle);

	delete[] new_data;

	// we do this after the vehicle for a respawn since farther down
	// the stream there is no reconnect
	if (interface_respawn_message != NULL)
	{	
		
		playerInterface->Respawn(interface_respawn_message);
		Check_Object(playerInterface);
	}


	// connect to the drop zone
	if (message->initialAge != 0.0f)
	{
		ElementRenderer::Element *element = vehicle->GetElement();
		Check_Object(element);
		ReceiveDropZone(element->GetNewLocalToParent());

	}
	else
	{
		dropZone = FindDropZone(m_Team);

//		Stuff::LinearMatrix4D drop_location = Stuff::LinearMatrix4D::Identity;
//		drop_location.BuildTranslation(Point3D::HellPoint);

//		vehicle->SetNewLocalToParent(drop_location);
//		vehicle->SyncMatrices(true);
//		Map::GetInstance()->UpdateZone(vehicle);
	}
	if (owningConnection != Connection::Local)
	{
		vehicle->SetAlignment(Entity::Enemy);
	}


	//
	//----------------------------
	//Store the Vehicle ResourceID
	//----------------------------
	//
	vehicleResourceID = message->vehicleResourceID;


}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MWPlayer::~MWPlayer()
{
	ClearPathLock (true);

#if 0

	// happens automaticly with plugs.

	if ((vehicle != 0) &&
		(m_Team != MWApplication::No_Team))
	{
		Team* team = MWApplication::GetInstance()->teams[m_Team]->GetCurrent();

		if (team != 0)
		{
			team->RemoveMemberFromTeam((Vehicle*)vehicle);
		}
	}
#endif
	
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

void
	MWPlayer::ConnectToVehicle(void)
{
	Check_Object(vehicle);
	Check_Object(playerInterface);

	Player::ConnectToVehicle();

	GetInterface()->ConnectVehicleToInterface(GetVehicle(),this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MWPlayer::PreCollisionExecute(Time till)
{
	Check_Object(this);
	PRECOLLISION_LOGIC("MWPlayer");

	Player::PreCollisionExecute(till);


	ClearPathLock (false);
	PathLock ();
}



//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MWPlayer::LancemateCommandProxy::LancemateCommandProxy()
	: m_Mode(INVALID)
{
}

bool
	MWPlayer::LancemateCommandProxy::SetMode(Mode mode)
{
	Verify(mode != INVALID);
	
	stlport::vector<MWObject *> lance;
	MW4AI::Groups::GetLancemates (lance);
	m_Mode = INVALID;
	switch (mode)
	{
		case LANCEMATE_1:
			if (lance.size () < 1)
				return false;
			break;	
		case LANCEMATE_2:
			if (lance.size () < 2)
				return false;
			break;	
		case LANCEMATE_3:
			if (lance.size () < 3)
				return false;
			break;	
		case LANCEMATE_ALL:
			if (lance.size ()== 0)
				return false;
			break;
	}

	m_Mode = mode;
	return (true);
}

bool
	MWPlayer::LancemateCommandProxy::IssueCommand(Vehicle* v, MW4AI::LancemateCommands::ID command)
{
	if ((v == 0) ||
		(v->GetGroups().size() == 0))
	{
		return (false);
	}

	Check_Object(v);

	Group::identifier group_id;
	
	if (MW4AI::Groups::GetLancemateGroup(v->GetGroups(),group_id) == false)
	{
		return (false);
	}

	std::vector<unsigned int> indices_to_add;

	switch (m_Mode)
	{
		case LANCEMATE_1:	{	indices_to_add.push_back(1);	break;	}
		case LANCEMATE_2:	{	indices_to_add.push_back(2);	break;	}
		case LANCEMATE_3:	{	indices_to_add.push_back(3);	break;	}
		case LANCEMATE_ALL:
		{
			indices_to_add.push_back(1);
			indices_to_add.push_back(2);
			indices_to_add.push_back(3);
			break;
		}
		default:
		{
			return (false);
		}
	}

	MW4AI::LancemateCommands::lancemate_list lancemates;

	{for (std::vector<unsigned int>::const_iterator i = indices_to_add.begin();
		  i != indices_to_add.end();
		  ++i)
	{
		Adept::ObjectID id;
		if (MW4AI::Groups::GetMember(group_id,*i,id) == true)
		{
			lancemates.push_back(id);
		}
	}}

	MW4AI::LancemateCommands::Execute(command,lancemates);

	m_Mode = INVALID;

	return (true);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//void
//	MWPlayer::PostCollisionExecute(Time till)
//{
//	Check_Object(this);

//	Player::PostCollisionExecute(till);


//}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MWPlayer::PostCollisionExecute(Time till)
{
	Check_Object(this);
	POSTCOLLISION_LOGIC("MWPlayer");

	
	if (playerInterface)
	{

		VehicleInterface *veh_inf = Cast_Object(VehicleInterface *, playerInterface);
		if (veh_inf->IsObserving(true) && (veh_inf->observingEntity.GetCurrent() != NULL))
		{
			SetNewLocalToParent(veh_inf->observingEntity.GetCurrent()->GetLocalToWorld());
		}
		else
		{
			SetNewLocalToParent(vehicle->GetLocalToWorld());
		}

	}
	else
	{
		SetNewLocalToParent(vehicle->GetLocalToWorld());
	}

	if (m_ObjectsToDestroy.size() > 0)
	{
		{for (stlport::vector<ObjectID>::iterator i = m_ObjectsToDestroy.begin();
			  i != m_ObjectsToDestroy.end();
			  ++i)
		{
			Entity* ent = NameTable::GetInstance()->FindData(*i);

			if (ent != 0)
			{
				Entity::TakeDamageMessage damage_message(
					ent->GetReplicatorID(),
					ent->GetReplicatorID(),
//					100000,
					0.0f,
// MSL 5.05 Armor Mode
					0,
					Adept::RammingDamageFromAboveType,
					Stuff::UnitVector3D(1,0,0),
					(Stuff::Point3D)ent->GetLocalToWorld(),
					0,
					false,
					Entity::TakeDamageMessage::DefaultWeaponType
				);

				ent->Receive(&damage_message);
			}
		}}

		m_ObjectsToDestroy.clear();
	}

	SyncMatrices(true);

	//skip player & driver
	Entity::PostCollisionExecute(till);
}



//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void MWPlayer::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}

void MWPlayer::ClearPathLock (bool quitting)
{
	stlport::vector<LockData *>::iterator iter;
	
	for (iter = m_PathLock.begin ();iter != m_PathLock.end ();iter++)
	{
		if (!quitting)
		{
			MW4AI::g_MissionGraph->PassableLocalOverride ((*iter)->locx,(*iter)->locz,(*iter)->olddata);
			MW4AI::RemoveBlockLocal ((*iter)->locx,(*iter)->locz,(MWObject *) this);
		}
		delete (*iter);
	}
	m_PathLock.clear ();

	Verify (m_LockedRects.size () == 0);
	stlport::vector<Rect4D *>::iterator iter2;
	for (iter2 = m_LockedRects.begin ();iter2 != m_LockedRects.end ();iter2++)
	{
		g_Rect4DHash->RemoveRect (*iter2);
		g_Rect4DHash->VerifyRectGone (*iter2);
		delete (*iter2);
	}
	m_LockedRects.clear ();
}

bool MWPlayer::AddLockCell (const Stuff::Point3D& loc)
{
	unsigned short value;
	int locx,locz;
	Point3D pt;

	if (loc.x < MW4AI::MinX)
		return true;
	if (loc.z < MW4AI::MinX)
		return true;
	if (loc.x >= MW4AI::MaxX)
		return true;
	if (loc.z >= MW4AI::MaxZ)
		return true;

	locx = (int) (loc.x / BLOCK_GRID_RES);
	locz = (int) (loc.z / BLOCK_GRID_RES);

	MWObject *who;

	who = MW4AI::BlockerLocal (locx,locz);
	if (who == (MWObject *) this)
		return true;
	if (who)
		return false;

	value = MW4AI::g_MissionGraph->PassableLocal (locx,locz);
	if (value & (MOVEPASSNOW_FLAG))
		return false;

	MW4AI::g_MissionGraph->PassableLocalOverride (locx,locz,(unsigned short)(value|MOVEPASSNOW_FLAG));
	m_PathLock.push_back (new LockData (locx,locz,value));
	MW4AI::AddBlockLocal (locx,locz,(MWObject *) this);

	return true;
}

bool MWPlayer::LockSquare (const Point3D& loc,Stuff::Scalar rad)
{
	int sqrindex[8][2] = {
		{-1,-1},
		{0,-1},
		{1,-1},
		{-1,0},
		{1,0},
		{-1,1},
		{0,1},
		{1,1}
	};
	int i;
	Point3D pt (0,0,0);

	if (rad < 10.0f)
		rad = 10.0f;
	for (i=0;i<8;i++)
	{
		pt.x = loc.x+(sqrindex[i][0]*rad);
		pt.z = loc.z+(sqrindex[i][1]*rad);

		if (LockedByMe (pt))
			continue;

		AddLockCell (pt);
	}
	return false;
}
bool MWPlayer::PathLock (bool secondpass)
{
	Check_Object (GetVehicle ());
	Check_Object (GetVehicle ()->GetGameModel ());
	
	if (!GetVehicle())
		return false;


	if (playerInterface)
	{
		Check_Object (playerInterface);

		if (playerInterface->executionState->GetState () == VehicleInterface::ExecutionStateEngine::AIRunningState)
			return false;
	}

	OBB *obb;
	CollisionVolume *col;
	col = vehicle->GetSolidVolume ();
	if (!col)
		return false;
	obb = &(col->m_localSpaceBounds);
	if (!obb)
		return false;
	Stuff::Scalar rad = obb->sphereRadius;
	rad *= 1.5f;


	Stuff::Scalar speed,movemps,slowtime;
	UnitVector3D forward;
	Point3D step,loc;
	int i,timelen;

	const Vehicle__GameModel *model = static_cast<const Vehicle__GameModel *> (vehicle->GetGameModel ()) ;

	loc = (Point3D) vehicle->GetLocalToWorld ();
	vehicle->GetLocalToWorld ().GetLocalForwardInWorld (&forward);
	step = forward;

	speed = GetVehicle ()->currentSpeedMPS;
	LockSquare (loc,rad);
	AddLockCell (loc);
	if (speed == 0)
	{
		return false;
	}
	movemps = speed;
	Max_Clamp(movemps, model->maxSpeed);
	Min_Clamp(movemps, model->minSpeed);
	slowtime = movemps / model->decceleration;
	
	Min_Clamp (movemps,1);
	step *= movemps;

	slowtime += 1.0f;		//extra second of buffer
	timelen = (int) (slowtime);

	int maxi;
	Scalar delta;
	delta = 10.0f/movemps;
	step *= delta;

	maxi = (int) (timelen / delta);

	Min_Clamp (maxi,1);
	loc += step;
	for (i=0;i<maxi;i++)
	{
			LockSquare (loc,rad);
			AddLockCell (loc);
			loc += step;
	}

	return false;
}

void
	MWPlayer::AddObjectToDestroy(ObjectID who)
{
	m_ObjectsToDestroy.push_back(who);
}
