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>
1056 lines
28 KiB
C++
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());
|
|
}
|