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

#include "MW4Headers.hpp"

#include "WeaponMover.hpp"
#include "Vehicle.hpp"
#include "DeathEntity.hpp"
#include "missile.hpp"

#include <Adept\Map.hpp>
#include <Adept\Effect.hpp>
#include <Adept\NameTable.hpp>
#include <Adept\EntityManager.hpp>
#include <Adept\Effect.hpp>

#include <gosFX\gosFX.hpp>
#include <ElementRenderer\Element.hpp>

#include "MWDamageObject.hpp"
#include <Adept\CollisionGrid.hpp>
#include <Adept\CollisionVolume.hpp>
#include "mwmission.hpp"
#include "bucket.hpp"
#include "MWApplication.hpp"

#include <Compost\CompostHeaders.hpp>

//#############################################################################
//###############################    WeaponMover    ###########################
//#############################################################################

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

DWORD MechWarrior4::Executed_WeaponMover_Count = 0;


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


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	WeaponMover::InitializeClass()
{
	Check_Object(BaseClass::DefaultData);
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			WeaponMoverClassID,
			"MechWarrior4::WeaponMover",
			BaseClass::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);

	INDIRECT_STATE_ATTRIBUTE(
		WeaponMover,
		ExecutionState,
		executionState,
		WeaponMover__ExecutionStateEngine
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		WeaponMover__GameModel,
		MaxDistance,
		maxDistance,
		Scalar
	);

