Regression fromd213c98(the pod PadRIO/panel coupling fix). BTGlassPanels_Destroy calls SaveLayout FIRST, unconditionally, before it looks at whether any windows are left.d213c98added a SECOND caller (~LBE4ControlsManager) alongside the existing one in ~PadRIO, so on the desktop path both run: ~LBE4ControlsManager does `delete rioPointer`, which fires ~PadRIO -> destroy #1 saves the live windows and zeroes gWinCount -> destroy #2 then rewrites the whole file from an empty list. Why only the MFDs vanished, which is the detail that identifies it: external windows (plasma) already cached a last-known rect (gExtern[].haveLast) and were written from the cache; the per-display glass windows had no cache and were simply skipped once their HWND was gone. Hence the reported signature, "all the MFDs and secondary lines missing, but plasma was still there". The pod was never affected -- no PadRIO there, so only one destroy, and it runs BT_GLASS_LAYOUT=load off a frozen master regardless. Two fixes, because the guard alone would leave the trap armed for the next teardown-ordering change: 1. glass windows get the same remembered-geometry cache the extern windows have, kept ACROSS teardown. The file is now monotonic -- a save can update a line or add one, never drop one. 2. the teardown save is guarded on there being windows to report. Also corrects the comment at the L4CTRL call site, which claimed the second call was "a no-op on the PadRIO path". That claim is what made it look safe. Verified (scratchpad/night13/layoutsave.sh -- the tester's round trip, not a single launch, since the report was "saved fine, reset on relaunch"): run 1 one "saved 8 window position(s)" line (was two, the second wiping) cfg holds all 7 glass windows + plasma run 2 "restored 7 window position(s)", cfg byte-identical after the trip Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
2825 lines
72 KiB
C++
2825 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). The HARDWARE RIO's dtor
|
|
// knows nothing about the panels, and windows that outlive the surfaces
|
|
// they blit are a crash waiting for the next mission cycle.
|
|
//
|
|
// CORRECTION (2026-08-07, #140): the original comment here claimed this was
|
|
// "a no-op" on the PadRIO path because ~PadRIO tears the panels down itself.
|
|
// It was not. ~PadRIO runs first (delete rioPointer, above), zeroing the
|
|
// window list -- and BTGlassPanels_Destroy unconditionally ran SaveLayout
|
|
// BEFORE looking at whether anything was left, so this second call rewrote
|
|
// glass_layout.cfg with every MFD and radar line missing. Only the plasma
|
|
// window survived, because external windows cached a last-known rect and the
|
|
// per-display windows did not. That is the regression testers hit on the
|
|
// desktop the same day the pod panels were wired up; the pod itself was
|
|
// unaffected (no PadRIO, so only ONE destroy, and it runs BT_GLASS_LAYOUT=
|
|
// load anyway). Fixed on both sides in L4GLASSWIN: the geometry is now
|
|
// remembered across teardown, and the teardown save is guarded.
|
|
//
|
|
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
|