Files
BT411/engine/MUNGA_L4/L4CTRL.cpp
T
Joe DiPrimaandClaude Opus 5 d213c980a9 fix: MFD panels never appeared with the HARDWARE RIO (PadRIO coupling)
Nick ran the cab with the real board and the mission came up on the main view
with every MFD dark -- and not one [glasswin] line in the log.

BTGlassPanels_Create() was called only from the END OF THE PadRIO CONSTRUCTOR.
The panels began as the frames around the on-screen RIO button banks, so "the
buttons ride the device" was reasonable then; it is backwards on a real cab.
L4CONTROLS=RIO:COM1 means PadRIO is never constructed, so the MFD windows were
never created -- silently, since nothing is wrong from the display layer's
point of view.

The panels are a DISPLAY concern.  Creation moves to LBE4ControlsManager,
after the L4CONTROLS parse where every device branch converges, and a
symmetric BTGlassPanels_Destroy() goes in its destructor: ~PadRIO tore them
down, ~RIO knows nothing about them, and windows outliving the surfaces they
blit would crash on the next mission cycle.  Both calls are idempotent
(Create returns on gWinCount != 0), so the proven PadRIO path is unchanged
and simply arrives first.  Both guarded by BT_GLASS -- L4GLASSWIN is only in
the build when the gate is on.

Verified on the cart: hardware RIO up AND all three surfaces on their intended
displays in the same run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rw7No5wLTpkaUgA3ANbtZZ
2026-08-06 19:05:04 -05:00

