//===========================================================================//
// File:	ProjectileWeapon.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 "ProjectileWeapon.hpp"
#include "GUIWeaponManager.hpp"
#include "Vehicle.hpp"
#include "WeaponMover.hpp"
#include "SubsystemClassData.hpp"
#include "HeatManager.hpp"

#include <Adept\Map.hpp>
#include <Adept\Site.hpp>
#include <Adept\EntityManager.hpp>
#include <adept\damageobject.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"

//#############################################################################
//##########################    ExecutionStateEngine    #######################
//#############################################################################

ProjectileWeapon::ExecutionStateEngine::ClassData*
	ProjectileWeapon::ExecutionStateEngine::DefaultData = NULL;

const StateEngine::StateEntry
	ProjectileWeapon::ExecutionStateEngine::StateEntries[]=
{
	STATE_ENTRY(ProjectileWeapon__ExecutionStateEngine, Firing),
	STATE_ENTRY(ProjectileWeapon__ExecutionStateEngine, Reloading),
	STATE_ENTRY(ProjectileWeapon__ExecutionStateEngine, Jammed),
	STATE_ENTRY(ProjectileWeapon__ExecutionStateEngine, HeatJammed),
	STATE_ENTRY(ProjectileWeapon__ExecutionStateEngine, AmmoFire),
	STATE_ENTRY(ProjectileWeapon__ExecutionStateEngine, Recharging)
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::ExecutionStateEngine::InitializeClass()
{
	Check_Object(StateEngine::DefaultData);
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			ProjectileWeapon__ExecutionStateEngineClassID,
			"ProjectileWeapon::ExecutionStateEngine",
			Weapon::ExecutionStateEngine::DefaultData,
			ELEMENTS(StateEntries), StateEntries,
			(Entity::ExecutionStateEngine::Factory)Make,
			(Entity::ExecutionStateEngine::FactoryRequest::Factory)
				&FactoryRequest::ConstructFactoryRequest
		);
	Register_Object(DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::ExecutionStateEngine::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ProjectileWeapon::ExecutionStateEngine*
	ProjectileWeapon::ExecutionStateEngine::Make(
		ProjectileWeapon *weapon,
		FactoryRequest *request
	)
{
	Check_Object(weapon);
	Check_Object(request);
	gos_PushCurrentHeap(Heap);
	ProjectileWeapon::ExecutionStateEngine *engine =
		new ProjectileWeapon::ExecutionStateEngine(DefaultData, weapon, request);
	gos_PopCurrentHeap();
	return engine;
		
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ProjectileWeapon::ExecutionStateEngine::RequestState(
		int new_state,
		void* data
	)
{
	Check_Object(this);
	Check_Object(owningEntity);

	//
	//------------------------------
	// Quick exit if no state change
	//------------------------------
	//
	if (new_state == currentState)
	{
		return currentState;
	}

	//
	//-------------------------------------------------------
	// If we are currently reloading, ignore a firing request
	//-------------------------------------------------------
	//
	if((currentState == AmmoFireState) && (new_state == FiringState || new_state == RechargingState || new_state == ReloadingState || new_state == JammedState || new_state == HeatJammedState))
	{
		return currentState;
	}
	
	if((currentState == ReloadingState) && new_state == FiringState)
	{
		return currentState;
	}

// MSL 5.03 Weapon Jammed
	if((currentState == JammedState || currentState == HeatJammedState) && (new_state == FiringState || new_state == RechargingState || new_state == ReloadingState))
	{
		return currentState;
	}

	if(currentState == NeverExecuteState && new_state == FiringState) 
	{
		// Set the initial time of fire
		ProjectileWeapon* owner = Cast_Object( ProjectileWeapon*, owningEntity );		
		owner->m_fireStartTime = owner->m_fireEndTime = gos_GetElapsedTime();
	}

// MSL 5.03 Ammo Bay Fire
	if(new_state == AmmoFireState)
		{
			ProjectileWeapon* owner = Cast_Object( ProjectileWeapon*, owningEntity );		
			Check_Object(owner);
			owner->StartFireState();
		}

// MSL 5.03 Weapon Jammed
	if(new_state == JammedState)
		{
			ProjectileWeapon* owner = Cast_Object( ProjectileWeapon*, owningEntity );		
			Check_Object(owner);
			owner->StartJamState();
		}

// MSL 5.03 Weapon Heat Jammed
	if(new_state == HeatJammedState)
		{
			ProjectileWeapon* owner = Cast_Object( ProjectileWeapon*, owningEntity );		
			Check_Object(owner);
			owner->StartHeatJamState();
		}

// MSL 5.03 Weapon Jammed
	if(new_state == RechargingState)
		{
			ProjectileWeapon* owner = Cast_Object( ProjectileWeapon*, owningEntity );		
			Check_Object(owner);
			owner->m_enterRecharge = gos_GetElapsedTime();
		}
	//
	//
	//---------------------
	// Deal with our states
	//---------------------
	//
	ProjectileWeapon *weapon;
	weapon = Cast_Object(ProjectileWeapon *, owningEntity);
	switch (BaseClass::RequestState(new_state, data))
	{
	case FiringState:
		{
			weapon->targetQueryEntity.Remove();
			if(weapon->targetQuery.m_raySource)
				weapon->targetQueryEntity.Add(weapon->targetQuery.m_raySource);
			weapon->UsePostCollision();
			Check_Object(EntityManager::GetInstance());
			EntityManager::GetInstance()->RequestPostCollisionExecution(weapon);
			weapon->lastExecuted = gos_GetElapsedTime();
			weapon->fireRequest = true;
			if(weapon->guiWeapon.GetCurrent())
				weapon->guiWeapon.GetCurrent()->Reload();
			weapon->ApplyHeat();
			break;
		}
	case ReloadingState:
		{
			weapon->IgnorePostCollision();
			weapon->lastParameterization = gos_GetElapsedTime();
			break;
		}
//	case AmmoFireState:
//		{
//			weapon->UsePostCollision();
//			Check_Object(EntityManager::GetInstance());
//			EntityManager::GetInstance()->RequestPostCollisionExecution(weapon);
//			break;
//		}
	};
	return currentState;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::ExecutionStateEngine::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}

//#############################################################################
//###############################    Weapon    ##############################
//#############################################################################

ProjectileWeapon::ClassData*
	ProjectileWeapon::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			ProjectileWeaponClassID,
			"MechWarrior4::ProjectileWeapon",
			Weapon::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);

	INDIRECT_STATE_ATTRIBUTE(
		ProjectileWeapon,
		ExecutionState,
		executionState,
		ProjectileWeapon__ExecutionStateEngine
	);

	CUSTOM_DIRECT_ATTRIBUTE(
		ProjectileWeapon,
		ProjectileStartSFX,
		projectileStartSFX,
		int,
		IntClassID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		FireRate,
		fireRate,
		Scalar
	);


	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		InitialLinearVelocity,
		initialLinearVelocity,
		Vector3D
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		InitialLinearAcceleration,
		initialLinearAcceleration,
		Vector3D
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		ProjectileModelResource,
		projectileModelResource,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		TargetLockTime,
		targetLockTime,
		Scalar
	);
	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		TimeToPotentialJam,
		m_timeToPotentialJam,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		TimeToJam,
		m_timeToJam,
		Scalar

	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		TimeToUnjam,
		m_timeToUnjam,
		Scalar
	);

