//===========================================================================//
// File:	MissileWeapon.cpp										
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 05/18/99  DPB  Created File
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive										 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "MW4Headers.hpp"

#include "MissileWeapon.hpp"
#include "Missile.hpp"
#include "MWObject.hpp"
#include "MWMission.hpp"
#include "SubsystemClassData.hpp"

#include <adept\damageobject.hpp>
#include <Adept\Map.hpp>
#include <Adept\Site.hpp>
#include <MLR\MLRTexturePool.hpp>

// ngLog addition
#include "mech.hpp"
#if !defined(NO_LOG)
#include "nglog_mw4.hpp"
#endif // !defined(NO_LOG)
#include "MWApplication.hpp"
#include "sensor.hpp"


//#############################################################################
//###############################    MissileWeapon    #########################
//#############################################################################

MissileWeapon::ClassData*
	MissileWeapon::DefaultData = NULL;

void MechWarrior4::MissileSecurityCheckStart()
{
	_asm
	{
		nop
	}	
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MissileWeapon::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			MissileWeaponClassID,
			"MechWarrior4::MissileWeapon",
			ProjectileWeapon::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,
			(Subsystem::StreamCreate) CreateStream
		);
	Register_Object(DefaultData);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		MissileWeapon__GameModel,
		BurnTime,
		burnTime,
		Scalar
	);
}

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

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

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

	CommonCreation(message);
}

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

	numberEffectedByAMS = 0;
	rollAMSNumber = true;
	m_lockedTargetEntityPartOffset = Stuff::Point3D::Identity;
	// in Mercs, Arrow missiles can't be shot down by AMS
	unaffectedByAMS = false;

}

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

	ProjectileWeapon::Respawn(message);
	CreateMessage *missile_message = Cast_Pointer(CreateMessage *, message);
	CommonCreation(missile_message);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MissileWeapon::~MissileWeapon()
{
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MissileWeapon::CreateProjectile(Time till)
{
	Check_Object(this);
	const GameModel *model = GetGameModel();
	Check_Object(model);

	if(model->projectileModelResource != ResourceID::Null)
	{
		//
		//---------------------------------
		//Get the ClassID from the Resource
		//---------------------------------
		//
		ClassID class_ID;
		class_ID = GetClassIDFromDataListID(model->projectileModelResource);
		Verify(class_ID != NullClassID);

		ReplicatorID site_id = ReplicatorID::Null;
		Normal3D target_normal;

		Motion3D initial_velocity = Motion3D::Identity;
		Motion3D initial_acceleration = Motion3D::Identity;
		LinearMatrix4D site_local_to_world;

		//
		//-------------------------
		//Initialize the velocities
		//-------------------------
		//
		Check_Object(sitePointer);
		site_local_to_world = sitePointer->GetLocalToWorld();
		initial_velocity.linearMotion.Multiply(
			model->initialLinearVelocity,
			site_local_to_world
		);
		initial_acceleration.linearMotion = model->initialLinearAcceleration;

		ReplicatorID target_id;
		target_id = ReplicatorID::Null;
		Point3D target_point;
		Entity *ray_source;
		ray_source = targetQueryEntity.GetCurrent();
		if(ray_source)
		{
			Check_Object(ray_source);
			target_id = ray_source->GetReplicatorID();
//			Point3D trans;
//			targetQuery.m_line->FindEnd(&trans);
//			target_point.MultiplyByInverse(
//				trans,
//				ray_source->GetLocalToWorld()
//			);
			target_point = m_lockedTargetEntityPartOffset;
		}
		else
		{
			targetQuery.m_line->FindEnd(&target_point);
		}



		//
		//--------------------------------
		//Create the Missile CreateMessage
		//--------------------------------
		//
		int flags = WeaponMover::DefaultFlags;

		if(numberEffectedByAMS > 0)
		{
			numberEffectedByAMS --;
			flags |= Missile::DoesAMSDestroyFlag;
		}

		Missile::CreateMessage projectile_create_message(
			sizeof(Missile::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			flags,
			model->projectileModelResource,
			sitePointer->GetLocalToWorld(),
			0.0f,
			Missile::ExecutionStateEngine::SRMMotionState,
			NameTable::NullObjectID,
			Entity::DefaultAlignment,
			initial_velocity,
			initial_acceleration,
			model->damageAmount,
			model->heatToDeal,
			model->maxDistance,
// MSL 5.03 Min Distance
//			model->minDistance,
			GetParentVehicle()->GetReplicatorID(),
			model->splashRadius,
			model->percentageOfDamageToDirectHit,
			model->minPercentageOfDamageToSphereHit,
			model->maxPercentageOfDamageToSphereHit,
			model->itemID,
			target_id,
			target_point,
			model->burnTime
		);
		MemoryStream stream(&projectile_create_message, projectile_create_message.messageLength);

		Entity *entity;
		ReplicatorID projectile_id = Connection::Hermit->GetNextReplicatorID();
		entity = CreateEntity(&stream, &projectile_id, false);
		Check_Object(entity);
		Map::GetInstance()->AddChild(entity);
		if (vehicleInterface)
		{
			vehicleInterface->PlayForce (VehicleInterface::FireMissleForce);
		}
	}
}

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

	PRECOLLISION_LOGIC("Subsystem::MissileWeapon");

	const GameModel* model = GetGameModel();
	Check_Object(model);
	Check_Object(executionState);
	int pre_state = executionState->GetState();

	switch (pre_state)
	{
		case ExecutionStateEngine::ReloadingState:
			{
				if (model->reloadTime > 0.49f)
				{
					if ((!m_PlayedRechargeSound) && (GetTimeParameter(till) >= model->reloadTime-0.5f))
					{
						if (GetParentVehicle ())
							if (GetParentVehicle()->vehicleInterface)
								GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::RECHARGE_MISSILE);
						m_PlayedRechargeSound = true;
					}
				}
			}
	}
	ProjectileWeapon::PreCollisionExecute(till);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void MissileWeapon::QuickFire (Adept::Entity *target)
{
	const GameModel *model = GetGameModel();
	Check_Object(model);
	Check_Object(this);

	executionState->RequestState(ExecutionStateEngine::ReloadingState);

	if (target == 0)
	{
		return;
	}

	Check_Object(target);
	ReplicatorID inflicting_id = ReplicatorID::Null;
	inflicting_id = GetParentVehicle()->GetReplicatorID();

// MSL 5.06 Advance Mode
	MWApplication *m_App;
	m_App = MWApplication::GetInstance ();
	m_ArmorMode = m_App->GetLocalNetParams()->m_armormodeOn;

	Entity::TakeDamageMessage damage_message(
		target->GetReplicatorID(),
		inflicting_id,
		model->damageAmount,
		m_ArmorMode,
		MissileDamageType,
		Stuff::UnitVector3D(1,0,0),
		Point3D (-1,-1,-1),
		model->heatToDeal,
		true,
		model->itemID
	);
	
	target->Receive(&damage_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MissileWeapon::Fire(CollisionQuery *query, Time time_locked, const Stuff::Point3D& lockedTargetEntityPartOffset)
{
	Check_Object(this);
	Check_Object(executionState);

	const GameModel *model = GetGameModel();
	Check_Object(model);

	targetQuery.m_collisionMask = query->m_collisionMask;
	targetQuery.m_material = query->m_material;
	targetQuery.m_raySource = query->m_raySource;
	m_lockedTargetEntityPartOffset = lockedTargetEntityPartOffset;
	collisionLine = *query->m_line;
	collisionNormal = *query->m_normal;

	//
	//--------------------------------------------------
	//Check to see if we are locked on the entity or not
	//--------------------------------------------------
	//
	
// MSL 5.04 Corrects tracking of Missile from root of Building Object to target point
	if(targetQuery.m_raySource)
	{
		if (targetQuery.m_raySource->IsDerivedFrom(Building::DefaultData) == true)
		{
			targetQuery.m_raySource = NULL;
		}
	}

	//
	// If this model doesn't have a lock time, it won't even lock for Narc's
	//

	if(Small_Enough(model->targetLockTime))
	{
		targetQuery.m_raySource = NULL;
	}
	else
	{
		// if we couldn't have locked legitimately yet, start with no lock

		//
		// .001 is a special value indicating immediate lock time. (SSRM's)
		//
		if (model->targetLockTime  > time_locked && model->targetLockTime >= 0.0011f)
			targetQuery.m_raySource = NULL;

// MSL 5.04 no tracking beyond maximum missile range
		if (targetQuery.m_line->m_length > model->maxDistance)
			targetQuery.m_raySource = NULL;

// MSL 5.04 no tracking within minimum missile range
//		if (targetQuery.m_line->m_length < model->minDistance)
//			targetQuery.m_raySource = NULL;

// MSL 5.04 no tracking if affected by ECM
		if(targetQuery.m_raySource)
		{
// MSL 5.05 Advance Mode
			MWApplication *m_App;
			m_App = MWApplication::GetInstance ();
			m_AdvanceMode = m_App->GetLocalNetParams()->m_advancemodeOn;

			if (m_AdvanceMode)
			{
				if (targetQuery.m_raySource->IsDerivedFrom(MWMover::DefaultData) == true)
				{
					MWMover* mover = Cast_Object(MWMover*,targetQuery.m_raySource);
					MWObject *parent;
					parent = mover->GetParentVehicle ();
					if (parent)
					{
						MWObject *mobj = GetParentVehicle();
						if(mobj && mobj->IsDerivedFrom(Mech::DefaultData))
						{
							Mech *mech = Cast_Object(Mech *, mobj);
							if (mech->GetSensor()->GetSensorMode() == Sensor::PassiveMode)
							{
								targetQuery.m_raySource = NULL;
							}
						}

						if (parent->GetSensor()->GetSensorMode() == Sensor::PassiveMode)
						{
							MWObject *mobj = GetParentVehicle();
							if(mobj && mobj->IsDerivedFrom(Mech::DefaultData))
							{
								Mech *mech = Cast_Object(Mech *, mobj);
								if (mech->DoesHaveBeagle())
								{
									if (targetQuery.m_line->m_length > 600.0f)
										targetQuery.m_raySource = NULL;
								}
								else
								{
									if (targetQuery.m_line->m_length > 500.0f)
										targetQuery.m_raySource = NULL;
								}
							}
						}

						if(parent->DoesHaveECM())
						{
							MWObject *mobj = GetParentVehicle();
							if(mobj && mobj->IsDerivedFrom(Mech::DefaultData))
							{
								Mech *mech = Cast_Object(Mech *, mobj);
								if (mech->DoesHaveBeagle())
								{
									if (targetQuery.m_line->m_length > 700.0f)
										targetQuery.m_raySource = NULL;
								}
								else
								{
									if (targetQuery.m_line->m_length > 500.0f)
										targetQuery.m_raySource = NULL;
								}
							}
						}
					}
				}
			}
		}

		bool does_have_lock = false;

		if(targetQuery.m_raySource)
		{
			if(targetQuery.m_raySource == Map::GetInstance())
			{
				targetQuery.m_raySource = NULL;
			}
			else
			{
				does_have_lock = true;
				if (targetQuery.m_raySource->IsDerivedFrom(MWMover::DefaultData) == true)
				{
					MWMover* mover = Cast_Object(MWMover*,targetQuery.m_raySource);
					mover->NotifyProjectileApproaching(targetQuery.m_line->m_origin);
					MWObject *parent;
					parent = mover->GetParentVehicle ();
					if (parent)
					{
						if(parent->GetAMS() && !unaffectedByAMS)
						{
							//
							//-------------------------------------------------
							//pre-determine how many missiles AMS will take out
							//-------------------------------------------------
							//
							if ((rollAMSNumber) && (executionState->GetState() != ExecutionStateEngine::FiringState))
							{
// MSL 5.02 AMS
//								numberEffectedByAMS = 1;
								const GameModel *model = GetGameModel();
								Check_Object(model);

								int upperLimit = model->numFire+1;
								if (model->numFire > 1)
								{
									Max_Clamp(upperLimit, model->numFire); // stop 0 to 8 missile groups
								}
								else
								{
									Max_Clamp(upperLimit, model->numFire+1); // stop 0 to 8 missile groups
								}
//								Max_Clamp(upperLimit, model->numFire+1); // stop 0 to 8 missile groups
								numberEffectedByAMS = Stuff::Random::GetLessThan(upperLimit);
							}
						}
					}
				}
			}
		}

		//
		//-------------------------------------------------------------
		//If we don't have a lock then we need to check the narc status
		//-------------------------------------------------------------
		//
		if(!does_have_lock)
		{
			if(Mission::GetInstance()->IsDerivedFrom(MWMission::DefaultData))
			{
				MWMission *temp_mission =
					Cast_Object(MWMission *, Mission::GetInstance());
				Entity *entity = (Entity *)temp_mission->GetNarc(GetParentVehicle()->GetAlignment());
				if(entity)
				{
					targetQuery.m_raySource = entity;
					m_lockedTargetEntityPartOffset = Point3D::Identity;
				}
			}
		}
	}

	if(DoesHaveAmmo())
	{
		if(ReadyToFire())
		{
			// ngLog addition
#if !defined(NO_LOG)
			MWObject *mobj = GetParentVehicle();

			if(mobj && mobj->IsDerivedFrom(Mech::DefaultData))
			{
				Mech *mech = Cast_Object(Mech *, mobj);
				if(mech && mech->IsLanceMate())
				{
					int p_id = mech->lancemateIndex;
					MWApplication::GetInstance()->lancemateConnectionData[p_id].wep_atts[GetGameModel()->itemID] += GetGameModel()->ammoPerShot;
				}
				else
				{
					int p_id = GetParentVehicle()->GetReplicatorID().connectionID;
					MWApplication::GetInstance()->servedConnectionData[p_id].wep_atts[GetGameModel()->itemID] += GetGameModel()->ammoPerShot;
				}
			}
#endif // !defined(NO_LOG)

#if !defined(NO_MR)
			if(GetParentVehicle()->IsPlayerVehicle())
			{
				MWApplication::GetInstance()->SetMissionReviewWeaponFired(GetParentVehicle()->GetReplicatorID().connectionID);
			}
#endif // !defined(NO_MR)

			Mech_CheckDamage(targetQuery.m_raySource, GetParentVehicle());
			executionState->RequestState(ExecutionStateEngine::FiringState, &targetQuery);
		}
		else
		{
#if 0
			if (GetParentVehicle ()->vehicleInterface)
			{
				GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::OUT_OF_AMMO);
			}
#endif
		}
	}

// MSL 5.03 Heat Jam
	if(model->m_heatToJam > 0)
	{
		if (executionState->GetState() == ExecutionStateEngine::FiringState)
		{
			MWObject *mobj = GetParentVehicle();
			if(mobj && mobj->IsDerivedFrom(Mech::DefaultData))
			{
				Mech *mech = Cast_Object(Mech *, mobj);
				Check_Object(mech);
				float heatlevel = mech->m_heatManager->GetHeatPercentage() * 100.0f;
				float heattojam = model->m_heatToJam;
//				float heatjam = Stuff::Random::GetFraction();
				float heatjam = (Stuff::Random::GetLessThan(100) * 100);
//				float heatjamlevel = (heatlevel - heattojam) / (100 - heattojam);

				if (heatlevel > heattojam)
				{
					if (heatjam < 20)
					{
						m_heatJamThisFrame = true;
					}
				}
			}
		}
	}
}


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

bool MissileWeapon::IsAMSValid(Entity *target)
{
	if (target == NULL)
		return false;

	if(target == Map::GetInstance())
		return false;

// MSL 5.02 bug fix
	// traverse parents looking for an MWObject
	// (bug fix # 4718)
	Entity *testEntity = target;
	while (testEntity!=NULL)
	{
		// stop at the MWObject
		if (testEntity->IsDerivedFrom(MWObject::DefaultData))
		{
			break;
		}
		testEntity = testEntity->GetParentEntity();
	}

	if (testEntity && testEntity->IsDerivedFrom(MWObject::DefaultData))
	{
		MWObject* mw_object = Cast_Object(MWObject *, testEntity);
		if(mw_object->GetAMS())
		{
			return true;
		}
	}

	return false;
}

void MechWarrior4::MissileSecurityCheckStop()
{
	_asm
	{
		nop
	}	
}


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

