#include "rp.h"
#pragma hdrstop

#include "vtvmppr.h"
#include "vtvpwr.h"
#include "..\munga\icom.h"
#include "..\munga\app.h"
#include "rpplayer.h"
#include "vtv.h"

//#############################################################################
// Shared Data Support
//
VTVControlsMapper::SharedData
	VTVControlsMapper::DefaultData(
		VTVControlsMapper::GetClassDerivations(),
		VTVControlsMapper::GetMessageHandlers(),
		VTVControlsMapper::GetAttributeIndex(),
		VTVControlsMapper::StateCount
	);

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

//#############################################################################
// Messaging Support
//
const Receiver::HandlerEntry
	VTVControlsMapper::MessageHandlerEntries[]=
{
	MESSAGE_ENTRY(VTVControlsMapper, ConfigureControls),
	MESSAGE_ENTRY(VTVControlsMapper, ConfigureSideSlipMappables),
	MESSAGE_ENTRY(VTVControlsMapper, ChooseSideSlip),
	MESSAGE_ENTRY(VTVControlsMapper, ConfigureLiftCutMappables),
	MESSAGE_ENTRY(VTVControlsMapper, ChooseLiftCut),
	MESSAGE_ENTRY(VTVControlsMapper, ConfigureHornMappables),
	MESSAGE_ENTRY(VTVControlsMapper, ChooseHorn),
	MESSAGE_ENTRY(VTVControlsMapper, ActivateHorn),
	MESSAGE_ENTRY(VTVControlsMapper, ConfigurePTTMappables),
	MESSAGE_ENTRY(VTVControlsMapper, ChoosePTT),
	MESSAGE_ENTRY(VTVControlsMapper, ActivatePTT),
	MESSAGE_ENTRY(VTVControlsMapper, ToggleReticle)
};

Receiver::MessageHandlerSet& VTVControlsMapper::GetMessageHandlers()
{
	static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(VTVControlsMapper::MessageHandlerEntries), VTVControlsMapper::MessageHandlerEntries, VTVSubsystem::GetMessageHandlers());
	return messageHandlers;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::ConfigureControlsMessageHandler(
		#if DEBUG_LEVEL>0
			ReceiverDataMessageOf<ControlsButton> *message
		#else
			ReceiverDataMessageOf<ControlsButton> *
		#endif
	)
{
	Check(this);
	Check(message);

#if 0
		THIS STUFF IS BASICALLY OBSOLETE
	//
	//------------------------
	// Get the control manager
	//------------------------
	//
	Check(application);
	ControlsManager *control = application->GetControlsManager();
	Check(control);

	//
	//----------------------------------------------------------------------
	// If the config button was pressed, set all hardwired buttons to config
	// mode, otherwise set them all back to active mode, and clear out any
	// temporary mappings to the mappable buttons
	//----------------------------------------------------------------------
	//
	if (message->dataContents > 0)
	{
		if (GetSimulationState() == ConfigurationState)
		{
			control->SetHardwiredButtonsFilter(ActiveFilter, 0);
			control->StopMappableButtonsConfigure(
				ConfigurationFilter,
				mustMatch,
				mayMatch
			);
			SetSimulationState(DefaultState);
		}
		else
		{
			if (controlMode != BasicMode)
			{
				control->SetHardwiredButtonsFilter(ConfigurationFilter, 0);
				SetSimulationState(ConfigurationState);
			}
		}
	}
#endif
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::ConfigureSideSlipMappablesMessageHandler(
		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(
			&sideSlip,							// direct target
			this,									// receiver
			(Receiver::MessageID) 0,		// activation message ID (none here)
			ChooseSideSlipMessageID			// configuration message ID
		);
	}
	else
	{
		ExitConfiguration();
	}
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		AddOrErase(message->dataContents, &sideSlip);
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::ConfigureLiftCutMappablesMessageHandler(
		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(
			&liftCut,							// direct target
			this,									// receiver
			(Receiver::MessageID) 0,		// activation message ID (none here)
			ChooseLiftCutMessageID			// configuration message ID
		);
	}
	else
	{
		ExitConfiguration();
	}
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		AddOrErase(message->dataContents, &liftCut);
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::ConfigureHornMappablesMessageHandler(
		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(
			NULL,									// direct target (none here)
			this,									// receiver
			ActivateHornMessageID,			// activation message ID
			ChooseHornMessageID				// configuration message ID
		);
	}
	else
	{
		ExitConfiguration();
	}
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		AddOrErase(message->dataContents, this, ActivateHornMessageID);
	}
	Check_Fpu();
}

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

	hornBlast = message->dataContents;