// MSL 5.03 Min Distance
//	DIRECT_GAME_MODEL_ATTRIBUTE(
//		WeaponMover__GameModel,
//		MinDistance,
//		minDistance,
//		Scalar
//	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		WeaponMover__GameModel,
		TrailResource,
		trailResource,
		ResourceID
	);

	CUSTOM_DIRECT_GAME_MODEL_ATTRIBUTE(
		WeaponMover__GameModel,
		DoesPlaceCrater,
		doesPlaceCrater,
		bool,
		BoolClassID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		WeaponMover__GameModel,
		HitEffectResource,
		hitEffectResourceID,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		WeaponMover__GameModel,
		SecondaryHitEffectResource,
		secondaryHitEffectResourceID,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		WeaponMover__GameModel,
		DamageDecalResource,
		damageDecalResourceID,
		ResourceID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		WeaponMover__GameModel,
		LightAmpFlareOut,
		m_lightAmpFlareOut,
		Scalar
	);
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
WeaponMover*
	WeaponMover::Make(
		CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(message);
	gos_PushCurrentHeap(Heap);
	Check_Object(EntityManager::GetInstance());
	WeaponMover *new_entity;
	Entity *entity = EntityManager::GetInstance()->RequestFromArmory(message->dataListID);
	if (entity)
	{
		new_entity = Cast_Object(WeaponMover*, entity);
		new_entity->Reuse(message, base_id);
	}
	else
		new_entity = new WeaponMover(DefaultData, message, base_id, NULL);
	gos_PopCurrentHeap();
	Check_Object(new_entity);
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
WeaponMover::WeaponMover(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Mover(class_data, message, base_id, element),
	splashedEntities(NULL),
	trailEffect(NULL)
{
	Check_Pointer(this);
	Check_Object(message);

	damageAmount = message->damageAmount;
	heatAmount = message->heatAmount;
	maxDistance = message->maxDistance;
// MSL 5.03 Min Distance
//	minDistance = message->minDistance;
	
	itemID = message->itemID;

	Check_Object(entityElement);
	const LinearMatrix4D &local_to_parent = entityElement->GetNewLocalToParent();
	Check_Object(&local_to_parent);

	startPoint = local_to_parent;

	inflictingEntityID = message->inflictingEntityID;

	if (MWApplication::GetInstance()->networkingFlag == true)
	{
		MWMission *mission;
		Check_Object (MWMission::GetInstance());
		mission = Cast_Object (MWMission *,MWMission::GetInstance());
		mission->m_BucketManager->NotifyAction (CBucketManager::SHOTS_FIRED,inflictingEntityID,1);
	}
	
	Check_Object(Network::GetInstance());
	Connection	*connection = Network::GetInstance()->GetConnection(message->inflictingEntityID.connectionID);
	Check_Object(connection);

	Replicator *replicator;
	replicator = connection->FindReplicator(message->inflictingEntityID);
	Verify(replicator->IsDerivedFrom(MWObject::DefaultData));
	inflictingVehicle = Cast_Object(MWObject*, replicator);
	dontHit = inflictingVehicle;

	const GameModel *model = GetGameModel();
	Check_Object(model);
	if(model->trailResource != ResourceID::Null)
	{
		CreateMissileTrail();
	}

	splashSize = 0;


	
	splashRadius = message->splashRadius;
	percentageOfDamageToDirectHit = message->percentageOfDamageToDirectHit;
	minPercentageOfDamageToSphereHit = message->minPercentageOfDamageToSphereHit;
	maxPercentageOfDamageToSphereHit = message->maxPercentageOfDamageToSphereHit;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
WeaponMover::~WeaponMover()
{
	DESTRUCTOR("WeaponMover");
	Check_Object(this);
}

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

	BaseClass::Reuse(message, base_id);

	damageAmount = message->damageAmount;
	maxDistance = message->maxDistance;
// MSL 5.03 Min Distance
//	minDistance = message->minDistance;

	Check_Object(entityElement);
	const LinearMatrix4D &local_to_parent = entityElement->GetNewLocalToParent();
	Check_Object(&local_to_parent);

	startPoint = local_to_parent;

	inflictingEntityID = message->inflictingEntityID;
	
	Check_Object(Network::GetInstance());
	Connection	*connection = Network::GetInstance()->GetConnection(message->inflictingEntityID.connectionID);
	Check_Object(connection);

	Replicator *replicator;
	replicator = connection->FindReplicator(message->inflictingEntityID);
	Verify(replicator->IsDerivedFrom(MWObject::DefaultData));
	inflictingVehicle = Cast_Object(MWObject*, replicator);
	dontHit = inflictingVehicle;

	const GameModel *model = GetGameModel();
	Check_Object(model);
	if(model->trailResource != ResourceID::Null)
	{
		CreateMissileTrail();
	}

	splashSize = 0;

	splashRadius = message->splashRadius;
	percentageOfDamageToDirectHit = message->percentageOfDamageToDirectHit;
	minPercentageOfDamageToSphereHit = message->minPercentageOfDamageToSphereHit;
	maxPercentageOfDamageToSphereHit = message->maxPercentageOfDamageToSphereHit;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void WeaponMover::SentenceToDeathRow()
{
	Check_Object(this);

	if(trailEffect.GetCurrent())
		trailEffect.GetCurrent()->executionState->RequestState(Effect::ExecutionStateEngine::StoppingState);

	BaseClass::SentenceToDeathRow();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	WeaponMover::GetExecutionSlot()
{
	Check_Object(this);
	return BuildingExecutionSlot;
}

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

	if (oldCollisions)
	{
		worldSpaceVelocity = Motion3D::Identity;
		localSpaceVelocity = Motion3D::Identity;
		worldSpaceAcceleration = Motion3D::Identity;
		localSpaceAcceleration = Motion3D::Identity;
		SentenceToDeathRow();
		UsePostCollision();
		BaseClass::PreCollisionExecute(till);
		return;
	}
	Vector3D distance;
	Point3D initial_position;
	initial_position = startPoint;
	Point3D pointStartThisFrame;
	pointStartThisFrame = GetLocalToParent();

	distance.Subtract(pointStartThisFrame, initial_position);
	Scalar maxDistanceSquared = maxDistance * maxDistance;
// MSL 5.03 Min Distance
//	Scalar minDistanceSquared = minDistance * minDistance;

//	if(distance.GetLengthSquared() >= maxDistanceSquared)
//	{
//		Set_Statistic(Executed_WeaponMover_Count, Executed_WeaponMover_Count+1);
//		UsePostCollision();
//		SentenceToDeathRow();
//		return;
//	}
	//
	//----------------------------------------
	// Let our base class set up the variables
	//----------------------------------------
	//
	Set_Statistic(Executed_WeaponMover_Count, Executed_WeaponMover_Count+1);
	UsePostCollision();
	BaseClass::PreCollisionExecute(till);

	Check_Object(executionState);
	int execution_state = executionState->GetState();
	if(execution_state == ExecutionStateEngine::StraightLineMotionState)
	{
		Point3D current_position;
		ElementRenderer::Element *element = GetElement();
		current_position = element->GetNewLocalToParent();
		distance.Subtract(current_position, initial_position);
		//
		// We still need to run post collision this frame on anything within our
		// range, we can't just skip in the same frame as we went to far so clamp
		// the far point.
		//
		if(distance.GetLengthSquared() >= maxDistanceSquared)
		{
			//
			// Clamp the end point
			//
			Vector3D direction;
				direction.Normalize(distance);
			Line3D line;
			line.m_origin = initial_position;
			line.m_direction = direction;
			line.m_length = maxDistance+1.0f;
			line.FindEnd(&pointStartThisFrame); // reusing variable
			LinearMatrix4D new_local_to_parent;
			new_local_to_parent = element->GetNewLocalToParent();
			new_local_to_parent.BuildTranslation(pointStartThisFrame);
			SetNewLocalToParent(new_local_to_parent);

		}
	}

	BlowUpIfOffMap();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#if defined(LAB_ONLY)
	void WeaponMover::SyncMatrices(bool update_matrix)
	{
		SYNC_LOGIC("WeaponMover");
		BaseClass::SyncMatrices(update_matrix);
	}
#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	WeaponMover::CollisionHandler(
		Stuff::LinearMatrix4D *new_position,
		Stuff::DynamicArrayOf<CollisionData> *collisions
	)
{
	Check_Object(this);
	Check_Object(collisions);
	Verify(collisions->GetLength() == 1);

	Verify(EntityManager::GetInstance()->IsInPostCollisionExecution(this));
	newCollisions = collisions;
	Verify(GetInterestLevel() != DormantInterestLevel);

	//
	//-----------------------
	// Time for us to blow up
	//-----------------------
	//
	if (oldCollisions)
		SentenceToDeathRow();
	else
		// make sure we're on the post collision chain
	{

		Verify(collisions->GetLength() == 1);
		CollisionData *data = &(*collisions)[0];
		const GameModel *model = GetGameModel();
		Check_Object(model);
    //
		// Make sure we're not past our effective range. This is most important for missiles because burn time
		// makes their distance hard to judge,and we want them to not do damage once they run out of fuel.
		//
		//
		Vector3D distance;
		Point3D initial_position;
		initial_position = startPoint;
		Point3D pointStartThisFrame;
		pointStartThisFrame = data->m_worldIntersectionPoint;

		distance.Subtract(pointStartThisFrame, initial_position);
		Scalar maxDistanceSquared = (maxDistance + 1.5f) * (maxDistance + 1.5f);
// MSL 5.03 Min Distance
//		Scalar minDistanceSquared = (minDistance + 1.5f) * (minDistance + 1.5f);
		//
		//---------------------------------
		// Create the hit and damage decals
		//			Do this here since it causes problems in post collision
		//---------------------------------
		//

		//
		// We are not going to do ground hit effects if they are under water, but we will if it hits anything else
		//
		Check_Object (Map::GetInstance());
		const Adept::Map__GameModel *mapModel = Map::GetInstance()->GetGameModel();
		Check_Object(mapModel);

		if(distance.GetLengthSquared() <= maxDistanceSquared)
		{
			if (data->m_worldIntersectionPoint.y >= ((mapModel->m_waterLevel) -.001f) ||
				 !data->m_otherEntity || 
					!data->m_otherEntity->IsDerivedFrom(Map::DefaultData))

			{

				CreateHitEffect(
					data->m_normal,
					data->m_worldIntersectionPoint,
					data->m_material
				);

				if(data->m_otherEntity)
				{
					if(!(data->m_otherEntity->IsDerivedFrom(Map::DefaultData)))
					{
					CreateSecondaryHitEffect(
						data->m_normal,
						data->m_worldIntersectionPoint,
						data->m_material,
						data->m_otherEntity
					);
					}

					if((data->m_otherEntity->IsDerivedFrom(Map::DefaultData)) &&
						(model->doesPlaceCrater))
					{
						CreateDamageDecal(
							data->m_otherEntity,
							data->m_normal,
							data->m_worldIntersectionPoint
						);
						if(Compost::TerrainTextureLogistic::Instance)
						{
							const GameModel *model = GetGameModel();
							Check_Object(model);
							if(model->m_craterID >= 0)
							{
								Point3D local_to_world;
								local_to_world = GetLocalToWorld();
								Compost::TerrainTextureLogistic::Instance->AddFeature(
									model->m_craterID, local_to_world.x, local_to_world.z);
							}
						}
					}	// if it's the map and we need a crater
					else if(data->m_otherEntity->IsDerivedFrom(MWMover::DefaultData))
					{
						MWMover *hit_mover;
						hit_mover = Cast_Object(MWMover *, data->m_otherEntity);
						
						if(hit_mover->GetParentVehicle())
						{
							if(hit_mover->GetParentVehicle()->IsDerivedFrom(MWObject::DefaultData))
							{
								MWObject *hit_object;
								hit_object = Cast_Object(MWObject *, hit_mover->GetParentVehicle());

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

								if(hit_object->vehicleInterface)
								{
									hit_object->vehicleInterface->SetLightAmpFlareOut(model->m_lightAmpFlareOut);
								}
							}
						}	// if it's the player mech
					} // else (it's an object)
				} // if we hit an object (non-map)
			}	// if we hit nothing (just in case) an object (non-map), or we hit above water
		}
		//
		// if we just hit water then splash and continue
		//
		initialLocalToParent = GetLocalToWorld();
		initialLocalToParent.BuildTranslation(data->m_worldIntersectionPoint);


		if (data->m_otherEntity && data->m_material == WaterMaterial)
		{
			Point3D pointIntersection;
			pointIntersection = data->m_worldIntersectionPoint;
			Vector3D normalIntersection;
			normalIntersection.Multiply(data->m_normal, -.001f);
			pointIntersection += normalIntersection;
			initialLocalToParent.BuildTranslation(pointIntersection);
	


			delete newCollisions;
			newCollisions = NULL;

			//
			// Take water collision off since we are now done with it.
			//

/*
			CollisionMask currentMask;

			currentMask = GetCollisionMask();
			currentMask &= (~Entity::WaterSurfaceFlag);
			SetCollisionMask(currentMask);
*/
		}

		*new_position = initialLocalToParent;
		SetNewLocalToParent(*new_position);
		lastParameterization = gos_GetElapsedTime();
		return true;

	}

//
//	Moved to actual post collision processing as per JM's request to make sure splash damage works correctly.
//
//	else
//		PostCollision(collisions);
	return false;
}

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

	Vector3D distance;
	Point3D initial_position;
	initial_position = startPoint;
	Point3D pointStartThisFrame;
	pointStartThisFrame = GetLocalToParent();
	distance.Subtract(pointStartThisFrame, initial_position);
	//
	// Need to put on a slop factor of 1.5 just in case the last frame went past the max length
	// and ended up clamping it to +1, we don't want to not apply damage under those circumstances.
	//
	Scalar maxDistanceSquared = (maxDistance) * (maxDistance);
	Scalar maxDistanceSquaredForDamage = (maxDistance + 1.5f) * (maxDistance + 1.5f);
	Scalar distanceSquared = distance.GetLengthSquared();
// MSL 5.03 Min Distance
//	Scalar minDistanceSquared = (minDistance) * (minDistance);
//	Scalar minDistanceSquaredForDamage = (minDistance + 1.5f) * (minDistance + 1.5f);
	//
	// [johnyo] According to JM we need to assign the splash damage during 
	//					post collision, or else the splash search may not work.
	//
	// Don't apply damage if we are past our range.
	//
	if (distanceSquared >= maxDistanceSquared)
	{
		//
		// There is a slop factor that needs to happen when we clamp the last frame of the motion to the
		// max distance + 1.0 
		//
		if (distanceSquared <= maxDistanceSquaredForDamage && newCollisions && !oldCollisions) // this would indicate we just hit things, and it's not time to die
		{

			PostCollision(newCollisions);
		}
		
		BaseClass::PostCollisionExecute(till);
		//
		// Missiles will continue their path and succomb to gravity and the ground eventually.
		//
		if (!this->IsDerivedFrom(Missile::DefaultData))
		{
			SentenceToDeathRow();
		}
	}
	else
	{
		if (newCollisions && !oldCollisions) // this would indicate we just hit things, and it's not time to die
		{

			PostCollision(newCollisions);
		}
		BaseClass::PostCollisionExecute(till);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	WeaponMover::PostCollision(Stuff::DynamicArrayOf<CollisionData> *collisions)
{
	Check_Object(this);
	POSTCOLLISION_LOGIC("WeaponMover::Explode");
	
	#ifdef _ARMOR	// otherwise, this doesn't compile -- PAULTOZ
	const GameModel *model = GetGameModel();
	Check_Object(model);
	#endif

	Verify(collisions->GetLength() == 1);
	CollisionData *data = &(*collisions)[0];

	//This function should be called by all collision handlers that are derived
	//From WeaponMover
	//
	//---------------------------------
	// Apply damage to the thing we hit
	//---------------------------------
	//

	if (splashRadius == 0.0f)
	{
// MSL 5.06 Armor Mode
		MWApplication *m_App;
		m_App = MWApplication::GetInstance ();
		m_ArmorMode = m_App->GetLocalNetParams()->m_armormodeOn;

		Entity::TakeDamageMessage damage_message(
			data->m_otherEntity->GetReplicatorID(),
			inflictingEntityID,
			damageAmount,
			m_ArmorMode,
			GetDamageType(),		//Telling it that I am a projectile
			data->m_normal,
			data->m_worldIntersectionPoint,
			heatAmount,
			false,
			itemID
		);
		
		data->m_otherEntity->Receive(&damage_message);
		//data->m_otherEntity->Dispatch(&damage_message);
		
	}
	else
	{
		DealSplashDamage(data);
	}
	
}

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

void WeaponMover::DealSplashDamage(const CollisionData *data)
{
	Check_Object(this);
	Check_Object(CollisionGrid::Instance);
	POSTCOLLISION_LOGIC("WeaponMover::Splash");
	
    extern g_nMR;
    if (g_nMR == 2)
        return;

	Stuff::Sphere sphere;

//
// We need to apply direct damage before checking for splash damage radius. Otherwise if the 
// collision info is a little off, or the collision radius is too small, no damage is done
//
// MSL 5.04 Building Missile Damage
	// if we hit something deal a set percentage to that part
	if (data->m_otherEntity != NULL && data->m_otherEntity != Map::GetInstance())
	{
		if (data->m_otherEntity->IsDerivedFrom(Building::DefaultData))
		{
// MSL 5.06 Armor Mode
			MWApplication *m_App;
			m_App = MWApplication::GetInstance ();
			m_ArmorMode = m_App->GetLocalNetParams()->m_armormodeOn;

			Entity::TakeDamageMessage damage_message(
					data->m_otherEntity->GetReplicatorID(),
					inflictingEntityID,
					damageAmount,			//Do full damage
					m_ArmorMode,
					GetDamageType(),		//Telling it that I am a projectile
					data->m_normal,
					data->m_worldIntersectionPoint,
					heatAmount,
					false,
					itemID
				);
	
			data->m_otherEntity->Receive(&damage_message);
		}
		else
		{
// MSL 5.06 Armor Mode
			MWApplication *m_App;
			m_App = MWApplication::GetInstance ();
			m_ArmorMode = m_App->GetLocalNetParams()->m_armormodeOn;

			Entity::TakeDamageMessage damage_message(
					data->m_otherEntity->GetReplicatorID(),
					inflictingEntityID,
					damageAmount * percentageOfDamageToDirectHit, // Do percentage of full damage
					m_ArmorMode,
					GetDamageType(),		//Telling it that I am a projectile
					data->m_normal,
					data->m_worldIntersectionPoint,
					heatAmount * percentageOfDamageToDirectHit,
					false,
					itemID
				);

			data->m_otherEntity->Receive(&damage_message);
		}
	}
	
	//
	// The splash damage has to be applied to the collision location, not the current entity location
	//		While this is especially true for proximity teleports it's true of slow frame rates as well.
	//sphere.center = GetLocalToWorld();
	sphere.center = data->m_worldIntersectionPoint;
	sphere.radius = splashRadius;

	CollisionGrid::Instance->FindEntitiesWithin(sphere, (Adept::CollisionGrid::WithinCallback)SplashCallBack, this);


	if (splashSize == 0)
		return;

	// iterate the list	

	ChainIteratorOf<Entity *> splashed_entities(&splashedEntities);
	//
	// First, iterate through the list once to clear the been hit flag so we don't do
	// double/or more damage to multiple entities, connected to the same damage zones
	//
	Entity *hit_entity;
	while((hit_entity = splashed_entities.ReadAndNext()) != NULL)
	{	
		if (hit_entity->damageObject)
			hit_entity->damageObject->ClearTakenDamageFromThisEvent();
	}
	// Set the damage object that received direct damage so it won't take more.
	if (data->m_otherEntity && data->m_otherEntity->damageObject)
	{
		data->m_otherEntity->damageObject->SetTakenDamageFromThisEvent();
	}
	splashed_entities.First();

	Scalar splash_damage = damageAmount * (1.0f - percentageOfDamageToDirectHit);
	Scalar heat_damage = heatAmount * (1.0f - percentageOfDamageToDirectHit);

	while((hit_entity = splashed_entities.GetCurrent()) != NULL)
	{	
		if (hit_entity->damageObject)
		{
			//
			// If there is a damage object verify we aren't hitting it twice
			//
			if (!hit_entity->damageObject->GetTakenDamageFromThisEvent())
			{
				hit_entity->damageObject->SetTakenDamageFromThisEvent() ;
			}
			else
			{
			 splashed_entities.Remove();
			 continue;
			}
		}

		if (data->m_otherEntity != hit_entity) // don't double deal
		{
// MSL 5.06 Armor Mode
			MWApplication *m_App;
			m_App = MWApplication::GetInstance ();
			m_ArmorMode = m_App->GetLocalNetParams()->m_armormodeOn;

			Verify(splashSize != 0);
			Adept::Entity__TakeDamageMessage message(
				hit_entity->GetReplicatorID(),
				inflictingEntityID,
				splash_damage,
				m_ArmorMode,
				SplashDamageType,
				data->m_normal,
				data->m_worldIntersectionPoint,
				heat_damage,
				false,
				itemID
			);
			
			hit_entity->Receive(&message);
		}
		splashed_entities.Remove();
	}

	splashSize = 0;

}

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

void WeaponMover::SplashCallBack(Entity *entity, Entity *original_caller, const Stuff::Sphere &sphere)
{

	Check_Object(entity);
	Check_Object(original_caller);

	WeaponMover *weapon = Cast_Object( WeaponMover*, original_caller);


	if (!entity->IsDestroyed())
	{
		
		CollisionVolume *volume = entity->GetHierarchicalVolume();
		if (volume)
		{

			volume->FindSolidsWithin(
				sphere,
				(Adept::CollisionVolume::WithinCallback)SubSplashCallBack,
				weapon,
				entity,
				entity->GetLocalToWorld()
			);	
		}
		else
		{
			weapon->splashedEntities.Add(entity);
			++weapon->splashSize;
		}
	}
}



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

void WeaponMover::SubSplashCallBack(CollisionVolume *solid, Entity *original_caller, const Stuff::Sphere &sphere)
{

	Check_Object(solid);
	Check_Object(original_caller);

	WeaponMover *weapon = Cast_Object( WeaponMover*, original_caller);


	weapon->splashedEntities.Add(solid->m_entity);

	++weapon->splashSize;

}


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

void
	WeaponMover::CreateHitEffect(
		Normal3D normal,
		Point3D target_point,
		BYTE hit_material
	)
{
	Check_Object(this);
	
	const GameModel *model = GetGameModel();
	Check_Object(model);
	
	ResourceID hit_effect_resource_id = ResourceID::Null;

	if((model->hitEffectMaterialTable != ResourceID::Null) &&
		(hit_material >= 0) && (hit_material < MaterialCount))
	{
		Entity__GameModel::GetEffectResourceID(&hit_effect_resource_id,
			model->hitEffectMaterialTable, hit_material
		);
	}
	else
	{
		hit_effect_resource_id = model->hitEffectResourceID;
	}

	if(hit_effect_resource_id != ResourceID::Null)
	{
		Resource hit_effect_resource(hit_effect_resource_id);
		Verify(hit_effect_resource.DoesResourceExist());

		ClassID class_ID;
		class_ID = GetClassIDFromDataListID(hit_effect_resource_id);
		Verify(class_ID != NullClassID);

		LinearMatrix4D effect_position = LinearMatrix4D::Identity;
		effect_position.AlignLocalAxisToWorldVector(normal, Y_Axis, X_Axis, Z_Axis);
		effect_position.BuildTranslation(target_point);

		Effect::CreateMessage effect_create_message(
			sizeof(Effect::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			Effect::DefaultFlags,
			hit_effect_resource_id,
			effect_position,
			0.0f,
			ExecutionStateEngine::AlwaysExecuteState,
			NameTable::NullObjectID,
			Entity::DefaultAlignment
		);
		MemoryStream stream(&effect_create_message, effect_create_message.messageLength);

		Entity *entity;
		ReplicatorID beam_id = Connection::Hermit->GetNextReplicatorID();
		entity = CreateEntity(&stream, &beam_id, false);
		Check_Object(entity);
		Check_Object(Map::GetInstance());
		Map::GetInstance()->AddChild(entity);
		entity->SyncMatrices(true);

		// Sound stuff here...
	}
}

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

void
	WeaponMover::CreateSecondaryHitEffect(
		Normal3D normal,
		Point3D target_point,
		BYTE hit_material,
		Entity *entity_hit
	)
{
	Check_Object(this);
	
	const GameModel *model = GetGameModel();
	Check_Object(model);
	
	ResourceID hit_effect_resource_id = ResourceID::Null;

	if((model->secondaryHitEffectMaterialTable != ResourceID::Null) &&
		(hit_material >= 0) && (hit_material < MaterialCount))
	{
		Entity__GameModel::GetEffectResourceID(&hit_effect_resource_id,
			model->secondaryHitEffectMaterialTable, hit_material
		);
	}
	else
	{
		hit_effect_resource_id = model->secondaryHitEffectResourceID;
	}

	if(hit_effect_resource_id != ResourceID::Null)
	{
		Resource hit_effect_resource(hit_effect_resource_id);
		Verify(hit_effect_resource.DoesResourceExist());

		ClassID class_ID;
		class_ID = GetClassIDFromDataListID(hit_effect_resource_id);
		Verify(class_ID != NullClassID);

		LinearMatrix4D effect_position = LinearMatrix4D::Identity;
		effect_position.AlignLocalAxisToWorldVector(normal, Y_Axis, X_Axis, Z_Axis);
		effect_position.BuildTranslation(target_point);

		Effect::CreateMessage effect_create_message(
			sizeof(Effect::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			Effect::DefaultFlags,
			hit_effect_resource_id,
			effect_position,
			0.0f,
			ExecutionStateEngine::AlwaysExecuteState,
			NameTable::NullObjectID,
			Entity::DefaultAlignment
		);
		MemoryStream stream(&effect_create_message, effect_create_message.messageLength);

		Effect *entity;
		ReplicatorID beam_id = Connection::Hermit->GetNextReplicatorID();
		entity = Cast_Object(Effect *, CreateEntity(&stream, &beam_id, false));
		Check_Object(entity);
		Check_Object(Map::GetInstance());
		Map::GetInstance()->AddChild(entity);
		entity->SyncMatrices(true);

		if(entity_hit)
		{
			// We need the local offset from the entity for where we hit. 
			LinearMatrix4D entity_hit_world_to_local;
			entity_hit_world_to_local = entity_hit->GetLocalToWorld();
			entity_hit_world_to_local.Invert();
			LinearMatrix4D target_point_local_to_world(target_point);
			LinearMatrix4D target_point_local_to_entity;
			target_point_local_to_entity.Multiply(target_point_local_to_world, entity_hit_world_to_local);

			entity->SetFollowEntity(entity_hit);
			entity->PlaceOnEntity(target_point_local_to_entity);
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	WeaponMover::CreateDamageDecal(
		Entity *hit_entity,
		Normal3D normal,
		Point3D target_point
	)
{
	Check_Object(this);
	Check_Object(Map::GetInstance());

	const Map__GameModel *model = Map::GetInstance()->GetGameModel();
	Check_Object(model);
	if (model->m_damageCraterResourceID != ResourceID::Null)
	{
		Resource damage_effect_resource(model->m_damageCraterResourceID);
		Verify(damage_effect_resource.DoesResourceExist());
		ClassID class_ID;
		class_ID = GetClassIDFromDataListID(model->m_damageCraterResourceID);
		Verify(class_ID != NullClassID);

		LinearMatrix4D effect_position = LinearMatrix4D::Identity;
		effect_position.AlignLocalAxisToWorldVector(normal, Y_Axis, X_Axis, Z_Axis);
		effect_position.BuildTranslation(target_point);

		DeathEntity::CreateMessage decal_create_message(
			sizeof(DeathEntity::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			DeathEntity::DefaultFlags,
			model->m_damageCraterResourceID,
			effect_position,
			0.0f,
			ExecutionStateEngine::NeverExecuteState,
			NameTable::NullObjectID,
			Entity::DefaultAlignment
		);
		MemoryStream stream(&decal_create_message, decal_create_message.messageLength);

		Entity *entity;
		ReplicatorID beam_id = Connection::Hermit->GetNextReplicatorID();
		entity = CreateEntity(&stream, &beam_id, false);
		Check_Object(entity);
		Check_Object(hit_entity);
		hit_entity->AddChild(entity);
		entity->SyncMatrices(true);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	WeaponMover::BlowUpIfOffMap()
{
	Check_Object(this);

	//
	//--------------------------------------------------
	//Need to check to see if we are moving off the edge
	//--------------------------------------------------
	//
	ElementRenderer::Element *element = GetElement();
	Check_Object(element);

	Point3D new_translation;
	new_translation = element->GetNewLocalToParent();
	Check_Object(Map::GetInstance());
	if(Map::GetInstance()->FindZone(new_translation) == -1 || HasTriedToMoveOffMapThisFrameFlag())
	{
		LinearMatrix4D current_local_to_world = LinearMatrix4D::Identity;
		current_local_to_world = element->GetLocalToWorld();
		SetNewLocalToParent(current_local_to_world);
		SentenceToDeathRow();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	WeaponMover::CreateMissileTrail()
{
	Check_Pointer(this);

	const GameModel *model = GetGameModel();
	Check_Object(model);
	Resource trail_resource(model->trailResource);
	if(trail_resource.DoesResourceExist())
	{
		ClassID class_ID;
		class_ID = GetClassIDFromDataListID(model->trailResource);
		Verify(class_ID != NullClassID);

		LinearMatrix4D initial_position = LinearMatrix4D::Identity;
		initial_position.Multiply(gosFX::Effect_Against_Motion, GetElement()->GetNewLocalToParent());
		
		Effect::CreateMessage effect_create_message(
			sizeof(Effect::CreateMessage),
			DefaultEventPriority,
			CreateMessage::DefaultFlags,
			class_ID,
			Effect::DefaultFlags|Effect::LoopFlag,
			model->trailResource,
			initial_position,
			0.0f,
			ExecutionStateEngine::AlwaysExecuteState,
			NameTable::NullObjectID,
			Entity::DefaultAlignment
		);
		MemoryStream stream(&effect_create_message, effect_create_message.messageLength);

		Entity *entity;
		ReplicatorID trail_id = Connection::Hermit->GetNextReplicatorID();
		entity = CreateEntity(&stream, &trail_id, false);
		Check_Object(entity);
		Check_Object(Map::GetInstance());
		Map::GetInstance()->AddChild(entity);
		trailEffect.Remove();
		trailEffect.Add(Cast_Object(Effect *, entity));
		trailEffect.GetCurrent()->SetFollowEntity(this);
		trailEffect.GetCurrent()->executionState->RequestState(Effect::ExecutionStateEngine::RunningState);
		entity->SyncMatrices(true);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
char 
	WeaponMover::GetDamageType()
{
	Check_Object(this);

	if((itemID >= FirstBeamID) && 
		(itemID <= LastBeamID))
			return BeamDamageType;

	if((itemID >= FirstMissileID) && 
		(itemID <= LastMissileID))
			return MissileDamageType;


	return ProjectileDamageType;
}

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


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