RP412INPUTSCRIPT names a timeline file - one row per change, throttle, stick X and Y, pedals, held until the next row's time - and the pod drives it instead of listening to the controls. Times are SIMULATION seconds from the green light, evaluated per step in the one place every mapper funnels through (VTVControlsMapper::InterpretControls), so the same script is the same lap at any frame rate. Rows hold rather than interpolate on purpose: interpolation would sample differently at different physics rates, and nothing on this path is allowed to. The script shares the green-light anchor with RP412PHYSTRACE - its clock starts at the instant the vehicle is stopped dead - because a timeline that starts when the loader happens to finish is a different lap every run. A race is only deterministic if somebody DRIVES it, and a human cannot drive the same lap twice. The first scripted lap - full throttle, a steer, a crash at speed - earned the harness immediately: - The drive, the crash, the death and the respawn teleport were all BIT-EXACT between identical runs, through t=13.5. Collisions with world geometry and the damage path are step-deterministic, which is better news than the code reading suggested. - The first divergence is the step AFTER the respawn: the DropZoneReply that stands a dead pod back up is posted at wall-clock Now()+1.0 (RPPLAYER.cpp), so the reset lands on a different sim step every run and everything after is time-shifted. The crash is deterministic; the RECOVERY is not. That is the next fix, and it is now a measurement, not a theory. Values are clamped at load, once and visibly, so a script asking for throttle 2.0 cannot trip the mapper's own range Verifies. Off unless the environment names a file; it would be a cheat in a real race. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
990 lines
26 KiB
C++
990 lines
26 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
|
|
//
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
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);
|
|
|
|
//
|
|
//----------------------------------------------------------------
|
|
// 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());
|
|
}
|