//	hornBlast = (hornBlast == -1) ? 1 : -1;

	// HACK - ECH 7/31/95 - Propagate horn blast
	Check(GetEntity());
	GetEntity()->hornBlast = hornBlast;
	GetEntity()->ForceUpdate();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::ConfigurePTTMappablesMessageHandler(
		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(
			NULL,									// direct target (none here)
			this,									// receiver
			ActivatePTTMessageID,			// activation message ID
			ChoosePTTMessageID				// configuration message ID
		);
	}
	else
	{
		ExitConfiguration();
	}
	Check_Fpu();
}

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

	if (message->dataContents > 0)
	{
		AddOrErase(message->dataContents, &pttStatus);
	}
	Check_Fpu();
}

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

	pttStatus = message->dataContents;
}

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

	if (message->dataContents > 0)
	{
		VTV* vtv = GetEntity();
		Check(vtv);
		Reticle *reticle = &vtv->targetReticle;
		Check(reticle);
		reticle->reticleState =
			(Reticle::ReticleState)(reticle->reticleState^Reticle::ReticleOn);
	}
}

//#############################################################################
// Attribute Support
//
const VTVControlsMapper::IndexEntry
	VTVControlsMapper::AttributePointers[]=
{
	ATTRIBUTE_ENTRY(VTVControlsMapper, StickPosition,		stickPosition),
	ATTRIBUTE_ENTRY(VTVControlsMapper, ThrottlePosition,	throttlePosition),
	ATTRIBUTE_ENTRY(VTVControlsMapper, PedalsPosition,		pedalsPosition),
	ATTRIBUTE_ENTRY(VTVControlsMapper, ReverseThrust,		reverseThrust),
	ATTRIBUTE_ENTRY(VTVControlsMapper, LiftCut,				liftCut),
	ATTRIBUTE_ENTRY(VTVControlsMapper, SideSlip,				sideSlip),
	ATTRIBUTE_ENTRY(VTVControlsMapper, PowerDemand,			powerDemand),
	ATTRIBUTE_ENTRY(
		VTVControlsMapper,
		AngularVelocityDemand,
		angularVelocityDemand
	),
	ATTRIBUTE_ENTRY(VTVControlsMapper, BrakeLights,			brakeLights),
	ATTRIBUTE_ENTRY(VTVControlsMapper, LookLeft,				lookLeft),
	ATTRIBUTE_ENTRY(VTVControlsMapper, LookRight,			lookRight),
	ATTRIBUTE_ENTRY(VTVControlsMapper, LookBehind,			lookBehind),
	ATTRIBUTE_ENTRY(VTVControlsMapper, LookUp,				lookUp),
	ATTRIBUTE_ENTRY(VTVControlsMapper, ControlMode,			controlMode),
	ATTRIBUTE_ENTRY(VTVControlsMapper, HornBlast,			hornBlast),
	ATTRIBUTE_ENTRY(VTVControlsMapper, MustMatch,			mustMatch),
	ATTRIBUTE_ENTRY(VTVControlsMapper, ReverseThrustEngaged,reverseThrustEngaged), // ECH 8/3/95 - HACK - deal with thrust
	ATTRIBUTE_ENTRY(VTVControlsMapper, LiftCutEngaged,		liftCutEngaged),
	ATTRIBUTE_ENTRY(VTVControlsMapper, PTTStatus,		   pttStatus)
};

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::InterpretControls(Scalar)
{
	Scalar demand;

	Check(this);

	VTV* vtv = GetEntity();
	Check(vtv);
	VTVPower *power_system =
		Cast_Object(VTVPower*, vtv->GetSubsystem(VTV::PowerSubsystem));

	//
	//----------------------------------------------
	// Make sure the control inputs are within range
	//----------------------------------------------
	//
	Verify(throttlePosition >= 0.0f && throttlePosition <= 1.0f);
	Verify(stickPosition.x >= -1.0f && stickPosition.x <= 1.0f);
	Verify(stickPosition.y >= -1.0f && stickPosition.y <= 1.0f);
   Verify(pedalsPosition >= -1.0f && pedalsPosition <= 1.0f);

	//
	//----------------------------------------------------------------
	// Figure out the proper control model to use based upon the speed
	//----------------------------------------------------------------
	//
	Scalar lateral_power = power_system->maxAccelerationOutput * ZIPPY;
	powerDemand = Vector3D::Identity;
	int slow_mode =
		fabs(vtv->localVelocity.linearMotion.z) <
			lateral_power /  vtv->maxYawVelocity;
	Check_Fpu();
	angularVelocityDemand = Vector3D::Identity;
	Scalar kick_turn = (reverseThrust>0) ? throttlePosition : -throttlePosition;
	powerDemand.z = kick_turn;
	Logical no_steal = False;
	liftCutEngaged = False;
	reverseThrustEngaged = False;

	//
	//----------------------------------------------------
	// If we are in basic mode, do that autothrottle thing
	//----------------------------------------------------
	//
	if (controlMode == BasicMode)
	{
		Scalar desired_speed =
			powerDemand.z
			 * power_system->maxAccelerationOutput
			 / vtv->negativeLinearDragCoefficients.z;
		Check_Fpu();
		Scalar reverse = (reverseThrust>0) ? 1.0f : -1.0f;

		//
		//-----------------------------------------------------------------------
		// If we have no boosters on, and we are going too fast, and the stick is
		// not pushed near full forward, change the throttle setting
		//-----------------------------------------------------------------------
		//
		if (
			!vtv->BoosterOn()
			 && throttlePosition < 0.1f
			 && desired_speed > vtv->localVelocity.linearMotion.z
		)
		{
			no_steal = True;
			powerDemand.z = -reverse;

			// ECH 8/3/95 - HACK - deal with thrust
			Scalar temp = Abs(vtv->localVelocity.linearMotion.z) / 60.0f;
			Clamp(temp, 0.0f, 1.0f);
			powerDemand.z *= pow(temp, 0.5f);
		}

		//
		//--------------------------------------------------------
		// If we are sliding against the engines, go to full power
		//--------------------------------------------------------
		//
		else if (vtv->localVelocity.linearMotion.z * reverse < 0.0f)
		{
			no_steal = True;

			// ECH 8/3/95 - HACK - deal with thrust
			if (throttlePosition < 0.1f)
			{
				Scalar temp = Abs(vtv->localVelocity.linearMotion.z) / 60.0f;
				Clamp(temp, 0.0f, 1.0f);
				powerDemand.z *= pow(temp, 0.5f);
			}
		}
	}
	brakeLights = (powerDemand.z * vtv->localVelocity.linearMotion.z) < 0.0f;

	//
	//--------------------------------------------------------------------------
	// If we are in slow mode, match the lateral power to the turn rate, giving
	// it priority over thrust, so that we will be able to turn a perfect circle
	//--------------------------------------------------------------------------
	//
	if (slow_mode && sideSlip < 1 && controlMode < MasterMode)
	{
		angularVelocityDemand.y += stickPosition.x * vtv->maxYawVelocity;
		powerDemand.x =
			-vtv->maxYawVelocity
			 * fabs(vtv->localVelocity.linearMotion.z)
			 * stickPosition.x
			 / lateral_power;
		Check_Fpu();
		if (reverseThrust>0 && vtv->localVelocity.linearMotion.z > 0.0f)
		{
			powerDemand.x = -powerDemand.x;
		}
		demand = powerDemand.x*powerDemand.x + powerDemand.z*powerDemand.z;
		Check_Fpu();
		if (demand>1.0f)
		{
			if (powerDemand.z > 0.0f)
			{
				powerDemand.z = Sqrt(1.0f - powerDemand.x*powerDemand.x);
			}
			else
			{
				powerDemand.z = -Sqrt(1.0f - powerDemand.x*powerDemand.x);
			}
		}
		Check_Fpu();
	}

	//
	//-------------------------------------------------------------------------
	// If we aren't in slow mode, match the turning rate to the power setting
	// on the stick, sharing power equally between forward and sideways demands
	//-------------------------------------------------------------------------
	//
	else
	{
		powerDemand.x = -stickPosition.x;
		demand = powerDemand.x*powerDemand.x + powerDemand.z*powerDemand.z;
		Check_Fpu();

		//
		//-----------------------------------------------------------------------
		// If we are asking for more power than is available, steal from anything
		// but braking so we can turn quicker if we are in a wimp mode
		//-----------------------------------------------------------------------
		//
		if (demand>1.0f)
		{
			if (controlMode < MasterMode && powerDemand.z < 0.0f && !no_steal)
			{
				powerDemand.z = -Sqrt(1.0f - powerDemand.x*powerDemand.x);
				Check_Fpu();
			}
			else
			{
				demand = Sqrt(demand);
				powerDemand.x /= demand;
				powerDemand.z /= demand;
				Check_Fpu();
			}
		}
	}

	//
	//-------------------------------------------------------------------------
	// The amount of power required for height as requested by the stick should
	// range between 1m height and the service ceiling
	//-------------------------------------------------------------------------
	//
	JointSubsystem *joint_subsystem = vtv->GetJointSubsystem();
	Check(joint_subsystem);
	Verify(joint_subsystem->GetJointCount());

	//
	//------------------------------------------------------------------------
	// If the lift cut button is on, go ahead and put all the power on X and Z.
	// In basic mode, if the height is greater than 5m and the stick is pushed
	// mostly forward
	//------------------------------------------------------------------------
	//
	if (
		liftCut > 0
		 || controlMode < VeteranMode
			&& stickPosition.y <= -0.8f
			&& vtv->heightAboveTerrain >= 5.0f
	)
	{
		powerDemand.y = vtv->powerDive;
		if (powerDemand.y)
		{
			Scalar reduction = Sqrt(1.0f - powerDemand.y*powerDemand.y);
			powerDemand.x *= reduction;
			powerDemand.z *= reduction;
		}
		liftCutEngaged = True;
	}

	//
	//------------------------------------------------------------------------
	// Otherwise, give altitude everything it wants, and split the rest up for
	// X and Z
	//------------------------------------------------------------------------
	//
	else
	{
		if (!vtv->groundEffectDomainSquared)
		{
			if (vtv->heightAboveTerrain >= 2.0f)
			{
				powerDemand.y = 0.0f;
				liftCutEngaged = True;
			}
			else
			{
				demand =
					power_system->maxAccelerationOutput +
					joint_subsystem->GetJointCount() *
					vtv->groundEffectRange;
				powerDemand.y = vtv->GetEnvironment()->gravityConstant/demand;
			}
		}
		else if (controlMode > StandardMode)
		{
			demand =
				power_system->maxAccelerationOutput
				 + joint_subsystem->GetJointCount()
					* (vtv->groundEffectRange
					/ (1.0f + vtv->groundEffectDomainSquared));
			Check_Fpu();
			demand = vtv->GetEnvironment()->gravityConstant/demand;
			powerDemand.y =
				0.5f * ((1.0f - demand) * stickPosition.y + (1.0f + demand));
			Max_Clamp(powerDemand.y, 1.0f);
			Check_Fpu();
		}
		else
		{
			Scalar seek_height = vtv->heightAboveTerrain;
			Scalar height_clamp = 30.0f;
			Max_Clamp(seek_height, height_clamp);
			if (stickPosition.y < 0.0f)
			{
				seek_height =
					::Lerp(seek_height, 1.0f, -stickPosition.y);
			}
			else
			{
				seek_height =
					::Lerp(seek_height, height_clamp, stickPosition.y);
			}
			demand =
				power_system->maxAccelerationOutput +
				joint_subsystem->GetJointCount() *
				 (
					vtv->groundEffectRange
					 / (
						1.0f
						 + vtv->groundEffectDomainSquared*seek_height*seek_height
					 )
				 );
			Check_Fpu();
			powerDemand.y = vtv->GetEnvironment()->gravityConstant/demand;
			if (controlMode == BasicMode)
			{
				Max_Clamp(powerDemand.y, 0.85f);
			}
			else
			{
				Max_Clamp(powerDemand.y, 1.0f);
			}
			Check_Fpu();
		}
		demand = Sqrt(1.0f - powerDemand.y*powerDemand.y);
		powerDemand.x *= demand;
		powerDemand.z *= demand;
	}

	//
	//----------------------------------------------------------
	// Apply the rudders, and simulate a kick turn in basic mode
	//----------------------------------------------------------
	//
	if (controlMode == BasicMode)
	{
		Scalar temp = stickPosition.x * stickPosition.x;
		temp *= stickPosition.x;
		angularVelocityDemand.y += temp * vtv->maxYawVelocity;
	}
	else if (pedalsPosition < 0.0f)
	{
		angularVelocityDemand.y -=
			vtv->maxYawVelocity * pedalsPosition*pedalsPosition;
	}
	else
	{
		angularVelocityDemand.y +=
			vtv->maxYawVelocity * pedalsPosition*pedalsPosition;
	}

	//
	//-------------------------------------------------------------------------
	// If we are not is slow mode, apply yaw to the craft to sufficient to make
	// it turn a perfect circle, then clamp to maximum velocity
	//-------------------------------------------------------------------------
	//
	if (!slow_mode && controlMode < MasterMode && sideSlip < 1)
	{
		angularVelocityDemand.y +=
			-powerDemand.x
			* ZIPPY
			* power_system->maxAccelerationOutput
			/ fabs(vtv->localVelocity.linearMotion.z);
		Check_Fpu();
		if (reverseThrust>0 && vtv->localVelocity.linearMotion.z > 0.0f)
		{
			powerDemand.x = -powerDemand.x;
		}
	}
	Clamp(
		angularVelocityDemand.y,
		-vtv->maxYawVelocity,
		vtv->maxYawVelocity
	);

	//
	//----------------------------------------
	// Turn the eyepoint the direction we want
	//----------------------------------------
	//
	vtv->eyepointRotation = EulerAngles::Identity;
	if (lookLeft > 0)
	{
		vtv->eyepointRotation.yaw = 0.3f * PI;
	}
	else if (lookRight > 0)
	{
		vtv->eyepointRotation.yaw = -0.3f * PI;
	}
	else if (lookBehind > 0)
	{
		vtv->eyepointRotation.yaw = PI;
	}
	else if (lookUp > 0)
	{
		vtv->eyepointRotation.pitch = PI / 8.0f;
	}

	// ECH - HACK - deal with thrust and lift cut
	averageOfForwardThrustDemand.Add(powerDemand.z);
	forwardThrustDemand = averageOfForwardThrustDemand.CalculateAverage();
	switch (controlMode)
	{
	case BasicMode:
		// Lift  cut has been set, see above
		// Reverse thrust if demand over 0.15
		reverseThrustEngaged = (forwardThrustDemand > 0.15f);
		break;

	case StandardMode:
		// Lift  cut has been set, see above
		// Reverse thrust based on button
		reverseThrustEngaged = (reverseThrust > 0);
		break;

	case VeteranMode:
	case MasterMode:
		// Lift cut based on button
		// Reverse thrust based on button
		liftCutEngaged = (liftCut > 0);
		reverseThrustEngaged = (reverseThrust > 0);
		break;
	}
	Check(GetEntity());
	if (GetEntity()->GetSimulationState() == VTV::BurningState)
	{
		liftCutEngaged = False;
		reverseThrustEngaged = False;
	}

	//
	//----------------------------------------------------------------
	// Send PTT status to intercom
	//----------------------------------------------------------------
	//
	if (pttStatus != previousPTTStatus)
	{
		previousPTTStatus = pttStatus;

		Player
			*player_pointer = vtv->GetPlayerLink();
		Check(player_pointer);
		Icom
			*intercom = player_pointer->GetIntercom();
		Check(intercom);

		intercom->SetPTTStatus((Logical) pttStatus > 0);
	}

	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::CreateTemporaryEventMappings(
		Receiver */*receiver*/,
		Receiver::MessageID /*config_message_id*/
	)
{
	Fail("Unhandled mapping!\n");
}

void
	VTVControlsMapper::RemoveTemporaryEventMappings()
{
	Fail("Unhandled mapping!\n");
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::AddOrErase(
		unsigned int,
		Receiver *,
		Receiver::MessageID
	)
{
	Fail("Unhandled mapping!\n");
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::AddOrErase(
		unsigned int,
		ControlsButton *
	)
{
	Fail("Unhandled mapping!\n");
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VTVControlsMapper::SetConfigurationState(Logical enter_config)
{
	Check(this);

	if (enter_config)
	{
		if (GetSimulationState() != ConfigurationState)
		{
			SetSimulationState(ConfigurationState);
			NotifyOfConfigurationModeChange(enter_config);
		}
	}
	else
	{
		if (GetSimulationState() == ConfigurationState)
		{
			SetSimulationState(DefaultState);
			NotifyOfConfigurationModeChange(enter_config);
		}
	}
	Check_Fpu();
}

void
	VTVControlsMapper::NotifyOfControlModeChange(int /*new_mode*/)
{
	Check(this);
	// defaults to no-operation: intended to be processed by
	// platform-specific derived class
	Check_Fpu();
}

void
	VTVControlsMapper::NotifyOfConfigurationModeChange(Logical /*new_state*/)
{
	Check(this);
	// defaults to no-operation: intended to be processed by
	// platform-specific derived class
	Check_Fpu();
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
VTVControlsMapper::VTVControlsMapper(
	VTV *owner,
	int subsystem_ID,
	SubsystemResource *subsystem_resource,
	SharedData &shared_data
):
	VTVSubsystem(
		owner,
		subsystem_ID,
		subsystem_resource,
		shared_data
	),
	averageOfForwardThrustDemand(4) // ECH 8/3/95 - HACK - To deal with reverse thrust
{
	if (owner->GetInstance() == VTV::MasterInstance && owner->IsDynamic())
	{
		SetPerformance(&VTVControlsMapper::InterpretControls);
	}

	stickPosition.x = 0.0f;
	stickPosition.y = 0.0f;
	throttlePosition = 0.0f;
	pedalsPosition = 0.0f;

	reverseThrust = 0;
	liftCut = 0;
	sideSlip = 0;
	powerDemand = Vector3D::Identity;
	angularVelocityDemand = Vector3D::Identity;
	brakeLights = 0;
	lookLeft = 0;
	lookRight = 0;
	lookBehind = 0;
	lookUp = 0;
	hornBlast = 0;
	controlMode = BasicMode;
	forwardThrustDemand = 0.0f;
	liftCutEngaged = False;
	reverseThrustEngaged = False;

	pttStatus = 0;
	previousPTTStatus = 0;

	Check_Fpu();
}

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

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