2814 lines
72 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4ctrl.h"
#include "l4keybd.h"
#include "l4app.h"
#include "l4dinput.h"
#include "..\munga\appmgr.h"
#include "dxutils.h"
#ifdef BT_GLASS
#include "l4padrio.h"
#ifdef BT_GLASS
#include "l4glasswin.h" // the per-display MFD windows -- created below for
// EITHER RIO (they used to ride the PadRIO ctor)
#endif
#endif
//
// BT port (input remap): keys claimed by a CONTROLS.MAP binding are consumed
// by the binding engine (game/reconstructed/btinput.cpp) and must NOT also
// reach the key-command channel -- that was the historic double-dispatch
// ('w' drove AND selected pilot 0; F5's key-up value 0x74 aliased to the
// 't' hotkey). The symbol resolves from the game lib at the btl4 exe link.
//
extern "C" int BTInputSuppressKey(unsigned int key_value, int is_char);
// #define LOCAL_TEST
#if defined(LOCAL_TEST)
# define Test_Tell(n) Tell(n << std::flush);
#else
# define Test_Tell(n)
#endif
static const char *initFileName = "JOYSTICK.INI";
#define RIO_SPOOL_SIZE 12000
//###########################################################################
//########################### Button type table #############################
//###########################################################################
#define DARKBTN (char) 0
#define AUTOBTN (char) 1
#define MAPBTN (char) 2
#define DI_TRIGGER 0
#define DI_THUMB1 2
#define DI_THUMB2 1
#define DI_THUMB3 3
#define DI_THROTTLE 10
static char
buttonType[LBE4ControlsManager::ButtonCount] =
{
AUTOBTN, // ButtonAuxLowerLeft8=0x00
AUTOBTN, // ButtonAuxLowerLeft7
AUTOBTN, // ButtonAuxLowerLeft6,
AUTOBTN, // ButtonAuxLowerLeft5,
AUTOBTN, // ButtonAuxLowerLeft4,
AUTOBTN, // ButtonAuxLowerLeft3,
AUTOBTN, // ButtonAuxLowerLeft2,
AUTOBTN, // ButtonAuxLowerLeft1,
AUTOBTN, // ButtonAuxLowerRight8=0x08
AUTOBTN, // ButtonAuxLowerRight7,
AUTOBTN, // ButtonAuxLowerRight6,
AUTOBTN, // ButtonAuxLowerRight5,
AUTOBTN, // ButtonAuxLowerRight4,
AUTOBTN, // ButtonAuxLowerRight3,
AUTOBTN, // ButtonAuxLowerRight2,
AUTOBTN, // ButtonAuxLowerRight1,
AUTOBTN, // ButtonSecondary1=0x10
AUTOBTN, // ButtonSecondary2,
AUTOBTN, // ButtonSecondary3,
AUTOBTN, // ButtonSecondary4,
AUTOBTN, // ButtonSecondary5,
AUTOBTN, // ButtonSecondary6,
DARKBTN, //
DARKBTN, //
AUTOBTN, // ButtonSecondary7=0x18
AUTOBTN, // ButtonSecondary8,
AUTOBTN, // ButtonSecondary9,
AUTOBTN, // ButtonSecondary10,
AUTOBTN, // ButtonSecondary11,
AUTOBTN, // ButtonSecondary12,
DARKBTN, //
DARKBTN, //
AUTOBTN, // ButtonAuxUpperCenter8=0x20
AUTOBTN, // ButtonAuxUpperCenter7,
AUTOBTN, // ButtonAuxUpperCenter6,
AUTOBTN, // ButtonAuxUpperCenter5,
AUTOBTN, // ButtonAuxUpperCenter4,
AUTOBTN, // ButtonAuxUpperCenter3,
AUTOBTN, // ButtonAuxUpperCenter2,
AUTOBTN, // ButtonAuxUpperCenter1,
AUTOBTN, // ButtonAuxUpperLeft8=0x28
AUTOBTN, // ButtonAuxUpperLeft7,
AUTOBTN, // ButtonAuxUpperLeft6,
AUTOBTN, // ButtonAuxUpperLeft5,
AUTOBTN, // ButtonAuxUpperLeft4,
AUTOBTN, // ButtonAuxUpperLeft3,
AUTOBTN, // ButtonAuxUpperLeft2,
AUTOBTN, // ButtonAuxUpperLeft1,
AUTOBTN, // ButtonAuxUpperRight8=0x30
AUTOBTN, // ButtonAuxUpperRight7,
AUTOBTN, // ButtonAuxUpperRight6,
AUTOBTN, // ButtonAuxUpperRight5,
AUTOBTN, // ButtonAuxUpperRight4,
AUTOBTN, // ButtonAuxUpperRight3,
AUTOBTN, // ButtonAuxUpperRight2,
AUTOBTN, // ButtonAuxUpperRight1,
AUTOBTN, // ButtonPanic=0x38,
DARKBTN, //
DARKBTN, //
DARKBTN, //
DARKBTN, // ButtonDoor=0x3C,
DARKBTN, //
DARKBTN, //
DARKBTN, // ButtonThrottle1=0x3F,
MAPBTN, // ButtonJoystickTrigger=0x40
MAPBTN, // ButtonJoystickHatDown=0x41
MAPBTN, // ButtonJoystickHatUp=0x42
MAPBTN, // ButtonJoystickHatRight=0x43
MAPBTN, // ButtonJoystickHatLeft=0x44
MAPBTN, // ButtonJoystickPinky=0x45
MAPBTN, // ButtonJoystickThumbLow=0x46
MAPBTN // ButtonJoystickThumbHigh=0x47
};
//###########################################################################
//############################ Joystick classes #############################
//###########################################################################
Joystick::Joystick()
{
NotationFile
init(initFileName);
channel[0] = new FilterChannel(
&init,
FilterChannel::joystickXName,
10,20,30 // bogus values in case file didn't previously exist
);
Check(channel[0]);
channel[1] = new FilterChannel(
&init,
FilterChannel::joystickYName,
10,20,30 // bogus values in case file didn't previously exist
);
Check(channel[1]);
ButtonReleased = Joystick::NoButton;
ButtonPressed = Joystick::NoButton;
Check_Fpu();
}
Joystick::~Joystick()
{
Check(this);
NotationFile
init(initFileName);
//-------------------------------------------
// Save DOS timestamp
//-------------------------------------------
time_t
dos_time;
time(&dos_time);
init.SetEntry("operator", "time", ctime(&dos_time));
for(int i=0; i<2; ++i)
{
Check(channel[i]);
delete channel[i];
channel[i] = NULL;
}
Check_Fpu();
}
void
Joystick::BeginAlignment()
{
Check(this);
for(int i=0; i<2; ++i)
{
Check(channel[i]);
channel[i]->BeginAlignment();
}
Check_Fpu();
}
void
Joystick::EndAlignment()
{
Check(this);
//------------------------------------------------------------
// If channel was never adjusted, synthesize reasonable values
//------------------------------------------------------------
NotationFile
init(initFileName);
EndAndSaveAlignment(&init);
Check_Fpu();
}
void
Joystick::EndAndSaveAlignment(NotationFile *init_file)
{
Check(this);
Check(init_file);
for(int i=0; i<2; ++i)
{
Check(channel[i]);
channel[i]->EndAlignment(init_file);
}
Check_Fpu();
}
void
Joystick::Update()
{
Fail("Joystick::Update should not be called!\n");
}
void
Joystick::SetDeadBand(Scalar dead_band)
{
Check(this);
for(int i=0; i<2; ++i)
{
Check(channel[i]);
channel[i]->SetDeadBand(dead_band);
}
Check_Fpu();
}
OrdinaryJoystick::OrdinaryJoystick()
{
//STUBBED: JOYSTICK RB 1/14/07
//PC_Joystick_Mask = PCJ_Standard_Mask;
//ExtendedValue.x = (Scalar) 0;
//ExtendedValue.y = (Scalar) 0;
//Check_Fpu();
}
OrdinaryJoystick::~OrdinaryJoystick()
{
Check_Fpu();
}
void OrdinaryJoystick::Update()
{
//STUBBED: JOYSTICK RB 1/14/07
//int newButtons, changedButtons;
//Check(this);
////
////---------------------------------------------------
//// Update analog values
////---------------------------------------------------
////
//PC_Joystick_Update();
//Check(channel[0]);
//Value.x = channel[0]->Update(PC_Joystick1_Raw_X);
//Check(channel[1]);
//Value.y = channel[1]->Update(PC_Joystick1_Raw_Y);
////
////---------------------------------------------------
//// Handle button changes
////---------------------------------------------------
////
//newButtons = (int) PC_Joystick_Buttons;
//changedButtons = newButtons ^ oldButtons;
//ButtonPressed = Joystick::NoButton;
//ButtonReleased = Joystick::NoButton;
//if (changedButtons)
//{
// if (changedButtons & PCJ_Standard_Button1)
// {
// if (newButtons & PCJ_Standard_Button1)
// {
// ButtonPressed |= Joystick::Trigger;
// }
// else
// {
// ButtonReleased |= Joystick::Trigger;
// }
// }
// if (changedButtons & PCJ_Standard_Button2)
// {
// if (newButtons & PCJ_Standard_Button2)
// {
// ButtonPressed |= Joystick::Thumb3;
// }
// else
// {
// ButtonReleased |= Joystick::Thumb3;
// }
// }
//}
//oldButtons = newButtons;
//Check_Fpu();
}
Logical
Joystick::TestInstance() const
{
return True;
}
FlightStickPro::FlightStickPro()
{
//STUBBED: JOYSTICK RB 1/14/07
//NotationFile
// init(initFileName);
//throttle = new FilterChannel(
// &init,
// FilterChannel::throttleName,
// 10,30 // bogus values in case file doesn't exist
//);
//Check(throttle);
//PC_Joystick_Mask = PCJ_FSP_Mask;
//ExtendedValue.x = (Scalar) 0;
//Check_Fpu();
}
FlightStickPro::~FlightStickPro()
{
Check(this);
NotationFile
init(initFileName);
//-------------------------------------------
// Save DOS timestamp
//-------------------------------------------
time_t
dos_time;
time(&dos_time);
init.SetEntry("operator", "time", ctime(&dos_time));
Check(throttle);
delete throttle;
throttle = NULL;
Check_Fpu();
}
void FlightStickPro::Update()
{
//STUBBED: JOYSTICK RB 1/14/07
//int newButtons;
//Check(this);
////
////---------------------------------------------------
//// Update analog values
////---------------------------------------------------
////
//PC_Joystick_Update();
//Check(channel[0]);
//Value.x = channel[0]->Update(PC_Joystick1_Raw_X);
//Check(channel[1]);
//Value.y = channel[1]->Update(PC_Joystick1_Raw_Y);
//// Note inverted direction (below) for throttle
//Check(throttle);
//ExtendedValue.y = 1.0 - throttle->Update(PC_Joystick2_Raw_Y);
////
////---------------------------------------------------
//// Handle button changes
////---------------------------------------------------
////
//newButtons = int (PC_Joystick_Buttons);
//ButtonPressed = Joystick::NoButton;
//ButtonReleased = Joystick::NoButton;
//if (newButtons != oldButtons)
//{
// switch(oldButtons)
// {
// case PCJ_FSP_Up: ButtonReleased = Joystick::HatUp; break;
// case PCJ_FSP_Down: ButtonReleased = Joystick::HatDown; break;
// case PCJ_FSP_Left: ButtonReleased = Joystick::HatLeft; break;
// case PCJ_FSP_Right: ButtonReleased = Joystick::HatRight; break;
// case PCJ_FSP_Trigger: ButtonReleased = Joystick::Trigger; break;
// case PCJ_FSP_LButton: ButtonReleased = Joystick::Thumb3; break;
// case PCJ_FSP_RButton: ButtonReleased = Joystick::Thumb1; break;
// case PCJ_FSP_CButton: ButtonReleased = Joystick::Thumb2; break;
// }
// switch(newButtons)
// {
// case PCJ_FSP_Up: ButtonPressed = Joystick::HatUp; break;
// case PCJ_FSP_Down: ButtonPressed = Joystick::HatDown; break;
// case PCJ_FSP_Left: ButtonPressed = Joystick::HatLeft; break;
// case PCJ_FSP_Right: ButtonPressed = Joystick::HatRight; break;
// case PCJ_FSP_Trigger: ButtonPressed = Joystick::Trigger; break;
// case PCJ_FSP_LButton: ButtonPressed = Joystick::Thumb3; break;
// case PCJ_FSP_RButton: ButtonPressed = Joystick::Thumb1; break;
// case PCJ_FSP_CButton: ButtonPressed = Joystick::Thumb2; break;
// }
// oldButtons = newButtons;
//}
//Check_Fpu();
}
void
FlightStickPro::BeginAlignment()
{
Check(this);
Check(throttle);
throttle->BeginAlignment();
Joystick::BeginAlignment();
Check_Fpu();
}
void
FlightStickPro::EndAlignment()
{
Check(this);
NotationFile
init(initFileName);
Check(throttle);
throttle->EndAlignment(&init);
Joystick::EndAndSaveAlignment(&init);
Check_Fpu();
}
BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext)
{
ThrustMaster *joystick = (ThrustMaster*)pContext;
if (FAILED(joystick->mDI->CreateDevice(pdidInstance->guidInstance, &joystick->mJoystick, NULL)))
return DIENUM_CONTINUE;
return DIENUM_STOP;
}
BOOL CALLBACK EnumAxesCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef)
{
ThrustMaster *joystick = (ThrustMaster*)pvRef;
DIPROPRANGE diprg;
diprg.diph.dwSize = sizeof(DIPROPRANGE);
diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
diprg.diph.dwHow = DIPH_BYID;
diprg.diph.dwObj = lpddoi->dwType;
diprg.lMax = diprg.lMax = 0;
if (lpddoi->guidType == GUID_XAxis)
{
if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
{
joystick->xAxisMin = diprg.lMin;
joystick->xAxisMax = diprg.lMax;
}
}
else if (lpddoi->guidType == GUID_YAxis)
{
if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
{
joystick->yAxisMin = diprg.lMin;
joystick->yAxisMax = diprg.lMax;
}
}
else if (lpddoi->guidType == GUID_ZAxis)
{
if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
{
joystick->zAxisMin = diprg.lMin;
joystick->zAxisMax = diprg.lMax;
}
}
else if (lpddoi->guidType == GUID_RxAxis)
{
}
else if (lpddoi->guidType == GUID_RyAxis)
{
}
else if (lpddoi->guidType == GUID_RzAxis)
{
if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
{
joystick->rzAxisMin = diprg.lMin;
joystick->rzAxisMax = diprg.lMax;
}
}
else if (lpddoi->guidType == GUID_Slider)
{
if (SUCCEEDED(joystick->mJoystick->GetProperty(DIPROP_RANGE, &diprg.diph)))
{
joystick->zAxisMin = diprg.lMin;
joystick->zAxisMax = diprg.lMax;
}
}
return DIENUM_CONTINUE;
}
ThrustMaster::ThrustMaster(HINSTANCE hInstance, HWND hWnd)
{
//STUBBED: JOYSTICK RB 1/14/07
ExtendedValue.x = (Scalar) 0;
ExtendedValue.y = (Scalar) 0;
hatMax = 0;
prevHatState = 0;
mDI = NULL;
mJoystick = NULL;
if (SUCCEEDED(DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&mDI, NULL)))
{
mDI->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, this, DIEDFL_ATTACHEDONLY);
if (mJoystick)
if (SUCCEEDED(mJoystick->SetDataFormat(&c_dfDIJoystick2)))
if (SUCCEEDED(mJoystick->SetCooperativeLevel(hWnd, DISCL_EXCLUSIVE | DISCL_BACKGROUND)))
if (SUCCEEDED(mJoystick->EnumObjects(EnumAxesCallback, this, DIDFT_AXIS)))
return;
}
if (mDI)
{
mDI->Release();
mDI = NULL;
}
if (mJoystick)
{
mJoystick->Release();
mJoystick = NULL;
}
}
ThrustMaster::~ThrustMaster()
{
Check_Fpu();
}
bool ThrustMaster::Poll()
{
HRESULT hr;
if (mJoystick)
{
V( mJoystick->Poll() );
if (FAILED(hr))
{
V( mJoystick->Acquire() );
while (hr == DIERR_INPUTLOST)
V( mJoystick->Acquire() );
return false;
}
V( mJoystick->GetDeviceState(sizeof(DIJOYSTATE2), &mJoystate) );
if (FAILED(hr))
return false;
}
return true;
}
void ThrustMaster::Update()
{
if (!Poll())
return;
//
//---------------------------------------------------
// Update analog values
//---------------------------------------------------
//
Value.x = ((Scalar)(mJoystate.lX - xAxisMin) / (Scalar)(xAxisMax - xAxisMin)) * 2.0f - 1.0f;
Value.y = ((Scalar)(mJoystate.lY - yAxisMin) / (Scalar)(yAxisMax - yAxisMin)) * 2.0f - 1.0f;
LONG z = (mJoystate.lZ == 0 ? mJoystate.rglSlider[0] : mJoystate.lZ);
ExtendedValue.y = 1.0f - (Scalar)(z - zAxisMin) / (Scalar)(zAxisMax - zAxisMin);
// full left rotation = 0, center rotation = 1/2 range, full right = full range
LONG rot = (Scalar)(mJoystate.lRz - rzAxisMin);
Scalar half = (Scalar)(rzAxisMax - rzAxisMin) / 2.0f;
Rotation.x = (Scalar)0;
Rotation.y = (Scalar)0;
if (rot <= half)
Rotation.x = ((half - rot) / half) * 470.0;
else
Rotation.y = ((rot - half) / half) * 470.0;
//
//---------------------------------------------------
// Handle button changes
//---------------------------------------------------
//
ButtonPressed = Joystick::NoButton;
ButtonReleased = Joystick::NoButton;
int newButtons = 0;
if (mJoystate.rgbButtons[DI_TRIGGER])
newButtons |= Joystick::Trigger;
if (mJoystate.rgbButtons[DI_THUMB1])
newButtons |= Joystick::Thumb1;
if (mJoystate.rgbButtons[DI_THUMB2])
newButtons |= Joystick::Thumb2;
if (mJoystate.rgbButtons[DI_THUMB3])
newButtons |= Joystick::Thumb3;
if (mJoystate.rgbButtons[DI_THROTTLE])
newButtons |= Joystick::Throttle;
if (mJoystate.rgdwPOV[0] == 0)
newButtons |= Joystick::HatUp;
if (mJoystate.rgdwPOV[0] == 9000)
newButtons |= Joystick::HatRight;
if (mJoystate.rgdwPOV[0] == 18000)
newButtons |= Joystick::HatDown;
if (mJoystate.rgdwPOV[0] == 27000)
newButtons |= Joystick::HatLeft;
ButtonPressed = (oldButtons ^ newButtons) & newButtons;
ButtonReleased = (oldButtons ^ newButtons) & oldButtons;
oldButtons = newButtons;
}
//#########################################################################
//########################## LBE4ControlsManager ##########################
//#########################################################################
//---------------------------------------------------------------------------
// Class derivation
//---------------------------------------------------------------------------
Derivation* LBE4ControlsManager::GetClassDerivations()
{
static Derivation classDerivations(ControlsManager::GetClassDerivations(), "L4ControlsManager");
return &classDerivations;
}
//---------------------------------------------------------------------------
// Message support
//---------------------------------------------------------------------------
//const Receiver::HandlerEntry
// LBE4ControlsManager::MessageHandlerEntries[]=
//{
// MESSAGE_ENTRY(LBE4ControlsManager,BeginCalibration),
// MESSAGE_ENTRY(LBE4ControlsManager,EndCalibration)
//};
//
//Receiver::MessageHandlerSet
// LBE4ControlsManager::MessageHandlers(
// ELEMENTS(LBE4ControlsManager::MessageHandlerEntries),
// LBE4ControlsManager::MessageHandlerEntries,
// ControlsManager::MessageHandlers
// );
//---------------------------------------------------------------------------
// Shared data
//---------------------------------------------------------------------------
LBE4ControlsManager::SharedData
LBE4ControlsManager::DefaultData(
LBE4ControlsManager::GetClassDerivations(),
LBE4ControlsManager::GetMessageHandlers()
);
//
//############################################################################
// LBE4ControlsManager
//
// Creator method
//----------------------------------------------------------------------------
// Enter: virtualDataPtr
//############################################################################
//
LBE4ControlsManager::LBE4ControlsManager():
ControlsManager(L4ControlsManagerClassID, DefaultData)
{
Check_Pointer(this);
//
//-------------------------------------------------------------------------
// configure
//-------------------------------------------------------------------------
//
flags.mouseExists = 0;
flags.keyboardExists = 0;
flags.joystickExists = 0;
flags.RIOExists = 0;
virtualJoystickX = 0;
virtualJoystickY = 0;
lastJoystickUpdate = 0.0f;
joystickType = LBE4ControlsManager::none;
mousePointer = NULL;
joystickPointer = NULL;
rioPointer = NULL;
lastRioRequest = 0L;
primaryControlType = LBE4ControlsManager::None;
char *controlString = getenv("L4CONTROLS");
if (controlString != NULL)
{
//
//-------------------------------------------------
// parse environment variable, build configuration
//-------------------------------------------------
//
char
temp[128];
char
*dest;
char
c;
int
noMore(0);
do
{
//
// Get the next token
// Can't use strtok(), because RIO::RIO uses it,
// and it's not re-entrant.
//
dest = temp;
if (noMore)
{
*dest = '\0';
}
else
{
do
{
switch(c=*controlString++)
{
case '\0':
case '\n':
noMore = 1;
// deliberately falls through into next case
case ',':
case ';':
c = '\0';
// deliberately falls through into default case
default:
*dest++=c;
break;
}
}
while (c != '\0');
}
//Win32 Serial support: ADB 06/30/07
if(strncmp(temp, "RIO", 3) == 0)
{
if(strncmp(temp, "RIO:COM", 7) == 0)
{
int l = strlen(temp+4) + 5;
char* comm = new char[l];
strcpy(comm, "\\\\.\\");
strcpy(comm+4, temp+4);
rioPointer = new RIO(comm);//temp + 4);
Check(rioPointer);
Register_Object(rioPointer);
flags.RIOExists = 1;
primaryControlType = LBE4ControlsManager::PrimaryRIO;
delete comm;
}
else
{
rioPointer = new RIO("COM1");
Check(rioPointer);
Register_Object(rioPointer);
flags.RIOExists = 1;
primaryControlType = LBE4ControlsManager::PrimaryRIO;
}
}
else if (strcmpi(temp, "PAD") == 0)
{
#ifdef BT_GLASS
//
// The hardware-less cockpit device (glass cockpit, step 2b):
// XInput pad + PC keyboard synthesize the RIO control
// surface; the whole stock RIO path above the seam (mapper,
// lamps, streamed .CTL mappings) runs unchanged.
//
rioPointer = new PadRIO();
Check(rioPointer);
Register_Object(rioPointer);
flags.RIOExists = 1;
primaryControlType = LBE4ControlsManager::PrimaryRIO;
#else
DEBUG_STREAM << "L4CONTROLS=PAD ignored: PadRIO is not in "
"this build (configure with -DBT_GLASS=ON)\n" << std::flush;
#endif
}
else if (strcmpi(temp, "KEYBOARD") == 0)
{
flags.keyboardExists = 1;
}
else if (strcmpi(temp, "MOUSE") == 0)
{
if (!flags.mouseExists)
{
flags.mouseExists = 1;
mousePointer = new Mouse;
Check(mousePointer);
Register_Object(mousePointer);
if (mousePointer->Errors)
{
Unregister_Object(mousePointer);
delete mousePointer;
mousePointer = NULL;
flags.mouseExists = 0;
} else
{
primaryControlType = LBE4ControlsManager::PrimaryThrustMaster;
}
}
}
else if (strcmpi(temp, "JOYSTICK") == 0)
{
flags.joystickExists = 1;
joystickType = LBE4ControlsManager::ordinary;
joystickPointer = new OrdinaryJoystick();
Register_Object(joystickPointer);
primaryControlType = LBE4ControlsManager::PrimaryGenericJoystick;
}
else if (strcmpi(temp, "FLIGHTSTICKPRO") == 0)
{
flags.joystickExists = 1;
joystickType = LBE4ControlsManager::flightstickPro;
joystickPointer = new FlightStickPro();
Register_Object(joystickPointer);
primaryControlType = LBE4ControlsManager::PrimaryFlightStickPro;
}
else if (strcmpi(temp, "THRUSTMASTER") == 0)
{
flags.joystickExists = 1;
joystickType = LBE4ControlsManager::thrustMaster;
joystickPointer = new ThrustMaster(ApplicationManager::GetCurrentManager()->GetHInstance(), ApplicationManager::GetCurrentManager()->GetHWnd());
Register_Object(joystickPointer);
primaryControlType = LBE4ControlsManager::PrimaryThrustMaster;
}
else if (strcmpi(temp, "DIJOYSTICK") == 0)
{
joystickPointer = new DIJoystick(ApplicationManager::GetCurrentManager()->GetHInstance(), ApplicationManager::GetCurrentManager()->GetHWnd());
if (((DIJoystick*)joystickPointer)->IsValid())
{
flags.joystickExists = 1;
joystickType = LBE4ControlsManager::directInputJoystick;
Register_Object(joystickPointer);
primaryControlType = LBE4ControlsManager::PrimaryThrustMaster;
}
else
{
delete joystickPointer;
joystickPointer = NULL;
}
}
}
while (temp[0] != '\0');
}
//--------------------------------------------------
// The per-display MFD windows (2026-08-06)
//--------------------------------------------------
// These used to be created ONLY at the end of the PadRIO constructor,
// because they began life as the frames around the on-screen RIO button
// banks -- "the buttons ride the device". On a real cab that coupling is
// backwards: selecting the HARDWARE RIO means PadRIO is never built, so
// the MFD panels never appeared at all. Found on the crash cart, where
// the mission came up on the main view with every MFD dark.
//
// The windows are a DISPLAY concern, so create them here -- after the
// L4CONTROLS parse, where every device branch converges -- and let them
// exist whichever RIO won. In pod surface mode there are no on-screen
// buttons on them at all. Create() is idempotent (`gWinCount != 0`
// returns), so the PadRIO call site stays valid and simply arrives first.
//
#ifdef BT_GLASS
if (rioPointer != NULL && BTGlassPanelsActive())
{
BTGlassPanels_Create();
}
#endif
//--------------------------------------------------
// Clear all lamps (and reset analog I/O)
//--------------------------------------------------
if (rioPointer != NULL)
{
Check(rioPointer);
rioPointer->GeneralReset();
}
//--------------------------------------------------
// Get mode mask for updating
//--------------------------------------------------
Check(application);
Check(application->GetModeManager());
ModeMask
mode_mask = application->GetModeManager()->GetModeMask();
//--------------------------------------------------
// Initialize scalar group
//--------------------------------------------------
{
int
i;
ControlsScalar
initial_scalar = 0.0;
for(i=0; i<ScalarCount; ++i)
{
scalarGroup[i].ForceUpdate(&initial_scalar, mode_mask);
}
}
//--------------------------------------------------
// Initialize joystick group
//--------------------------------------------------
{
int
i;
ControlsJoystick
initial_stick;
initial_stick.x = 0.0;
initial_stick.y = 0.0;
for(i=0; i<JoystickCount; ++i)
{
joystickGroup[i].ForceUpdate(&initial_stick, mode_mask);
}
}
//--------------------------------------------------
// Initialize keyboard group
//--------------------------------------------------
{
int
i;
ControlsKey
initial_key = 0;
for(i=0; i<KeyboardCount; ++i)
{
keyboardGroup[i].ForceUpdate(&initial_key, mode_mask);
}
}
//--------------------------------------------------
// Initialize button control groups
//--------------------------------------------------
{
int
i;
ControlsButton
initial_button_value = 0;
for(i=0; i<ButtonCount; ++i)
{
buttonGroup[i].ForceUpdate(&initial_button_value, mode_mask);
buttonActivateModeMask[i] = mode_mask;
}
}
//--------------------------------------------------
// Initialize control mode
//--------------------------------------------------
l4ControlsMode = normalMode;
{
int i;
for(i=0; i<maxStringIDs; ++i)
{
temporaryStringID[i] = 0;
}
}
// stringManager.Add("A90", KeyboardPilot, this, BeginCalibrationMessageID, 0);
//
//-----------------------------------------
// Initialize the RIO stream spoofing stuff
//-----------------------------------------
//
rioStream = NULL;
streamStart = Now();
playbackMode = False;
streamEnabled = False;
previousJoystickX = (Scalar) 0;
previousJoystickY = (Scalar) 0;
previousThrottle = (Scalar) 0;
previousPedalLeft = (Scalar) 0;
previousPedalRight = (Scalar) 0;
//
//-------------------------------------
// See if we need to load in a RIO file
//-------------------------------------
//
CString
playback = L4Application::GetRIOPlaybackFileName();
if (playback && strlen(playback))
{
if (!flags.RIOExists)
{
DEBUG_STREAM << "RIO must be enabled by setenv!\n" << std::flush;
PostQuitMessage(1);
}
LoadPlayback(playback);
playbackMode = True;
}
CString
record = L4Application::GetRIORecordingFileName();
if (record && strlen(record))
{
if (!flags.RIOExists)
{
DEBUG_STREAM << "RIO must be enabled by setenv!\n" << std::flush;
PostQuitMessage(1);
}
RIOStreamEvent
*events = new RIOStreamEvent[RIO_SPOOL_SIZE];
Register_Pointer(events);
Verify(rioStream == NULL);
rioStream =
new MemoryStream(events, RIO_SPOOL_SIZE * sizeof(RIOStreamEvent));
Register_Object(rioStream);
}
Check_Fpu();
}
//
//############################################################################
// ~LBE4ControlsManager
//
// Destructor method
//############################################################################
//
LBE4ControlsManager::~LBE4ControlsManager()
{
Check(this);
//
//----------------------------------------
// If we are recording, save the recording
//----------------------------------------
//
if (IsRecording())
{
CString
record = L4Application::GetRIORecordingFileName();
SaveRecording(record);
}
if (rioStream)
{
Check(rioStream);
rioStream->Rewind();
RIOStreamEvent
*start = (RIOStreamEvent*)rioStream->GetPointer();
Unregister_Object(rioStream);
delete rioStream;
Unregister_Pointer(start);
delete[] start;
rioStream = NULL;
streamEnabled = False;
}
//--------------------------------------------------
// Delete mouse
//--------------------------------------------------
if (mousePointer != NULL)
{
Check(mousePointer);
Unregister_Object(mousePointer);
delete mousePointer;
mousePointer = NULL;
}
//--------------------------------------------------
// Delete joystick
//--------------------------------------------------
if (joystickPointer != NULL)
{
Check(joystickPointer);
Unregister_Object(joystickPointer);
delete joystickPointer;
joystickPointer = NULL;
}
//--------------------------------------------------
// Delete string manager temporary groups
//--------------------------------------------------
ExitControlsMode();
//--------------------------------------------------
// Delete RIO AFTER buttons!
// ...the 'autoManager' button class sends
// lamp commands when deleted.
//--------------------------------------------------
if (rioPointer != NULL)
{
Check(rioPointer);
Unregister_Object(rioPointer);
delete rioPointer;
rioPointer = NULL;
}
#ifdef BT_GLASS
//
// Symmetric with the Create above (2026-08-06). ~PadRIO tears the panels
// down itself, so this is a no-op there -- but the HARDWARE RIO's dtor
// knows nothing about them, and windows that outlive the surfaces they
// blit are a crash waiting for the next mission cycle. Destroy is safe
// if they were never created.
//
BTGlassPanels_Destroy();
#endif
Check_Fpu();
}
//
//#############################################################################
// LocalShutdown
//#############################################################################
//
void
LBE4ControlsManager::LocalShutdown()
{
Check_Pointer(this);
if (rioPointer != NULL)
{
Check(rioPointer);
Unregister_Object(rioPointer);
delete rioPointer;
rioPointer = NULL;
}
ControlsManager::LocalShutdown();
Check_Fpu();
}
//
//############################################################################
// Remove
//
// Remove all controlsInstance records bearing the given ID
//############################################################################
//
void
LBE4ControlsManager::Remove(ModeMask mode_mask)
{
int i;
Check(this);
for(i=0; i<ScalarCount; ++i)
{
Check(&scalarGroup[i]);
scalarGroup[i].Remove(mode_mask);
}
for(i=0; i<JoystickCount; ++i)
{
Check(&joystickGroup[i]);
joystickGroup[i].Remove(mode_mask);
}
for(i=0; i<KeyboardCount; ++i)
{
Check(&keyboardGroup[i]);
keyboardGroup[i].Remove(mode_mask);
}
for(i=0; i<ButtonCount; ++i)
{
Check(&buttonGroup[i]);
buttonGroup[i].Remove(mode_mask);
}
Check(&mouseGroup[0]);
mouseGroup[0].Remove(mode_mask);
Check_Fpu();
}
//
//############################################################################
// SetLamp
//
// Sets a specific lamp to the designated state
//############################################################################
//
void
LBE4ControlsManager::SetLamp(
int lamp_number,
int state
)
{
if (rioPointer != NULL)
{
Check(rioPointer);
Verify(lamp_number >= 0);
Verify(lamp_number < LBE4ControlsManager::LampCount);
rioPointer->SetLamp(lamp_number, (RIO::LampState) state);
}
Check_Fpu();
}
//
//############################################################################
// Game-side device query (glass-cockpit step 2c): True when a real cockpit
// device (serial RIO on the pod, PadRIO under BT_GLASS) is present AND
// delivering input. The game's dev keyboard input bridges (mech4.cpp,
// mechmppr.cpp) stand down then -- they would clobber the device's engine
// push every frame. This automates the documented pod intent
// ("BT_KEY_BRIDGE=0 to disable on pods").
//############################################################################
//
int
BTRIODevicePresent(void)
{
if (application == NULL)
{
return 0;
}
LBE4ControlsManager *controls =
(LBE4ControlsManager *)application->GetControlsManager();
return controls != NULL
&& controls->rioPointer != NULL
&& controls->rioPointer->IsOperational();
}
//############################################################################
// Execute
//
// Execution method
//############################################################################
//
void
LBE4ControlsManager::Execute()
{
ControlsJoystick virtual_joystick;
ControlsKey keyValue;
ControlsMouse mouseValue;
static Scalar mouseWheel = 0.0;
Logical new_RIO_values(False);
Logical mouseMoved = false;
Logical mouseWheelMoved = false;
static int oldX = 0;
static int oldY = 0;
Check(this);
//--------------------------------------------------
// Get mode mask for updating
//--------------------------------------------------
Check(application);
Check(application->GetModeManager());
ModeMask
mode_mask = application->GetModeManager()->GetModeMask();
//-------------------------------------------------------------------------
// Process operator control mode
//-------------------------------------------------------------------------
// ManageControlsMode();
//-------------------------------------------------------------------------
// Process RIO
//-------------------------------------------------------------------------
if (flags.RIOExists)
{
Check(rioPointer);
RIO::RIOEvent rio_event;
//
//------------------------------------
// Request analog update
// (held down to 5 Hz unless reply
// is received: reply allows another
// request to be sent next invocation)
//------------------------------------
//
if (!playbackMode)
{
//Scalar
// delta_t(Now() - lastRioRequest);
//Scalar
// limit;
//static Logical
// first_time = True;
//ADB 10/01/07
static DWORD last_check = GetTickCount();
DWORD limit = 15000;
if(application->GetApplicationState() == Application::RunningMission)
{
//Posible Bug: ADB 03/30/07
//limit = 15.0; // 0.2
limit = 50;//reduced to improve responsiveness
}
//else
//{
// //limit = 15.0;
// limit = 15000;
//}
//if (first_time)
//{
// first_time = False;
//}
//else
{
DWORD this_check = GetTickCount();
//if(this_check - last_check >= 1000)
//DEBUG_STREAM << "No Request in " << (this_check - last_check) << "ms." << std::endl << std::flush;
if((this_check - last_check) >= limit)
//if (delta_t >= limit) // 5 Hz minimum rate, in case of no reply
{
//-------------------------
// Debugging aid
//-------------------------
//long time_in_ticks;
//time_in_ticks = lastRioRequest;
//if (time_in_ticks != 0L)
//{
// DEBUG_STREAM << "LBE4ControlsManager::Execute, lost RIO analog request\n";
//STUBBED: JOYSTICK RB 1/14/07
/*extern Logical
iThinkIRQIsOn;
extern int
pcspakCharactersSent;
DEBUG_STREAM <<
"iThinkIRQIsOn =" << iThinkIRQIsOn <<
", actual state =" << PCSPAKIRQState() <<
", characters sent =" << pcspakCharactersSent << "\n";*/
//}
rioPointer->RequestAnalogUpdate();
//lastRioRequest = Now();
last_check = this_check;
}
//-------------------------
// Debugging aid
//-------------------------
//else if (delta_t < 0.0)
//{
// DEBUG_STREAM <<
// "LBE4ControlsManager::Execute, delta_t went negative (" <<
// delta_t << ")\n";
// rioPointer->RequestAnalogUpdate();
// lastRioRequest = Now();
//}
}
}
//
//------------------------------------
// Process events
//------------------------------------
//
while (GetNextRIOEvent(&rio_event))
{
ProcessRIOEvent(rio_event, &new_RIO_values);
}
}
//
//-------------------------------------------------------------------------
// Read physical joystick inputs, filter the values
//-------------------------------------------------------------------------
//
if (flags.joystickExists)
{
Scalar delta_t = (Scalar)Now() - lastJoystickUpdate;
if (delta_t > .05)
{
Check(joystickPointer);
joystickPointer->Update();
joystickPointer->Value.x = -joystickPointer->Value.x; // HACK!!!
lastJoystickUpdate = Now();
}
}
//
//-------------------------------------------------------------------------
// Read physical mouse inputs, generate virtual joystick values
//-------------------------------------------------------------------------
//
if (flags.mouseExists)
{
Check(mousePointer);
//OLD WAY: mousePointer->ReadCounters(&mouseValue.x, &mouseValue.y);
//NEW WAY
/*MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
{
if (msg.message == WM_MOUSEMOVE)
{
mouseMoved = true;
GetMessage(&msg, NULL, 0, 0);
int x = (msg.lParam & 0xFFFF);
int y = (msg.lParam & 0xFFFF0000) >> 16;
mouseValue.x = x - oldX;
mouseValue.y = y - oldY;
oldX = x;
oldY = y;
virtualJoystickX += mouseValue.x;
virtualJoystickY += mouseValue.y;
#define MAXEXTENT 400 // This is a guess. We'll have to try it and see.
if (virtualJoystickX > MAXEXTENT) { virtualJoystickX = MAXEXTENT; }
if (virtualJoystickX < -MAXEXTENT) { virtualJoystickX = -MAXEXTENT; }
virtual_joystick.x = (Scalar) virtualJoystickX / (Scalar) MAXEXTENT;
if (virtualJoystickY > MAXEXTENT) { virtualJoystickY = MAXEXTENT; }
if (virtualJoystickY < -MAXEXTENT) { virtualJoystickY = -MAXEXTENT; }
virtual_joystick.y = (Scalar) virtualJoystickY / (Scalar) MAXEXTENT;
}*/
//else if (msg.message == 0x020A /*WM_MOUSEWHEEL*/)
/*{
#define MAXEXTENT 1200.0f // Just a guess
mouseWheelMoved = true;
GetMessage(&msg, NULL, 0, 0);
//mouseWheel = (signed int)((msg.wParam & 0xFFFF0000) >> 16) * 1.0f;
int delta = (mouseWheel * MAXEXTENT) + ((short)HIWORD(msg.wParam));
if (delta > MAXEXTENT)
delta = MAXEXTENT;
if (delta < 0.0f)
delta = 0.0f;
mouseWheel = delta / MAXEXTENT;
}
}*/
}
//
//-------------------------------------------------------------------------
// Update the scalar mapping groups using the analog values
//-------------------------------------------------------------------------
//
if (flags.RIOExists && new_RIO_values)
{
Check(rioPointer);
Check(&scalarGroup[LBE4ControlsManager::ScalarThrottle]);
scalarGroup[LBE4ControlsManager::ScalarThrottle].Update(
&rioPointer->Throttle, mode_mask
);
Check(&scalarGroup[LBE4ControlsManager::ScalarLeftPedal]);
scalarGroup[LBE4ControlsManager::ScalarLeftPedal].Update(
&rioPointer->LeftPedal, mode_mask
);
Check(&scalarGroup[LBE4ControlsManager::ScalarRightPedal]);
scalarGroup[LBE4ControlsManager::ScalarRightPedal].Update(
&rioPointer->RightPedal, mode_mask
);
Check(&scalarGroup[LBE4ControlsManager::ScalarJoystickX]);
scalarGroup[LBE4ControlsManager::ScalarJoystickX].Update(
&rioPointer->JoystickX, mode_mask
);
Check(&scalarGroup[LBE4ControlsManager::ScalarJoystickY]);
scalarGroup[LBE4ControlsManager::ScalarJoystickY].Update(
&rioPointer->JoystickY, mode_mask
);
}
else if (flags.joystickExists)
{
scalarGroup[LBE4ControlsManager::ScalarLeftPedal].Update(
&joystickPointer->Rotation.x, mode_mask
);
scalarGroup[LBE4ControlsManager::ScalarRightPedal].Update(
&joystickPointer->Rotation.y, mode_mask
);
Check(&scalarGroup[LBE4ControlsManager::ScalarThrottle]);
scalarGroup[LBE4ControlsManager::ScalarThrottle].Update(
&joystickPointer->ExtendedValue.y, mode_mask
);
Check(&scalarGroup[LBE4ControlsManager::ScalarJoystickX]);
scalarGroup[LBE4ControlsManager::ScalarJoystickX].Update(
&joystickPointer->Value.x, mode_mask
);
Check(&scalarGroup[LBE4ControlsManager::ScalarJoystickY]);
scalarGroup[LBE4ControlsManager::ScalarJoystickY].Update(
&joystickPointer->Value.y, mode_mask
);
}
if (flags.mouseExists)
{
if (mouseMoved)
{
Check(&scalarGroup[LBE4ControlsManager::ScalarMouseX]);
scalarGroup[LBE4ControlsManager::ScalarMouseX].Update(
&virtual_joystick.x, mode_mask
);
Check(&scalarGroup[LBE4ControlsManager::ScalarMouseY]);
scalarGroup[LBE4ControlsManager::ScalarMouseY].Update(
&virtual_joystick.y, mode_mask
);
}
else
{
Scalar clear = 0;
scalarGroup[LBE4ControlsManager::ScalarMouseX].Update(&clear, mode_mask);
scalarGroup[LBE4ControlsManager::ScalarMouseY].Update(&clear, mode_mask);
}
if (mouseWheelMoved)
{
scalarGroup[LBE4ControlsManager::ScalarThrottle].Update(
&mouseWheel, mode_mask
);
}
}
//
//-------------------------------------------------------------------------
// Update the joystick mapping groups using the analog values
//-------------------------------------------------------------------------
//
if (flags.RIOExists && new_RIO_values)
{
Check(rioPointer);
ControlsJoystick
stick;
stick.x = rioPointer->JoystickX;
stick.y = rioPointer->JoystickY;
//
// BT_CTRLMAP_LOG=1: periodic trace of the wire stick reaching the
// joystick-group push (stick-path diagnosis).
//
{
static const int s_stickLog =
(getenv("BT_CTRLMAP_LOG") != 0 && *getenv("BT_CTRLMAP_LOG") != '0');
static unsigned long s_lastStickLog = 0;
unsigned long stick_now = timeGetTime();
if (s_stickLog && stick_now - s_lastStickLog >= 1000)
{
s_lastStickLog = stick_now;
DEBUG_STREAM << "[ctrlmap] push stick x=" << stick.x
<< " y=" << stick.y
<< " mode 0x" << std::hex << (unsigned)mode_mask
<< std::dec << "\n" << std::flush;
}
}
Check(&joystickGroup[LBE4ControlsManager::JoystickPhysical]);
joystickGroup[LBE4ControlsManager::JoystickPhysical].Update(
&stick, mode_mask
);
}
else if (flags.joystickExists)
{
Check(&joystickGroup[LBE4ControlsManager::JoystickPhysical]);
joystickGroup[LBE4ControlsManager::JoystickPhysical].Update(
&joystickPointer->Value, mode_mask
);
}
if (flags.mouseExists && mouseMoved)
{
Check(&joystickGroup[LBE4ControlsManager::JoystickVirtual]);
/*joystickGroup[LBE4ControlsManager::JoystickVirtual].Update(
&virtual_joystick, mode_mask
);*/
joystickGroup[LBE4ControlsManager::JoystickPhysical].Update(
&virtual_joystick, mode_mask
);
}
//
//-------------------------------------------------------------------------
// Update the PC keyboard mapping group
//-------------------------------------------------------------------------
//
if (flags.keyboardExists)
{
//RB 1/20/07
//keyValue = (ControlsKey) PC_Keyboard_Read();
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
{
if (msg.message == WM_SYSKEYUP || msg.message == WM_KEYUP || msg.message == WM_CHAR)
{
GetMessage(&msg, NULL, 0, 0);
TranslateMessage(&msg);
keyValue = (msg.wParam & VK_MASK);
if (msg.message == WM_SYSKEYUP)
keyValue |= ALT_BIT;
//
// BT port fix: for WM_KEYUP this wParam is a VIRTUAL-KEY code, not a
// character, but every consumer (Application::KeyCommandMessageHandler
// and friends) compares against typed CHARACTERS. The navigation
// block VK_PRIOR..VK_DELETE (0x21-0x2E: PgUp/PgDn/End/Home/arrows/
// Insert/Delete) collides with the '!'..'.' hotkey characters --
// releasing the UP ARROW (VK_UP==0x26=='&') silently killed the
// mission via the '&' stop command. Drop non-character keyups;
// deliberately TYPED hotkeys still arrive via WM_CHAR.
//
if (msg.message == WM_KEYUP
&& msg.wParam >= VK_PRIOR && msg.wParam <= VK_DELETE)
{
// swallowed: navigation key, not a character command
}
else if (BTInputSuppressKey((unsigned int)keyValue,
msg.message == WM_CHAR))
{
// swallowed: remapped by CONTROLS.MAP
}
#ifdef BT_GLASS
else if (PadRIO::SuppressKey((unsigned int)keyValue,
msg.message == WM_CHAR))
{
// swallowed: claimed by a bindings.txt row (PadRIO) --
// the glass-side analog of the CONTROLS.MAP suppression
// above (btinput stands down when PadRIO owns the path)
}
#endif
else
{
keyboardGroup[KeyboardPC].ForceUpdate(&keyValue, mode_mask);
stringManager.Update(KeyboardPC, keyValue);
}
}
}
}
//
//-------------------------------------------------------------------------
// Update the button mapping groups
//-------------------------------------------------------------------------
//
//
//--------------------------
// Joystick buttons
//--------------------------
//
if (flags.joystickExists)
{
int i;
ControlsButton b;
Check(joystickPointer);
i = joystickPointer->ButtonReleased;
if(i != Joystick::NoButton)
{
if (i & Joystick::Trigger)
{
b = - ButtonJoystickTrigger - 1;
buttonGroup[ButtonJoystickTrigger].Update(&b, mode_mask);
}
if (i & Joystick::Thumb1)
{
b = - ButtonJoystickPinky - 1;
buttonGroup[ButtonJoystickPinky].Update(&b, mode_mask);
}
if (i & Joystick::Thumb2)
{
b = - ButtonJoystickThumbLow - 1;
buttonGroup[ButtonJoystickThumbLow].Update(&b, mode_mask);
}
if (i & Joystick::Thumb3)
{
b = - ButtonJoystickThumbHigh - 1;
buttonGroup[ButtonJoystickThumbHigh].Update(&b, mode_mask);
}
if (i & Joystick::Throttle)
{
b = - ButtonThrottle1 - 1;
buttonGroup[ButtonThrottle1].Update(&b, mode_mask);
}
if (i & Joystick::HatUp)
{
b = - ButtonJoystickHatUp - 1;
buttonGroup[ButtonJoystickHatUp].Update(&b, mode_mask);
}
if (i & Joystick::HatDown)
{
b = - ButtonJoystickHatDown - 1;
buttonGroup[ButtonJoystickHatDown].Update(&b, mode_mask);
}
if (i & Joystick::HatLeft)
{
b = - ButtonJoystickHatLeft - 1;
buttonGroup[ButtonJoystickHatLeft].Update(&b, mode_mask);
}
if (i & Joystick::HatRight)
{
b = - ButtonJoystickHatRight - 1;
buttonGroup[ButtonJoystickHatRight].Update(&b, mode_mask);
}
}
i = joystickPointer->ButtonPressed;
if(i != Joystick::NoButton)
{
if (i & Joystick::Trigger)
{
b = ButtonJoystickTrigger + 1;
buttonGroup[ButtonJoystickTrigger].Update(&b, mode_mask);
}
if (i & Joystick::Thumb1)
{
b = ButtonJoystickPinky + 1;
buttonGroup[ButtonJoystickPinky].Update(&b, mode_mask);
}
if (i & Joystick::Thumb2)
{
b = ButtonJoystickThumbLow + 1;
buttonGroup[ButtonJoystickThumbLow].Update(&b, mode_mask);
}
if (i & Joystick::Thumb3)
{
b = ButtonJoystickThumbHigh + 1;
buttonGroup[ButtonJoystickThumbHigh].Update(&b, mode_mask);
}
if (i & Joystick::Throttle)
{
b = ButtonThrottle1 - 1;
buttonGroup[ButtonThrottle1].Update(&b, mode_mask);
}
if (i & Joystick::HatUp)
{
b = ButtonJoystickHatUp + 1;
buttonGroup[ButtonJoystickHatUp].Update(&b, mode_mask);
}
if (i & Joystick::HatDown)
{
b = ButtonJoystickHatDown + 1;
buttonGroup[ButtonJoystickHatDown].Update(&b, mode_mask);
}
if (i & Joystick::HatLeft)
{
b = ButtonJoystickHatLeft + 1;
buttonGroup[ButtonJoystickHatLeft].Update(&b, mode_mask);
}
if (i & Joystick::HatRight)
{
b = ButtonJoystickHatRight + 1;
buttonGroup[ButtonJoystickHatRight].Update(&b, mode_mask);
}
}
}
//
//--------------------------
// Mouse buttons
//--------------------------
//
if (flags.mouseExists)
{
int i, j, k;
ControlsButton b;
k = mousePointer->ButtonCount;
for(i=0, j=LBE4ControlsManager::ButtonMouseLeft; i<k; ++i,++j)
{
if (mousePointer->ButtonPressed(i))
{
b = (ControlsButton) (j+1);
buttonGroup[j].Update(&b, mode_mask);
}
if (mousePointer->ButtonReleased(i))
{
b = (ControlsButton) (-j-1);
buttonGroup[j].Update(&b, mode_mask);
}
}
//
//-------------------------------------------------------------------------
// Update the mouse mapping group
//-------------------------------------------------------------------------
//
mouseGroup[0].Update(&mouseValue, mode_mask);
}
// Tell("LBE4ControlsManager::Execute ends\n" << std::flush);
Check_Fpu();
}
//
//############################################################################
// BeginAlignJoystick
//
// Start alignment process for joystick and analog inputs
//############################################################################
//
void
LBE4ControlsManager::BeginAlignJoystick()
{
Check(this);
if (flags.joystickExists)
{
Check(joystickPointer);
joystickPointer->BeginAlignment();
}
Check_Fpu();
}
//
//############################################################################
// EndAlignJoystick
//
// End alignment process for joystick and analog inputs
//############################################################################
//
void
LBE4ControlsManager::EndAlignJoystick()
{
Check(this);
if (flags.RIOExists)
{
Check(rioPointer);
rioPointer->ForceCenterJoystick();
}
if (flags.joystickExists)
{
Check(joystickPointer);
joystickPointer->EndAlignment();
}
Check_Fpu();
}
void
LBE4ControlsManager::CenterRIOJoystick()
{
Check(this);
if (flags.RIOExists)
{
Check(rioPointer);
rioPointer->ForceCenterJoystick();
}
Check_Fpu();
}
void
LBE4ControlsManager::SetJoystickDeadBand(Scalar dead_band)
{
Check(this);
if (flags.RIOExists)
{
Check(rioPointer);
rioPointer->SetJoystickDeadBand(dead_band);
}
if (flags.joystickExists)
{
Check(joystickPointer);
joystickPointer->SetDeadBand(dead_band);
}
Check_Fpu();
}
void
LBE4ControlsManager::SetThrottleDeadBand(Scalar dead_band)
{
Check(this);
if (flags.RIOExists)
{
Check(rioPointer);
rioPointer->SetThrottleDeadBand(dead_band);
}
Check_Fpu();
}
void
LBE4ControlsManager::SetPedalsDeadBand(Scalar dead_band)
{
Check(this);
if (flags.RIOExists)
{
Check(rioPointer);
rioPointer->SetPedalsDeadBand(dead_band);
}
Check_Fpu();
}
const char*
LBE4ControlsManager::GetLampName(int lamp_number)
{
const char
*lamp_name[] =
{
"AuxLowerRight8",
"AuxLowerRight7",
"AuxLowerRight6",
"AuxLowerRight5",
"AuxLowerRight4",
"AuxLowerRight3",
"AuxLowerRight2",
"AuxLowerRight1",
"AuxLowerLeft8",
"AuxLowerLeft7",
"AuxLowerLeft6",
"AuxLowerLeft5",
"AuxLowerLeft4",
"AuxLowerLeft3",
"AuxLowerLeft2",
"AuxLowerLeft1",
"Secondary1", //=0x10
"Secondary2",
"Secondary3",
"Secondary4",
"Secondary5",
"Secondary6",
"TeslaRelay3",
"undefined_0x17",
"Secondary7", //=0x18
"Secondary8",
"Secondary9",
"Secondary10",
"Secondary11",
"Secondary12",
"TeslaRelay1",
"TeslaRelay2",
"AuxUpperCenter8", //=0x20
"AuxUpperCenter7",
"AuxUpperCenter6",
"AuxUpperCenter5",
"AuxUpperCenter4",
"AuxUpperCenter3",
"AuxUpperCenter2",
"AuxUpperCenter1",
"AuxUpperLeft8", //=0x28
"AuxUpperLeft7",
"AuxUpperLeft6",
"AuxUpperLeft5",
"AuxUpperLeft4",
"AuxUpperLeft3",
"AuxUpperLeft2",
"AuxUpperLeft1",
"AuxUpperRight8", //=0x30
"AuxUpperRight7",
"AuxUpperRight6",
"AuxUpperRight5",
"AuxUpperRight4",
"AuxUpperRight3",
"AuxUpperRight2",
"AuxUpperRight1",
"PanicButton", //=0x38
"IcomAmpEnableRelay",//=0x39
"IcomIncRelay", //=0x3A
"IcomPTTRelay", //=0x03B
"IcomDecRelay", //=0x3C
"undefined_0x3D", //=0x3D unimplemented convenience lamp
"FloorEntry" //=0x3E
};
if (lamp_number >= LampCount)
{
return "overrange";
}
else if (lamp_number < 0)
{
return "underrange";
}
else
{
return lamp_name[lamp_number];
}
}
#define ID_ENTRY(name,group)\
{ #name , LBE4ControlsManager::##group, LBE4ControlsManager::##name }
struct {
const char* controlName;
Enumeration
controlsGroup,
controlsElement;
}
Control_IDs[]=
{
//-----------------------------------------------
// Throttle
//-----------------------------------------------
ID_ENTRY(ScalarThrottle,ScalarGroup),
ID_ENTRY(ButtonThrottle1,ButtonGroup),
//-----------------------------------------------
// Pedals
//-----------------------------------------------
ID_ENTRY(ScalarLeftPedal,ScalarGroup),
ID_ENTRY(ScalarRightPedal,ScalarGroup),
//-----------------------------------------------
// Joystick
//-----------------------------------------------
ID_ENTRY(JoystickPhysical,JoystickGroup),
ID_ENTRY(ButtonJoystickHatUp,ButtonGroup),
ID_ENTRY(ButtonJoystickHatDown,ButtonGroup),
ID_ENTRY(ButtonJoystickHatLeft,ButtonGroup),
ID_ENTRY(ButtonJoystickHatRight,ButtonGroup),
ID_ENTRY(ButtonJoystickTrigger,ButtonGroup),
ID_ENTRY(ButtonJoystickThumbHigh,ButtonGroup),
ID_ENTRY(ButtonJoystickThumbLow,ButtonGroup),
ID_ENTRY(ButtonJoystickPinky,ButtonGroup),
//-----------------------------------------------
// Secondary buttons
//-----------------------------------------------
ID_ENTRY(ButtonSecondary1,ButtonGroup),
ID_ENTRY(ButtonSecondary2,ButtonGroup),
ID_ENTRY(ButtonSecondary3,ButtonGroup),
ID_ENTRY(ButtonSecondary4,ButtonGroup),
ID_ENTRY(ButtonSecondary5,ButtonGroup),
ID_ENTRY(ButtonSecondary6,ButtonGroup),
ID_ENTRY(ButtonSecondary7,ButtonGroup),
ID_ENTRY(ButtonSecondary8,ButtonGroup),
ID_ENTRY(ButtonSecondary9,ButtonGroup),
ID_ENTRY(ButtonSecondary10,ButtonGroup),
ID_ENTRY(ButtonSecondary11,ButtonGroup),
ID_ENTRY(ButtonSecondary12,ButtonGroup),
//-----------------------------------------------
// Upper left auxiliary
//-----------------------------------------------
ID_ENTRY(ButtonAuxUpperLeft8,ButtonGroup),
ID_ENTRY(ButtonAuxUpperLeft7,ButtonGroup),
ID_ENTRY(ButtonAuxUpperLeft6,ButtonGroup),
ID_ENTRY(ButtonAuxUpperLeft5,ButtonGroup),
ID_ENTRY(ButtonAuxUpperLeft4,ButtonGroup),
ID_ENTRY(ButtonAuxUpperLeft3,ButtonGroup),
ID_ENTRY(ButtonAuxUpperLeft2,ButtonGroup),
ID_ENTRY(ButtonAuxUpperLeft1,ButtonGroup),
//-----------------------------------------------
// Center auxiliary
//-----------------------------------------------
ID_ENTRY(ButtonAuxUpperCenter8,ButtonGroup),
ID_ENTRY(ButtonAuxUpperCenter7,ButtonGroup),
ID_ENTRY(ButtonAuxUpperCenter6,ButtonGroup),
ID_ENTRY(ButtonAuxUpperCenter5,ButtonGroup),
ID_ENTRY(ButtonAuxUpperCenter4,ButtonGroup),
ID_ENTRY(ButtonAuxUpperCenter3,ButtonGroup),
ID_ENTRY(ButtonAuxUpperCenter2,ButtonGroup),
ID_ENTRY(ButtonAuxUpperCenter1,ButtonGroup),
//-----------------------------------------------
// Upper right auxiliary
//-----------------------------------------------
ID_ENTRY(ButtonAuxUpperRight8,ButtonGroup),
ID_ENTRY(ButtonAuxUpperRight7,ButtonGroup),
ID_ENTRY(ButtonAuxUpperRight6,ButtonGroup),
ID_ENTRY(ButtonAuxUpperRight5,ButtonGroup),
ID_ENTRY(ButtonAuxUpperRight4,ButtonGroup),
ID_ENTRY(ButtonAuxUpperRight3,ButtonGroup),
ID_ENTRY(ButtonAuxUpperRight2,ButtonGroup),
ID_ENTRY(ButtonAuxUpperRight1,ButtonGroup),
//-----------------------------------------------
// Lower left auxiliary
//-----------------------------------------------
ID_ENTRY(ButtonAuxLowerLeft8,ButtonGroup),
ID_ENTRY(ButtonAuxLowerLeft7,ButtonGroup),
ID_ENTRY(ButtonAuxLowerLeft6,ButtonGroup),
ID_ENTRY(ButtonAuxLowerLeft5,ButtonGroup),
ID_ENTRY(ButtonAuxLowerLeft4,ButtonGroup),
ID_ENTRY(ButtonAuxLowerLeft3,ButtonGroup),
ID_ENTRY(ButtonAuxLowerLeft2,ButtonGroup),
ID_ENTRY(ButtonAuxLowerLeft1,ButtonGroup),
//-----------------------------------------------
// Lower right auxiliary
//-----------------------------------------------
ID_ENTRY(ButtonAuxLowerRight8,ButtonGroup),
ID_ENTRY(ButtonAuxLowerRight7,ButtonGroup),
ID_ENTRY(ButtonAuxLowerRight6,ButtonGroup),
ID_ENTRY(ButtonAuxLowerRight5,ButtonGroup),
ID_ENTRY(ButtonAuxLowerRight4,ButtonGroup),
ID_ENTRY(ButtonAuxLowerRight3,ButtonGroup),
ID_ENTRY(ButtonAuxLowerRight2,ButtonGroup),
ID_ENTRY(ButtonAuxLowerRight1,ButtonGroup),
//-----------------------------------------------
// Miscellaneous buttons
//-----------------------------------------------
ID_ENTRY(ButtonPanic,ButtonGroup),
ID_ENTRY(ButtonDoor,ButtonGroup)
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
LBE4ControlsManager::SetControlMappingID(
const char *control_name,
ControlsMapping *mapping
)
{
Check_Pointer(control_name);
Check_Pointer(mapping);
int i;
for (i=0; i<ELEMENTS(Control_IDs); ++i)
{
if (!stricmp(control_name, Control_IDs[i].controlName))
{
mapping->controlsGroup = Control_IDs[i].controlsGroup;
mapping->controlsElement = Control_IDs[i].controlsElement;
break;
}
}
Check_Fpu();
return i < ELEMENTS(Control_IDs);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Lamp *
LBE4ControlsManager::MakeLinkedLamp(
int element,
ModeMask mode_mask
)
{
Check(this);
Check(application);
if (application->GetGaugeRenderer() == NULL)
{
return NULL;
}
else
{
Check(application->GetGaugeRenderer());
//------------------------------------------
// Create the lamp
//------------------------------------------
L4LampManager
*lamp_manager = (L4LampManager *) application->
GetGaugeRenderer()->GetLampManager();
// DEV-COMPOSITE GUARD: when the gauge renderer is woken (BT_DEV_GAUGES) but its
// reconstructed lamp manager isn't wired (GetLampManager returns NULL -- a recon
// offset gap in BTL4GaugeRenderer), skip the linked cockpit lamp rather than
// constructing an L4Lamp with a NULL manager (crashes in LampManager::AddLamp).
// The lamp is a secondary cockpit indicator; inert on the pod (manager valid).
if (lamp_manager == NULL)
return NULL;
Check(lamp_manager);
Lamp
*lamp = new L4Lamp(
element,
mode_mask,
lamp_manager
);
Register_Object(lamp);
//------------------------------------------
// Add button mapping to lamp
//------------------------------------------
buttonGroup[element].Add(
mode_mask,
&((L4Lamp *)lamp)->automaticValue,
lamp
);
Check_Fpu();
return lamp;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
LBE4ControlsManager::CreateStreamedMappings(
Entity *player,
const int *control_mappings
)
{
Check(this);
Check_Pointer(control_mappings);
int
controls_count = *control_mappings;
++control_mappings;
ControlsMapping
*mapping = (ControlsMapping*)control_mappings;
Simulation
*subsystem;
void
*attribute;
for (int i=0; i<controls_count; ++i)
{
subsystem = player->GetSimulation(mapping->subsystemID);
Verify(subsystem);
//
//---------------------------
// Handle the direct mappings
//---------------------------
//
int
element = mapping->controlsElement;
ModeMask
mode_mask = mapping->modeMask;
if (mapping->mappingType == ControlsMapping::DirectMapping)
{
attribute = subsystem->GetAttributePointer(mapping->attributeID);
//
// BT_CTRLMAP_LOG=1: dump each streamed .CTL record and the member
// OFFSET its attributeID resolved to on the live subsystem. The
// stream carries the 1995 binary's positional attribute ids, so a
// resolved offset that differs from the binary's member offset for
// that id means the reconstruction's id chain is misaligned (the
// gotcha-#11 class). Audit trail: docs/GLASS_COCKPIT.md step 2a.
//
{
static const int s_ctrlmapLog =
(getenv("BT_CTRLMAP_LOG") != 0 && *getenv("BT_CTRLMAP_LOG") != '0');
if (s_ctrlmapLog)
{
DEBUG_STREAM << "[ctrlmap] rec " << i
<< " subsys " << mapping->subsystemID
<< " group " << (int)mapping->controlsGroup
<< " elem " << element
<< " mask 0x" << std::hex << (unsigned)mode_mask << std::dec
<< " attrID " << (int)mapping->attributeID
<< " -> +0x" << std::hex
<< (attribute ? (int)((char*)attribute - (char*)subsystem) : -1)
<< std::dec << "\n" << std::flush;
}
}
switch (mapping->controlsGroup)
{
case ScalarGroup:
scalarGroup[element].Add(
mode_mask,
(ControlsScalar*)attribute,
subsystem
);
break;
case JoystickGroup:
joystickGroup[element].Add(
mode_mask,
(ControlsJoystick*)attribute,
subsystem
);
break;
case ButtonGroup:
//----------------------------------------------
// Add button
//----------------------------------------------
buttonGroup[element].Add(
mode_mask,
(ControlsButton*)attribute,
subsystem
);
//----------------------------------------------
// Create associated lamp
//----------------------------------------------
if(buttonType[element] == AUTOBTN)
{
# if DEBUG_LEVEL > 0
Lamp *
lamp =
# endif
MakeLinkedLamp(element, mode_mask);
# if DEBUG_LEVEL > 0
if (lamp != NULL)
{
Check(lamp);
}
# endif
}
break;
default:
Fail("Unknown mapping group!\n");
break;
}
}
//
//--------------------------------------------------
// Otherwise, it is an event mapping, so set that up
//--------------------------------------------------
//
else if (mapping->mappingType == ControlsMapping::EventMapping)
{
Receiver::MessageID
msg_id = mapping->messageID;
//
// BT_CTRLMAP_LOG=1: dump the streamed EVENT records too (button ->
// subsystem message). Gitea #6 needed this to identify which pod
// console buttons carried the mapper's CycleControlMode (0x14) /
// CycleDisplayMode (0x15) messages.
//
{
static const int s_ctrlmapLog =
(getenv("BT_CTRLMAP_LOG") != 0 && *getenv("BT_CTRLMAP_LOG") != '0');
if (s_ctrlmapLog)
{
DEBUG_STREAM << "[ctrlmap] rec " << i
<< " subsys " << mapping->subsystemID
<< " group " << (int)mapping->controlsGroup
<< " elem 0x" << std::hex << element
<< " mask 0x" << (unsigned)mode_mask
<< " EVENT msg 0x" << (unsigned)msg_id
<< std::dec << "\n" << std::flush;
}
}
switch (mapping->controlsGroup)
{
case ScalarGroup:
scalarGroup[element].Add(mode_mask,subsystem,msg_id,subsystem);
break;
case JoystickGroup:
joystickGroup[element].Add(mode_mask,subsystem,msg_id,subsystem);
break;
case ButtonGroup:
//----------------------------------------------
// Add button
//----------------------------------------------
buttonGroup[element].Add(
mode_mask,
subsystem,
msg_id,
subsystem
);
//----------------------------------------------
// Create associated lamp
//----------------------------------------------
if(buttonType[element] == AUTOBTN)
{
# if DEBUG_LEVEL > 0
Lamp *
lamp =
# endif
MakeLinkedLamp(element, mode_mask);
# if DEBUG_LEVEL > 0
if (lamp != NULL)
{
Check(lamp);
}
# endif
}
break;
default:
Fail("Unknown mapping group!\n");
break;
}
}
else
{
Fail("Unknown mapping type!\n");
}
mapping = (ControlsMapping*)((char*)mapping + sizeof(ControlsMapping));
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
LBE4ControlsManager::EnterControlsMode(
L4ControlsMode new_mode,
Scalar time_until_exit,
Receiver::MessageID message_on_exit
)
{
Check(this);
l4ControlsMode = new_mode;
exitModeTime = ((Scalar) Now()) + time_until_exit;
exitMessageID = message_on_exit;
SetLamp(LampPanic, RIO::flashFast+RIO::state1Off+RIO::state2Bright);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
LBE4ControlsManager::ExitControlsMode()
{
Check(this);
l4ControlsMode = normalMode;
int
i;
for(i=0; i<maxStringIDs; ++i)
{
if (temporaryStringID[i] != 0)
{
stringManager.Remove(temporaryStringID[i]);
}
}
SetLamp(LampPanic, RIO::solid+RIO::state1Off+RIO::state2Off);
Verify(heapcheck() != _HEAPCORRUPT);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
LBE4ControlsManager::ManageControlsMode()
{
Check(this);
if (l4ControlsMode != normalMode)
{
if (Now() >= exitModeTime)
{
Receiver::Message
message(exitMessageID, sizeof(Receiver__Message));
Dispatch(&message);
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
LBE4ControlsManager::TestInstance() const
{
Verify(IsDerivedFrom(*GetClassDerivations()));
return ControlsManager::TestInstance();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
LBE4ControlsManager::AnalogHasChanged()
{
Check(this);
Logical
result = False;
if (rioPointer != NULL)
{
Check(rioPointer);
result = (rioPointer->Throttle != previousThrottle);
result |= (rioPointer->LeftPedal != previousPedalLeft);
result |= (rioPointer->RightPedal != previousPedalRight);
result |= (rioPointer->JoystickX != previousJoystickX);
result |= (rioPointer->JoystickY != previousJoystickY);
if (result)
{
previousThrottle = rioPointer->Throttle;
previousPedalLeft = rioPointer->LeftPedal;
previousPedalRight = rioPointer->RightPedal;
previousJoystickX = rioPointer->JoystickX;
previousJoystickY = rioPointer->JoystickY;
}
}
Check_Fpu();
return result;
}
Logical
LBE4ControlsManager::GetNextRIOEvent(RIO::RIOEvent *rio_event)
{
//
//-------------------------------------------------
// If we aren't in playback mode, read the real RIO
//-------------------------------------------------
//
if (!playbackMode)
{
Logical
result = rioPointer->GetNextEvent(rio_event);
//----------------------------------------------------------------------
// If the RIO stream exists, and a rio event was added,
// store this event in the rioStream
//----------------------------------------------------------------------
if (result && streamEnabled)
{
Check(rioStream);
if (rioStream->GetBytesRemaining() >= sizeof(RIOStreamEvent))
{
Check(rioStream);
RIOStreamEvent
*event = (RIOStreamEvent*)rioStream->GetPointer();
Check_Pointer(event);
event->timeStamp.ticks = Now().ticks - streamStart.ticks;
if (rio_event->Type == RIO::AnalogEvent)
{
if (AnalogHasChanged())
{
DEBUG_STREAM << "AnalogEvent at" << event->timeStamp.ticks << "\n";
DEBUG_STREAM << "Pointer=" << std::hex << event << std::dec << "\n";
//----------------------------
// Set the event type
//----------------------------
event->eventType = analog;
//----------------------------
// Get the analog state
//----------------------------
event->data.analog.throttle = rioPointer->Throttle;
event->data.analog.pedalLeft = rioPointer->LeftPedal;
event->data.analog.pedalRight = rioPointer->RightPedal;
event->data.analog.joystickX = rioPointer->JoystickX;
event->data.analog.joystickY = rioPointer->JoystickY;
//----------------------------
// Save the event
//----------------------------
rioStream->AdvancePointer(sizeof(RIOStreamEvent));
}
}
else
{
DEBUG_STREAM << "DigitalEvent at" << event->timeStamp.ticks << "\n";
DEBUG_STREAM << "Pointer=" << std::hex << event << std::dec << "\n";
//----------------------------
// Set the event type
//----------------------------
event->eventType = digital;
//----------------------------
// Save the event
//----------------------------
event->data.rioEvent = *rio_event;
rioStream->AdvancePointer(sizeof(RIOStreamEvent));
}
}
}
return result;
}
//
//----------------------------------------------------------------------
// Otherwise, we are in playback mode, so get the next record out of the
// memory block
//----------------------------------------------------------------------
//
else if (streamEnabled)
{
Check(rioStream);
if (rioStream->GetBytesRemaining() >= sizeof(RIOStreamEvent))
{
RIOStreamEvent
*event = (RIOStreamEvent*)rioStream->GetPointer();
Check_Pointer(event);
Time
now;
now.ticks = Now().ticks - streamStart.ticks;
if (event->timeStamp.ticks <= now.ticks)
{
DEBUG_STREAM << "Event timestamp=" << event->timeStamp.ticks << "\n";
switch(event->eventType)
{
case analog:
DEBUG_STREAM << "AnalogEvent\n";
//----------------------------
// Send event type ONLY
//----------------------------
rio_event->Type = RIO::AnalogEvent;
//----------------------------
// Restore the analog state
//----------------------------
rioPointer->Throttle = event->data.analog.throttle;
rioPointer->LeftPedal = event->data.analog.pedalLeft;
rioPointer->RightPedal = event->data.analog.pedalRight;
rioPointer->JoystickX = event->data.analog.joystickX;
rioPointer->JoystickY = event->data.analog.joystickY;
break;
case digital:
DEBUG_STREAM << "DigitalEvent\n";
//----------------------------
// Send the entire event
//----------------------------
*rio_event = event->data.rioEvent;
break;
}
rioStream->AdvancePointer(sizeof(RIOStreamEvent));
return True;
}
}
}
return False;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
LBE4ControlsManager::ProcessRIOEvent(
RIO::RIOEvent &rio_event,
Logical *new_RIO_values
)
{
//--------------------------------------------------
// Get mode mask for updating
//--------------------------------------------------
Check(application);
Check(application->GetModeManager());
ModeMask
mode_mask = application->GetModeManager()->GetModeMask();
ControlsButton temp;
switch(rio_event.Type)
{
case RIO::ButtonPressedEvent:
Verify(rio_event.Data.Unit >= 0);
Verify(rio_event.Data.Unit < ButtonCount);
temp = rio_event.Data.Unit + 1;
// DIAG (BT_DUCK_LOG): the mode mask a press dispatches under -- the
// crouch-button (0x13) MP-delivery hunt (mode-gated streamed rows).
if (getenv("BT_DUCK_LOG"))
DEBUG_STREAM << "[duck] RIO press unit=0x" << std::hex
<< (int)rio_event.Data.Unit << " modeMask=0x"
<< (unsigned)mode_mask << std::dec << "\n" << std::flush;
Check(&buttonGroup[rio_event.Data.Unit]);
buttonGroup[rio_event.Data.Unit].Update(&temp, mode_mask);
//----------------------------------------
// Save 'pressed' mode mask for 'release'
//----------------------------------------
buttonActivateModeMask[rio_event.Data.Unit] = mode_mask;
// (NO eng-page eject-key hook: REMOVED 2026-08-03 for strict binary
// fidelity. The soft keys 0x0B/0x23/0x03 never pilot-eject in the
// binary -- every page routes them to authored functions (weapon eng
// pages: EjectAmmo 0xB -- a hook here HIJACKED the unjam while armed,
// self-destructing a pilot trying to clear a jam). The flashing
// engEject lamp is the INVITE; the authored press is the pilot KEYPAD
// bank / PANIC key, both live on the desktop.)
break;
case RIO::ButtonReleasedEvent:
Verify(rio_event.Data.Unit >= 0);
Verify(rio_event.Data.Unit < ButtonCount);
temp = -rio_event.Data.Unit - 1;
Check(&buttonGroup[rio_event.Data.Unit]);
//----------------------------------------
// Use 'pressed' mode mask for 'release'
//----------------------------------------
buttonGroup[rio_event.Data.Unit].Update(
&temp,
buttonActivateModeMask[rio_event.Data.Unit]
);
break;
case RIO::KeyEvent:
{
Verify(rio_event.Data.Keyboard.Unit >= 0);
Verify(rio_event.Data.Keyboard.Unit < KeyboardCount);
//--------------------------------------
// Convert key to ASCII
//--------------------------------------
ControlsKey
new_key = rio_event.Data.Keyboard.Key;
if (new_key <= 9)
{
new_key += '0';
}
else
{
new_key += 'A'-10;
}
Test_Tell(
"LBE4ControlsManager::Execute, new_key=" << (int) new_key <<
"\n"
);
Check(&keyboardGroup[rio_event.Data.Keyboard.Unit]);
{
static int s_klog = -1;
if (s_klog < 0) s_klog = (getenv("BT_PAD_LOG") != 0) ? 1 : 0;
if (s_klog)
DEBUG_STREAM << "[lbe4key] unit "
<< (int)rio_event.Data.Keyboard.Unit
<< " key " << (int)new_key
<< " mode 0x" << std::hex << (unsigned)mode_mask
<< std::dec << "\n" << std::flush;
}
keyboardGroup[rio_event.Data.Keyboard.Unit].
ForceUpdate(&new_key, mode_mask);
Check(&stringManager);
stringManager.Update(
rio_event.Data.Keyboard.Unit,
new_key
);
}
break;
case RIO::AnalogEvent:
lastRioRequest = 0L; // force another RIO update request
*new_RIO_values = True;
break;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
LBE4ControlsManager::SaveRecording(const char* file_name)
{
Check(this);
Check_Pointer(file_name);
Check(rioStream);
FileStream
output(file_name, True);
size_t
length = rioStream->GetBytesUsed();
if (length)
{
rioStream->Rewind();
output << length;
output << *rioStream;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
LBE4ControlsManager::LoadPlayback(const char* file_name)
{
Check(this);
Check_Pointer(file_name);
FileStream
input(file_name);
Verify(input.IsFileOpened());
//
//-------------------------------
// Figure out how big the file is
//-------------------------------
//
size_t
spool_size = 0;
input >> spool_size;
Verify(spool_size);
Verify(spool_size <= RIO_SPOOL_SIZE * sizeof(RIOStreamEvent));
RIOStreamEvent
*events = new RIOStreamEvent[RIO_SPOOL_SIZE];
Register_Pointer(events);
Verify(rioStream == NULL);
rioStream = new MemoryStream(events, spool_size);
Register_Object(rioStream);
input.ReadBytes(events, spool_size);
}
#if defined(TEST_CLASS)
# include "l4ctrl.tcp"
#endif