// MSL 5.03 Weapon Jammed
	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		HeatToJam,
		m_heatToJam,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		HeatToUnjam,
		m_heatToUnjam,
		Scalar
	);

// MSL 5.03 Ammo Bay Fire
	DIRECT_GAME_MODEL_ATTRIBUTE(
		ProjectileWeapon__GameModel,
		TimeToAmmoBay,
		m_timeToAmmoBay,
		Scalar
	);
}

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ProjectileWeapon::ProjectileWeapon(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Weapon(class_data, message, base_id, element),
	targetQuery(NULL, NULL, CanBeShotFlag, NULL),
	targetQueryEntity(NULL),
	m_jamStartTime( 0 ), 
	m_fireStartTime( 0 ), 
	m_fireEndTime( 0 ),
	m_ammofireStartTime( 0 )

{
	Check_Pointer(this);
	Check_Object(message);
	m_jamThisFrame = false;
	m_heatJamThisFrame = false;
	targetQuery.m_line = &collisionLine;
	targetQuery.m_normal = &collisionNormal;

	CommonCreation(message);
}

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

	Weapon::Respawn(message);
	CreateMessage *projectile_message = Cast_Pointer(CreateMessage *, message);
	CommonCreation(projectile_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::CommonCreation(CreateMessage *message)
{
	Check_Object(this);
	Check_Object(message);
	
	projectileStartSFX = 0;
	fireRequest = false;
	projectilesFired = 0;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ProjectileWeapon::~ProjectileWeapon()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ProjectileWeapon::PreCollisionExecute(Time till)
{
	Check_Object(this);
	PRECOLLISION_LOGIC("Subsystem::Projectile");

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

	Stuff::Scalar timeSlice = GetTimeSlice( till );

	int pre_state = executionState->GetState();

	switch (pre_state)
	{
		case ExecutionStateEngine::FiringState:
			{
				UsePostCollision();
				m_PlayedRechargeSound = false;
				break;
			}
		case ExecutionStateEngine::ReloadingState:
			{
				Scalar count = model->reloadTime - GetTimeParameter(till);
				Min_Clamp(count, 0.0f);
				SetCount(count);

				if (model->reloadTime > 0.49f)
				{
					if ((!m_PlayedRechargeSound) && (GetTimeParameter(till) >= model->reloadTime-0.5f))
					{
						if (GetParentVehicle ())
							if (GetParentVehicle()->vehicleInterface)
							{
								if ((model->itemID == W_ClanERPPC) || (model->itemID == W_PPC) || (model->itemID == W_ERPPC))
									GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::RECHARGE_PPC);
								else if (!((model->itemID == W_MachineGun) || (model->itemID == W_ClanMachineGun)))
									GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::RECHARGE_PROJECTILE);
							}
						m_PlayedRechargeSound = true;
					}
				}
// MSL 5.03 Weapon Jammed
				if(GetTimeParameter(till) > model->reloadTime)
				{
//					if(model->m_timeToJam > 0)
//					{
//						executionState->RequestState(ExecutionStateEngine::RechargingState);
//						if(guiWeapon.GetCurrent())
//							guiWeapon.GetCurrent()->Ready();
//						m_PlayedRechargeSound = false;
//					}
//					else
//					{
						executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
						if(guiWeapon.GetCurrent())
							guiWeapon.GetCurrent()->Ready();
						m_PlayedRechargeSound = false;
//					}
				}
				IgnorePostCollision();
				break;
			}
// MSL 5.03 Weapon Jammed
		case ExecutionStateEngine::RechargingState:
			{
				// In order to make the server and client agree on cumulative time more, I'll put a buffer in.
				// but if we don't get a new fire request with .25 seconds of entering this state we'll assume recharge.
				const GameModel* model = GetGameModel();
				Check_Object(model);
				Check_Object(executionState);

				if(model->m_timeToJam > 0)
				{
					if ( (gos_GetElapsedTime() - m_enterRecharge ) >= 0.25f)
					{
						m_fireStartTime += timeSlice*2; 
						m_fireEndTime += timeSlice;

						double cumulativeFiringTime = m_fireEndTime - m_fireStartTime;
				 
						if ( cumulativeFiringTime <= 0 )
						{
							executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
//							if(guiWeapon.GetCurrent())
//								guiWeapon.GetCurrent()->Ready();
						}
					}
				}
				break;
			}
// MSL 5.03 Weapon Jammed
		case ExecutionStateEngine::JammedState:
			{
				const GameModel* model = GetGameModel();
				Check_Object(model);
				Check_Object(executionState);
				if(model->m_timeToJam > 0)
				{
					if( gos_GetElapsedTime() - m_jamStartTime > model->m_timeToUnjam )
					{
						executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
					
						if(guiWeapon.GetCurrent())
							guiWeapon.GetCurrent()->Ready();
						m_jamThisFrame = false;
					}
					else
					{
						// Update the gui time
						if(guiWeapon.GetCurrent())
						{
							guiWeapon.GetCurrent()->SetJamCount( (Stuff::Scalar)(gos_GetElapsedTime() - m_jamStartTime) );
						}
					}
					IgnorePostCollision();
				}
				break;
			}
// MSL 5.03 Weapon Jammed
		case ExecutionStateEngine::HeatJammedState:
			{
				const GameModel* model = GetGameModel();
				Check_Object(model);
				Check_Object(executionState);
				if(model->m_heatToUnjam > 0)
				{
					MWObject *mobj = GetParentVehicle();
					if(mobj && mobj->IsDerivedFrom(Mech::DefaultData))
					{
						Mech *mech = Cast_Object(Mech *, mobj);
						Check_Object(mech);
						if ((mech->m_heatManager->GetHeatPercentage() * 100.0f) <= model->m_heatToUnjam)
						{
							executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
							if(guiWeapon.GetCurrent())
								guiWeapon.GetCurrent()->Ready();
							m_heatJamThisFrame = false;
						}
						IgnorePostCollision();
					}
				}
				break;
			}	
// MSL 5.04 Ammo Bay Fire
		case ExecutionStateEngine::AmmoFireState:
			{
				const GameModel* model = GetGameModel();
				Check_Object(model);
				Check_Object(executionState);
				if(model->m_timeToAmmoBay > 0)
				{
					MWObject *mobj = GetParentVehicle();
					if(mobj && mobj->IsDerivedFrom(Mech::DefaultData))
					{
						if (DoesHaveAmmo())
						{
							if( gos_GetElapsedTime() - m_ammofireStartTime > model->m_timeToAmmoBay )
							{
								Check_Object(this);
								int ammo_count = GetAmmoCount();
								float damage_count = ammo_count * model->damageAmount * model->numFire;
// MSL 5.04 Ammo Bay Fire
								SetAmmoCount(0);
								executionState->RequestState(ExecutionStateEngine::DestroyedState);
							}
							else
							{
								// Update the gui time
								if(guiWeapon.GetCurrent())
								{
									guiWeapon.GetCurrent()->SetFireCount( (Stuff::Scalar)(gos_GetElapsedTime() - m_ammofireStartTime) );
								}
							}
						}
					}
					else
					{
						executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
//						if(guiWeapon.GetCurrent())
//							guiWeapon.GetCurrent()->Ready();
					}
					UsePostCollision();
				}
				break;
			}
	}
	Weapon::PreCollisionExecute(till);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::PostCollisionExecute(Time till)
{

	Check_Object(this);
	POSTCOLLISION_LOGIC("Subsystem::Projectile");
	const GameModel *model = GetGameModel();
	Check_Object(model);
	Check_Object(executionState);

// MSL 5.03 Weapon Jammed
	bool jammed = false;

	MWApplication *m_App;
	m_App = MWApplication::GetInstance ();
	if (m_App->GetLocalNetParams()->m_weaponjamOn)
	{
		if ( executionState->GetState() == ExecutionStateEngine::FiringState )
		{
			if (m_jamThisFrame)
			{
				executionState->RequestState(ExecutionStateEngine::JammedState);
				jammed = true;
			}
			if (m_heatJamThisFrame)
			{
				executionState->RequestState(ExecutionStateEngine::HeatJammedState);
				jammed = true;
			}
		}
	}

	m_heatJamThisFrame = false;
	m_jamThisFrame = false;
	if (jammed)
	{
		if (GetParentVehicle ())
		{
			if (GetParentVehicle()->vehicleInterface)
			{
				GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::ZOOM_FAIL);
			}
		}
		return;
	}

	if(fireRequest)
	{
		if(!DoesHaveAmmo())
		{
			executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
			if(guiWeapon.GetCurrent())
				guiWeapon.GetCurrent()->Ready();
			IgnorePostCollision();
		}
		else
		{
			if((GetTimeParameter(till) > model->fireRate) ||
				(projectilesFired == 0))
			{
				lastParameterization = gos_GetElapsedTime();
				CreateProjectile(till);
				CreateMuzzleFlash();
				CreateEjectEffect();
				CreateLightAmpFlareOut();
				projectilesFired ++;
				projectileStartSFX = 1;
				if(projectilesFired >= model->numFire)
				{
					fireRequest = false;
					projectilesFired = 0;
					Check_Object(executionState);
					executionState->RequestState(ExecutionStateEngine::ReloadingState);
					UseAmmo();
				}
			}
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::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;
		ReplicatorID target_id = ReplicatorID::Null;
		Normal3D target_normal;

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

		//
		//-------------------------
		//Initialize the velocities
		//-------------------------
		//
		Check_Object(sitePointer);
		site_local_to_world = sitePointer->GetLocalToWorld();
		Vector3D projectile_direction;
		Point3D target_point;
		targetQuery.m_line->FindEnd(&target_point);
		projectile_direction.Subtract(
			target_point,
			site_local_to_world
		);
		Check_Object(sitePointer);
		site_local_to_world = sitePointer->GetLocalToWorld();

		LinearMatrix4D direction_matrix = LinearMatrix4D::Identity;
		direction_matrix.BuildTranslation((Stuff::Point3D)site_local_to_world);
		direction_matrix.AlignLocalAxisToWorldVector(projectile_direction, Z_Axis, Y_Axis, X_Axis);
		
		Vector3D initial_scale = model->initialLinearVelocity;
		if(GetParentAsVehicle())
		{
			initial_scale.z += GetParentAsVehicle()->currentSpeedMPS;
		}

		initial_velocity.linearMotion.Multiply(
			initial_scale,
			direction_matrix
		);

		initial_acceleration.linearMotion = model->initialLinearAcceleration;

		//
		//------------------------------------
		//Create the WeaponMover CreateMessage
		//------------------------------------
		//

		WeaponMover::CreateMessage projectile_create_message(
			sizeof(WeaponMover::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			WeaponMover::DefaultFlags,
			model->projectileModelResource,
			direction_matrix,
			0.0f,
			WeaponMover::ExecutionStateEngine::StraightLineMotionState,
			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
		);
		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)
		{
			if (!((model->itemID == W_MachineGun) || (model->itemID == W_ClanMachineGun)))
				vehicleInterface->PlayForce (VehicleInterface::FireProjectileForce);
		}
	}
}

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

	//copy the query

	targetQuery.m_collisionMask = query->m_collisionMask;
	targetQuery.m_material = query->m_material;
	targetQuery.m_raySource = query->m_raySource;

	collisionLine = *query->m_line;
	collisionNormal = *query->m_normal;
	
	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)
		}

		executionState->RequestState(ExecutionStateEngine::FiringState);
	}
	else
	{
#if 0
		if (GetParentVehicle ()->vehicleInterface)
		{
			GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::OUT_OF_AMMO);
		}
