#include "rp.h"
#pragma hdrstop

#include "vtv.h"
#include "vtvmppr.h"
#include "vtvpwr.h"
#include "weapsys.h"
#include "thruster.h"
#include "booster.h"
#include "chute.h"
#include "rpplayer.h"
#include "..\munga\door.h"
#include "..\munga\line.h"
#include "..\munga\random.h"
#include "..\munga\fileutil.h"
#include "..\munga\boxsolid.h"
#include "..\munga\app.h"
#include "..\munga\hostmgr.h"
#include "..\munga\namelist.h"

//#############################################################################
// Shared Data Support
//
Derivation* VTV::GetClassDerivations()
{	static Derivation classDerivations(JointedMover::GetClassDerivations(), "VTV");
	return &classDerivations;
}


VTV::SharedData
	VTV::DefaultData(
		VTV::GetClassDerivations(),
		VTV::MessageHandlers,
		VTV::GetAttributeIndex(),
		VTV::StateCount,
		(Entity::MakeHandler)VTV::Make
	);

//#############################################################################
// Message Support
//
const VTV::HandlerEntry
	VTV::MessageHandlerEntries[]=
{
	MESSAGE_ENTRY(VTV, TakeDamage),
	MESSAGE_ENTRY(VTV, Kavorkian),
	MESSAGE_ENTRY(VTV, MarkSpot),
	MESSAGE_ENTRY(VTV, RestoreToSpot),
	MESSAGE_ENTRY(VTV, ZoomIn),
	MESSAGE_ENTRY(VTV, ZoomOut),
   MESSAGE_ENTRY(VTV, PlayerLink)
};

VTV::MessageHandlerSet
	VTV::MessageHandlers(
		ELEMENTS(VTV::MessageHandlerEntries),
		VTV::MessageHandlerEntries,
		JointedMover::GetMessageHandlers()
	);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::PlayerLinkMessageHandler(PlayerLinkMessage *message)
{
	Entity::PlayerLinkMessageHandler(message);
	RPPlayer
		*rp_player = Cast_Object(RPPlayer*, playerLink);
	rp_player->SetPlayerVehicle(this);

	//
	//----------------------------------------------------------------------
	// If our player is a runner, add two boosters to each booster subsystem
	//----------------------------------------------------------------------
	//
	switch (rp_player->playerType)
	{
	case RPPlayer::RunnerPlayerType:
		{
			Check_Pointer(subsystemArray);
			for (int i=0; i<subsystemCount; ++i)
			{
				if (subsystemArray[i])
				{
					Check(subsystemArray[i]);
					if (subsystemArray[i]->GetClassID() == BoosterClassID)
					{
						Booster *booster = (Booster*)subsystemArray[i];
						booster->maxBoosters += 2;
						booster->boostersRemaining += 2;
					}
				}
			}
		}
		goto Is_A_Scorer;

	case RPPlayer::GeneralPlayerType:
	case RPPlayer::CrusherPlayerType:
	case RPPlayer::BlockerPlayerType:
Is_A_Scorer:
		rp_player->SetScoringPlayerFlag();
		break;
	}
	Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::TakeDamageMessageHandler(TakeDamageMessage *message)
{
	Check(this);
	Check(message);

	//
	//-------------------------------------------------
	// Check If the damage was caused by another Player
	//-------------------------------------------------
	//
	Check(application);
	HostManager
		*host = application->GetHostManager();
	Check(host);
	Entity
		*offensive_entity = Cast_Object(
			Entity*,
			host->GetEntityPointer(message->inflictingEntity)
		);

	//
	//------------------------------------
	// make sure we can enter something...
	//------------------------------------
	//
	if (offensive_entity)
	{
		Check(offensive_entity);
		RPPlayer
			*offensive_player = (RPPlayer*)offensive_entity->GetPlayerLink();

		if (offensive_player)
		{
			//
			//---------------------------------------------------------------
			// Notify this player that damaged was caused by offensive_player
			//---------------------------------------------------------------
			//
			Check(offensive_player);
			RPPlayer::OffensivePlayer
				*shooter	= new RPPlayer::OffensivePlayer(
					offensive_player,
					message->damageData.damageType,
					Now()
				);
			Register_Object(shooter);
			RPPlayer
				*our_player = (RPPlayer*)GetPlayerLink();
			Check(our_player);
			our_player->AddOffensivePlayer(*shooter);
		}
	}

	//
	//--------------------------------------------------------------------
	// We we have received a collision packet, we should calculate our own
	// damage loss
	//--------------------------------------------------------------------
	//
	switch (message->damageData.damageType)
	{
		case Damage::CollisionDamageType:
		{
			//
			//-----------------------------------------------------------
			// Find out who hit us, and get our velocity relative to them
			//-----------------------------------------------------------
			//
			Check(application);
			HostManager
				*host = application->GetHostManager();
			Check(host);
			Mover
				*mover = (Mover*)host->GetEntityPointer(message->inflictingEntity);
			Verify(mover->IsDerivedFrom(Mover::GetClassDerivations()));
			Vector3D
				v;
			v.Subtract(worldLinearVelocity, mover->worldLinearVelocity);

			//
			//-----------------------------------------------------------------------
			// The normal to bounce against is passed via the damage force, so see if
			// it opposes our motion.  If not, we can ignore this message
			//-----------------------------------------------------------------------
			//
			if (v*message->damageData.surfaceNormal <= 0.0f)
			{
				Check_Fpu();
				return;
			}

			//
			//----------------------------------------------------------------------
			// Otherwise, do a dynamic bounce, and apply the damage to ourselves.
			// We save the original positions of the vehicles because we do not know
			// what time slice to use
			//----------------------------------------------------------------------
			//
			Point3D
				old_us = localOrigin.linearPosition,
				old_them = mover->localOrigin.linearPosition;
			Scalar
				elasticity = elasticityCoefficient;

			message->damageData.surfaceNormal.x =
				-message->damageData.surfaceNormal.x;
			message->damageData.surfaceNormal.y =
				-message->damageData.surfaceNormal.y;
			message->damageData.surfaceNormal.z =
				-message->damageData.surfaceNormal.z;

			message->damageData.damageAmount =
				DynamicBounce(
					mover,
					0.1f,
					1.0f,
					message->damageData.surfaceNormal,
					&elasticity
				);
			localOrigin.linearPosition = old_us;
			mover->localOrigin.linearPosition = old_them;
			break;
		}

		default:
		{
			message->damageData.damageForce /= moverMass;
			Check_Fpu();
			worldLinearVelocity  += message->damageData.damageForce;
			break;
		}
	}
	Entity::TakeDamageMessageHandler(message);
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		Damage
			damage;

		Check_Pointer(damageZones);
		Check(*damageZones);
		damage.damageType = Damage::ExplosiveDamageType;
		damage.damageAmount =
			1.0f / (*damageZones)->damageScale[Damage::ExplosiveDamageType];
		Check_Fpu();
		damage.damageForce = Vector3D::Identity;
		(*damageZones)->TakeDamage(damage);
	}
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		savedOrigin = localOrigin;
	}
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		Reset(savedOrigin, VTV::RegularReset);
	}
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		targetRangeExponent;
		if (--targetRangeExponent < 0.0f)
		{
			targetRangeExponent = 0.0f;
		}
	}
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		if (++targetRangeExponent > 5.0f)
		{
			targetRangeExponent = 5.0f;
		}
	}
	Check_Fpu();
}

//#############################################################################
// Attribute Support
//
const VTV::IndexEntry
	VTV::AttributePointers[]=
{
	ATTRIBUTE_ENTRY(VTV, HeightAboveTerrain,		heightAboveTerrain),
	ATTRIBUTE_ENTRY(VTV, ThrusterPitch,				thrusterPitch),
	ATTRIBUTE_ENTRY(VTV, CollisionState,			collisionState),
	ATTRIBUTE_ENTRY(VTV, CollisionMaterialType,	collisionMaterialType),
	ATTRIBUTE_ENTRY(VTV, CollisionNormal,			collisionNormal),
	ATTRIBUTE_ENTRY(VTV, CollisionSpeed,			collisionSpeed),
	ATTRIBUTE_ENTRY(VTV, EyepointRotation,			eyepointRotation),
	ATTRIBUTE_ENTRY(VTV, BoosterOn,					boosterOn),
	ATTRIBUTE_ENTRY(VTV, BoosterScale,				boosterScale),
	ATTRIBUTE_ENTRY(VTV, BoosterSmokeDensity,		boosterSmokeDensity),
	ATTRIBUTE_ENTRY(VTV, ForwardVelocity,			forwardVelocity),
	ATTRIBUTE_ENTRY(VTV, CompassHeading,			compassHeading),
	ATTRIBUTE_ENTRY(VTV, TargetReticle,				targetReticle),
	ATTRIBUTE_ENTRY(VTV, NavigationRange,			navigationRange),
	ATTRIBUTE_ENTRY(VTV, NavigationLinearPosition,navigationLinearPosition),
	ATTRIBUTE_ENTRY(VTV, NavigationAngularPosition,navigationAngularPosition),
	ATTRIBUTE_ENTRY(VTV, ScoreState,					scoreState),
	ATTRIBUTE_ENTRY(VTV, ScoreType,					scoreType),
	ATTRIBUTE_ENTRY(VTV, HornBlast,					hornBlast) // HACK - ECH 7/31/95 - For replicants
};

VTV::AttributeIndexSet& VTV::GetAttributeIndex()
{
	static VTV::AttributeIndexSet attributeIndex(ELEMENTS(VTV::AttributePointers),
		VTV::AttributePointers,
		JointedMover::GetAttributeIndex()
	);
	return attributeIndex;
}

