Workstream A prototype: play without the cockpit
Splits the control surface the game consumes from the RIO board into RIOBase (8 virtuals + the five analog scalars); the serial RIO is now one implementation of it. Adds two new ones: - PadRIO (L4CONTROLS=PAD): in-process RIO speaking the full surface from an XInput controller + PC keyboard using vRIO's default profile (left stick/WASD = stick, triggers/Q,E = pedals, right stick Y/PgUp,PgDn = rate throttle that holds position, A/Space = trigger, B/R = reverse, dpad/arrows = hat, Start,Back/F1,F2 = config). Samples in GetNextEvent so button latency does not depend on the 15 s menu-time analog cadence; hot-plugs pads; L4PADFLIP=XY inverts stick axes; lamp commands land in lampState[] for the planned on-screen cockpit panel. The stock VTVRIOMapper/lamp/button path runs unchanged. - PlasmaScreen (L4PLASMA=SCREEN): the 128x32 plasma glass as a desktop window in plasma orange (L4PLASMASCALE, default x4), rendering the same Video8BitBuffered surface the gauge system always drew; no COM port. Verified in the sandbox with vRIO off and no serial devices: boots to a running mission, controller hot-detected, plasma window drawing live game content (score readout). BUILD.md 4 documents the desktop environ.ini and bindings; roadmap updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,47 @@ For runtime debugging the v143 build produces full PDBs — run
|
|||||||
`cdb -g -G -lines -y Release rpl4opt.exe ...` from the working directory
|
`cdb -g -G -lines -y Release rpl4opt.exe ...` from the working directory
|
||||||
(cdb ships in this machine's Windows Kits).
|
(cdb ships in this machine's Windows Kits).
|
||||||
|
|
||||||
|
### Running without the cockpit (Workstream A prototype)
|
||||||
|
|
||||||
|
Two new environment options remove the hardware dependency entirely:
|
||||||
|
|
||||||
|
- **`L4CONTROLS=PAD;KEYBOARD`** — selects **PadRIO**
|
||||||
|
([MUNGA_L4/L4PADRIO.cpp](MUNGA_L4/L4PADRIO.cpp)), an in-process RIO that
|
||||||
|
speaks the full RIO control surface (buttons, axes, lamps) from an XInput
|
||||||
|
controller + the PC keyboard. The stock `VTVRIOMapper` path runs unchanged.
|
||||||
|
Hot-plugging works; with no controller it falls back to keyboard only.
|
||||||
|
- **`L4PLASMA=SCREEN`** — renders the 128×32 plasma display as its own
|
||||||
|
desktop window ("Plasma Display", plasma orange, `L4PLASMASCALE` sets the
|
||||||
|
pixel size, default 4). No COM port. Closing the window hides it.
|
||||||
|
|
||||||
|
Bindings (vRIO's default profile, condensed):
|
||||||
|
|
||||||
|
| Input | Pod control |
|
||||||
|
|-------|-------------|
|
||||||
|
| Left stick / WASD | joystick X/Y |
|
||||||
|
| LT / RT, Q / E | left / right pedal |
|
||||||
|
| Right stick Y, PgUp / PgDn | throttle (rate; position holds) |
|
||||||
|
| A / Space | joystick trigger |
|
||||||
|
| B / R | reverse thrust (ButtonThrottle1) |
|
||||||
|
| DPad / arrows | joystick hat (look) |
|
||||||
|
| X, Y, LB, RB | pinky / thumb-low / thumb-low / thumb-high |
|
||||||
|
| Start / F1, Back / F2 | config buttons (AuxUpperRight 1 / 2) |
|
||||||
|
|
||||||
|
`L4PADFLIP=XY` (or `X` / `Y`) inverts the stick axes if the feel is wrong.
|
||||||
|
Example desktop `environ.ini`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
L4CONTROLS=PAD;KEYBOARD
|
||||||
|
DPLARG=1
|
||||||
|
L4DPLCFG=RPDPL.INI
|
||||||
|
L4GAUGE=640x480x16
|
||||||
|
L4PLASMA=SCREEN
|
||||||
|
TARGETFPS=60
|
||||||
|
```
|
||||||
|
|
||||||
|
Lamp commands land in `PadRIO::lampState[]` — the hook for the planned
|
||||||
|
on-screen cockpit panel (vRIO buttons arranged around the displays).
|
||||||
|
|
||||||
Also note: `Fail()` compiles to `abort()` in release (`MUNGA/DEBUGOFF.h`) —
|
Also note: `Fail()` compiles to `abort()` in release (`MUNGA/DEBUGOFF.h`) —
|
||||||
e.g. running with `L4CONTROLS=KEYBOARD` alone aborts at VTV creation because
|
e.g. running with `L4CONTROLS=KEYBOARD` alone aborts at VTV creation because
|
||||||
no keyboard-only pod mapper exists (the old CRT showed a blocking R6010
|
no keyboard-only pod mapper exists (the old CRT showed a blocking R6010
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "mungal4.h"
|
#include "mungal4.h"
|
||||||
#pragma hdrstop
|
#pragma hdrstop
|
||||||
|
|
||||||
|
#include "l4padrio.h"
|
||||||
|
|
||||||
#include "l4ctrl.h"
|
#include "l4ctrl.h"
|
||||||
#include "l4keybd.h"
|
#include "l4keybd.h"
|
||||||
#include "l4app.h"
|
#include "l4app.h"
|
||||||
@@ -830,6 +832,19 @@ LBE4ControlsManager::LBE4ControlsManager():
|
|||||||
joystickPointer = NULL;
|
joystickPointer = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (strcmpi(temp, "PAD") == 0)
|
||||||
|
{
|
||||||
|
//------------------------------------------------------
|
||||||
|
// Cockpit-less play: PadRIO speaks the RIO control
|
||||||
|
// surface from an XInput pad + the PC keyboard, so the
|
||||||
|
// full RIO mapper/lamp/button path runs unchanged.
|
||||||
|
//------------------------------------------------------
|
||||||
|
rioPointer = new PadRIO();
|
||||||
|
Check(rioPointer);
|
||||||
|
Register_Object(rioPointer);
|
||||||
|
flags.RIOExists = 1;
|
||||||
|
primaryControlType = LBE4ControlsManager::PrimaryRIO;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
while (temp[0] != '\0');
|
while (temp[0] != '\0');
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -542,9 +542,9 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// RIO data
|
// RIO data (serial hardware or the PadRIO pad/keyboard synthesizer)
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
RIO
|
RIOBase
|
||||||
*rioPointer;
|
*rioPointer;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
+11
-2
@@ -7,6 +7,7 @@
|
|||||||
#include "l4gauge.h"
|
#include "l4gauge.h"
|
||||||
#include "..\munga\mission.h"
|
#include "..\munga\mission.h"
|
||||||
#include "l4plasma.h"
|
#include "l4plasma.h"
|
||||||
|
#include "l4plasmascreen.h"
|
||||||
#include "..\munga\notation.h"
|
#include "..\munga\notation.h"
|
||||||
|
|
||||||
// #define LOCAL_TEST
|
// #define LOCAL_TEST
|
||||||
@@ -100,8 +101,16 @@ L4GaugeRenderer::L4GaugeRenderer(bool windowed, int *secondaryIndex, int *aux1In
|
|||||||
// Tell("Plasma display created on COM1\n");
|
// Tell("Plasma display created on COM1\n");
|
||||||
// externalDisplay = new PlasmaDisplay(PCS_COM1);
|
// externalDisplay = new PlasmaDisplay(PCS_COM1);
|
||||||
//}
|
//}
|
||||||
Tell("Plasma display created on "); Tell(plasma_string); Tell("\n");
|
if (_stricmp(plasma_string, "SCREEN") == 0)
|
||||||
externalDisplay = new PlasmaDisplay(plasma_string);
|
{
|
||||||
|
Tell("Plasma display created as an on-screen window\n");
|
||||||
|
externalDisplay = new PlasmaScreen();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Tell("Plasma display created on "); Tell(plasma_string); Tell("\n");
|
||||||
|
externalDisplay = new PlasmaDisplay(plasma_string);
|
||||||
|
}
|
||||||
|
|
||||||
if (externalDisplay != NULL)
|
if (externalDisplay != NULL)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,377 @@
|
|||||||
|
#include "mungal4.h"
|
||||||
|
#pragma hdrstop
|
||||||
|
|
||||||
|
#include "l4padrio.h"
|
||||||
|
|
||||||
|
#include <XInput.h>
|
||||||
|
#pragma comment(lib, "xinput9_1_0.lib")
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
// Binding tables (vRIO default profile, condensed)
|
||||||
|
//########################################################################
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct KeyBinding
|
||||||
|
{
|
||||||
|
int virtualKey;
|
||||||
|
int rioUnit;
|
||||||
|
};
|
||||||
|
|
||||||
|
const KeyBinding keyboardButtonMap[] =
|
||||||
|
{
|
||||||
|
{ VK_SPACE, 0x40 }, // ButtonJoystickTrigger
|
||||||
|
{ 'R', 0x3F }, // ButtonThrottle1 (reverse thrust)
|
||||||
|
{ VK_UP, 0x42 }, // ButtonJoystickHatUp
|
||||||
|
{ VK_DOWN, 0x41 }, // ButtonJoystickHatDown
|
||||||
|
{ VK_RIGHT, 0x43 }, // ButtonJoystickHatRight
|
||||||
|
{ VK_LEFT, 0x44 }, // ButtonJoystickHatLeft
|
||||||
|
{ VK_F1, 0x37 }, // ButtonAuxUpperRight1 (config)
|
||||||
|
{ VK_F2, 0x36 }, // ButtonAuxUpperRight2 (config)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PadBinding
|
||||||
|
{
|
||||||
|
unsigned short padMask;
|
||||||
|
int rioUnit;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PadBinding padButtonMap[] =
|
||||||
|
{
|
||||||
|
{ XINPUT_GAMEPAD_A, 0x40 }, // trigger
|
||||||
|
{ XINPUT_GAMEPAD_B, 0x3F }, // reverse thrust
|
||||||
|
{ XINPUT_GAMEPAD_X, 0x45 }, // pinky
|
||||||
|
{ XINPUT_GAMEPAD_Y, 0x46 }, // thumb low
|
||||||
|
{ XINPUT_GAMEPAD_LEFT_SHOULDER, 0x46 }, // thumb low
|
||||||
|
{ XINPUT_GAMEPAD_RIGHT_SHOULDER, 0x47 }, // thumb high
|
||||||
|
{ XINPUT_GAMEPAD_DPAD_UP, 0x42 },
|
||||||
|
{ XINPUT_GAMEPAD_DPAD_DOWN, 0x41 },
|
||||||
|
{ XINPUT_GAMEPAD_DPAD_RIGHT, 0x43 },
|
||||||
|
{ XINPUT_GAMEPAD_DPAD_LEFT, 0x44 },
|
||||||
|
{ XINPUT_GAMEPAD_START, 0x37 }, // config 1
|
||||||
|
{ XINPUT_GAMEPAD_BACK, 0x36 }, // config 2
|
||||||
|
};
|
||||||
|
|
||||||
|
// Full throttle sweep takes ~1.3 s at full stick deflection.
|
||||||
|
const Scalar throttleRatePerSecond = 0.75f;
|
||||||
|
|
||||||
|
Scalar StickValue(int raw, int dead_zone)
|
||||||
|
{
|
||||||
|
if (raw > -dead_zone && raw < dead_zone)
|
||||||
|
{
|
||||||
|
return (Scalar) 0;
|
||||||
|
}
|
||||||
|
Scalar value =
|
||||||
|
(raw > 0)
|
||||||
|
? (Scalar)(raw - dead_zone) / (Scalar)(32767 - dead_zone)
|
||||||
|
: (Scalar)(raw + dead_zone) / (Scalar)(32768 - dead_zone);
|
||||||
|
if (value > 1.0f) value = 1.0f;
|
||||||
|
if (value < -1.0f) value = -1.0f;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scalar Clamp01(Scalar value)
|
||||||
|
{
|
||||||
|
if (value < 0.0f) return 0.0f;
|
||||||
|
if (value > 1.0f) return 1.0f;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical KeyDown(int virtual_key)
|
||||||
|
{
|
||||||
|
return (GetAsyncKeyState(virtual_key) & 0x8000) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
//############################### PadRIO #################################
|
||||||
|
//########################################################################
|
||||||
|
|
||||||
|
PadRIO::PadRIO()
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
|
||||||
|
queueHead = 0;
|
||||||
|
queueTail = 0;
|
||||||
|
lastPollTick = GetTickCount();
|
||||||
|
lastPadCheckTick = 0;
|
||||||
|
padIndex = -1;
|
||||||
|
padReported = False;
|
||||||
|
analogRequested = False;
|
||||||
|
throttleAccum = (Scalar) 0;
|
||||||
|
|
||||||
|
sentThrottle = sentLeftPedal = sentRightPedal = (Scalar) 0;
|
||||||
|
sentJoystickX = sentJoystickY = (Scalar) 0;
|
||||||
|
|
||||||
|
memset(buttonDown, 0, sizeof(buttonDown));
|
||||||
|
memset(lampState, 0, sizeof(lampState));
|
||||||
|
|
||||||
|
invertX = False;
|
||||||
|
invertY = False;
|
||||||
|
const char *flip = getenv("L4PADFLIP");
|
||||||
|
if (flip != NULL)
|
||||||
|
{
|
||||||
|
if (strchr(flip, 'X') || strchr(flip, 'x'))
|
||||||
|
{
|
||||||
|
invertX = True;
|
||||||
|
}
|
||||||
|
if (strchr(flip, 'Y') || strchr(flip, 'y'))
|
||||||
|
{
|
||||||
|
invertY = True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report as a v4.2 board, like vRIO does
|
||||||
|
MajorRevision = 4;
|
||||||
|
MinorRevision = 2;
|
||||||
|
|
||||||
|
DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
PadRIO::~PadRIO()
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical
|
||||||
|
PadRIO::TestInstance() const
|
||||||
|
{
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// The controls manager drains events every frame; sampling lives here so
|
||||||
|
// button latency does not depend on the analog request cadence (which is
|
||||||
|
// 15 s outside of missions).
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Logical
|
||||||
|
PadRIO::GetNextEvent(RIOEvent *destinationPointer)
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
Check_Pointer(destinationPointer);
|
||||||
|
|
||||||
|
PollInputs();
|
||||||
|
|
||||||
|
if (queueTail == queueHead)
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
*destinationPointer = eventQueue[queueTail];
|
||||||
|
queueTail = (queueTail + 1) % queueSize;
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PadRIO::RequestAnalogUpdate()
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
analogRequested = True;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PadRIO::GeneralReset()
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
throttleAccum = (Scalar) 0;
|
||||||
|
Throttle = (Scalar) 0;
|
||||||
|
LeftPedal = (Scalar) 0;
|
||||||
|
RightPedal = (Scalar) 0;
|
||||||
|
JoystickX = (Scalar) 0;
|
||||||
|
JoystickY = (Scalar) 0;
|
||||||
|
analogRequested = True;
|
||||||
|
memset(lampState, 0, sizeof(lampState));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PadRIO::ResetThrottle()
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
throttleAccum = (Scalar) 0;
|
||||||
|
Throttle = (Scalar) 0;
|
||||||
|
analogRequested = True;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PadRIO::SetLamp(int lampNumber, int state)
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
if (lampNumber >= 0 && lampNumber < lampCount)
|
||||||
|
{
|
||||||
|
lampState[lampNumber] = (unsigned char) state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PadRIO::QueueEvent(const RIOEvent &an_event)
|
||||||
|
{
|
||||||
|
int next = (queueHead + 1) % queueSize;
|
||||||
|
if (next == queueTail)
|
||||||
|
{
|
||||||
|
// full: drop the oldest event
|
||||||
|
queueTail = (queueTail + 1) % queueSize;
|
||||||
|
}
|
||||||
|
eventQueue[queueHead] = an_event;
|
||||||
|
queueHead = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PadRIO::PollInputs()
|
||||||
|
{
|
||||||
|
unsigned long now = GetTickCount();
|
||||||
|
if (now - lastPollTick < 10)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Scalar delta_t = (Scalar)(now - lastPollTick) / 1000.0f;
|
||||||
|
if (delta_t > 0.25f)
|
||||||
|
{
|
||||||
|
delta_t = 0.25f;
|
||||||
|
}
|
||||||
|
lastPollTick = now;
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Find / keep the XInput pad. Probing empty slots is slow, so an
|
||||||
|
// absent pad is only re-probed every 3 seconds.
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
XINPUT_STATE pad;
|
||||||
|
memset(&pad, 0, sizeof(pad));
|
||||||
|
Logical pad_live = False;
|
||||||
|
|
||||||
|
if (padIndex >= 0)
|
||||||
|
{
|
||||||
|
pad_live = (XInputGetState((DWORD) padIndex, &pad) == ERROR_SUCCESS);
|
||||||
|
if (!pad_live)
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "PadRIO: controller " << padIndex << " disconnected\n" << std::flush;
|
||||||
|
padIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (padIndex < 0 && (now - lastPadCheckTick) >= 3000)
|
||||||
|
{
|
||||||
|
lastPadCheckTick = now;
|
||||||
|
for (DWORD i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
if (XInputGetState(i, &pad) == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
padIndex = (int) i;
|
||||||
|
pad_live = True;
|
||||||
|
DEBUG_STREAM << "PadRIO: controller " << padIndex << " connected\n" << std::flush;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (padIndex < 0 && !padReported)
|
||||||
|
{
|
||||||
|
padReported = True;
|
||||||
|
DEBUG_STREAM << "PadRIO: no controller found - keyboard only\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Buttons: build the desired state across pad + keyboard, then
|
||||||
|
// diff against what we last reported.
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
unsigned char desired[buttonUnits];
|
||||||
|
memset(desired, 0, sizeof(desired));
|
||||||
|
|
||||||
|
if (pad_live)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < (int)(sizeof(padButtonMap)/sizeof(padButtonMap[0])); ++i)
|
||||||
|
{
|
||||||
|
if (pad.Gamepad.wButtons & padButtonMap[i].padMask)
|
||||||
|
{
|
||||||
|
desired[padButtonMap[i].rioUnit] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < (int)(sizeof(keyboardButtonMap)/sizeof(keyboardButtonMap[0])); ++i)
|
||||||
|
{
|
||||||
|
if (KeyDown(keyboardButtonMap[i].virtualKey))
|
||||||
|
{
|
||||||
|
desired[keyboardButtonMap[i].rioUnit] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int unit = 0; unit < buttonUnits; ++unit)
|
||||||
|
{
|
||||||
|
if (desired[unit] != buttonDown[unit])
|
||||||
|
{
|
||||||
|
buttonDown[unit] = desired[unit];
|
||||||
|
|
||||||
|
RIOEvent an_event;
|
||||||
|
an_event.Type = desired[unit] ? ButtonPressedEvent : ButtonReleasedEvent;
|
||||||
|
an_event.Data.Unit = unit;
|
||||||
|
QueueEvent(an_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Axes
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
Scalar x = (Scalar) 0, y = (Scalar) 0;
|
||||||
|
Scalar left_pedal = (Scalar) 0, right_pedal = (Scalar) 0;
|
||||||
|
Scalar throttle_rate = (Scalar) 0;
|
||||||
|
|
||||||
|
if (pad_live)
|
||||||
|
{
|
||||||
|
x += StickValue(pad.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
|
||||||
|
y += StickValue(pad.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
|
||||||
|
throttle_rate += StickValue(pad.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
|
||||||
|
|
||||||
|
if (pad.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
|
||||||
|
{
|
||||||
|
left_pedal += (Scalar)(pad.Gamepad.bLeftTrigger) / 255.0f;
|
||||||
|
}
|
||||||
|
if (pad.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
|
||||||
|
{
|
||||||
|
right_pedal += (Scalar)(pad.Gamepad.bRightTrigger) / 255.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyboard: WASD stick deflect, Q/E pedals, PgUp/PgDn throttle
|
||||||
|
if (KeyDown('A')) x -= 1.0f;
|
||||||
|
if (KeyDown('D')) x += 1.0f;
|
||||||
|
if (KeyDown('W')) y += 1.0f;
|
||||||
|
if (KeyDown('S')) y -= 1.0f;
|
||||||
|
if (KeyDown('Q')) left_pedal += 1.0f;
|
||||||
|
if (KeyDown('E')) right_pedal += 1.0f;
|
||||||
|
if (KeyDown(VK_PRIOR)) throttle_rate += 1.0f; // PgUp
|
||||||
|
if (KeyDown(VK_NEXT)) throttle_rate -= 1.0f; // PgDn
|
||||||
|
|
||||||
|
if (x > 1.0f) x = 1.0f;
|
||||||
|
if (x < -1.0f) x = -1.0f;
|
||||||
|
if (y > 1.0f) y = 1.0f;
|
||||||
|
if (y < -1.0f) y = -1.0f;
|
||||||
|
|
||||||
|
throttleAccum = Clamp01(throttleAccum + throttle_rate * throttleRatePerSecond * delta_t);
|
||||||
|
|
||||||
|
Throttle = throttleAccum;
|
||||||
|
LeftPedal = Clamp01(left_pedal);
|
||||||
|
RightPedal = Clamp01(right_pedal);
|
||||||
|
JoystickX = invertX ? -x : x;
|
||||||
|
JoystickY = invertY ? -y : y;
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Emit an analog event when asked to, or when anything moved
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
Logical changed =
|
||||||
|
(Throttle != sentThrottle) ||
|
||||||
|
(LeftPedal != sentLeftPedal) ||
|
||||||
|
(RightPedal != sentRightPedal) ||
|
||||||
|
(JoystickX != sentJoystickX) ||
|
||||||
|
(JoystickY != sentJoystickY);
|
||||||
|
|
||||||
|
if (analogRequested || changed)
|
||||||
|
{
|
||||||
|
analogRequested = False;
|
||||||
|
sentThrottle = Throttle;
|
||||||
|
sentLeftPedal = LeftPedal;
|
||||||
|
sentRightPedal = RightPedal;
|
||||||
|
sentJoystickX = JoystickX;
|
||||||
|
sentJoystickY = JoystickY;
|
||||||
|
|
||||||
|
RIOEvent an_event;
|
||||||
|
an_event.Type = AnalogEvent;
|
||||||
|
an_event.Data.Unit = 0;
|
||||||
|
QueueEvent(an_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "l4rio.h"
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
//############################### PadRIO #################################
|
||||||
|
//########################################################################
|
||||||
|
//
|
||||||
|
// A cockpit-less RIO: synthesizes the RIOBase control surface from an
|
||||||
|
// XInput controller and the PC keyboard, following vRIO's default
|
||||||
|
// binding profile. Selected with L4CONTROLS=PAD.
|
||||||
|
//
|
||||||
|
// Left stick -> joystick X/Y [-1..1]
|
||||||
|
// Left/right trigger-> left/right pedal [0..1]
|
||||||
|
// Right stick Y -> throttle rate (position holds) [0..1]
|
||||||
|
// A / B -> joystick trigger / reverse thrust
|
||||||
|
// X / Y / LB / RB -> pinky / thumb-low / thumb-low / thumb-high
|
||||||
|
// DPad -> joystick hat
|
||||||
|
// Back / Start -> config buttons (AuxUpperRight2 / 1)
|
||||||
|
//
|
||||||
|
// Keyboard: WASD = stick deflect (spring-back), PgUp/PgDn = throttle,
|
||||||
|
// Q/E = pedals, Space = trigger, R = reverse, arrows = hat, F1/F2 =
|
||||||
|
// config buttons.
|
||||||
|
//
|
||||||
|
// Set L4PADFLIP to a string containing 'X' and/or 'Y' to invert the
|
||||||
|
// stick axes.
|
||||||
|
//
|
||||||
|
class PadRIO :
|
||||||
|
public RIOBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PadRIO();
|
||||||
|
~PadRIO();
|
||||||
|
|
||||||
|
Logical
|
||||||
|
TestInstance() const;
|
||||||
|
|
||||||
|
Logical
|
||||||
|
GetNextEvent(RIOEvent *destinationPointer);
|
||||||
|
|
||||||
|
void
|
||||||
|
RequestAnalogUpdate();
|
||||||
|
|
||||||
|
void
|
||||||
|
GeneralReset();
|
||||||
|
void
|
||||||
|
ResetThrottle();
|
||||||
|
|
||||||
|
void
|
||||||
|
SetLamp(int lampNumber, int state);
|
||||||
|
|
||||||
|
// Lamp state the game has commanded, indexed by RIO lamp number.
|
||||||
|
// A future on-screen panel reads this to light its buttons.
|
||||||
|
enum { lampCount = 64 };
|
||||||
|
unsigned char
|
||||||
|
lampState[lampCount];
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void
|
||||||
|
PollInputs();
|
||||||
|
void
|
||||||
|
QueueEvent(const RIOEvent &an_event);
|
||||||
|
|
||||||
|
enum { queueSize = 64 };
|
||||||
|
enum { buttonUnits = 0x48 }; // through LastMappableButton
|
||||||
|
|
||||||
|
RIOEvent
|
||||||
|
eventQueue[queueSize];
|
||||||
|
int
|
||||||
|
queueHead, queueTail;
|
||||||
|
|
||||||
|
unsigned long
|
||||||
|
lastPollTick,
|
||||||
|
lastPadCheckTick;
|
||||||
|
int
|
||||||
|
padIndex; // connected XInput slot, -1 = none
|
||||||
|
Logical
|
||||||
|
padReported; // one-time connect/disconnect log flags
|
||||||
|
Logical
|
||||||
|
analogRequested;
|
||||||
|
|
||||||
|
unsigned char
|
||||||
|
buttonDown[buttonUnits];
|
||||||
|
|
||||||
|
Scalar
|
||||||
|
throttleAccum;
|
||||||
|
Logical
|
||||||
|
invertX, invertY;
|
||||||
|
|
||||||
|
Scalar
|
||||||
|
sentThrottle, sentLeftPedal, sentRightPedal,
|
||||||
|
sentJoystickX, sentJoystickY;
|
||||||
|
};
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
#include "mungal4.h"
|
||||||
|
#pragma hdrstop
|
||||||
|
|
||||||
|
#include "l4plasmascreen.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const char plasmaWindowClass[] = "RPPlasmaScreen";
|
||||||
|
|
||||||
|
LRESULT CALLBACK
|
||||||
|
PlasmaWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
PlasmaScreen *screen =
|
||||||
|
(PlasmaScreen *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case WM_PAINT:
|
||||||
|
if (screen != NULL)
|
||||||
|
{
|
||||||
|
// handled below through the friend-free public surface:
|
||||||
|
// the window procedure only asks the screen to repaint.
|
||||||
|
PAINTSTRUCT ps;
|
||||||
|
HDC hdc = BeginPaint(hwnd, &ps);
|
||||||
|
// PlasmaScreen stores what StretchDIBits needs in
|
||||||
|
// window properties to keep the class decoupled.
|
||||||
|
const BITMAPINFO *bmi =
|
||||||
|
(const BITMAPINFO *) GetPropA(hwnd, "PlasmaBitmapInfo");
|
||||||
|
const void *pixels = GetPropA(hwnd, "PlasmaPixels");
|
||||||
|
RECT client;
|
||||||
|
GetClientRect(hwnd, &client);
|
||||||
|
if (bmi != NULL && pixels != NULL)
|
||||||
|
{
|
||||||
|
SetStretchBltMode(hdc, COLORONCOLOR);
|
||||||
|
StretchDIBits(
|
||||||
|
hdc,
|
||||||
|
0, 0, client.right, client.bottom,
|
||||||
|
0, 0, PlasmaScreen::plasmaWidth, PlasmaScreen::plasmaHeight,
|
||||||
|
pixels, bmi, DIB_RGB_COLORS, SRCCOPY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
EndPaint(hwnd, &ps);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_CLOSE:
|
||||||
|
// The glass is part of the cockpit; just hide it.
|
||||||
|
ShowWindow(hwnd, SW_HIDE);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case WM_ERASEBKGND:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return DefWindowProcA(hwnd, message, wParam, lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
//############################ PlasmaScreen ##############################
|
||||||
|
//########################################################################
|
||||||
|
|
||||||
|
PlasmaScreen::PlasmaScreen():
|
||||||
|
Video8BitBuffered(plasmaWidth, plasmaHeight)
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
|
||||||
|
window = NULL;
|
||||||
|
bitmapInfo = NULL;
|
||||||
|
|
||||||
|
scale = 4;
|
||||||
|
const char *scale_string = getenv("L4PLASMASCALE");
|
||||||
|
if (scale_string != NULL)
|
||||||
|
{
|
||||||
|
int requested = atoi(scale_string);
|
||||||
|
if (requested >= 1 && requested <= 16)
|
||||||
|
{
|
||||||
|
scale = requested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Build the 8bpp DIB header with the plasma-orange palette:
|
||||||
|
// index 0 is the dark glass, everything else lights up.
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
BITMAPINFOHEADER *header = (BITMAPINFOHEADER *) new char[
|
||||||
|
sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
|
||||||
|
memset(header, 0, sizeof(BITMAPINFOHEADER));
|
||||||
|
header->biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
header->biWidth = plasmaWidth;
|
||||||
|
header->biHeight = -plasmaHeight; // top-down, matching PixelMap8
|
||||||
|
header->biPlanes = 1;
|
||||||
|
header->biBitCount = 8;
|
||||||
|
header->biCompression = BI_RGB;
|
||||||
|
header->biClrUsed = 256;
|
||||||
|
|
||||||
|
RGBQUAD *palette = (RGBQUAD *)((char *) header + sizeof(BITMAPINFOHEADER));
|
||||||
|
for (int i = 0; i < 256; ++i)
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
palette[i].rgbRed = 24; palette[i].rgbGreen = 10; palette[i].rgbBlue = 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
palette[i].rgbRed = 255; palette[i].rgbGreen = 144; palette[i].rgbBlue = 32;
|
||||||
|
}
|
||||||
|
palette[i].rgbReserved = 0;
|
||||||
|
}
|
||||||
|
bitmapInfo = header;
|
||||||
|
|
||||||
|
CreateGlassWindow();
|
||||||
|
|
||||||
|
Tell("PlasmaScreen: on-screen plasma display, scale x" << scale << "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaScreen::~PlasmaScreen()
|
||||||
|
{
|
||||||
|
Check_Pointer(this);
|
||||||
|
|
||||||
|
if (window != NULL)
|
||||||
|
{
|
||||||
|
RemovePropA((HWND) window, "PlasmaBitmapInfo");
|
||||||
|
RemovePropA((HWND) window, "PlasmaPixels");
|
||||||
|
DestroyWindow((HWND) window);
|
||||||
|
window = NULL;
|
||||||
|
}
|
||||||
|
delete [] (char *) bitmapInfo;
|
||||||
|
bitmapInfo = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical
|
||||||
|
PlasmaScreen::TestInstance() const
|
||||||
|
{
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlasmaScreen::CreateGlassWindow()
|
||||||
|
{
|
||||||
|
HINSTANCE instance = GetModuleHandleA(NULL);
|
||||||
|
|
||||||
|
static Logical class_registered = False;
|
||||||
|
if (!class_registered)
|
||||||
|
{
|
||||||
|
class_registered = True;
|
||||||
|
|
||||||
|
WNDCLASSA window_class;
|
||||||
|
memset(&window_class, 0, sizeof(window_class));
|
||||||
|
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||||
|
window_class.lpfnWndProc = PlasmaWndProc;
|
||||||
|
window_class.hInstance = instance;
|
||||||
|
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
|
window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
|
||||||
|
window_class.lpszClassName = plasmaWindowClass;
|
||||||
|
RegisterClassA(&window_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT bounds;
|
||||||
|
bounds.left = 0;
|
||||||
|
bounds.top = 0;
|
||||||
|
bounds.right = plasmaWidth * scale;
|
||||||
|
bounds.bottom = plasmaHeight * scale;
|
||||||
|
AdjustWindowRect(&bounds, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);
|
||||||
|
|
||||||
|
window = CreateWindowExA(
|
||||||
|
0,
|
||||||
|
plasmaWindowClass,
|
||||||
|
"Plasma Display",
|
||||||
|
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
|
||||||
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||||
|
bounds.right - bounds.left,
|
||||||
|
bounds.bottom - bounds.top,
|
||||||
|
NULL, NULL, instance, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (window != NULL)
|
||||||
|
{
|
||||||
|
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
|
||||||
|
SetPropA((HWND) window, "PlasmaBitmapInfo", bitmapInfo);
|
||||||
|
SetPropA((HWND) window, "PlasmaPixels", pixelBuffer->Data.MapPointer);
|
||||||
|
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "PlasmaScreen: window creation failed\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// Called from the gauge renderer's background task, same slot the serial
|
||||||
|
// plasma used for streaming. Repaint when any line changed.
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Logical
|
||||||
|
PlasmaScreen::Update(Logical forceall)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
Logical dirty = forceall;
|
||||||
|
|
||||||
|
for (int y = 0; y < plasmaHeight; ++y)
|
||||||
|
{
|
||||||
|
if (changedLine[y])
|
||||||
|
{
|
||||||
|
changedLine[y] = 0;
|
||||||
|
dirty = True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirty && window != NULL)
|
||||||
|
{
|
||||||
|
InvalidateRect((HWND) window, NULL, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return False; // 'done' - nothing left to stream
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PlasmaScreen::WaitForUpdate()
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "l4vb8.h"
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
//############################ PlasmaScreen ##############################
|
||||||
|
//########################################################################
|
||||||
|
//
|
||||||
|
// The cockpit's 128x32 plasma display rendered as a desktop window
|
||||||
|
// instead of streamed to serial hardware. The gauge system draws into
|
||||||
|
// the same Video8BitBuffered surface it always has; Update() just blits
|
||||||
|
// the buffer into a small "glass" window in plasma orange.
|
||||||
|
//
|
||||||
|
// Selected with L4PLASMA=SCREEN. L4PLASMASCALE sets the pixel size
|
||||||
|
// (default 4 -> a 512x128 window).
|
||||||
|
//
|
||||||
|
class PlasmaScreen :
|
||||||
|
public Video8BitBuffered
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
plasmaWidth=128,
|
||||||
|
plasmaHeight=32
|
||||||
|
};
|
||||||
|
|
||||||
|
PlasmaScreen();
|
||||||
|
|
||||||
|
~PlasmaScreen();
|
||||||
|
|
||||||
|
Logical
|
||||||
|
TestInstance() const;
|
||||||
|
|
||||||
|
Logical
|
||||||
|
Update(Logical forceall);
|
||||||
|
|
||||||
|
void
|
||||||
|
WaitForUpdate();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void
|
||||||
|
CreateGlassWindow();
|
||||||
|
|
||||||
|
void
|
||||||
|
*window; // HWND, kept untyped so this header stays lean
|
||||||
|
void
|
||||||
|
*bitmapInfo; // BITMAPINFOHEADER + 256-entry orange palette
|
||||||
|
int
|
||||||
|
scale;
|
||||||
|
};
|
||||||
+96
-27
@@ -109,26 +109,17 @@ protected:
|
|||||||
lowestInput;
|
lowestInput;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RIO :
|
//########################################################################
|
||||||
public PCSerialPacket
|
//############################### RIOBase ################################
|
||||||
|
//########################################################################
|
||||||
|
//
|
||||||
|
// The control surface the game consumes from the cockpit RIO board,
|
||||||
|
// independent of transport. RIO (below) is the serial-hardware
|
||||||
|
// implementation; PadRIO (l4padrio.h) synthesizes the same surface from
|
||||||
|
// an XInput controller + the PC keyboard for cockpit-less play.
|
||||||
|
//
|
||||||
|
class RIOBase
|
||||||
{
|
{
|
||||||
protected:
|
|
||||||
enum RIOCommand{
|
|
||||||
CheckRequest=0x80,
|
|
||||||
VersionRequest,
|
|
||||||
AnalogRequest,
|
|
||||||
ResetRequest,
|
|
||||||
LampRequest,
|
|
||||||
CheckReply,
|
|
||||||
VersionReply,
|
|
||||||
AnalogReply,
|
|
||||||
ButtonPressed,
|
|
||||||
ButtonReleased,
|
|
||||||
KeyPressed,
|
|
||||||
KeyReleased,
|
|
||||||
TestModeChange
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum RIOStatusType {
|
enum RIOStatusType {
|
||||||
BoardOk=0, BoardMissing=1, BoardBad=2,
|
BoardOk=0, BoardMissing=1, BoardBad=2,
|
||||||
@@ -166,6 +157,90 @@ public:
|
|||||||
}Data;
|
}Data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RIOBase()
|
||||||
|
{
|
||||||
|
TestModeActive = 0;
|
||||||
|
Throttle = (Scalar) 0;
|
||||||
|
LeftPedal = (Scalar) 0;
|
||||||
|
RightPedal = (Scalar) 0;
|
||||||
|
JoystickX = (Scalar) 0;
|
||||||
|
JoystickY = (Scalar) 0;
|
||||||
|
MajorRevision = 0;
|
||||||
|
MinorRevision = 0;
|
||||||
|
}
|
||||||
|
virtual ~RIOBase() {}
|
||||||
|
|
||||||
|
virtual Logical
|
||||||
|
TestInstance() const
|
||||||
|
{ return True; }
|
||||||
|
|
||||||
|
virtual Logical
|
||||||
|
GetNextEvent(RIOEvent *destinationPointer) = 0;
|
||||||
|
|
||||||
|
virtual void
|
||||||
|
ForceCenterJoystick() {}
|
||||||
|
virtual void
|
||||||
|
SetJoystickDeadBand(Scalar) {}
|
||||||
|
virtual void
|
||||||
|
SetThrottleDeadBand(Scalar) {}
|
||||||
|
virtual void
|
||||||
|
SetPedalsDeadBand(Scalar) {}
|
||||||
|
|
||||||
|
virtual void
|
||||||
|
RequestCheck() {}
|
||||||
|
virtual void
|
||||||
|
RequestVersion() {}
|
||||||
|
virtual void
|
||||||
|
RequestAnalogUpdate() {}
|
||||||
|
|
||||||
|
virtual void
|
||||||
|
GeneralReset() {}
|
||||||
|
virtual void
|
||||||
|
ResetThrottle() {}
|
||||||
|
virtual void
|
||||||
|
ResetLeftPedal() {}
|
||||||
|
virtual void
|
||||||
|
ResetRightPedal() {}
|
||||||
|
virtual void
|
||||||
|
ResetVerticalJoystick() {}
|
||||||
|
virtual void
|
||||||
|
ResetHorizontalJoystick() {}
|
||||||
|
|
||||||
|
virtual void
|
||||||
|
SetLamp(int lampNumber, int state) = 0;
|
||||||
|
|
||||||
|
int
|
||||||
|
TestModeActive;
|
||||||
|
|
||||||
|
Scalar
|
||||||
|
Throttle, LeftPedal, RightPedal, JoystickX, JoystickY;
|
||||||
|
|
||||||
|
int
|
||||||
|
MajorRevision, MinorRevision;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RIO :
|
||||||
|
public RIOBase,
|
||||||
|
public PCSerialPacket
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
enum RIOCommand{
|
||||||
|
CheckRequest=0x80,
|
||||||
|
VersionRequest,
|
||||||
|
AnalogRequest,
|
||||||
|
ResetRequest,
|
||||||
|
LampRequest,
|
||||||
|
CheckReply,
|
||||||
|
VersionReply,
|
||||||
|
AnalogReply,
|
||||||
|
ButtonPressed,
|
||||||
|
ButtonReleased,
|
||||||
|
KeyPressed,
|
||||||
|
KeyReleased,
|
||||||
|
TestModeChange
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
//Win32 Serial support: ADB 02/13/07
|
//Win32 Serial support: ADB 02/13/07
|
||||||
//RIO(Word port, Word intNum, Logical perform_tests = True);
|
//RIO(Word port, Word intNum, Logical perform_tests = True);
|
||||||
RIO(const char* port, Logical perform_tests = True);
|
RIO(const char* port, Logical perform_tests = True);
|
||||||
@@ -267,14 +342,8 @@ public:
|
|||||||
TransmitQueueCount()
|
TransmitQueueCount()
|
||||||
{ return PCSerialPacket::TransmitQueueCount(); }
|
{ return PCSerialPacket::TransmitQueueCount(); }
|
||||||
|
|
||||||
int
|
// TestModeActive, the five analog Scalars, and Major/MinorRevision
|
||||||
TestModeActive;
|
// now live in RIOBase.
|
||||||
|
|
||||||
Scalar
|
|
||||||
Throttle, LeftPedal, RightPedal, JoystickX, JoystickY;
|
|
||||||
|
|
||||||
int
|
|
||||||
MajorRevision, MinorRevision;
|
|
||||||
|
|
||||||
int remoteRetryCount;
|
int remoteRetryCount;
|
||||||
int remoteAbandonCount;
|
int remoteAbandonCount;
|
||||||
|
|||||||
@@ -249,8 +249,10 @@
|
|||||||
<ClCompile Include=".\L4MPPR.cpp" />
|
<ClCompile Include=".\L4MPPR.cpp" />
|
||||||
<ClCompile Include=".\L4NET.CPP" />
|
<ClCompile Include=".\L4NET.CPP" />
|
||||||
<ClCompile Include=".\L4PARTICLES.cpp" />
|
<ClCompile Include=".\L4PARTICLES.cpp" />
|
||||||
|
<ClCompile Include=".\L4PADRIO.cpp" />
|
||||||
<ClCompile Include=".\L4PCSPAK.cpp" />
|
<ClCompile Include=".\L4PCSPAK.cpp" />
|
||||||
<ClCompile Include=".\L4PLASMA.cpp" />
|
<ClCompile Include=".\L4PLASMA.cpp" />
|
||||||
|
<ClCompile Include=".\L4PLASMASCREEN.cpp" />
|
||||||
<ClCompile Include=".\L4RIO.cpp" />
|
<ClCompile Include=".\L4RIO.cpp" />
|
||||||
<ClCompile Include=".\L4SERIAL.cpp" />
|
<ClCompile Include=".\L4SERIAL.cpp" />
|
||||||
<ClCompile Include=".\L4SPLR.cpp" />
|
<ClCompile Include=".\L4SPLR.cpp" />
|
||||||
@@ -436,8 +438,10 @@
|
|||||||
<ClInclude Include=".\L4MPPR.h" />
|
<ClInclude Include=".\L4MPPR.h" />
|
||||||
<ClInclude Include=".\L4NET.H" />
|
<ClInclude Include=".\L4NET.H" />
|
||||||
<ClInclude Include=".\L4PARTICLES.h" />
|
<ClInclude Include=".\L4PARTICLES.h" />
|
||||||
|
<ClInclude Include=".\L4PADRIO.h" />
|
||||||
<ClInclude Include=".\L4PCSPAK.h" />
|
<ClInclude Include=".\L4PCSPAK.h" />
|
||||||
<ClInclude Include=".\L4PLASMA.h" />
|
<ClInclude Include=".\L4PLASMA.h" />
|
||||||
|
<ClInclude Include=".\L4PLASMASCREEN.h" />
|
||||||
<ClInclude Include=".\L4RIO.h" />
|
<ClInclude Include=".\L4RIO.h" />
|
||||||
<ClInclude Include=".\L4SERIAL.H" />
|
<ClInclude Include=".\L4SERIAL.H" />
|
||||||
<ClInclude Include=".\L4SPLR.h" />
|
<ClInclude Include=".\L4SPLR.h" />
|
||||||
|
|||||||
@@ -561,6 +561,12 @@
|
|||||||
<ClCompile Include=".\L4PLASMA.cpp">
|
<ClCompile Include=".\L4PLASMA.cpp">
|
||||||
<Filter>Source Files\MUNGA_L4</Filter>
|
<Filter>Source Files\MUNGA_L4</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include=".\L4PADRIO.cpp">
|
||||||
|
<Filter>Source Files\MUNGA_L4</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include=".\L4PLASMASCREEN.cpp">
|
||||||
|
<Filter>Source Files\MUNGA_L4</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include=".\L4RIO.cpp">
|
<ClCompile Include=".\L4RIO.cpp">
|
||||||
<Filter>Source Files\MUNGA_L4</Filter>
|
<Filter>Source Files\MUNGA_L4</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -1118,6 +1124,12 @@
|
|||||||
<ClInclude Include=".\L4PLASMA.h">
|
<ClInclude Include=".\L4PLASMA.h">
|
||||||
<Filter>Header Files\MUNGA_L4</Filter>
|
<Filter>Header Files\MUNGA_L4</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include=".\L4PADRIO.h">
|
||||||
|
<Filter>Header Files\MUNGA_L4</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include=".\L4PLASMASCREEN.h">
|
||||||
|
<Filter>Header Files\MUNGA_L4</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include=".\L4RIO.h">
|
<ClInclude Include=".\L4RIO.h">
|
||||||
<Filter>Header Files\MUNGA_L4</Filter>
|
<Filter>Header Files\MUNGA_L4</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|||||||
+14
-10
@@ -40,16 +40,20 @@ geometry reference — it is already the faithful software copy of the physical
|
|||||||
panel. On-screen buttons light with the same lamp states the game commands,
|
panel. On-screen buttons light with the same lamp states the game commands,
|
||||||
so press-feedback works like the real button field.
|
so press-feedback works like the real button field.
|
||||||
|
|
||||||
- Replace the RIO serial path in the L4 layer with a native input layer
|
- ✅ **Prototype landed (2026-07-12).** The seam existed: the controls manager
|
||||||
(keyboard / mouse / XInput, later Steam Input). Port vRIO's binding model —
|
consumes a small RIO surface (8 methods + 5 analog scalars), now split out
|
||||||
its axis-travel semantics (throttle ratchet, spring-back pedals, ±80 stick)
|
as `RIOBase`. **PadRIO** (`L4CONTROLS=PAD`) implements it from XInput + the
|
||||||
are exactly what the game expects. **[investigate]** where the L4 layer reads
|
PC keyboard with vRIO's default profile — the stock `VTVRIOMapper`, lamps,
|
||||||
RIO input, and whether an abstraction seam already exists.
|
and button field run unchanged, no serial, hot-plug supported. Verified:
|
||||||
- Render the plasma display in-game (HUD overlay) instead of streaming
|
boots and plays with vRIO off and no COM ports.
|
||||||
`ESC P` graphics to COM2 — `L4PLASMA.CPP` already composes the frame into a
|
- ✅ Plasma display: **PlasmaScreen** (`L4PLASMA=SCREEN`) renders the same
|
||||||
local 1bpp buffer before serializing, so the seam is likely right there.
|
`Video8BitBuffered` surface into a desktop "Plasma Display" window in
|
||||||
- Lamp feedback (button lighting) maps to on-screen highlight / controller
|
plasma orange — verified drawing live game content (score readout).
|
||||||
rumble / RGB later; low priority.
|
See BUILD.md §4 for bindings and the desktop `environ.ini`.
|
||||||
|
- Next in A: on-screen RIO panel fed by `PadRIO::lampState[]` (the
|
||||||
|
cockpit-feel layout — vRIO button clusters around the displays); port the
|
||||||
|
full vRIO bindings-file model (deflect/rate/deadzone per axis, rebinding);
|
||||||
|
pilot keypad (numpad → KeyEvent); Steam Input once C starts.
|
||||||
- vRIO itself stays useful as a dev harness against unmodified builds.
|
- vRIO itself stays useful as a dev harness against unmodified builds.
|
||||||
|
|
||||||
## Workstream B — In-game sessions (from TeslaConsole)
|
## Workstream B — In-game sessions (from TeslaConsole)
|
||||||
|
|||||||
Reference in New Issue
Block a user