#endif
	}

// MSL 5.03 Weapon Jammed
	const GameModel* model = GetGameModel();
	Check_Object(model);
	Check_Object(executionState);

	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;
					}
				}
			}
		}
	}

// MSL 5.03 Weapon Jammed
	if(model->m_timeToJam > 0)
	{
		if (executionState->GetState() == ExecutionStateEngine::FiringState)
		{
			Check_Object(GetParentVehicle());
			bool fServer = Network::GetInstance()->AmIServer(); 
			bool fLocalPlayer =	(GetReplicatorID().connectionID == Connection::Local->GetID());
			if (fServer || fLocalPlayer) 
				{
				// if we fired, check for jam
				m_fireEndTime = gos_GetElapsedTime();
				double cumulativeFiringTime = m_fireEndTime - m_fireStartTime;
					//
				// For servers, the rules are going to be different. If the fire time is at the absolute max, plus a buffer for lag
				// then we will gaurantee a jam on the server, and have it refresh.
				//
				// This allows for some tolerance of various net traffic since we'd still prefer that the 
				// client handle this, but puts a cap on what we tolerate from a potential cheater.
				if ( fLocalPlayer)
				{
					if ( cumulativeFiringTime >= model->m_timeToPotentialJam)
					{
						Stuff::Scalar idPermutation = (replicatorID.localID % 3)/3.0f; // have weapon's ID permutate it's jam time
						Stuff::Scalar r = (Stuff::Scalar)fmod( gos_GetElapsedTime()+idPermutation, 1.0 );
							Stuff::Scalar e = (Stuff::Scalar)( 
							( cumulativeFiringTime - model->m_timeToPotentialJam ) 
							/ model->m_timeToJam );
						// Potential to jam: 
						if ( r < e )
						{
							m_jamThisFrame = true;
						}
					}					
				}
				else if (fServer)
				{
					if (cumulativeFiringTime >= (model->m_timeToJam + model->m_timeToPotentialJam + 2.0f))
					{
						m_jamThisFrame = true;
					}
				}
			}
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ProjectileWeapon::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 Armor 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,
		ProjectileDamageType,
		Stuff::UnitVector3D(1,0,0),
		Point3D (-1,-1,-1),
		model->heatToDeal,
		true,
		model->itemID
	);
	
	target->Receive(&damage_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	ProjectileWeapon::ReadyToFire()
{
	Check_Object(this);
	Check_Object(executionState);

	// if this is the local mech, always true in single player.
	if (GetReplicatorID().connectionID == Connection::Local->GetID())
	{
		if (CanFireFromCurrentArm() == false)
		{
			return (false);
		}

		bool result;
		result = executionState->GetState()==ExecutionStateEngine::NeverExecuteState &&
			DoesHaveAmmo();
		return result;
	}

	if (executionState->GetState()==ExecutionStateEngine::ReloadingState)
	{
		if (gos_GetElapsedTime() - lastParameterization <= 0.1f)
		{
			executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
		}

		return DoesHaveAmmo();
	}

	bool result;
	result = executionState->GetState()==ExecutionStateEngine::NeverExecuteState &&
		DoesHaveAmmo();
	return result;

}

// MSL 5.03 Ammo Bay Fire
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ProjectileWeapon::StartFireState()
{
	const GameModel *model = GetGameModel();
	Check_Object(model);
	m_ammofireStartTime = gos_GetElapsedTime();
	if ( guiWeapon.GetCurrent() )
	{
		guiWeapon.GetCurrent()->RecoverFromFire();
		guiWeapon.GetCurrent()->SetFireCount( model->m_timeToAmmoBay );
	}
}

// MSL 5.03 Weapon Jammed
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ProjectileWeapon::StartJamState()
{
	const GameModel *model = GetGameModel();
	Check_Object(model);
	m_jamStartTime = gos_GetElapsedTime();
	if ( guiWeapon.GetCurrent() )
	{
		guiWeapon.GetCurrent()->RecoverFromJam();
		guiWeapon.GetCurrent()->SetJamCount( model->m_timeToUnjam );
	}
}

// MSL 5.03 Weapon Jammed
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ProjectileWeapon::StartHeatJamState()
{
//	const GameModel *model = GetGameModel();
//	Check_Object(model);
//	m_jamStartTime = gos_GetElapsedTime();
	if ( guiWeapon.GetCurrent() )
	{
		guiWeapon.GetCurrent()->RecoverFromJam();
//		guiWeapon.GetCurrent()->SetJamCount( model->m_timeToUnjam );
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::DestroySubsystem()
{
	Check_Object(this);
	
	Weapon::DestroySubsystem();
	EnterNeverExecuteState();
	IgnorePostCollision();
	CleanUpProjectiles();
	ClearAmmo();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ProjectileWeapon::CleanUpProjectiles()
{
	Check_Object(this);
	
	fireRequest = false;
	projectilesFired = 0;
}

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

