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:
Cyd
2026-07-12 13:13:37 -05:00
co-authored by Claude Fable 5
parent e4afc5ca19
commit de5a97d37d
12 changed files with 937 additions and 41 deletions
+15
View File
@@ -1,6 +1,8 @@
#include "mungal4.h"
#pragma hdrstop
#include "l4padrio.h"
#include "l4ctrl.h"
#include "l4keybd.h"
#include "l4app.h"
@@ -830,6 +832,19 @@ LBE4ControlsManager::LBE4ControlsManager():
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');
}
+2 -2
View File
@@ -542,9 +542,9 @@ public:
public:
//-------------------------------------------------------------------
// RIO data
// RIO data (serial hardware or the PadRIO pad/keyboard synthesizer)
//-------------------------------------------------------------------
RIO
RIOBase
*rioPointer;
protected:
+11 -2
View File
@@ -7,6 +7,7 @@
#include "l4gauge.h"
#include "..\munga\mission.h"
#include "l4plasma.h"
#include "l4plasmascreen.h"
#include "..\munga\notation.h"
// #define LOCAL_TEST
@@ -100,8 +101,16 @@ L4GaugeRenderer::L4GaugeRenderer(bool windowed, int *secondaryIndex, int *aux1In
// Tell("Plasma display created on COM1\n");
// externalDisplay = new PlasmaDisplay(PCS_COM1);
//}
Tell("Plasma display created on "); Tell(plasma_string); Tell("\n");
externalDisplay = new PlasmaDisplay(plasma_string);
if (_stricmp(plasma_string, "SCREEN") == 0)
{
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)
{
+377
View File
@@ -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);
}
}
+93
View File
@@ -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;
};
+222
View File
@@ -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);
}
+50
View File
@@ -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
View File
@@ -109,26 +109,17 @@ protected:
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:
enum RIOStatusType {
BoardOk=0, BoardMissing=1, BoardBad=2,
@@ -166,6 +157,90 @@ public:
}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
//RIO(Word port, Word intNum, Logical perform_tests = True);
RIO(const char* port, Logical perform_tests = True);
@@ -267,14 +342,8 @@ public:
TransmitQueueCount()
{ return PCSerialPacket::TransmitQueueCount(); }
int
TestModeActive;
Scalar
Throttle, LeftPedal, RightPedal, JoystickX, JoystickY;
int
MajorRevision, MinorRevision;
// TestModeActive, the five analog Scalars, and Major/MinorRevision
// now live in RIOBase.
int remoteRetryCount;
int remoteAbandonCount;
+4
View File
@@ -249,8 +249,10 @@
<ClCompile Include=".\L4MPPR.cpp" />
<ClCompile Include=".\L4NET.CPP" />
<ClCompile Include=".\L4PARTICLES.cpp" />
<ClCompile Include=".\L4PADRIO.cpp" />
<ClCompile Include=".\L4PCSPAK.cpp" />
<ClCompile Include=".\L4PLASMA.cpp" />
<ClCompile Include=".\L4PLASMASCREEN.cpp" />
<ClCompile Include=".\L4RIO.cpp" />
<ClCompile Include=".\L4SERIAL.cpp" />
<ClCompile Include=".\L4SPLR.cpp" />
@@ -436,8 +438,10 @@
<ClInclude Include=".\L4MPPR.h" />
<ClInclude Include=".\L4NET.H" />
<ClInclude Include=".\L4PARTICLES.h" />
<ClInclude Include=".\L4PADRIO.h" />
<ClInclude Include=".\L4PCSPAK.h" />
<ClInclude Include=".\L4PLASMA.h" />
<ClInclude Include=".\L4PLASMASCREEN.h" />
<ClInclude Include=".\L4RIO.h" />
<ClInclude Include=".\L4SERIAL.H" />
<ClInclude Include=".\L4SPLR.h" />
+12
View File
@@ -561,6 +561,12 @@
<ClCompile Include=".\L4PLASMA.cpp">
<Filter>Source Files\MUNGA_L4</Filter>
</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">
<Filter>Source Files\MUNGA_L4</Filter>
</ClCompile>
@@ -1118,6 +1124,12 @@
<ClInclude Include=".\L4PLASMA.h">
<Filter>Header Files\MUNGA_L4</Filter>
</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">
<Filter>Header Files\MUNGA_L4</Filter>
</ClInclude>