//#############################################################################
// Model support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::SetMappingSubsystem(VTVControlsMapper *mapper)
{
	Check(this);
	Check(mapper);

	Check_Pointer(subsystemArray);
	if (subsystemArray[ControlsMapperSubsystem])
	{
		Unregister_Object(subsystemArray[ControlsMapperSubsystem]);
		delete subsystemArray[ControlsMapperSubsystem];
	}
	subsystemArray[ControlsMapperSubsystem] = mapper;
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	VTV::BoosterOn()
{
	Check(this);
	for(int ii=BasicSubsystemCount;ii<subsystemCount;ii++)
	{
		Subsystem
			*subsystem = GetSubsystem(ii);
		if (subsystem)
		{
			Check(subsystem);
			if (subsystem->GetClassID() == BoosterClassID)
			{
				Booster
					*booster = (Booster*) subsystem;
				Check(booster);
				if (booster->GetSimulationState() == Booster::BoosterBurning)
				{
					Check_Fpu();
					return True;
				}
			}
		}
	}
	Check_Fpu();
	return False;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::MoveAndCollide(Scalar time_slice)
{
	Check(this);
	JointSubsystem
		*joint_subsystem  = GetJointSubsystem();
	Check(joint_subsystem);
	Verify(joint_subsystem->GetJointCount());

	VTVControlsMapper
		*mapper = Cast_Object(
			VTVControlsMapper*,
			GetSubsystem(VTV::ControlsMapperSubsystem)
		);
	Check(mapper);

	//
	//-------------------------------------------------------------------------
	// Figure out the average height and thruster rotation.  While doing this,
	// calculate the differences in the corner forces so that we can figure out
	// the roll and pitch accelerations due to terrain
	//-------------------------------------------------------------------------
	//
	heightAboveTerrain = 0.0f;
	thrusterPitch.rotationAmount = 0.0f;
	Vector3D difference = Vector3D::Identity;
	int valid_thruster=0;

	for (int i=0; i<MAX_THRUSTERS; ++i)
	{
		Thruster
			*thruster = (Thruster*) subsystemArray[Thruster1Subsystem + i];
		if (thruster)
		{
			heightAboveTerrain += thruster->currentHeight;
			switch (joint_subsystem->GetJoint(valid_thruster)->GetJointType())
			{

			case Joint::BallJointType:
				thrusterPitch.rotationAmount +=
					joint_subsystem->GetJoint(valid_thruster)->
						GetEulerAngles().pitch;
				break;
			case Joint::HingeXJointType:
				thrusterPitch.rotationAmount +=
					joint_subsystem->GetJoint(valid_thruster)->
						GetHinge().rotationAmount;
				break;

			}
			++valid_thruster;

			//
			//----------------------------------------------------------------
			// Generate the pseudoforce.  Ignore the center engines of the VTV
			//----------------------------------------------------------------
			//
			if ((i == 2 || i == 3) && joint_subsystem->GetJointCount() > 4)
			{
				continue;
			}
			if (thruster->thrusterOffset.x > 0.0f)
			{
				difference.z -= thruster->currentHeight;
			}
			else
			{
				difference.z += thruster->currentHeight;
			}
			if (thruster->thrusterOffset.z > 0.0f)
			{
				difference.x += thruster->currentHeight;
			}
			else
			{
				difference.x -= thruster->currentHeight;
			}
		}
	}
	heightAboveTerrain /= joint_subsystem->GetJointCount();
	thrusterPitch.rotationAmount /= joint_subsystem->GetJointCount();
	Check_Fpu();

	//
	//---------------------------------------------------
	// If we are not burning, follow the terrain and turn
	//---------------------------------------------------
	//
	if (GetSimulationState() != BurningState)
	{
		difference *= angularSpringFactor / (15.0f + 2.0f*heightAboveTerrain);
		Check_Fpu();

		//
		//-------------------------------------------------------------------
		// If this is a two-engine VTV, try to straighten out the pitch value
		//-------------------------------------------------------------------
		//
		if (joint_subsystem->GetJointCount() == 2)
		{
			Scalar
				len =
					localToWorld(2,1)*localToWorld(2,1)
					 + localToWorld(1,1)*localToWorld(1,1);
			if (!Small_Enough(len))
			{
				difference.x = 3.0f*localToWorld(2,1) / Sqrt(len);
				Check_Fpu();
			}
			difference.z *= 0.5f;
		}
		else
		{
			difference *= 0.25f;
		}
		if (groundEffectDomainSquared)
		{
			difference.z -= bankFactor * mapper->powerDemand.x;
			localAcceleration.angularMotion += difference;

			//
			//-------------------------------------
			// Stabilize the craft at high altitude
			//-------------------------------------
			//
			if (heightAboveTerrain > 10.0f)
			{
				Vector3D
					world_g = Vector3D::Identity,
					local_g,
					torque;

				world_g.y = -GetEnvironment()->gravityConstant;
				local_g.MultiplyByInverse(world_g, localToWorld);
				world_g.y = -1.0f;
				torque.Cross(world_g, local_g);
				torque *= momentOfInertia;
				localAcceleration.angularMotion += torque;
			}
		}

		//
		//---------------------------------------
		// Turn the VTV as required by the mapper
		//---------------------------------------
		//
		Vector3D
			vel;
		vel.Multiply(localVelocity.angularMotion, localToWorld);
		Scalar
			step = mapper->angularVelocityDemand.y - vel.y;
		difference = Vector3D::Identity;
		if (step > 0.0f)
		{
			if (maxAngularAcceleration.y * time_slice > step)
			{
				difference.y += step / time_slice;
				Check_Fpu();
			}
			else
			{
				difference.y += maxAngularAcceleration.y;
			}
		}
		else if (step < 0.0f)
		{
			if (maxAngularAcceleration.y * time_slice > -step)
			{
				difference.y += step / time_slice;
				Check_Fpu();
			}
			else
			{
				difference.y -= maxAngularAcceleration.y;
			}
		}

		vel.MultiplyByInverse(difference,localToWorld);
		localAcceleration.angularMotion += vel;
	}

	//
	//--------------------------------------------------------------------
	// Apply the standard air resistance and gravity, then project the VTV
	// motion through the time slice
	//--------------------------------------------------------------------
	//
	ApplyAirResistanceAndGravity();

	Vector3D
		old_acceleration = worldLinearAcceleration;

	worldLinearAcceleration.x *= ZIPPY;
	worldLinearAcceleration.z *= ZIPPY;

	Point3D
		old_position(localOrigin.linearPosition);
	ApplyWorldAccelerations(time_slice);

	//
	//-------------------------------------------------------------------------
	// We have now moved the VTV.  We should now update our collision volume to
	// the new location and check it against the tree
	//-------------------------------------------------------------------------
	//
	Vector3D
		zippy_accel = worldLinearAcceleration,
		impact_velocity = localVelocity.linearMotion;

	worldLinearAcceleration = old_acceleration;
	MoveCollisionVolume();

	//
	//-------------------------------------------
	// If we are going fast enough, do a ray-cast
	//-------------------------------------------
	//
	BoxedSolidCollisionList* collision_list = AllocateCollisionList();
	Line line;
	line = old_position;

	Vector3D step;
	step.Subtract(localOrigin.linearPosition, old_position);
	if (step.LengthSquared() > SMALL)
	{
		line = step;
		Scalar diameter = collisionTemplate->maxX - collisionTemplate->minX;
		if (diameter < line.length)
		{
			CollideCenterOfMotion(&line, collision_list);
		}
	}
	if (!collision_list->GetCollisionCount())
	{
		GetCurrentCollisions(collision_list);
	}

	//
	//-------------------------------
	// Process each of the collisions
	//-------------------------------
	//
	collisionTemporaryState = NoCollisionState;

	Damage
		collision_damage;

	ProcessCollisionList(
		collision_list,
		time_slice,
		old_position,
		&collision_damage
	);

	//
	//-------------------------
	// Set Collision attributes
	//-------------------------
	//
	if (collision_damage.damageAmount > 0.0f)
	{
		BoxedSolid
			*boxed_solid;

		Check(&(*lastCollisionList)[0]);
		boxed_solid = (*lastCollisionList)[0].GetTreeVolume();
		Check(boxed_solid);
		collisionMaterialType = boxed_solid->materialType;

		collisionNormal.MultiplyByInverse(
			collision_damage.surfaceNormal,
			localToWorld
		);
		collisionSpeed = -(collisionNormal * impact_velocity);

      if (GetSimulationState() != BurningState)
      {
		   collisionState.SetState(collisionTemporaryState);
      }

		//
		//---------------------------------------------------------------------
		// Take the damage, allowing any slope less than 45 degrees relative to
		// the keel to take one-tenth the damage
		//---------------------------------------------------------------------
		//
		if (collisionNormal.y > 0.7f)
		{
			collision_damage.damageAmount *= bottomArmorScale;
		}

		Check_Pointer(damageZones);
		Check(*damageZones);

      //TODO:  IMPORTANT COLLISION CODE HERE

		(*damageZones)->TakeDamage(collision_damage);

		//
		//------------------------------------------------------------------
		// If the VTV is burning, apply the world-space force vectors to the
		// craft as torque.  The spinning will start next frame
		//------------------------------------------------------------------
		//
		if (GetSimulationState() == BurningState)
		{
			Vector3D
				force,
				torque;
			Point3D
				moment;

			force.MultiplyByInverse(collision_damage.damageForce, localToWorld);
			moment.MultiplyByInverse(
				(Vector3D)collision_damage.impactPoint,
				localToWorld
			);
			torque.Cross(moment, force);
			torque *= momentOfInertia;
			Clamp(torque.x, -20.0f, 20.0f);
			Clamp(torque.y, -20.0f, 20.0f);
			Clamp(torque.z, -20.0f, 20.0f);
			localVelocity.angularMotion += torque;
		}
	}
	else
	{
		collisionState.SetState(NoCollisionState);
	}

	//
	//------------------------------------------
	// Check to see if we should blow up the VTV
	//------------------------------------------
	//
	{
		InterestManager
			*interest_mgr = application->GetInterestManager();
		Check(interest_mgr);
		InterestZone
			*zone = interest_mgr->GetInterestZone(interestZoneID);
		Check(zone);
		BoundingBoxTree
			*tree = zone->GetExistanceRoot();
		Check(tree);
		Check_Pointer(damageZones);
		Check(*damageZones);
		insideWorld =
			tree->FindBoundingBoxContaining(localOrigin.linearPosition) != NULL;
		if (!insideWorld && (*damageZones)->damageLevel < 1.0f)
		{
			collision_damage.damageType = Damage::ExplosiveDamageType;
			collision_damage.damageAmount =
				1.0f / (*damageZones)->damageScale[Damage::ExplosiveDamageType];
			Check_Fpu();
			collision_damage.damageForce = Vector3D::Identity;
			Check_Pointer(damageZones);
			Check(*damageZones);
			(*damageZones)->TakeDamage(collision_damage);

			localVelocity.angularMotion.x += 8.0f * Random - 4.0f;
			localVelocity.angularMotion.y += 8.0f * Random - 4.0f;
			localVelocity.angularMotion.z += 8.0f * Random - 4.0f;
		}
	}
	worldLinearAcceleration = zippy_accel;
	if (collisionState.GetState() == RestState)
	{
		worldLinearAcceleration = Vector3D::Identity;
	}
	UpdateLocalMotion();

	//
	//-------------------------------------------------
	// Update the stuff that the secondary screen wants
	//-------------------------------------------------
	//
	compassHeading = localToWorld;
	forwardVelocity = -3.6f * localVelocity.linearMotion.z;
	if (targetRangeExponent > currentRangeExponent)
	{
		currentRangeExponent += time_slice;
		if (currentRangeExponent > targetRangeExponent)
		{
			currentRangeExponent = targetRangeExponent;
		}
		navigationRange = 250.0f * pow(2.0f, currentRangeExponent);
	}
	else if (targetRangeExponent < currentRangeExponent)
	{
		currentRangeExponent -= time_slice;
		if (currentRangeExponent < targetRangeExponent)
		{
			currentRangeExponent = targetRangeExponent;
		}
		navigationRange = 250.0f * pow(2.0f, currentRangeExponent);
	}
	Check_Fpu();

	//
	//------------------------------------------------------------------------
	// Run the dead reckoner, and then see how far apart the two positions are
	//------------------------------------------------------------------------
	//
	(this->*deadReckoner)();

	Vector3D
		error;
	error.Subtract(
		projectedOrigin.linearPosition,
		localOrigin.linearPosition
	);
	Quaternion
		angular_deviation;
	angular_deviation.Subtract(
		projectedOrigin.angularPosition,
		localOrigin.angularPosition
	);
	if (
		error.LengthSquared() > 0.04f
		 || Abs(angular_deviation.w) < 0.997f
		 || collisionState.GetState() != collisionState.GetOldState()
		 || lastPerformance - lastUpdate > 2.0f
	)
	{
		ForceUpdate();
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::PerformAndWatch(
		const Time &till,
		MemoryStream *update_stream
	)
{
	Check(this);
	Check(&till);

	if (GetInstance() != ReplicantInstance)
	{
		boosterOn = False;
		boosterScale = Vector3D::Identity;
		boosterSmokeDensity = 0.0f;
	}
	JointedMover::PerformAndWatch(till, update_stream);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::WriteUpdateRecord(Simulation::UpdateRecord *message, int update_model)
{
	Check(this);
	Check_Pointer(message);

	JointedMover::WriteUpdateRecord(message, update_model);

	UpdateRecord
		*record = (UpdateRecord*)message;

	record->recordLength = sizeof(*record);

	record->collisionState = collisionState.GetState();
	record->collisionMaterialType = collisionMaterialType;
	record->collisionNormal = collisionNormal;
	record->collisionSpeed = collisionSpeed;

	record->boosterOn = boosterOn;
	record->boosterScale = boosterScale;
	record->boosterSmokeDensity = boosterSmokeDensity;

	record->hornBlast = hornBlast; // HACK - ECH 7/31/95 - For replicants
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::ReadUpdateRecord(Simulation::UpdateRecord *message)
{
	Check(this);
	Check_Pointer(message);

	JointedMover::ReadUpdateRecord(message);

	UpdateRecord
		*record = (UpdateRecord*)message;

	collisionState.SetState(record->collisionState);
	collisionMaterialType = record->collisionMaterialType;
	collisionNormal = record->collisionNormal;
	collisionSpeed = record->collisionSpeed;

	boosterOn = record->boosterOn;
	boosterScale = record->boosterScale;
	boosterSmokeDensity = record->boosterSmokeDensity;

	hornBlast = record->hornBlast; // HACK - ECH 7/31/95 - For replicants

	if (
		GetSimulationState() == DefaultState
		 && simulationState.GetOldState() == BurningState
	)
	{
		localOrigin = updateOrigin;

		if (IsCollisionVolume())
		{
			MoveCollisionVolume();
		}
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::ProcessCollision(
		Scalar time_slice,
		BoxedSolidCollision &collision,
		const Point3D &old_position,
		Damage *damage
	)
{
	Scalar
		penetration,
		r,
		elasticity = elasticityCoefficient,
		friction = frictionCoefficient,
		added_damage = 0.0f;
	Door
		*door = NULL;

	//
	//-----------------------------------------------------------------------
	// Find out if we have hit a mover or something static.  Moving collision
	// volumes must have an owning entity
	//-----------------------------------------------------------------------
	//
	BoxedSolid
		*box = collision.GetTreeVolume();
	Check(box);

	Simulation
		*sim = box->GetOwningSimulation();
	if (!sim)
	{
		goto Static_Bounce;
	}
	if (sim->IsDerivedFrom(*Mover::GetClassDerivations()))
	{
		Mover *mover = (Mover*)sim;

		//
		//--------------------------------------------------------------------
		// If we hit a mover, make sure that the collision is valid, and if it
		// is, calculate the energy_loss
		//--------------------------------------------------------------------
		//
		if (
			!collisionVolume->ProcessCollision(
				collision,
				worldLinearVelocity,
				lastCollisionList,
				&damage->surfaceNormal,
				&penetration
			)
		)
		{
			Check_Fpu();
			return;
		}

		//
		//--------------------------------------------------------------
		// Make sure that we are not rearending somebody going faster...
		//--------------------------------------------------------------
		//
		Vector3D
			rel_v;
		rel_v.Subtract(worldLinearVelocity, mover->worldLinearVelocity);
		if (rel_v * damage->surfaceNormal >= -SMALL)
		{
			Check_Fpu();
			return;
		}

		//
		//---------------------------------
		// Calculate the moving energy loss
		//---------------------------------
		//
		Max_Clamp(penetration, time_slice);
		r = penetration / time_slice;
		Check_Fpu();

		damage->damageAmount =
			DynamicBounce(
				mover,
				time_slice,
				r,
				damage->surfaceNormal,
				&elasticity
			);

		//
		//--------------------------------------------------------------------
		// If we hit another VTV, we will have to send it a damage message and
		// enter it into the queue of guys what hit us in the last two seconds
		//--------------------------------------------------------------------
		//
		if (mover->GetClassID() == VTVClassID)
		{
			TakeDamageMessage
				message(
					TakeDamageMessageID,
					sizeof(TakeDamageMessage),
					GetEntityID(),
					0,
					*damage
				);
			mover->Dispatch(&message);

			RPPlayer
				*offensive_player = (RPPlayer*)mover->GetPlayerLink();
			if (offensive_player)
			{
				Check(offensive_player);
				RPPlayer::OffensivePlayer
					*shooter = new RPPlayer::OffensivePlayer(
						offensive_player,
						message.damageData.damageType,
						lastPerformance
					);
				Register_Object(shooter);
				RPPlayer
					*our_player = (RPPlayer*)GetPlayerLink();
				Check(our_player);
				our_player->AddOffensivePlayer(*shooter);
			}
		}

		//
		//-------------------------------------
		// Set up the collision state correctly
		//-------------------------------------
		//
		goto Set_Collision_State;
	}

	//
	//--------------------------------------------------------------------------
	// If we hit a moving door, adjust our relative velocity to the door.  If we
	// get hit by both doors, CRUSH the vtv!!!
	//--------------------------------------------------------------------------
	//
	else if (sim->IsDerivedFrom(*Door::GetClassDerivations()))
	{
		door = (Door*)sim;
		Check(door);
		worldLinearVelocity += door->currentVelocity;
	}

	//--------------------------------------------------------------------
	// If we really have a collision, do a static bounce off of the normal
	// generated
	//--------------------------------------------------------------------
	//
Static_Bounce:
	if (
		collisionVolume->ProcessCollision(
			collision,
			worldLinearVelocity,
			lastCollisionList,
			&damage->surfaceNormal,
			&penetration
		)
	)
	{
		Max_Clamp(penetration, time_slice);
		r = penetration / time_slice;
		Check_Fpu();

		damage->damageAmount =
			StaticBounce(
				old_position,
				time_slice,
				r,
				damage->surfaceNormal,
				&elasticity,
				minimumBounceSpeed,
				&friction
			) + added_damage;

		//
		//----------------------------------------
		// If we have hit a door, look for a crush
		//----------------------------------------
		//
		if (door)
		{
			if (
				lastPerformance - lastDoorHit < 0.1f
				 && doorHitNormal*damage->surfaceNormal < -0.7f
				 && damageZones[0]->damageLevel < 1.0f
			)
			{
				damageZones[0]->damageLevel = 1.0f;
			}
			doorHitNormal = damage->surfaceNormal;
			lastDoorHit = lastPerformance;
		}

		//
		//--------------------------
		// Determine collision state
		//--------------------------
		//
Set_Collision_State:
		if (!elasticity)
		{
			if (!friction)
			{
				if (collisionTemporaryState == NoCollisionState)
				{
					collisionTemporaryState = RestState;
				}
			}
			else if (collisionTemporaryState != InitialHitState)
			{
				collisionTemporaryState = SlideState;
			}
		}
		else
		{
			collisionTemporaryState = InitialHitState;
		}
	}

	if (door)
	{
		worldLinearVelocity -= door->currentVelocity;
	}
	Check_Fpu();
}

//#############################################################################
// Scoring support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// HACK - ECH 7/6/95 - Allow the player vehicle to respond to score
// messages, allows attribute system to be used for scoring
//
void
	VTV::RespondToScoreMessage(Message *message)
{
	Check(this);
	Check(message);

	RPPlayer::ScoreMessage
		*score_message = Cast_Object(RPPlayer::ScoreMessage*, message);

	//
	// Set the score state and toggle the state to trigger the watchers
	//
	Check(score_message);
	scoreType = score_message->scoreType;
	Verify(
		score_message->scoreType > RPPlayer::NullPointType &&
		score_message->scoreType < RPPlayer::PointTypeCount
	);
	scoreState.SetState(score_message->scoreType);
	scoreState.SetState(RPPlayer::NullPointType);
	Check_Fpu();
}

//#############################################################################
// Construction and Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
VTV::VTV(
	VTV::MakeMessage *creation_message,
	VTV::SharedData &virtual_data
):
	JointedMover(creation_message, virtual_data),
	collisionState(CollisionStateCount),
	collisionNormal(0.0f, 0.0f, 1.0f),
	scoreState(RPPlayer::PointTypeCount)
{
	SetValidFlag();

	//
	//------------------------
	// Setup the initial state
	//------------------------
	//
	Check(application);
	Check(application->GetInterestManager());

	InterestZone
		*interest_zone =
			application->GetInterestManager()->GetInterestZone(interestZoneID);
	Check(interest_zone);

	localEnvironment = interest_zone->GetEnvironment();
	Check(localEnvironment);

	//
	//----------------
	// Init attributes
	//----------------
	//
	collisionMaterialType = BoxedSolid::SteelMaterial;
	// collisionNormal = Normal::Identity; // Fails TestInstance
	collisionSpeed = 0.0f;
	collisionState.SetState(NoCollisionState);
	scoreState.SetState(RPPlayer::NullPointType);
	scoreType = -1;
	boosterOn = False;
	boosterScale = Vector3D::Identity;
	boosterSmokeDensity = 0.0f;
	doorHitNormal = Vector3D::Identity;
	lastDoorHit = Time::Null;
	heightAboveTerrain = 0.0f;
	forwardVelocity = 0.0f;
	hornBlast = -1;
	insideWorld = True;
	thrusterAngle = 0.0f;

	//
	//-----------------------------
	// Set the correct motion model
	//-----------------------------
	//
	BoxedSolid::Material
		material;

	SetDeadReckoner(&VTV::AcceleratedDeadReckoner);
	if (GetInstance() == ReplicantInstance)
	{
		SetPerformance(&VTV::DeadReckon);
		material = BoxedSolid::OtherCraftMaterial;
	}
	else
	{
		SetPerformance(&VTV::MoveAndCollide);
		material = BoxedSolid::OurCraftMaterial;
	}

	//
	//-----------------------------
	// Initialize the VTV variables
	//-----------------------------
	//
	ResourceFile
		*res_file = application->GetResourceFile();
	ResourceDescription
		*res = res_file->SearchList(
			resourceID,
			ResourceDescription::GameModelResourceType
		);
	Check(res);
	res->Lock();

	ModelResource
		*model_resource = (ModelResource*)res->resourceAddress;
	Check_Pointer(model_resource);

	groundEffectDomainSquared = model_resource->groundEffectDomainSquared;
	groundEffectRange = model_resource->groundEffectRange;

	maxAngularAcceleration = model_resource->maxAngularAcceleration;
	maxYawVelocity = model_resource->maxYawVelocity;
	angularSpringFactor = model_resource->angularSpringFactor;
	bankFactor = model_resource->bankFactor;
	maxImpactSpeed = model_resource->maxImpactSpeed;
	bottomArmorScale = model_resource->bottomArmorScale;

	Scalar death_speed_km = model_resource->deathSpeed;
	//
	// Convert to Meters/Second
	//
	deathSpeed = death_speed_km/3.6f;
	deathScoreLoss = model_resource->deathScoreLoss;
	powerDive = model_resource->powerDive;

	res->Unlock();

	deathConstant = (2000 * deathScoreLoss) /
		(
			(1 - (elasticityCoefficient * elasticityCoefficient)) *
			(deathSpeed * deathSpeed) * moverMass
		);
	Check_Fpu();

	thrusterPitch.rotationAmount = 0.0f;
	thrusterPitch.axisNumber = X_Axis;
	compassHeading = YawPitchRoll::Identity;
	eyepointRotation = EulerAngles::Identity;

	navigationRange = 1000.0;			// initial range = 1km
	currentRangeExponent = 3.0f;
	targetRangeExponent = 3.0f;

	Str_Copy(vehicleBadge, creation_message->vehicleBadge, sizeof(vehicleBadge));
	Str_Copy(vehicleColor, creation_message->vehicleColor, sizeof(vehicleColor));

	//
	//-------------------------------------------------
	// Set up secondary display variables
	// The indirection is required by the camera ship,
	// but may be exploited for viewing the nav
	// display at an arbitrary location, if desired.
	//-------------------------------------------------
	// If 'navigationLinearPosition' is NULL, it will
	// extract the position from the entity directly.
	// Not so for 'navigationAngularPosition': if it
	// is NULL, the display will not 'turn' with the
	// vehicle.  It must be explicitly set if used.
	//-------------------------------------------------
	//
	navigationLinearPosition = NULL;
	navigationAngularPosition = &localOrigin.angularPosition;
	//
	//---------------------------------------------------
	// Make sure to correctly set the material of the VTV
	//---------------------------------------------------
	//
	collisionVolume->materialType = material;

	//
	//------------------------------------------------------------------------
	// Setup the subsystems.  The mapper subsystem will be left blank, so that
	// the caller can hook in the appropriate mapper
	//------------------------------------------------------------------------
	//
	res =
		application->GetResourceFile()->SearchList(
			resourceID,
			ResourceDescription::SubsystemModelStreamResourceType
		);
	Check(res);
	res->Lock();

	Check_Pointer(res->resourceAddress);
	MemoryStream
		subsystems(res->resourceAddress, res->resourceSize);

	subsystemCount = *(int*)subsystems.GetPointer() + BasicSubsystemCount;
	subsystems.AdvancePointer(sizeof(int));
	Verify(!subsystemArray);
	subsystemArray = new (Subsystem (*[subsystemCount]));
	Register_Pointer(subsystemArray);

	if (GetInstance() == ReplicantInstance)
	{
		VTVControlsMapper::SubsystemResource
			control_subsystem_resource;

		Str_Copy(
			control_subsystem_resource.subsystemName,
			"ControlsMapper",
			sizeof(control_subsystem_resource.subsystemName)
		);
		control_subsystem_resource.classID = TrivialSubsystemClassID;
		control_subsystem_resource.subsystemModelSize =
			sizeof(control_subsystem_resource);
		control_subsystem_resource.segmentIndex = -1;
		control_subsystem_resource.subsystemFlags = DontExecuteFlag;

		subsystemArray[ControlsMapperSubsystem] =
			new VTVControlsMapper(
				this,
				VTV::ControlsMapperSubsystem,
				&control_subsystem_resource,
				VTVControlsMapper::DefaultData
			);
		Register_Object(subsystemArray[ControlsMapperSubsystem]);
	}
	else
	{
		subsystemArray[ControlsMapperSubsystem] = NULL;
	}

	res->Unlock();
	//
	//--------------------------------------------------------------------
	// Make the power subsystem.  All VTVs have one in the designated slot
	//--------------------------------------------------------------------
	//
	VTVPower::SubsystemResource
		power_subsystem_resource;

	Str_Copy(
		power_subsystem_resource.subsystemName,
		"Power",
		sizeof(power_subsystem_resource.subsystemName)
	);
	power_subsystem_resource.classID = TrivialSubsystemClassID;
	power_subsystem_resource.subsystemModelSize =
		sizeof(power_subsystem_resource);
	power_subsystem_resource.maxAcceleration = model_resource->maxAcceleration;
	power_subsystem_resource.segmentIndex = -1;
	power_subsystem_resource.subsystemFlags = 0;


	subsystemArray[PowerSubsystem] =
		new VTVPower(this, PowerSubsystem, &power_subsystem_resource);
	Register_Object(subsystemArray[PowerSubsystem]);

	//
	//---------------------------------------------------------------
	// Make the thruster subsystems.  We can handle up to 6 thrusters
	//---------------------------------------------------------------
	//
	Thruster::SubsystemResource
		thruster_subsystem_resource;

	Str_Copy(
		thruster_subsystem_resource.subsystemName,
		"Thruster?",
		sizeof(thruster_subsystem_resource.subsystemName)
	);
	thruster_subsystem_resource.classID = TrivialSubsystemClassID;
	thruster_subsystem_resource.subsystemModelSize =
		sizeof(thruster_subsystem_resource);
	thruster_subsystem_resource.subsystemFlags = 0;
	thruster_subsystem_resource.primeThruster = True;

	//
	// Initialize the thruster subsystems from the jointSubystem
	// Information
	//

	#if DEBUG_LEVEL>0
		JointSubsystem
			*joint_subsystem = GetJointSubsystem();
		Check(joint_subsystem);
		Verify(joint_subsystem->GetJointCount() <= MAX_THRUSTERS);
	#endif

	int i;	
	for (i=0; i<MAX_THRUSTERS; ++i)
	{
		if (
			model_resource->jointIndex[i] == -1
			 || model_resource->segmentIndex[i] == -1
		)
		{
			subsystemArray[Thruster1Subsystem + i] = NULL;
			continue;
		}
		//
		//               Append thruster number to subsystem name
		//
		thruster_subsystem_resource.subsystemName[8] = (char)(i + '0');

		//
		// assign joint and segment Indicies
		//
		thruster_subsystem_resource.jointIndex =
			model_resource->jointIndex[i];
		thruster_subsystem_resource.segmentIndex =
			model_resource->segmentIndex[i];

		//
		// Get maxThrusterRotation Rate
		//
		thruster_subsystem_resource.maxThrusterRotationRate =
			model_resource->maxThrusterRotationRate;

		//
		//               Construct the thruster Subsystem
		//
		subsystemArray[Thruster1Subsystem + i] =
			new Thruster(
				this,
				Thruster1Subsystem+i,
				&thruster_subsystem_resource);

		Register_Object(subsystemArray[Thruster1Subsystem + i]);
		thruster_subsystem_resource.primeThruster = False;
	}

	//
	// Add the JointSubsystem to the subsystemArray
	//
	subsystemArray[JointSubsystemID] = GetJointSubsystem();

	//
	//---------------------------------------------------
	// Read and process the subsystem array specification
	//---------------------------------------------------
	//
	for (i=BasicSubsystemCount; i<subsystemCount; ++i)
	{
		Subsystem::SubsystemResource
			*subsystem_resource =
				(Subsystem::SubsystemResource*)subsystems.GetPointer();
		if (
			!(subsystem_resource->subsystemFlags & Subsystem::DontReplicateFlag)
			 || GetInstance() != ReplicantInstance
		)
		{
			switch (subsystem_resource->classID)
			{
			case RegisteredClass::BoosterClassID:
				subsystemArray[i] =
					new Booster(
						this,
						i,
						(Booster::SubsystemResource*)subsystem_resource
					);
				break;
			case RegisteredClass::ChuteClassID:
				subsystemArray[i] =
					new Chute(this, i, (Chute::SubsystemResource*)subsystem_resource);
				break;
			case RegisteredClass::RivetGunClassID:
				subsystemArray[i] =
					new RivetGun(this, i, (RivetGun::SubsystemResource*)subsystem_resource);
				break;
			case RegisteredClass::LaserDrillClassID:
				subsystemArray[i] =
					new LaserGun(this, i, (LaserGun::SubsystemResource*)subsystem_resource);
				break;
			case RegisteredClass::DemolitionPackDropperClassID:
				subsystemArray[i] =
					new DemolitionPackDropper(
						this,
						i,
						(DemolitionPackDropper::SubsystemResource*)subsystem_resource);
				break;
			}
			Register_Object(subsystemArray[i]);
		}
		else
		{
			subsystemArray[i] = NULL;
		}
		subsystems.AdvancePointer(subsystem_resource->subsystemModelSize);
	}

	//
	//  Read In DamageZones and Create DamageZoneID's
	//

	ResourceDescription
		*dmg_res = application->GetResourceFile()->SearchList(
			resourceID,
			ResourceDescription::DamageZoneStreamResourceType
		);
	Check(dmg_res);
	dmg_res->Lock();

	DynamicMemoryStream
		damage_zone_stream(
			dmg_res->resourceAddress,
			dmg_res->resourceSize
		);
	//
	//-------------------------------------
	// skip over damageZoneCount in Stream
	//-------------------------------------
	//
	damage_zone_stream.AdvancePointer(sizeof(damageZoneCount));
	//
	// Allocate damageZones Array
	//
	for(int ii=0;ii<damageZoneCount;++ii)
	{
		damageZones[ii] = new VTV::DamageZone(
			this,
			ii,
			&damage_zone_stream
		);
		Register_Object(damageZones[ii]);
	}

	dmg_res->Unlock();
	//
	//--------------------
	// Init the reticle
	//--------------------
	//
	targetReticle.reticlePosition = model_resource->reticleResource.reticlePosition;
	Check_Fpu();
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::Reset(const Origin& new_origin, int reset_command)
{
	Check(this);
	Check(&new_origin);

	localOrigin = new_origin;
	localVelocity = Motion::Identity;
	localAcceleration = Motion::Identity;
	worldLinearVelocity = Vector3D::Identity;
	worldLinearAcceleration = Vector3D::Identity;
	localToWorld = localOrigin;
	SetSimulationState(VTV::DefaultState);
	damageZones[0]->damageLevel = 0.0f;
	collisionState.SetState(NoCollisionState);
	boosterOn = False;
	boosterScale = Vector3D::Identity;
	boosterSmokeDensity = 0.0f;

	//
	//-------------------------
	// Reset subsystems
	//-------------------------
	//
	for(int ii=BasicSubsystemCount;ii<subsystemCount;ii++)
	{
		Subsystem *subsystem = GetSubsystem(ii);
		if (subsystem)
		{
			Check(subsystem);
			subsystem->DeathReset(reset_command);
		}
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTV::DeathShutdown(int shutdown_command)
{
	//
	//-------------------------
	// Shutdown subsystems
	//-------------------------
	//
	for(int ii=BasicSubsystemCount;ii<subsystemCount;ii++)
	{
		Subsystem *subsystem = GetSubsystem(ii);
		subsystem->DeathShutdown(shutdown_command);
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription::ResourceID
	VTV::CreateModelResource(
		ResourceFile *resource_file,
		const char *model_name,
		NotationFile *model_file,
		const ResourceDirectories *directories,
		ModelResource *model
	)
{
	Check(resource_file);
	Check_Pointer(model_name);
	Check(model_file);
	Check_Pointer(directories);

	//
	//-----------------------------------------------------------------------
	// If we were not provided a buffer to write the model data into, we must
	// create it ourselves.  Then make sure that the model stuff is read in
	//-----------------------------------------------------------------------
	//
	ModelResource
		*local_model = model;
	if (!local_model)
	{
		local_model = new ModelResource;
		Register_Pointer(local_model);
	}
	if (
		JointedMover::CreateModelResource(
			resource_file,
			model_name,
			model_file,
			directories,
			local_model
		) == -1
	)
	{
Dump_And_Die:
		if (!model)
		{
			Unregister_Pointer(local_model);
			delete local_model;
		}
		Check_Fpu();
		return -1;
	}
		if (
		Reticle::CreateModelResource(
			resource_file,
			model_name,
			model_file,
			directories,
			&local_model->reticleResource
		) == -1
	)
	{
		goto Dump_And_Die;
	}
	//
	//-----------------------------
	// Read in the max acceleration
	//-----------------------------
	//
	if (
		!model_file->GetEntry(
			"gamedata",
			"MaxAcceleration",
			&local_model->maxAcceleration
		)
	)
	{
		DEBUG_STREAM << model_name << " missing MaxAcceleration!\n" << std::flush;
		goto Dump_And_Die;
	}
	if (
		!model_file->GetEntry(
			"gamedata",
			"MaxImpactSpeed",
			&local_model->maxImpactSpeed
		)
	)
	{
		DEBUG_STREAM << model_name << " missing MaxImpactSpeed!\n" << std::flush;
		goto Dump_And_Die;
	}

	//
	//-------------------------
	// Read in the bottom armor
	//-------------------------
	//
	local_model->bottomArmorScale = 0.1f;
	model_file->GetEntry(
		"gamedata",
		"BottomArmorScale",
		&local_model->bottomArmorScale
	);

	//
	//-------------------------
	// Read in the bottom armor
	//-------------------------
	//
	local_model->powerDive = 0.0f;
	model_file->GetEntry(
		"gamedata",
		"PowerDive",
		&local_model->powerDive
	);
	if (local_model->powerDive > 0.0 || local_model->powerDive < -1.0f)
	{
		DEBUG_STREAM << model_name << " has an invalid powerDive!\n" << std::flush;
		goto Dump_And_Die;
	}

	//
	//-----------------------------------
	// Read in the thruster rotation rate
	//-----------------------------------
	//
	if (
		!model_file->GetEntry(
			"gamedata",
			"MaxThrusterRotationRate",
			&local_model->maxThrusterRotationRate
		)
	)
	{
		DEBUG_STREAM << model_name << " missing MaxThrusterRotationRate!\n" << std::flush;
		goto Dump_And_Die;
	}

	//
	//--------------------------------
	// Read in the ground effect stuff
	//--------------------------------
	//
	if (
		!model_file->GetEntry(
			"gamedata",
			"GroundEffectDomainSquared",
			&local_model->groundEffectDomainSquared
		)
	)
	{
		DEBUG_STREAM << model_name << " missing GroundEffectDomainSquared!\n" << std::flush;
		goto Dump_And_Die;
	}

	if (
		!model_file->GetEntry(
			"gamedata",
			"GroundEffectRange",
			&local_model->groundEffectRange
		)
	)
	{
		DEBUG_STREAM << model_name << " missing GroundEffectRange!\n" << std::flush;
		goto Dump_And_Die;
	}


	//
	//----------------------------------------
	// Read in the angular acceleration limits
	//----------------------------------------
	//
	const char *entry;
	if (
		!model_file->GetEntry(
			"gamedata",
			"MaxAngularAcceleration",
			&entry
		)
	)
	{
		DEBUG_STREAM << model_name << " missing MaxAngularAcceleration!\n" << std::flush;
		goto Dump_And_Die;
	}
	sscanf(
		entry,
		"%f %f %f",
		&local_model->maxAngularAcceleration.x,
		&local_model->maxAngularAcceleration.y,
		&local_model->maxAngularAcceleration.z
	);

	if (
		!model_file->GetEntry(
			"gamedata",
			"AngularSpringFactor",
			&local_model->angularSpringFactor
		)
	)
	{
		DEBUG_STREAM << model_name << " missing AngularSpringFactor!\n" << std::flush;
		goto Dump_And_Die;
	}
	if (
		!model_file->GetEntry(
			"gamedata",
			"DeathSpeed",
			&local_model->deathSpeed
		)
	)
	{
		DEBUG_STREAM << model_name << " missing DeathSpeed!\n" << std::flush;
		goto Dump_And_Die;
	}
	if (
		!model_file->GetEntry(
			"gamedata",
			"DeathScoreLoss",
			&local_model->deathScoreLoss
		)
	)
	{
		DEBUG_STREAM << model_name << " missing DeathScoreLoss!\n" << std::flush;
		goto Dump_And_Die;
	}
	if (
		!model_file->GetEntry(
			"gamedata",
			"ElasticityCoefficient",
			&local_model->elasticityCoefficient
		)
	)
	{
		DEBUG_STREAM << model_name << " missing ElasticityCoefficient!\n" << std::flush;
		goto Dump_And_Die;
	}
	if (
		!model_file->GetEntry(
			"gamedata",
			"MaxYawVelocity",
			&local_model->maxYawVelocity
		)
	)
	{
		DEBUG_STREAM << model_name << " missing MaxYawVelocity!\n" << std::flush;
		goto Dump_And_Die;
	}
	local_model->maxYawVelocity *= RAD_PER_DEG;

	if (
		!model_file->GetEntry(
			"gamedata",
			"BankFactor",
			&local_model->bankFactor
		)
	)
	{
		DEBUG_STREAM << model_name << " missing BankFactor!\n" << std::flush;
		goto Dump_And_Die;
	}

	//
	//  Read in Segment and Joint information from the .skl file
	//    for the thrusters
	//
	//-------------------
	// Find the .skl file
	//-------------------
	//
	const char
		*skl_entry;
	if (
		!model_file->GetEntry(
			"video",
			"skeleton",
			&skl_entry
		)
	)
	{
		DEBUG_STREAM << model_name << " is missing .skl file specification!\n" << std::flush;
		return -1;
	}
	char
		*skl_filename =
			MakePathedFilename(directories->videoDirectory, skl_entry);
	Register_Pointer(skl_filename);

	NotationFile
		*skl_file = new NotationFile(skl_filename);
	Register_Object(skl_file);

	if (!skl_file->PageCount())
	{
		DEBUG_STREAM << skl_filename << " is empty or missing!\n" << std::flush;
Dump_And_Die_1:
		Unregister_Pointer(skl_filename);
		delete[] skl_filename;
		Unregister_Object(skl_file);
		delete skl_file;
		Check_Fpu();
		return ResourceDescription::NullResourceID;
	}

	//
	//      Make a pagelist of all the segments
	//
	NameList
		*segment_pages  = skl_file->MakePageList();
	Register_Object(segment_pages);

	NameList::Entry
		*segment_entry = segment_pages->GetFirstEntry();
	int segment_count=0;
	int joint_count=0;
	//
	//       Initialize the thruster Model Resource info to No Thruster!
	//
	for(int ii=0;ii<MAX_THRUSTERS;ii++)
	{
		local_model->jointIndex[ii] = -1;
		local_model->segmentIndex[ii] = -1;
	}

	while(segment_entry)
	{
		char entry[32];
		Str_Copy(entry, segment_entry->GetName(), sizeof(entry));
		//
		//
		// Skip LAB_ONLY and Damage_Zones
		if(
			(strcmp(entry, "LAB_ONLY") == 0) ||
			(strcmp(entry,"DamageZones") ==0)
			)
		{
			segment_entry = segment_entry->GetNextEntry();
			continue;
		}
		if (strncmp(entry,"joint",5)==0)
		{
			if (!stricmp(entry,"jointrfeng"))
			{
				 local_model->jointIndex[0] = joint_count;
				 local_model->segmentIndex[0] = segment_count;
			}
			else if (!stricmp(entry,"jointlfeng"))
			{
				 local_model->jointIndex[1] = joint_count;
				 local_model->segmentIndex[1] = segment_count;
			}
			else if (!stricmp(entry,"jointreng") || !stricmp(entry,"jointrmeng"))
			{
				local_model->jointIndex[2] = joint_count;
				local_model->segmentIndex[2] = segment_count;
			}
			else if (!stricmp(entry,"jointleng") || !stricmp(entry,"jointlmeng"))
			{
				local_model->jointIndex[3] = joint_count;
				local_model->segmentIndex[3] = segment_count;
			}
			else if (!strcmp(entry,"jointrbeng"))
			{
				 local_model->jointIndex[4] = joint_count;
				 local_model->segmentIndex[4] = segment_count;
			}
			else if (!strcmp(entry,"jointlbeng"))
			{
				local_model->jointIndex[5] = joint_count;
				local_model->segmentIndex[5] = segment_count;
			}

			++joint_count;
		}
		++segment_count;
		segment_entry = segment_entry->GetNextEntry();
	}

	//
	//-------------------------------------------------------------------------
	// If we created the model buffer, then we have the responsibility to write
	// it out to the resource file
	//-------------------------------------------------------------------------
	//
	if (!model)
	{
		ResourceDescription
			*new_res = resource_file->AddResource(
				model_name,
				ResourceDescription::GameModelResourceType,
				1,
				ResourceDescription::Preload,
				local_model,
				sizeof(*local_model)
			);
		Unregister_Pointer(local_model);
		delete local_model;
		Unregister_Pointer(skl_filename);
		delete skl_filename;
		Unregister_Object(skl_file);
		delete skl_file;
		Unregister_Object(segment_pages);
		delete segment_pages;

		Check(new_res);
		Check_Fpu();
		return new_res->resourceID;
	}
	else
	{
		Unregister_Pointer(local_model);
		delete local_model;
		Unregister_Pointer(skl_filename);
		delete skl_filename;
		Unregister_Object(skl_file);
		delete skl_file;
		Unregister_Object(segment_pages);
		delete segment_pages;

		Check_Fpu();
		return 0;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription::ResourceID
	VTV::CreateSubsystemStream(
		ResourceFile *resource_file,
		const char *model_name,
		NotationFile *model_file,
		const ResourceDirectories *directories
	)
{
	Check(resource_file);
	Check_Pointer(model_name);
	Check(model_file);
	Check_Pointer(directories);

	//
	//-------------------
	// Find the .sub file
	//-------------------
	//
	const char
		*entry_data;
	if (
		!model_file->GetEntry(
			"gamedata",
			"Subsystems",
			&entry_data
		)
	)
	{
		DEBUG_STREAM << model_name << " is missing .sub file specification!\n" << std::flush;
		Check_Fpu();
		return -1;
	}

	char
		*filename = MakePathedFilename(directories->modelDirectory, entry_data);
	Register_Pointer(filename);

	NotationFile
		*sys_file = new NotationFile(filename);
	Register_Object(sys_file);
	if (!sys_file->PageCount())
	{
		DEBUG_STREAM << filename << " cannot be found!\n" << std::flush;
		Unregister_Pointer(filename);
		delete filename;
		Unregister_Object(sys_file);
		delete sys_file;
		Check_Fpu();
		return -1;
	}

	//
	//-------------------------------------------------------
	// Figure out how many subsystems will have to be created
	//-------------------------------------------------------
	//
	NameList
		*namelist = sys_file->MakePageList();
	Register_Object(namelist);
	NameList::Entry
		*entry = namelist->GetFirstEntry();

	int
		count = 0;
	while (entry)
	{
		++count;
		entry = entry->GetNextEntry();
	}

	//
	//---------------------------------------------------------------------
	// Step through each of the pages, and create an entry in the subsystem
	// stream
	//---------------------------------------------------------------------
	//
	int
		*subsystems = new int[sizeof(RivetGun::SubsystemResource)*count];
	Register_Pointer(subsystems);

	MemoryStream
		stream(subsystems,sizeof(RivetGun::SubsystemResource)*count*sizeof(int));

	*(int*)stream.GetPointer() = count;
	stream.AdvancePointer(sizeof(int));

	entry = namelist->GetFirstEntry();
	while (entry)
	{
		const char
			*subsystem_name = entry->GetName(),
			*type;
		if (
			!sys_file->GetEntry(
				subsystem_name,
				"Type",
				&type
			)
		)
		{
			DEBUG_STREAM << model_name << ':' <<
				subsystem_name << " missing Type!\n" << std::flush;
Dump_And_Die:
			Unregister_Pointer(subsystems);
			delete[] subsystems;
			Unregister_Object(namelist);
			delete namelist;
			Unregister_Pointer(filename);
			delete filename;
			Unregister_Object(sys_file);
			delete sys_file;
			return -1;
		}

		Subsystem::SubsystemResource
			*subsystem_resource =
				(Subsystem::SubsystemResource*)stream.GetPointer();
		if (!strcmp(type, "BoosterClassID"))
		{
			if (
				Booster::CreateStreamedSubsystem(
					model_file,
					model_name,
					subsystem_name,
					(Booster::SubsystemResource*)subsystem_resource,
					sys_file,
					directories
				) == -1
			)
			{
				goto Dump_And_Die;
			}
		}
		else
		if (!strcmp(type, "ChuteClassID"))
		{
			if (
				Chute::CreateStreamedSubsystem(
					model_file,
					model_name,
					subsystem_name,
					(Chute::SubsystemResource*)subsystem_resource,
					sys_file,
					directories
				) == -1
			)
			{
				goto Dump_And_Die;
			}
		}
		else if (!strcmp(type, "RivetGunClassID"))
		{
			if (
				RivetGun::CreateStreamedSubsystem(
					model_file,
					model_name,
					subsystem_name,
					(RivetGun::SubsystemResource*)subsystem_resource,
					sys_file,
					resource_file,
					directories
				) == -1
			)
			{
				goto Dump_And_Die;
			}
		}
		else if (!strcmp(type, "DemolitionPackDropperClassID"))
		{
			if (
				DemolitionPackDropper::CreateStreamedSubsystem(
					model_file,
					model_name,
					subsystem_name,
					(DemolitionPackDropper::SubsystemResource*)subsystem_resource,
					sys_file,
					resource_file,
					directories
				) == -1
			)
			{
				goto Dump_And_Die;
			}
		}
		else if (!strcmp(type, "LaserDrillClassID"))
		{
			if (
				LaserGun::CreateStreamedSubsystem(
					model_file,
					model_name,
					subsystem_name,
					(LaserGun::SubsystemResource*)subsystem_resource,
					sys_file,
					resource_file,
					directories
				) == -1
			)
			{
				goto Dump_And_Die;
			}
		}
		else
		{
			DEBUG_STREAM << model_name << ':' << subsystem_name
				<< " has an unknown component type of " << type << std::endl << std::flush;
			goto Dump_And_Die;
		}

		stream.AdvancePointer(subsystem_resource->subsystemModelSize);
		entry = entry->GetNextEntry();
	}

	//
	//--------------------------------------------------------------------
	// Write the stream out to disk.  Size is equal to the byte difference
	// between stream and subsystems
	//--------------------------------------------------------------------
	//
	ResourceDescription
		*new_res = resource_file->AddResource(
			model_name,
			ResourceDescription::SubsystemModelStreamResourceType,
			1,
			ResourceDescription::Preload,
			subsystems,
			(char*)stream.GetPointer() - (char*)subsystems
		);
	Check(new_res);

	//
	//-------------------------
	// Clean up after ourselves
	//-------------------------
	//
	Unregister_Pointer(subsystems);
	delete[] subsystems;
	Unregister_Object(namelist);
	delete namelist;
	Unregister_Pointer(filename);
	delete filename;
	Unregister_Object(sys_file);
	delete sys_file;
	Check_Fpu();
	return new_res->resourceID;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Simulation::SharedData*
	VTV::GetSubsystemSharedData(const char *name)
{
	Check_Pointer(name);

	Check_Fpu();
	if (!strcmp(name, ""))
	{
		return &VTV::DefaultData;
	}
	else if (!strcmp(name, "BoosterClassID"))
	{
		return &Booster::DefaultData;
	}
	else if (!strcmp(name, "ChuteClassID"))
	{
		return &Chute::DefaultData;
	}
	else if (!strcmp(name, "Power"))
	{
		return &VTVPower::DefaultData;
	}
	else if (!strcmp(name, "ControlsMapper"))
	{
		return &VTVControlsMapper::DefaultData;
	}
	else if (!strcmp(name, "RivetGunClassID"))
	{
		return &RivetGun::DefaultData;
	}
	else if (!strcmp(name, "LaserDrillClassID"))
	{
		return &LaserGun::DefaultData;
	}
	else if (!strcmp(name, "DemolitionPackDropperClassID"))
	{
		return &DemolitionPackDropper::DefaultData;
	}
	else
	{
		return NULL;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription::ResourceID
	VTV::CreateControlMappingStream(
		const char* mapping_name,
		NotationFile *mapping_file,
		FindNameFunction find_name,
		ResourceFile *resource_file,
		const char *model_name,
		NotationFile *model_file,
		const ResourceDirectories *directories,
		PlatformTool *current_tool
	)
{
	Check(mapping_file);
	Check(resource_file);
	Check_Pointer(model_name);
	Check(model_file);
	Check_Pointer(directories);

	NameList
		*namelist = mapping_file->MakePageList();
	Register_Object(namelist);

	NameList::Entry
		*entry = namelist->GetFirstEntry();
	int
		count=mapping_file->PageCount();

	if (!count)
	{
		DEBUG_STREAM << model_name << " is missing control mappings!\n" << std::flush;
Unwind_1:
		Unregister_Object(namelist);
		delete namelist;
		Check_Fpu();
		return -1;
	}

	int
		*stream = (int*)new ControlsMapping[(count+1)*6];
	Register_Pointer(stream);
	int
		*mappings = stream;
	*mappings++ = count;
	int
		real_count = 0;

	//
	//----------------------------------
	// For each page, create one mapping
	//----------------------------------
	//
	for (int i=0; i<count; ++i)
	{
		ControlsMapping
			*mapping = (ControlsMapping*)mappings;
		const char
			*control_name,
			*page_name = entry->GetName();
		if (
			!mapping_file->GetEntry(
				page_name,
				"IOMapping",
				&control_name
			)
		)
		{
				DEBUG_STREAM << model_name << ':' << page_name
			 << " has no IOMapping !\n" << std::flush;
Unwind_2:
			Unregister_Pointer(stream);
			delete stream;
			goto Unwind_1;
		}
		if (!(*find_name)(control_name, mapping))
		{
			DEBUG_STREAM << model_name << ':' << control_name
			 << " does not exist!\n" << std::flush;
			goto Unwind_2;
		}

		//
		//--------------------------------------------------------
		// Now, find the right subsystem to attach this control to
		//--------------------------------------------------------
		//
		const char
			*subsystem_name;
		mapping->subsystemID = Entity::EntitySubsystemID;
		char
			subsystem_type[80];
		*subsystem_type = '\0';
		if (
			mapping_file->GetEntry(
				page_name,
				"Subsystem",
				&subsystem_name
			)
		)
		{
			//
			//--------------------------------------------------
			// See if this is one of the always there subsystems
			//--------------------------------------------------
			//
			if (!strcmp(subsystem_name, "ControlsMapper"))
			{
				mapping->subsystemID = ControlsMapperSubsystem;
				Str_Copy(subsystem_type, subsystem_name, sizeof(subsystem_type));
			}
			else if (!strcmp(subsystem_name, "Power"))
			{
				mapping->subsystemID = PowerSubsystem;
				Str_Copy(subsystem_type, subsystem_name, sizeof(subsystem_type));
			}
			else if (!strncmp(subsystem_name, "Thruster", 8))
			{
				mapping->subsystemID =
					Thruster1Subsystem + (subsystem_name[8] - '0');
				Str_Copy(subsystem_type, subsystem_name, sizeof(subsystem_type));
			}

			//
			//--------------------------------------------------------------
			// Otherwise, we have to open up the subsystem file and find the
			// matching page, keeping track of the count
			//--------------------------------------------------------------
			//
			else
			{
				const char
					*entry_data;
				model_file->GetEntry("gamedata", "Subsystems", &entry_data);

				char
					*filename =
						MakePathedFilename(directories->modelDirectory, entry_data);
				Register_Pointer(filename);

				NotationFile
					*sys_file = new NotationFile(
						filename,
						NotationFile::CleanListMode|NotationFile::IgnoreCaseMode
					);
				Register_Object(sys_file);

				NameList
					*sublist = sys_file->MakePageList();
				Register_Object(sublist);

				NameList::Entry
					*subsystem_entry = sublist->GetFirstEntry();

				//
				//-----------------------------------------------------------------
				// Look through all the pages of the subsystem file, looking for
				// our subsystem.  When we find it, remember what number it was and
				// what type it was
				//-----------------------------------------------------------------
				//
				mapping->subsystemID = BasicSubsystemCount;
				while (subsystem_entry)
				{
					if (!strcmp(subsystem_entry->GetName(), subsystem_name))
					{
						if (!sys_file->GetEntry(subsystem_name, "Type", &entry_data))
						{
							DEBUG_STREAM <<
								model_name << ':' << subsystem_name <<
								" does not have a 'Type' in file '" <<
								filename << "'!\n" << std::flush;
						}
						Str_Copy(subsystem_type, entry_data, sizeof(subsystem_type));
						subsystem_name = entry_data;
						break;
					}
					subsystem_entry = subsystem_entry->GetNextEntry();
					++mapping->subsystemID;
				}

				Unregister_Pointer(filename);
				delete filename;

				if (!subsystem_entry)
				{
					DEBUG_STREAM << model_name << ":subsystem '" << subsystem_name
					 << "' does not exist!\n" << std::flush;
					Unregister_Object(sys_file);
					delete sys_file;

					Unregister_Object(sublist);
					delete sublist;

					Unregister_Pointer(stream);
					delete[] stream;
					goto Unwind_1;
				}
				Unregister_Object(sys_file);
				delete sys_file;

				Unregister_Object(sublist);
				delete sublist;
			}
		}

		//
		//--------------------------
		// Find the Mode to use
		//--------------------------
		//
		const char
			*entry_string;
		if (
			!mapping_file->GetEntry(
				page_name,
				"Mode",
				&entry_string
			)
		)
		{
			DEBUG_STREAM <<
				model_name << ':' << page_name << " is missing Mode!\n" << std::flush;
Unwind_3:
			Unregister_Pointer(stream);
			delete[] stream;
			goto Unwind_1;
		}

		CString
			match_filter_string = entry_string,
			mode_string,
			null_string = NULL;

		ModeMask
			mode_mask;

		mapping->modeMask = (ModeMask) 0;
		{
			int
				i;

			for(
				i=0;
				(mode_string=match_filter_string.GetNthToken(i)) != null_string;
				++i
			)
			{
				Logical
					result = current_tool->ConvertStringToModeMask(
						mode_string,
						&mode_mask
					);

				if (result == False)
				{
					DEBUG_STREAM <<
						 model_name << ':' <<
						 page_name << " has an unsupported Mode type '" <<
						 mode_string << "'\n" << std::flush;
					goto Unwind_3;
				}

				mapping->modeMask |= mode_mask;
			}

			if (i == 0)
			{
				DEBUG_STREAM <<
					model_name << ':' <<
					page_name << " has no Mode specified!\n" << std::flush;
					goto Unwind_3;
			}
		}
		//
		//------------------------------------
		// Find the type of connection to make
		//------------------------------------
		//
		const char
			*type;
		if (
			!mapping_file->GetEntry(
				page_name,
				"Type",
				&type
			)
		)
		{
			DEBUG_STREAM <<
				model_name << ':' <<
				page_name << " is missing type!\n" << std::flush;
			goto Unwind_3;
		}

		Simulation::SharedData
			*shared_data =	GetSubsystemSharedData(subsystem_type);
		if (!shared_data)
		{
			DEBUG_STREAM <<
				model_name << ':' <<
				subsystem_name << " is an unsupported subsystem!\n" << std::flush;
			goto Unwind_3;
		}

		//
		//----------------------------------------------------------------------
		// If this is an event mapping, we have to have a message ID.
		//----------------------------------------------------------------------
		//
		if (!strcmp(type, "EventMapping"))
		{
			mapping->mappingType = ControlsMapping::EventMapping;
			const char* message;
			if (
				!mapping_file->GetEntry(
					page_name,
					"MessageID",
					&message
				)
			)
			{
				DEBUG_STREAM <<
					model_name << ':' <<
					page_name << " is missing MessageID!\n" << std::flush;
				goto Unwind_3;
			}
			const Receiver::HandlerEntry
				*entry =	shared_data->activeMessageHandlers->Find(message);
			if (!entry)
			{
				DEBUG_STREAM <<
					model_name << ':' <<
					subsystem_name << ':' <<
					message << " is an unsupported messageID!\n" << std::flush;
				goto Unwind_3;
			}
			mapping->messageID = entry->entryID;
		}

		//
		//----------------------------------------------------------------------
		// If this is a direct mapping, we have to have an attribute ID.  How do
		// we find an unknown attribute of an unknown subsystem?
		//----------------------------------------------------------------------
		//
		else if (!strcmp(type, "DirectMapping"))
		{
			mapping->mappingType = ControlsMapping::DirectMapping;
			const char
				*attribute;
			if (
				!mapping_file->GetEntry(
					page_name,
					"AttributeID",
					&attribute
				)
			)
			{
				DEBUG_STREAM <<
					model_name << ':' <<
					page_name << " is missing AttributeID!\n" << std::flush;
				goto Unwind_3;
			}
			const Simulation::IndexEntry
				*entry =	shared_data->activeAttributeIndex->FindEntry(attribute);
			if (!entry)
			{
				DEBUG_STREAM <<
					model_name << ':' <<
					subsystem_name << ':' <<
					attribute << " is an unsupported AttributeID!\n" << std::flush;
				goto Unwind_3;
			}
			mapping->attributeID = entry->entryID;
		}
		else
		{
			DEBUG_STREAM <<
				model_name << ':' <<
				page_name  << " has unknown mapping type!\n" << std::flush;
			goto Unwind_3;
		}

		mappings = (int*)((char*)mappings + sizeof(ControlsMapping));
		++real_count;

		//
		//-------------------------
		// Move to the next mapping
		//-------------------------
		//
		entry = entry->GetNextEntry();
	}

	//
	//--------------------------------------------------------------------
	// Write the stream out to disk.  Size is equal to the byte difference
	// between stream and subsystems
	//--------------------------------------------------------------------
	//
	*stream = real_count;
	ResourceDescription
		*new_res = resource_file->AddResource(
			mapping_name,
			ResourceDescription::ControlMappingStreamResourceType,
			1,
			ResourceDescription::Preload,
			stream,
			(char*)mappings - (char*)stream
		);
	Check(new_res);

	//
	//-------------------------
	// Clean up after ourselves
	//-------------------------
	//
	Unregister_Pointer(stream);
	delete[] stream;
	Unregister_Object(namelist);
	delete namelist;
	Check_Fpu();
	return new_res->resourceID;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
VTV*
	VTV::Make(VTV::MakeMessage *creation_message)
{
	Check_Fpu();
	return new VTV(creation_message);
}


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

ResourceDescription::ResourceID
	VTV::CreateDamageZoneStream(
		ResourceFile *resource_file,
		const char   *model_name,
		NotationFile *model_file,
		const ResourceDirectories *directories
	)
{
	Check(resource_file);
	Check_Pointer(model_name);
	Check(model_file);
	Check_Pointer(directories);

	//
	//-------------------
	// Find the .skl file
	//-------------------
	//
	const char
		*skl_entry;
	if (
		!model_file->GetEntry(
			"video",
			"skeleton",
			&skl_entry
			)
		)
	{
		DEBUG_STREAM << model_name << " is missing .skl file specification!\n" << std::flush;
		Check_Fpu();
		return -1;
	}
	char
		*skl_filename =
			MakePathedFilename(directories->videoDirectory, skl_entry);
	Register_Pointer(skl_filename);

	NotationFile
		*skl_file = new NotationFile(skl_filename);
	Register_Object(skl_file);

	if (!skl_file->PageCount())
	{
		DEBUG_STREAM << skl_filename << " is empty or missing!\n" << std::flush;
Dump_And_Die_1:
		Unregister_Pointer(skl_filename);
		delete[] skl_filename;
		Unregister_Object(skl_file);
		delete skl_file;
		Check_Fpu();
		return ResourceDescription::NullResourceID;
	}

	//
	//-------------------
	// Find the .dmg file
	//-------------------
	//
	const char
		*dmg_entry;
	if (
		!model_file->GetEntry(
			"gamedata",
			"DamageZones",
			&dmg_entry
		)
	)
	{
		DEBUG_STREAM << model_name << " is missing .dmg file specification!\n" << std::flush;
		goto Dump_And_Die_1;
	}

	char
		*dmg_filename =
			MakePathedFilename(directories->modelDirectory, dmg_entry);
	Register_Pointer(dmg_filename);

	NotationFile
		*dmg_file = new NotationFile(dmg_filename);
	Register_Object(dmg_file);

	if (!dmg_file->PageCount())
	{
		DEBUG_STREAM << dmg_filename << " is empty or missing!\n" << std::flush;
Dump_And_Die_2:
		Unregister_Pointer(dmg_filename);
		delete[] dmg_filename;
		Unregister_Object(dmg_file);
		delete dmg_file;
		goto Dump_And_Die_1;
	}

	//
	// Get the number of Damage zones
	//
	int
		dzone_count;
	if (
		!skl_file->GetEntry(
			"ROOT",
			"DZoneCount",
			&dzone_count
		)
	)
	{
		DEBUG_STREAM << model_name << " is missing DZoneCount \n" << std::flush;
		goto Dump_And_Die_2;
	}

	//
	// Make a stream big enough for all the damage zones
	//
	DynamicMemoryStream
		damage_zone_stream;

	//
	// write the Damage Zone Count Info from the
	// .dmg file to the stream
	//
	damage_zone_stream << dzone_count;

	//
	// Make an entry list of all the entries in the damage zone page
	//
	NameList
		*dzone_namelist =	skl_file->MakeEntryList("DamageZones","dz_");
	Register_Object(dzone_namelist);

	if (dzone_namelist->EntryCount() == 0)
	{
		DEBUG_STREAM << "No dZones listed in DamageZones Page"<<std::endl << std::flush;
Dump_And_Die:
		Unregister_Pointer(skl_filename);
		delete skl_filename;
		Unregister_Object(skl_file);
		delete skl_file;
		Unregister_Pointer(dmg_filename);
		delete dmg_filename;
		Unregister_Object(dmg_file);
		delete dmg_file;
		Check_Fpu();
		return -1;
	}
	NameList::Entry
		*dzone_entry;
	char
		current_dzone_name[32];
	int
		num_dzones_found = 0;

	dzone_entry = dzone_namelist->GetFirstEntry();

	while (dzone_entry)
	{
		++num_dzones_found;
		//
		// Get dzone name in .skl file
		//
		Str_Copy(
			current_dzone_name,
			dzone_entry->GetName(),
			sizeof(current_dzone_name)
			);

		//
		//  Create the stream for this damage zone
		//  This Should Default to VTV::DamageZone::Create
		//
		VTV__DamageZone::CreateStreamedDamageZone(
				 model_file,
				 model_name,
				 skl_file,
				 current_dzone_name,
				 &damage_zone_stream,
				 dmg_file,
				 directories
		);
		//
		// Get next dzone entry in this segment page
		//
		dzone_entry = dzone_entry->GetNextEntry();
	}	// End while more dzone_entries

	if(dzone_count != num_dzones_found)
	{
		DEBUG_STREAM <<
			"DZoneCount != damage zones found in Page DamageZones"<<std::endl << std::flush;
		goto Dump_And_Die;
	}

	//
	//--------------------------------------------------------------------
	// Write the stream out to disk.  Size is equal to the byte difference
	// between stream and damage zone buffer
	//--------------------------------------------------------------------
	//
	ResourceDescription
		*new_res = resource_file->AddResourceMemoryStream(
			model_name,
			ResourceDescription::DamageZoneStreamResourceType,
			1,
			ResourceDescription::Preload,
			&damage_zone_stream
		);
	Check(new_res);
	//
	//		 Free mem
	//
	Unregister_Pointer(skl_filename);
	delete skl_filename;
	Unregister_Object(skl_file);
	delete skl_file;
	Unregister_Pointer(dmg_filename);
	delete dmg_filename;
	Unregister_Object(dmg_file);
	delete dmg_file;
	Unregister_Object(dzone_namelist);
	delete dzone_namelist;
	Check_Fpu();
	return  new_res->resourceID;
}

//#############################################################################
//VTV::DamageZone
//
void
	VTV__DamageZone::TakeDamage(Damage& damage)
{
	Check(this);
	Check_Pointer(&damage);

	DamageZone::TakeDamage(damage);

	//
	//---------------------------------
	// If the VTV is dead, make it burn
	//---------------------------------
	//
	VTV
		*vtv = (VTV*)GetOwningSimulation();
	Check(vtv);
	if (damageLevel >= 1.0f)
	{
		vtv->SetSimulationState(VTV::BurningState);
	}

	//
	//----------------------------------------------------------------------
	// If this VTV is linked to a player, send the score loss to that player
	//----------------------------------------------------------------------
	//
	RPPlayer
		*rp_player = (RPPlayer*) vtv->GetPlayerLink();
	if (rp_player)
	{
		Check(rp_player);
		//
		//--------------------------------------------------------
		// If we died, send the I'm dead now message to the player
		//--------------------------------------------------------
		//
		if (
			vtv->GetSimulationState() == VTV::BurningState &&
			vtv->simulationState.GetOldState() != VTV::BurningState
		)
		{
			RPPlayer::VehicleDeadMessage
				dead(
					RPPlayer::VehicleDeadMessageID,
					sizeof(RPPlayer::VehicleDeadMessage)
				);
			rp_player->Dispatch(&dead);
		}

		//
		//-------------------------------------------
		// Notify this player that damaged was caused
		//-------------------------------------------
		//
		RPPlayer::TakeDamageMessage
			damage_message(
			  RPPlayer::TakeDamageMessageID,
			  sizeof(RPPlayer::TakeDamageMessage),
			  rp_player->GetEntityID(),
			  0,
			  damage
			);
		rp_player->Dispatch(&damage_message);
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
VTV__DamageZone::VTV__DamageZone(VTV *vtv, int damage_zone_index, MemoryStream *damage_zone_stream) :
	DamageZone(vtv, damage_zone_index, damage_zone_stream)
{
	Scalar
		dmg_data(vtv->maxImpactSpeed);

	dmg_data /= 3.6f;

	damageScale[Damage::CollisionDamageType] =
		2000.0f
		 / vtv->moverMass
		 / (dmg_data*dmg_data)
		 / (
				1.0f
				 - vtv->elasticityCoefficient
					* vtv->elasticityCoefficient
			);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	VTV__DamageZone::CreateStreamedDamageZone(
		NotationFile					*model_file,
		const char						*model_name,
		NotationFile					*skl_file,
		const char						*damage_zone_name,
		MemoryStream					*damage_zone_stream,
		NotationFile					*dmg_file,
		const ResourceDirectories	*directories
	)
{
	if(!DamageZone::CreateStreamedDamageZone(
		 model_file,
		 model_name,
		 skl_file,
		 damage_zone_name,
		 damage_zone_stream,
		 dmg_file,
		 directories
		)
	)
	{
		Check_Fpu();
		return -1;
	}
	Check_Fpu();
	return True;
}

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

