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

#include "MW4Headers.hpp"

#include "Tank.hpp"
#include "gameinfo.hpp"
#include "AI.hpp"
#include "AI_UserConstants.hpp"
#include "Mech.hpp"
#include "MWDamageObject.hpp"
#include <adept\effect.hpp>
#include "obstacle.hpp"

// MSL 5.04 Tank Scorable
#include "MWMission.hpp"

#include <Adept\EntityManager.hpp>
#include <Adept\CollisionVolume.hpp>
#include <Adept\DamageObject.hpp>

//#############################################################################
//###############################    Tank    ##############################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Tank::ClassData*
	Tank::DefaultData = NULL;

DWORD MechWarrior4::Executed_Tank_Count = 0;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Tank::InitializeClass()
{
	Check_Object(MWMover::ExecutionStateEngine::DefaultData);
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			TankClassID,
	        "MechWarrior4::Tank",
			BaseClass::DefaultData,
			0, NULL,
			(Entity::Factory)Make,
			(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
			ExecutionStateEngine::DefaultData,
			(Entity::GameModel::Factory)GameModel::ConstructGameModel,
			(Entity::GameModel::Factory)GameModel::ConstructOBBStream,
			(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
			(Entity::GameModel::ModelWrite)GameModel::WriteToText,
			(Entity::GameModel::ModelSave)GameModel::SaveGameModel,
			AnimationStateEngine::Make
		);
	Register_Object(DefaultData);

	
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Tank*
	Tank::Make(
		CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(message);
	gos_PushCurrentHeap(Heap);
	Tank *new_entity = new
		Tank(DefaultData, message, base_id, NULL);
	gos_PopCurrentHeap();
	Check_Object(new_entity);
	
	
	Check_Object(EntityManager::GetInstance());
	EntityManager::GetInstance()->AddMover(new_entity);
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Tank::Tank(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Vehicle(class_data, message, base_id, element)
{
	Check_Pointer(this);
	Check_Object(message);

	HookUpSubsystems();
	CommonCreation(message);

// MSL 5.04 Tank Scorable
	//should this tank be scored?
	m_scoreThisTank = false;

}

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

Tank::~Tank()
{
	Check_Object(EntityManager::GetInstance());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Tank::Reuse(
		const CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(this);
	Check_Object(message);

	STOP(("Not implemented"));
	BaseClass::Reuse(message, base_id);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Tank::CommonCreation(CreateMessage *message)
{
	Check_Object(this);
	Check_Object(message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Tank::Respawn(Entity::CreateMessage *message)
{
	Check_Object(this);
	Check_Object(message);

	BaseClass::Respawn(message);
	CreateMessage *vehicle_message = Cast_Pointer(CreateMessage *, message);
	CommonCreation(vehicle_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Tank::PreCollisionExecute(Time till)
{
	Check_Object(this);

	PRECOLLISION_LOGIC("Tank");
	#if defined(LAB_ONLY)
		if (!instanceName)
			MWGameInfo::g_LastVehicle[0] = '\0';
		else
		{
			strncpy(MWGameInfo::g_LastVehicle, instanceName, sizeof(MWGameInfo::g_LastVehicle)-1);
			MWGameInfo::g_LastVehicle[sizeof(MWGameInfo::g_LastVehicle)-1] = '\0';
		}
		MWGameInfo::g_LastVehiclePos = GetLocalToWorld ();
	#endif

	//
	//-------------------------------------------------------------
	// Let the base class deal with setting up the motion variables
	//-------------------------------------------------------------
	//
	BaseClass::PreCollisionExecute(till);
	Set_Statistic(Executed_Tank_Count, Executed_Tank_Count+1);

	//
	//----------------------------------------------------------------------
	// Any executing vehicle should not be in never execute state and should
	// always use post collision
	//----------------------------------------------------------------------
	//
	Check_Object(executionState);
	int pre_state = executionState->GetState();
	Verify(pre_state != ExecutionStateEngine::NeverExecuteState);
	
	//
	//-------------------------------------------------------------------------
	// Do the appropriate type of simulation.  Animated motion state is for BRB
	//-------------------------------------------------------------------------
	//
	Scalar time_slice = GetTimeSlice(till);
	switch (pre_state)
	{
		//
		//----------------------------------------------------
		// Always execute state is for the normal driving code
		//----------------------------------------------------
		//
		case ExecutionStateEngine::DyingState:
			ComputeFrictionSpeed (time_slice);
			UpdateVehiclePosition (time_slice);
			break;
		case ExecutionStateEngine::DrivingMotionState:
			ComputeForwardSpeed(time_slice);
			UpdateVehiclePosition(time_slice);
			break;

		//
		//----------------------------------------------------
		// Always execute state is for the normal driving code
		//----------------------------------------------------
		//
		case ExecutionStateEngine::FlyingMotionState:
			ComputeForwardSpeed(time_slice);
			UpdateVehiclePosition(time_slice);
			break;
	}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Tank::PostCollisionExecute (Stuff::Time time)
{
	Check_Object(this);

	POSTCOLLISION_LOGIC("Tank");
	lastParameterization = time;
	initialLocalToParent = GetLocalToParent();
	initialWorldSpaceVelocity = worldSpaceVelocity,
	initialWorldSpaceAcceleration = worldSpaceAcceleration;

	int pre_state = executionState->GetState();
	Verify(pre_state != ExecutionStateEngine::NeverExecuteState);
	
	switch (pre_state)
	{
		case ExecutionStateEngine::DyingState:
			// check for unit stopped falling and moving
			if ((localSpaceVelocity.linearMotion.z == 0.0f) && ((localSpaceVelocity.linearMotion.y == 0.0f)))
			{
				Verify (m_DeathDamageMode != -1);
				Verify (m_DeathDamageType != -1);
				const GameModel *model = GetGameModel();
				Check_Object(model);

				DealSplashDamage();

				SetDead ();
				Entity::ReactToDestruction(m_DeathDamageMode,m_DeathDamageType);

//				CreateEffect(model->secondaryDestroyedEffectResource, this);
				if(!m_deathEntity.GetCurrent())
				{
					Entity *death_entity = CreateStaticHermitEntity(model->deathEntityResource);
					if(death_entity) 
					{
						m_deathEntity.Remove();
						m_deathEntity.Add(death_entity);
					}
				}
				executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
				entityElement->SetAlwaysCullMode();
				RemoveCollision();
				lastParameterization = GetTimeSlice (time);
				initialLocalToParent = GetLocalToParent();
				SyncMatrices(true);
			}
			break;
	}
	BaseClass::PostCollisionExecute (time);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	Tank::CollisionHandler(
		Stuff::LinearMatrix4D *new_position,
		Stuff::DynamicArrayOf<CollisionData> *collisions
	)
{
	Check_Object(this);
	Check_Object(collisions);
	Verify(GetInterestLevel() != DormantInterestLevel);
	bool shoulddamage=true;
	//
	//----------------------------------------------------------
	// If we are destroyed, delete the collision list and return
	//----------------------------------------------------------
	//
	if (IsDestroyed())
	{
		Verify(!newCollisions);
		delete collisions;
		return false;
	}

	//
	//--------------------------------
	// Ask the AI about the collision
	//--------------------------------
	//
	if (m_AI)
	{
		if (!m_AI->ReactToCollision(collisions,shoulddamage))			// ai will return false if collision should not occur
		{
			Verify(!newCollisions);
			delete collisions;
			return false;
		}
	}


	//
	//----------------------------------------
	// Turn off collision if we are going fast
	//----------------------------------------
	//
	#ifdef LAB_ONLY
		if (m_DebugFast)
		{
			Verify(!newCollisions);
			delete collisions;
			return false;
		}
	#endif

	//
	//-----------------------
	// Iterate the collisions
	//-----------------------
	//
	bool just_bump = true;
	for (int i=0; i<collisions->GetLength(); ++i)
	{
		CollisionData *data = &(*collisions)[i];
		Check_Pointer(data);

		//
		//---------------------------------------------------------------------
		// Look to see if we have run into a collider, and if so, calculate the
		// relative velocity
		//---------------------------------------------------------------------
		//
		Entity *other = data->m_otherEntity;
		if (other->IsACollider())
		{
			just_bump = false;
			Adept::Mover *adeptmover = Cast_Object(Adept::Mover*, other); 
			Vector3D rel_vel;
			rel_vel.Subtract(adeptmover->worldSpaceVelocity.linearMotion, worldSpaceVelocity.linearMotion); 
			Scalar speed = rel_vel.GetLengthSquared();
			MWObject *mover = Cast_Object(MWObject*, other);

			//
			//-------------------------------
			// If its a mech, get its ratings
			//-------------------------------
			//
			if (other->GetClassID() == MechClassID)
			{
				Mech *mech = Cast_Object(Mech*, mover);
				int mech_class;
				Scalar damage_received;
				Scalar mech_speed;
				if (mech->GetTonage() >= MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_heavy_tonage))
				{
					damage_received = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_heavy_damage);
					mech_speed = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_heavy_building_speed);
					mech_class = HeavyCanWalkThruType;
				}
				else if (mech->GetTonage() >= MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_medium_tonage))
				{
					damage_received = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_medium_damage);
					mech_speed = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_medium_building_speed);
					mech_class = MediumCanWalkThruType;
				}
				else
				{
					damage_received = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_light_damage);
					mech_speed = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_light_building_speed);
					mech_class = AnythingCanWalkThruType;
				}

				//
				//----------------------
				// See if it squishes us
				//----------------------
				//
				if (GetWalkThruType() >= mech_class)
				{
					if (shoulddamage)
					{
						TakeDamageMessage damage_message(
							GetReplicatorID(),
							other->GetReplicatorID(),
							100000.0f,
// MSL 5.05 Advance Mode
							0,
							RammingDamageFromAboveType,
							data->m_normal,
							data->m_worldIntersectionPoint,
							0.0f,
							false,
							TakeDamageMessage::DefaultWeaponType
						);
						Receive(&damage_message);
					}
				}

				//
				//-----------------------------------------------
				// See if we are going fast enough to get damaged
				//-----------------------------------------------
				//
				else if (speed >= mech_speed)
				{
					speed = Sqrt(speed)*mover->GetTonage();

					//
					//------------------------
					// Evaluate the DFA states
					//------------------------
					//
					Scalar min_dfa_vel = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_dfa_speed);
					Entity::ExecutionStateEngine *engine = mech->executionState;
					bool their_dfa = mech->worldSpaceVelocity.linearMotion.y <= -min_dfa_vel && engine->GetState() == ExecutionStateEngine::FlyingMotionState;

					//
					//---------------------------------------
					// Use the appropriate collision modifier
					//---------------------------------------
					//
					char damage_type;
					if (their_dfa)
					{
						damage_type = RammingDamageFromAboveType;
						speed *= MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_dfa_damage_given);
					}
					else
					{
						damage_type = RammingDamageFromAheadType;
						speed *= damage_received;
					}

					//
					//----------------------------------
					// Set the damage message to ourself
					//----------------------------------
					//
// MSL 5.04 Tank Collision Damage
// MSL 5.06 Armor Mode
				MWApplication *m_App;
				m_App = MWApplication::GetInstance ();
				m_ArmorMode = m_App->GetLocalNetParams()->m_armormodeOn;

				if (shoulddamage)
					{
						if (m_ArmorMode)
						{
							TakeDamageMessage damage_message(
								GetReplicatorID(),
								other->GetReplicatorID(),
								0.1f,
								m_ArmorMode,
								damage_type,
								data->m_normal,
								data->m_worldIntersectionPoint,
								0.0f,
								false,
								TakeDamageMessage::DefaultWeaponType
							);
							Receive(&damage_message);
						}
						else
						{
							TakeDamageMessage damage_message(
								GetReplicatorID(),
								other->GetReplicatorID(),
								speed,
								m_ArmorMode,
								damage_type,
								data->m_normal,
								data->m_worldIntersectionPoint,
								0.0f,
								false,
								TakeDamageMessage::DefaultWeaponType
							);
							Receive(&damage_message);
						}
					}
				}
			}
		}

		//
		//--------------------------------------------------------------
		// If we've run into a static object, see if we can just kill it
		//--------------------------------------------------------------
		//
		else
		{
			if (GetWalkThruType() <= other->GetWalkThruType())
			{
				if (shoulddamage)
				{
					TakeDamageMessage damage_message(
						other->GetReplicatorID(),
						GetReplicatorID(),
						100000.0f,
// MSL 5.05 Advance Mode
						0,
						RammingDamageFromAboveType,
						data->m_otherNormal,
						data->m_worldIntersectionPoint,
						0.0f,
						false,
						TakeDamageMessage::DefaultWeaponType
					);
					other->Receive(&damage_message);
				}
			}

			//
			//------------------------------
			// Otherwise, just stop the tank
			//------------------------------
			//
			else
				just_bump = false;
		}
	}

	//
	//-------------------------------
	// Slow down the tank as required
	//-------------------------------
	//
	Scalar slowdown = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_tank_slowdown);
	bool result = false;
	if (just_bump)
	{
		if (currentSpeedMPS > 0.0f)
		{
			currentSpeedMPS -= slowdown;
			Min_Clamp(currentSpeedMPS, 0.0f);
		}
		if (currentSpeedMPS < 0.0f)
		{
			currentSpeedMPS += slowdown;
			Max_Clamp(currentSpeedMPS, 0.0f);
		}
	}

	//
	//-------------------------
	// Otherwise, stop the tank
	//-------------------------
	//
	else
	{
		result = true;
		currentSpeedMPS = 0.0f;
		*new_position = GetLocalToParent();
	}
	currentSpeedKPH = currentSpeedMPS * 3.6f;

	//
	//--------------------------------------------------------------------
	// If the tank isn't executing, it can't move or retain its collisions
	//--------------------------------------------------------------------
	//
	Entity::ExecutionStateEngine *engine = executionState;
	if (engine->GetState() == ExecutionStateEngine::NeverExecuteState || !IsUsingPostCollision())
	{
		Verify(!newCollisions);
		delete collisions;
		return false;
	}

	newCollisions = collisions;
	return result;
}

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

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

void Tank::ReactToDestruction(int damage_mode, int damage_type)
{
	
	if (damage_mode != InternalDamageObject::DestructionDamageMode)
	{
		SPEW(("jerryeds", "DYING WITHOUT DEATH!!!"));
		SPEW(("daberger", "DYING WITHOUT DEATH!!!"));

		damage_mode = InternalDamageObject::DestructionDamageMode;
	}

	if ((localSpaceVelocity.linearMotion.z == 0.0f) && ((localSpaceVelocity.linearMotion.y == 0.0f)))
	{
		BaseClass::ReactToDestruction (damage_mode,damage_type);
		return;
	}
	switch(damage_mode)
	{
	case InternalDamageObject::DestructionDamageMode:
		{
// MSL 5.04 Tank Scorable
			if (m_scoreThisTank)
			{	
				Check_Object (Mission::GetInstance());
				Mission::GetInstance()->ScoringReactToBuildingDeath (m_whoShotMeLast, GetReplicatorID());
			}

			if(m_AI)
			{
				Check_Object(m_AI);
				m_AI->Die();
			}
			else
			{
				MW4AI::g_Rect4DHash->RemovePermRect (this);
			}

			if((!IsDestroyed()) && (!m_SecondaryExplosion.GetCurrent ()))
			{
				m_DeathDamageMode = damage_mode;
				m_DeathDamageType = damage_type;
				const GameModel *model = GetGameModel();
				SetDying ();
				Check_Object(model);
				Effect *eff;
				eff = CreateEffect(model->secondaryDestroyedEffectResource, this,true);
				if (eff != NULL)
				{
				Check_Object (eff);
				m_SecondaryExplosion.Add (eff);
				executionState->RequestState(ExecutionStateEngine::DyingState);
				}
			}
		}
	}
}

