Files
RP412/RP/VTVMPPR.cpp
T
CydandClaude Opus 5 e2bcb29a53 The throttle a pod respawns under is nobody's order
Player request from the six-player night: a pod that dies at full throttle
reappears under a hand still holding full throttle, and launches on it.
The pod should respawn with the throttle at zero and stay there until the
player has brought the control back to zero themselves. The arcade never
met the problem in this form - its throttle was a physical lever that kept
its position regardless - but the same idea protects a real lever too,
which matters now that the pods want this code.

The latch lives in VTVControlsMapper, the one place every control source -
RIO lever, pad trigger, keyboard axis, input script - funnels through, per
simulation step, so it is deterministic and local to the owning machine by
construction (the mapper only runs on MasterInstance).

VTV::Reset arms it through DeathReset, with one wrinkle: the controls
mapper is subsystem ZERO, below BasicSubsystemCount, so Reset's subsystem
loop has never reached it. It is called explicitly now. Only RegularReset
latches - that is the death respawn and the first spawn (so a throttle
held through the countdown no longer buys a flying start). Football's
repositioning reset does not latch, and mission review has no controls.

While latched the mapper computes as though the throttle were zero, but
only for the pass: the true lever position is restored at the bottom of
the function, so the cockpit gauge and the watchers keep showing the
player the hand they need to bring down. The restore also keeps the
release test honest - RIO analog events arrive on CHANGE, so a zeroed
attribute would otherwise sit at zero, release the latch by itself, and
hand back a live throttle the moment the hand twitched.

Release is at 5% of travel: wide enough for a resting trigger or a real
lever's potentiometer sitting a few counts off its stop, narrow enough
that easing off does not satisfy it.

Proven with the input-script harness, both directions. Throttle held at
1.0 from the green light: the pod moves 1.3cm in 30 seconds, all of it
hover settle. Throttle at zero for two seconds then 1.0: the latch
releases in the quiet window and the pod is 665m down the track twenty
seconds later. The second run is also the normal-play case - a control
already at zero releases the latch on the first step, invisibly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 21:08:16 -05:00

1056 lines
28 KiB
C++

#include "rp.h"
#pragma hdrstop
#include "vtvmppr.h"
#include "vtvpwr.h"
#include "..\munga\icom.h"
#include "..\munga\app.h"
#include "..\munga\inputscript.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
//
//
// How close to zero the throttle control must come to release the latch,
// as a fraction of full travel. Wide enough for a resting pad trigger, a
// wound-down keyboard axis, or a real lever's potentiometer sitting a few
// counts off its stop - the pod bay hardware is where this will matter -
// and narrow enough that it cannot be satisfied by easing off.
//
static const Scalar kThrottleLatchRelease = 0.05f;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
VTVControlsMapper::DeathReset(int reset_command)
{
Check(this);
if (reset_command == VTV::RegularReset)
{
throttleLatched = True;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
VTVControlsMapper::InterpretControls(Scalar)
{
Scalar demand;
Check(this);
VTV* vtv = GetEntity();
Check(vtv);
VTVPower *power_system =
Cast_Object(VTVPower*, vtv->GetSubsystem(VTV::PowerSubsystem));
//
//----------------------------------------------------------------
// RP412INPUTSCRIPT: scripted driving, on this subsystem's own step
// clock.
//
// This is the one place every mapper - RIO, Thrustmaster, pad -
// funnels through, and it runs per SIMULATION STEP, so a scripted
// value lands on the same step of every run whatever the frame
// rate. Overriding at the RIO or the controls manager would key
// the timeline to the frame loop, which is wall clock, which is
// the thing the whole harness exists to keep out of the physics.
//
// Only the player's own vehicle: replicants get their state from
// the network, and the mapper does not run for them anyway.
//----------------------------------------------------------------
//
if (RPInputScript_Active())
{
float script_throttle, script_x, script_y, script_pedals;
if (RPInputScript_Sample(GetLastPerformance(),
&script_throttle, &script_x, &script_y, &script_pedals))
{
throttlePosition = script_throttle;
stickPosition.x = script_x;
stickPosition.y = script_y;
pedalsPosition = script_pedals;
}
}
//
//----------------------------------------------
// 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);
//
//----------------------------------------------------------------
// The respawn throttle latch.
//
// A pod that dies at full throttle reappears under a hand still
// holding full throttle, and used to launch on it. From a respawn
// until the control has been seen back at (near) zero, the mapper
// computes as though the throttle were zero.
//
// The zeroing is for this pass only - the true lever position is
// put back at the bottom of the function - so the attribute the
// cockpit gauge and the watchers read stays the player's actual
// hand, which is the thing they need to see to bring it down. It
// also means the release test reads the real control each step
// rather than last step's overwrite: RIO analog events arrive on
// CHANGE, so an overwritten attribute would otherwise sit at zero,
// release the latch on its own, and hand back a live throttle the
// moment the hand moved.
//
// Placed after the input script so a scripted run latches the same
// way a hand-driven one does, per step, deterministically.
//----------------------------------------------------------------
//
Scalar lever = throttlePosition;
if (throttleLatched)
{
if (lever <= kThrottleLatchRelease)
{
throttleLatched = False;
}
else
{
throttlePosition = 0.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);
}
//
// Put the real lever position back - see the latch above. Identity
// whenever the latch is idle.
//
throttlePosition = lever;
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;
throttleLatched = False; // the first-spawn Reset sets it
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());
}