#include "rp.h"
#pragma hdrstop

#include "weapsys.h"
#include "rivet.h"
#include "demopack.h"
#include "..\munga\line.h"
#include "..\munga\explode.h"
#include "..\munga\boxsolid.h"
#include "vtvmppr.h"
#include "vtv.h"
#include "..\munga\app.h"

//#############################################################################
// Shared Data Support
//
GenericGun::SharedData
	GenericGun::DefaultData(
		GenericGun::GetClassDerivations(),
		GenericGun::MessageHandlers,
		GenericGun::GetAttributeIndex(),
		GenericGun::StateCount
	);

Derivation* GenericGun::GetClassDerivations()
{
	static Derivation classDerivations(VTVSubsystem::GetClassDerivations(), "GenericGun");
	return &classDerivations;
}

//#############################################################################
// Messaging Support
//
const Receiver::HandlerEntry
	GenericGun::MessageHandlerEntries[]=
{
	MESSAGE_ENTRY(GenericGun, ConfigureMappables),
	MESSAGE_ENTRY(GenericGun, ChooseButton)
};

Receiver::MessageHandlerSet
	GenericGun::MessageHandlers(
		ELEMENTS(GenericGun::MessageHandlerEntries),
		GenericGun::MessageHandlerEntries,
		VTVSubsystem::GetMessageHandlers()
	);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	GenericGun::ConfigureMappablesMessageHandler(
		ReceiverDataMessageOf<ControlsButton> *message
	)
{
	Check(this);
	Check(message);

	//---------------------------------------------------------------------
	// If the hardwired button was pressed, process presses of the mappable
	// buttons, otherwise it was released, so erase any temporary mappings
	//---------------------------------------------------------------------
	if (message->dataContents > 0)
	{
		EnterConfiguration(
			&triggerState,						// direct target
			this,									// receiver
			(Receiver::MessageID) 0,		// activation message ID (none here)
			ChooseButtonMessageID			// configuration message ID
		);
	}
	else
	{
		ExitConfiguration();
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	GenericGun::ChooseButtonMessageHandler(
		ReceiverDataMessageOf<ControlsButton> *message
	)
{
	if (message->dataContents > 0)
	{
		//
		//---------------------------------------------------------------------
		// Find the control manager of the vehicle, and have it turn off an
		// existing mapping of a given type if it is there, otherwise it should
		// create a new one
		//---------------------------------------------------------------------
		//
		VTV* vtv = GetEntity();
		Check(vtv);
		VTVControlsMapper* controls =
			Cast_Object(
				VTVControlsMapper*,
				vtv->GetSubsystem(VTV::ControlsMapperSubsystem)
			);
		Check(controls);

		controls->AddOrErase(message->dataContents, &triggerState);
	}
	Check_Fpu();
}

//#############################################################################
// Attribute Support
//
const GenericGun::IndexEntry
	GenericGun::AttributePointers[]=
{
	ATTRIBUTE_ENTRY(GenericGun, TriggerState, triggerState),
	ATTRIBUTE_ENTRY(GenericGun, PercentDone, percentDone)
};

GenericGun::AttributeIndexSet& GenericGun::GetAttributeIndex()
{
	static GenericGun::AttributeIndexSet attributeIndex(ELEMENTS(GenericGun::AttributePointers),
		GenericGun::AttributePointers,
		VTVSubsystem::GetAttributeIndex()
	);
	return attributeIndex;
}

//#############################################################################
// Model Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void GenericGun::CalcAmmoOrigin(Origin *ammo_origin)
{
	Check(this);
	VTV *vtv = GetEntity();
	Check(vtv);
	//
	//-------------------------------
	// Check if we are looking behind
	//-------------------------------
	//
	VTVControlsMapper *ctrl_sub =
	(VTVControlsMapper *) vtv->GetSubsystem(VTV::ControlsMapperSubsystem);

	LinearMatrix site_transform;


	if(ctrl_sub->lookBehind > 0)
	{
		site_transform = siteRearTransform;
	}
	else
	{
		site_transform = siteFrontTransform;
	}
	LinearMatrix temp;
	temp.Multiply(site_transform, vtv->localToWorld);
	*ammo_origin = temp;
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	GenericGun::GenericGunSimulation(Scalar)
{
	VTV *vtv = GetEntity();
	Check(vtv);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// General Firing Simulation
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	if ( (triggerState > 0) && (vtv->GetSimulationState() != VTV::BurningState) )
	{
		Fire();
		SetSimulationState(Firing);
	}
	else
	{
		SetSimulationState(DefaultState);
	}
	Check_Fpu();
}


//#############################################################################
//	Construction And Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
GenericGun::GenericGun(
	VTV			*owner,
	int			subsystem_ID,
	SubsystemResource	*sub_res,
	SharedData		&shared_data
):
	VTVSubsystem(
		owner,
		subsystem_ID,
		sub_res,
		shared_data,
		&triggerState,					// direct mapping for controls
		(Receiver::MessageID) 0		// assumes no event mapping
	)
{
	Check(owner);
	Check_Pointer(sub_res);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Get the Resource Variables
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	ammoVideoResourceID = sub_res->ammoVideoResourceID;

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Initialize the Trigger to Released
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	triggerState	=	-1;
	oldTriggerState = triggerState;

	SetPerformance(&GenericGun::GenericGunSimulation);
	SetSimulationState(DefaultState);
	percentDone = 0.0f;
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Assumption:  No Joints in between a Gun and the
	// 		root node of a vtv, so we can get the
	//		transformation matrix to get to the
	//		position of the site so we know where
	//		the bullet is to come out of
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	EntitySegment *site_front_segment = owner->GetSegment(sub_res->segmentIndex);
	Check(site_front_segment);
	siteFrontTransform = site_front_segment->GetSegmentToEntity();

	if(sub_res->rearSiteIndex == -1)
	{
	   siteRearTransform =	siteFrontTransform;
	}
	else
	{
		EntitySegment *site_rear_segment = owner->GetSegment(sub_res->rearSiteIndex);
      Check(site_rear_segment);
		siteRearTransform = site_rear_segment->GetSegmentToEntity();
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
GenericGun::~GenericGun()
{
	Check(this);
	Check_Fpu();
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	GenericGun::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem
//
int
	GenericGun::CreateStreamedSubsystem(
		NotationFile			*model_file,
		const char				*model_name,
		const char				*subsystem_name,
		SubsystemResource		*subsystem_resource,
		NotationFile			*subsystem_file,
		ResourceFile			*resource_file,
		const ResourceDirectories	*directories
	)
{
	Check(model_file);
	Check_Pointer(model_name);
	Check_Pointer(subsystem_name);
	Check_Pointer(subsystem_resource);
	Check(subsystem_file);
	Check(resource_file);
	Check_Pointer(directories);

	if (
		!Subsystem::CreateStreamedSubsystem(
			model_file,
			model_name,
			subsystem_name,
			subsystem_resource,
			subsystem_file,
			directories
		)
	)
	{
		Check_Fpu();
		return False;
	}

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//	Save the model size and classID
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
	subsystem_resource->classID = RegisteredClass::RivetGunClassID;

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Get the name of the ammo model file which holds
	// all the info about the type of ammo this gun shoots
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	const char *ammo_model_file;
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"AmmoModelFile",
			&ammo_model_file
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing AmmoModelFile!\n" << std::flush;
		Check_Fpu();
		return False;
	}

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Find the description of the ammo in the current resource file
	// Note:  The ammo must be build before the gun which uses it
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	ResourceDescription *rivet_desc =
	resource_file->FindResourceDescription(
		ammo_model_file,
		ResourceDescription::ModelListResourceType
	);
	if(!rivet_desc)
	{
		DEBUG_STREAM <<
			ammo_model_file << " must be std::declared before "<< model_name <<
			" in the  .bld file" << std::endl << std::flush;
		Check_Fpu();
		return False;
	}
	subsystem_resource->ammoVideoResourceID = rivet_desc->resourceID;
	const char *rear_site_name;

	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"RearSiteName",
			&rear_site_name
		)
	)
	{
		subsystem_resource->rearSiteIndex = -1;
	}
	else
	{
		subsystem_resource->rearSiteIndex = Get_Segment_Index(
			model_file,
			model_name,
			directories,
			rear_site_name);
	}

		Check_Fpu();
	return True;
}

//############################################################################
//################		Class	Rivet Gun     ##################################
//############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
RivetGun::SharedData
	RivetGun::DefaultData(
		RivetGun::GetClassDerivations(),
		RivetGun::MessageHandlers,
		RivetGun::GetAttributeIndex(),
		RivetGun::StateCount
	);

Derivation* RivetGun::GetClassDerivations()
{
	static Derivation classDerivations(GenericGun::GetClassDerivations(), "RivetGun");
	return &classDerivations;
}

//#############################################################################
// Attribute Support
//
const RivetGun::IndexEntry
	RivetGun::AttributePointers[]=
{
	ATTRIBUTE_ENTRY(RivetGun, TimeToLoaded,	timeToLoaded),
	ATTRIBUTE_ENTRY(RivetGun, AmmoCount,		ammoCount)
};

RivetGun::AttributeIndexSet& RivetGun::GetAttributeIndex()
{
	static RivetGun::AttributeIndexSet attributeIndex(ELEMENTS(RivetGun::AttributePointers),
		RivetGun::AttributePointers,
		GenericGun::GetAttributeIndex()
	);
	return attributeIndex;
}

//#############################################################################
//	Construction And Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RivetGun::RivetGun(
	VTV			*owner,
	int			subsystem_ID,
	SubsystemResource	*sub_res,
	SharedData		&shared_data
)
	: GenericGun(owner, subsystem_ID, sub_res, shared_data)
{
	Check(owner);
	Check_Pointer(sub_res);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//	Get Resource Info
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	maxAmmoCount = sub_res->ammoCount;
	ammoCount		= maxAmmoCount;
	loadTime			=  sub_res->loadTime;
	muzzleVelocity		= sub_res->muzzleVelocity;

	timeToLoaded	=  loadTime;

	SetPerformance(&RivetGun::RivetGunSimulation);
	SetSimulationState(Loading);
	Check_Fpu();
}

RivetGun::~RivetGun()
{
	Check(this);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	RivetGun::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());
}

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

void RivetGun::CalcAmmoMotion(Motion *ammo_motion)
{
	Check(this);
	VTV *vtv = GetEntity();
	Check(vtv);
	*ammo_motion = vtv->localVelocity;
	ammo_motion->linearMotion.x += muzzleVelocity.x;
	ammo_motion->linearMotion.y += muzzleVelocity.y;
	ammo_motion->linearMotion.z -= muzzleVelocity.z;
	ammo_motion->angularMotion = Vector3D::Identity;

	Check_Fpu();
}

//#############################################################################
// Damage Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	RivetGun::DeathReset(int reset_command)
{
	//
	//---------------------------------------------------
	// Make sure we are not firing and reset variables
	//---------------------------------------------------
	//
	Check(this);
	if (reset_command == VTV::FootballReset)
	{
		ammoCount = maxAmmoCount;
		timeToLoaded = 0.0f;
		SetSimulationState(Loaded);
	}
	else
	{
		SetSimulationState(Loading);
	}
	Check_Fpu();
}

//#############################################################################
// Model Support
//
void
	RivetGun::RivetGunSimulation(Scalar time_slice)
{
	Check(this);
	VTV *vtv = GetEntity();
	Check(vtv);
	percentDone = (loadTime - timeToLoaded)/ loadTime;
	Check_Fpu();
	if(percentDone > 1.0f)
	{
		percentDone = 1.0f;
	}
	if(percentDone < 0.0f)
	{
		percentDone = 0.0f;
	}
	Verify(percentDone >= 0.0f);
	Verify(percentDone <= 1.0f);
	switch(GetSimulationState())
	{
		case NoAmmo:
Gun_NoAmmo:
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// If there is no ammo and we are trying to fire--Misfire
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
			if( (triggerState > 0) && (vtv->GetSimulationState() != VTV::BurningState) )
			{
				SetSimulationState(MisFire);
				SetSimulationState(NoAmmo);
			}
			break;
		case Loading:
Gun_Loading:
		{
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// See if there is any Ammo Left
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
			if(ammoCount <= 0)
			{
				SetSimulationState(NoAmmo);
				goto Gun_NoAmmo;
			}
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//	Ensure we don't run over our time slice
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
			Scalar time_left = time_slice - timeToLoaded;
			timeToLoaded -= time_slice;
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// If we are done loading update the time slice
		//	and goto the firing state
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//

			if(timeToLoaded <= 0.0f)
			{
				timeToLoaded = 0.0f;
				time_slice = time_left;
            SetSimulationState(Loaded);
				goto Gun_Loaded;
			}
			break;
		}
		case Loaded:
Gun_Loaded:
			if(
				(triggerState > 0)
				&&
				(vtv->GetSimulationState() != VTV::BurningState)
			)
			{
				goto Gun_Firing;
			}
			break;
		case Firing:
Gun_Firing:
			SetSimulationState(Firing);
			Fire();
			SetSimulationState(Loading);
			goto Gun_Loading;
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	RivetGun::Fire()
{
	Check(this);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// std::decrement ammoCount and reset the loadTime
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	--ammoCount;
	timeToLoaded = loadTime;
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Get the entity and it's current position
	// Add this to the siteTransform to determine
	// where to shoot the rivet from
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//

	Motion	rivet_motion;
	CalcAmmoMotion(&rivet_motion);

	//
	//-----------------------------
	// Move the rivet to the site
	//-----------------------------
	//
	Origin rivet_localOrigin;
	CalcAmmoOrigin(&rivet_localOrigin);

	VTV *vtv = GetEntity();
	Check(vtv);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Create a New Rivet Whose properties are listed in the
	// Resource File
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Rivet::MakeMessage
		create_rivet(
			Rivet::MakeMessageID,
			sizeof(Rivet::MakeMessage),
			(Entity::ClassID) Rivet::RivetClassID,
			RivetGunClassID,
			ammoVideoResourceID,
			Rivet::DefaultFlags,
			rivet_localOrigin,
			rivet_motion,
			Motion::Identity,
			vtv->GetEntityID()
		);
	#if DEBUG_LEVEL>0
		Rivet* my_rivet = Rivet::Make(&create_rivet);
		Register_Object(my_rivet);
	#else
		Rivet::Make(&create_rivet);
	#endif
	Check_Fpu();
	return 1;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem
//
int
	RivetGun::CreateStreamedSubsystem(
		NotationFile			*model_file,
		const char			*model_name,
		const char			*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile			*subsystem_file,
		ResourceFile			*resource_file,
		const ResourceDirectories	*directories
	)
{

	if (
		!GenericGun::CreateStreamedSubsystem(
			model_file,
			model_name,
			subsystem_name,
			subsystem_resource,
			subsystem_file,
			resource_file,
			directories
		)
	)
	{
		Check_Fpu();
		return False;
	}

	subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
	subsystem_resource->classID = RegisteredClass::RivetGunClassID;
	 if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"LoadTime",
			&subsystem_resource->loadTime
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing LoadTime!\n" << std::flush;
		return False;
	}
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"AmmoCount",
			&subsystem_resource->ammoCount
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing AmmoCount!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	const char*	muzzle_velocity;
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"MuzzleVelocity",
			&muzzle_velocity
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing MuzzleVelocity!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	else
	{
		Convert_From_Ascii(muzzle_velocity, &subsystem_resource->muzzleVelocity);
	}
	Check_Fpu();
	return True;
}


//############################################################################
//################		Class	Laser Gun     ##################################
//############################################################################

//#############################################################################
// Shared Data Support
//
LaserGun::SharedData
	LaserGun::DefaultData(
		LaserGun::GetClassDerivations(),
		LaserGun::MessageHandlers,
		LaserGun::GetAttributeIndex(),
		LaserGun::StateCount
	);

Derivation* LaserGun::GetClassDerivations()
{	static Derivation classDerivations(GenericGun::GetClassDerivations(), "LaserGun");
	return &classDerivations;
}


//#############################################################################
// Attribute Support
//
const LaserGun::IndexEntry
	LaserGun::AttributePointers[]=
{
	ATTRIBUTE_ENTRY(LaserGun, FrontLaserOn, frontLaserOn),
	ATTRIBUTE_ENTRY(LaserGun, RearLaserOn, rearLaserOn),
	ATTRIBUTE_ENTRY(LaserGun, LaserScale,	laserScale)
};

LaserGun::AttributeIndexSet& LaserGun::GetAttributeIndex()
{
	static LaserGun::AttributeIndexSet attributeIndex(ELEMENTS(LaserGun::AttributePointers),
		LaserGun::AttributePointers,
		GenericGun::GetAttributeIndex()
	);
	return attributeIndex;
}

//#############################################################################
// Model Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LaserGun::ReadUpdateRecord(Simulation::UpdateRecord *update_record)
{
	Check(this);
	Check_Pointer(update_record);
	GenericGun::ReadUpdateRecord(update_record);
	UpdateRecord *laser_update = (UpdateRecord*) update_record;

	laserScale = laser_update->laserScale;
	frontLaserOn = laser_update->frontLaserOn;
	rearLaserOn = laser_update->rearLaserOn;

	Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LaserGun::WriteUpdateRecord(
		Simulation::UpdateRecord *update_record,
		int update_model
	)
{
	Check(this);
	Check_Pointer(update_record);

	GenericGun::WriteUpdateRecord(update_record, update_model);
	UpdateRecord *laser_update = (UpdateRecord*) update_record;
	laser_update->recordLength = sizeof(*laser_update);
	laser_update->laserScale = laserScale;
	laser_update->frontLaserOn = frontLaserOn;
	laser_update->rearLaserOn = rearLaserOn;
	Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LaserGun::CalcPercentDone()
{
	percentDone =  chargeLevel / maxChargeLevel;
	Verify(percentDone >= 0.0f);
	Verify(percentDone <= 1.0f);
	Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LaserGun::ChargingSimulation(Scalar time_slice)
{
	Check(this);
	VTV *vtv = GetEntity();
	Check(vtv);

	chargeLevel += chargePerSecond * time_slice;
	if (chargeLevel >= maxChargeLevel)
	{
		chargeLevel = maxChargeLevel;
		SetSimulationState(DefaultState);
	}
	if (triggerState > 0 && vtv->GetSimulationState() != VTV::BurningState)
	{
		Fire();
	}

	CalcPercentDone();
	// Check_Fpu();	See CalcPercentDone, above.
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LaserGun::PulseFiringSimulation(Scalar time_slice)
{
	Check(this);
	VTV *vtv = GetEntity();
	Check(vtv);
	pulseDuration -= time_slice;
	if (pulseDuration <= 0.0f)
	{
		if (triggerState > 0 && vtv->GetSimulationState() != VTV::BurningState)
		{
			Fire();
		}
		else
		{
			frontLaserOn = False;
			rearLaserOn = False;
			ForceUpdate();
			SetSimulationState(Charging);
			SetPerformance(&LaserGun::ChargingSimulation);
		}
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LaserGun::FiringSimulation(Scalar time_slice)
{
	//
	//-----------------------------------------------------------------------
	// If our time with the laser is done, look at the trigger state and jump
	// to either the charging or pulsing state
	//-----------------------------------------------------------------------
	//
	laserDuration -= time_slice;
	if (laserDuration <= 0.0f)
	{
		SetSimulationState(PulseFiring);
		SetPerformance(&LaserGun::PulseFiringSimulation);
		pulseDuration = maxPulseDuration;
		frontLaserOn = False;
		rearLaserOn = False;
		ForceUpdate();
	}
	Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	LaserGun::Fire()
{
	Check(this);

	//
	//---------------------------------------------
	// Make sure we have enough power before firing
	//---------------------------------------------
	//
	Scalar drain = drainPerSecond;
	if (chargeLevel < drain)
	{
		SetSimulationState(Misfire);
		return False;
	}
	chargeLevel -= drain;
	SetPerformance(&LaserGun::FiringSimulation);
	laserDuration = maxLaserDuration;
	SetSimulationState(Firing);
	ForceUpdate();

	//
	//------------------------------
	// Calculate the max laser line
	//-----------------------------
	//
	Origin laser_origin;
	CalcAmmoOrigin(&laser_origin);

	//
	//-------------------------------
	// Check if we are looking behind
	//-------------------------------
	//
	Vector3D z_axis;
	VTV *vtv = GetEntity();
	Check(vtv);
	VTVControlsMapper *ctrl_sub =
		(VTVControlsMapper *) vtv->GetSubsystem(VTV::ControlsMapperSubsystem);
	if (ctrl_sub->lookBehind > 0)
	{
		z_axis.x = 0.0; z_axis.y = 0.0f; z_axis.z = 1.0;
		rearLaserOn = True;
	}
	else
	{
		z_axis.x = 0.0f; z_axis.y = 0.0f; z_axis.z = -1.0f;
		frontLaserOn = True;
	}
	Vector3D line_direction;
	line_direction.Multiply( z_axis , vtv->localToWorld);

	UnitVector	unit_direction;
	unit_direction = line_direction;
	Line
		laser_line(laser_origin.linearPosition, unit_direction, maxLaserLength);
	Check(&laser_line);

	//
	//------------------------------------
	// See if the laser line hit anything
	//------------------------------------
	//
	BoxedSolid* target = vtv->FindBoxedSolidHitBy(&laser_line, vtv);

	laserScale.z = laser_line.length / 100.0f;

	Check_Fpu();

	if (target)
	{
		//
		//-----------------------
		// Find the entity we hit
		//-----------------------
		//
		Simulation *sim = target->GetOwningSimulation();
		Check(sim);
		Entity *entity;
		if (sim->IsDerivedFrom(*Subsystem::GetClassDerivations()))
		{
			Subsystem *subsys = (Subsystem*)sim;
			Check(subsys);
			entity = subsys->GetEntity();
		}
		else
		{
			entity = (Entity*)sim;
		}
		Check(entity);
		//
		//--------------------------------------------
		// Make the explosion at the end of the laser
		//--------------------------------------------
		//
		Origin
			explode_origin = laser_origin;
		laser_line.FindEnd(&explode_origin.linearPosition);

		//
		//------------------
		// Make an Explosion
		//------------------
		//
		Check(application);
		ResourceFile
			*resource_file = application->GetResourceFile();
		Check(resource_file);

		ResourceDescription
			*explosion_res =
				resource_file->FindResourceDescription(explosionResourceID);
		Check(explosion_res);
		explosion_res->Lock();

		Explosion::MakeMessage
			exp_message(
				Explosion::MakeMessageID,
				sizeof(Explosion::MakeMessage),
				ExplosionClassID,
				EntityID::Null,
				explosion_res->resourceID,
				Explosion::DefaultFlags,
				explode_origin,
				entity->GetEntityID(),
				owningEntity->GetEntityID()
			);

		explosion_res->Unlock();

		#if DEBUG_LEVEL>0
			Explosion
				*exp =
		#endif
					Explosion::Make(&exp_message);
			Register_Object(exp);

		//
		//--------------------------------------------------------
		// Initialize the Positional info in the damage structure
		//--------------------------------------------------------
		//
		damageData.damageForce = Vector3D::Identity;
		damageData.impactPoint = explode_origin.linearPosition;
		//
		//------------------------------------------
		// Send a damage message to the thing we hit
		//------------------------------------------
		//
		Entity::TakeDamageMessage
			take_damage(
				Entity::TakeDamageMessageID,
				sizeof(Entity::TakeDamageMessage),
				vtv->GetEntityID(),
				0,
				damageData
			);
		entity->Dispatch(&take_damage);
		Check_Fpu();
		return True;
	}
	else
	{
		Check_Fpu();
		return False;
	}
}

//#############################################################################
// Damage Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LaserGun::DeathReset(int reset_command)
{
	Check(this);
	if (reset_command == VTV::FootballReset)
	{
		chargeLevel = maxChargeLevel;
	}
	frontLaserOn = False;
	rearLaserOn = False;
	laserScale.x = 1.0f;
	laserScale.y = 1.0f;
	laserScale.z = 1.0f;
	SetSimulationState(Charging);
	SetPerformance(&LaserGun::ChargingSimulation);
	ForceUpdate();
	Check_Fpu();
}

//#############################################################################
//	Construction And Destruction
//
LaserGun::LaserGun(
	VTV	*owner,
	int	subsystem_ID,
	SubsystemResource	*sub_res,
	SharedData	&shared_data
)
	: GenericGun(owner, subsystem_ID, sub_res, shared_data)
{
	Check(owner);
	Check_Pointer(&sub_res);

	//
	//------------------------------------
	// get values from subsystem resource
	//------------------------------------
	//
	maxChargeLevel = 1.0f;
	maxLaserLength = sub_res->laserLength;
	fullChargeDuration = sub_res->fullChargeDuration;
	chargePerSecond = sub_res->chargePerSecond;
	explosionResourceID = sub_res->explosionResourceID;
	maxLaserDuration = sub_res->laserDuration;
	maxPulseDuration = sub_res->pulseDuration;
	Scalar cycle_time = maxLaserDuration + maxPulseDuration;
	damageData = sub_res->damageData;
	damageData.damageAmount *= cycle_time;
	drainPerSecond = sub_res->drainPerSecond * cycle_time;

	//
	//-----------------------
	// Init local variables
	//-----------------------
	//
	chargeLevel = 1.0f;
	percentDone = 1.0f;
	laserDuration = 0.0f;
	hitSomething = False;
	frontLaserOn = False;
	rearLaserOn = False;
	laserScale.x = 1.0f;
	laserScale.y = 1.0f;
	laserScale.z = 1.0f;
	pulseDuration = 0.0f;

	SetPerformance(&LaserGun::ChargingSimulation);
	if (owner->GetInstance() == VTV::ReplicantInstance)
	{
		ExecuteOnUpdate();
	}
	SetSimulationState(Charging);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
LaserGun::~LaserGun()
{

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	LaserGun::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem
//
int
	LaserGun::CreateStreamedSubsystem(
		NotationFile			*model_file,
		const char			*model_name,
		const char			*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile			*subsystem_file,
		ResourceFile			*resource_file,
		const ResourceDirectories	*directories
	)
{

	if (
		!GenericGun::CreateStreamedSubsystem(
			model_file,
			model_name,
			subsystem_name,
			subsystem_resource,
			subsystem_file,
				resource_file,
			directories
		)
	)
	{
		Check_Fpu();
		return False;
	}

	subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
	subsystem_resource->classID = RegisteredClass::LaserDrillClassID;
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"ChargePerSecond",
			&subsystem_resource->chargePerSecond
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing ChargePerSecond!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"DrainPerSecond",
			&subsystem_resource->drainPerSecond
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing DrainPerSecond!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	 if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"DamageAmount",
			&subsystem_resource->damageData.damageAmount
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing DamageAmount!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	else
	{
		//
		//-------------------------
		// Initialize damage fields
		//-------------------------
		//
		subsystem_resource->damageData.damageType = Damage::LaserDamageType;
		subsystem_resource->damageData.damageForce = Vector3D::Identity;
		subsystem_resource->damageData.surfaceNormal.x = 0.0f;
		subsystem_resource->damageData.surfaceNormal.y = 1.0f;
		subsystem_resource->damageData.surfaceNormal.z = 0.0f;
		subsystem_resource->damageData.impactPoint = Vector3D::Identity;
	}
	 if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"FullChargeDuration",
			&subsystem_resource->fullChargeDuration
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing FullChargeDuration!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	else
	{
		//
		//-----------------
		// Calc chargeLevel
		//-----------------
		//
		subsystem_resource->chargeLevel =
				subsystem_resource->fullChargeDuration /
				subsystem_resource->drainPerSecond;
	}
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"PulseDuration",
			&subsystem_resource->pulseDuration
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing PulseDuration!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"LaserLength",
			&subsystem_resource->laserLength
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing LaserLength!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"LaserDuration",
			&subsystem_resource->laserDuration
		)
	)
	{
		DEBUG_STREAM << subsystem_name << "missing LaserDuration!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	//
	//----------------------------------------------------------------------
	//	Read in the Model Filename of the Explosion renderable, then find its
	// resource ID in the resource file
	//----------------------------------------------------------------------
	//
	const char *explosion;
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"ExplosionModelFile",
			&explosion
		)
	)
	{
		std::cout << model_name <<  " missing ExplosionModelFile!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	ResourceDescription *res =
		resource_file->FindResourceDescription(
			explosion,
			ResourceDescription::ModelListResourceType
		);
	if (!res)
	{
		std::cout << model_name <<  " cannot find " << explosion
		 << " in resource file!\n" << std::flush;
		Check_Fpu();
		return False;
	}
	subsystem_resource->explosionResourceID = res->resourceID;
	return True;
}


//############################################################################
//################		Class	DemolitionPackDropper   ########################
//############################################################################

//#############################################################################
// Shared Data Support
//
DemolitionPackDropper::SharedData
	DemolitionPackDropper::DefaultData(
		DemolitionPackDropper::GetClassDerivations(),
		DemolitionPackDropper::MessageHandlers,
		DemolitionPackDropper::GetAttributeIndex(),
		DemolitionPackDropper::StateCount
	);

Derivation* DemolitionPackDropper::GetClassDerivations()
{	static Derivation classDerivations(RivetGun::GetClassDerivations(), "DemolitionPackDropper");
	return &classDerivations;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	DemolitionPackDropper::DeathReset(int)
{
	//
	//---------------------------------------------------
	// Make sure we are not firing and reset variables
	//---------------------------------------------------
	//
	Check(this);
	SetSimulationState(Loading);
	Check_Fpu();
}

//#############################################################################
// 	Construction And Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DemolitionPackDropper::DemolitionPackDropper(
	VTV	*owner,
	int	subsystem_ID,
	SubsystemResource	*sub_res,
	SharedData	&shared_data
)
	: RivetGun(owner, subsystem_ID, sub_res, shared_data)
{ }

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DemolitionPackDropper::~DemolitionPackDropper()
{
	Check(this);
	Check_Fpu();
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	DemolitionPackDropper::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	DemolitionPackDropper::Fire()
{
	Check(this);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// std::decrement ammoCount and reset the loadTime
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	--ammoCount;
	timeToLoaded = loadTime;
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Get the entity and it's current position
	// Add this to the siteTransform to determine
	// where to shoot the rivet from
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//

	Motion	mine_motion;
	CalcAmmoMotion(&mine_motion);

	//
	//-----------------------------
	// Move the rivet to the site
	//-----------------------------
	//
	Origin mine_localOrigin;
	CalcAmmoOrigin(&mine_localOrigin);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Create a New Mine Whose properties are listed in the
	// Resource File
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	VTV *vtv = GetEntity();
	Check(vtv);
	DemolitionPack::MakeMessage
		create_mine(
			DemolitionPack::MakeMessageID,
			sizeof(Rivet::MakeMessage),
			(Entity::ClassID) DemolitionPack::DemolitionPackClassID,
			DemolitionPackDropperClassID,
			ammoVideoResourceID,
			DemolitionPack::DefaultFlags,
			mine_localOrigin,
			mine_motion,
			Motion::Identity,
			vtv->GetEntityID()
		);
	#if DEBUG_LEVEL>0
		DemolitionPack* my_mine = DemolitionPack::Make(&create_mine);
		Register_Object(my_mine);
	#else
		DemolitionPack::Make(&create_mine);
	#endif
	Check_Fpu();
	return 1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem
//
int
	DemolitionPackDropper::CreateStreamedSubsystem(
		NotationFile			*model_file,
		const char			*model_name,
		const char			*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile			*subsystem_file,
		ResourceFile			*resource_file,
		const ResourceDirectories	*directories
	)
{

	if (
		!RivetGun::CreateStreamedSubsystem(
			model_file,
			model_name,
			subsystem_name,
			subsystem_resource,
			subsystem_file,
				resource_file,
			directories
		)
	)
	{
		Check_Fpu();
		return False;
	}

	subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
	subsystem_resource->classID = RegisteredClass::DemolitionPackDropperClassID;

	Check_Fpu();
	return True;
}
