Files
BT411/engine/MUNGA_L4/L4RIOBANK.cpp
T
CydandClaude Opus 4.8 7d112ce309 Glass panels: flash at full rate when unfocused + red Panic/Eject button
Two glass-panel indicator fixes reported by playtesters.

1. FLASH SLOW UNLESS FOCUSED.  The per-display panel windows repaint off a
   62ms WM_TIMER, and Windows coalesces/throttles timer + paint messages for a
   window that is in the BACKGROUND (unfocused) -- so with the game window
   holding focus the panels' lamp flash (a repaint-driven animation) crawled,
   and giving a panel focus un-throttled it.  (My earlier surround fix drew in
   the main D3D frame and never touched these separate windows.)  New
   BTGlassPanels_Tick(), called once per frame from the main render loop
   (L4VIDEO), drives a synchronous InvalidateRect+UpdateWindow at the ~16Hz
   flash cadence -- the main loop runs every frame regardless of which window
   has focus, and the forced paint bypasses the throttled timer-message path.
   The WM_TIMER stays for its one-shot re-snap.

2. RED PANIC/EJECT.  0x3D is the Panic/Eject button; on the blue flight panel
   it was just another blue block.  The shared flight-grid geometry (L4RIOBANK)
   now tags 0x3D as colorClass 0 (red); L4GLASSWIN's AdoptBank maps colorClass
   0 -> ClrRed explicitly (the flight bank's ClrBlue default no longer swallows
   it), and the surround (L4VB16) already renders non-blue/yellow as red -- so
   the eject stands out red in both the surround and the exploded window.

Verified: Release links clean (40 tolerated /FORCE externals only); exploded
panels boot + run 13s no crash under the per-frame repaint pump; riobank dump
confirms 0x3d class=0 (red), neighbours class=2 (blue).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 19:28:11 -05:00

300 lines
10 KiB
C++

#include "mungal4.h"
#pragma hdrstop
//###########################################################################
// L4RIOBANK -- the ONE button-bank geometry for a pod display. Design +
// the under-glass rule: l4riobank.h. Consumed by the cockpit surround
// (L4VB16.cpp, canvas space) and the exploded per-display windows
// (L4GLASSWIN.cpp, client space); neither owns a copy any more.
//###########################################################################
#include "l4riobank.h"
#include <string.h>
#include <stdlib.h>
namespace
{
//-------------------------------------------------------------------
// The lamp strip at NATIVE glass size, scaled down with the display
// but floored so a half-scale surround lamp is still a bar and not a
// smudge. RP412 L4MFDVIEW works at native only and hardcodes 10.
//-------------------------------------------------------------------
const int nativeShortAxis = 480; // MFD 640x480 / map 480x640
const int nativeStrip = 10;
const int minimumStrip = 6;
const int defaultGap = 4;
//-------------------------------------------------------------------
// Per-column horizontal nudge for the 4 MFD buttons, left->right, so
// they line up with the (unevenly spaced) DISPLAY / PROGRAM / ...
// legends painted in the imagery. Measured against the native 640
// glass in L4GLASSWIN (RedOffsetX + kRedColDX) and scaled here, so
// both renderers inherit the same alignment. Tunable.
//-------------------------------------------------------------------
const int nativeMfdW = 640;
const int mfdColumnDX[4] = { 0, 1, 5, 5 };
//-------------------------------------------------------------------
// The map paints its own legend beside each side button (the six
// cells: MAP+, MAP-, IR, crouch, searchlight, display-mode), and the
// buttons have to line up with THAT grid, not with height/6.
//
// MEASURED off a native 480x640 capture (scratchpad/measurelegend.py
// over an exploded Secondary/Radar window): the first cell border sits
// 3 rows down and the six cells are 102 tall on a 107 pitch. RP412's
// map measures 13 + 6x102 on a 105 pitch -- same cell height, different
// top and pitch, so its numbers do NOT transfer; these are ours.
//
// Our previous even division (displayH/6 + 1 = 107) had the pitch right
// by luck and sat 3px high of the labels.
//-------------------------------------------------------------------
const int mapLegendSpan = 640;
const int mapLegendTop = 3;
const int mapLegendCell = 102;
const int mapLegendPitch = 107;
int Scaled(int nativeValue, int actual, int nativeExtent)
{
if (nativeExtent <= 0) return nativeValue;
return (nativeValue * actual) / nativeExtent;
}
void Push(
BTRioBank *bank,
int address, int x, int y, int w, int h,
int colorClass, int inert, const char *label
)
{
if (bank->buttonCount >= BTRioBankMaxButtons) return;
if (w <= 0 || h <= 0) return;
BTRioButton *button = &bank->buttons[bank->buttonCount++];
button->address = address;
button->x = x;
button->y = y;
button->w = w;
button->h = h;
button->colorClass = colorClass;
button->inert = inert;
button->label = label;
}
void GrowBounds(BTRioBank *bank, int x, int y, int w, int h)
{
if (bank->boundsW == 0 && bank->boundsH == 0)
{
bank->boundsX = x; bank->boundsY = y;
bank->boundsW = w; bank->boundsH = h;
return;
}
int left = (x < bank->boundsX) ? x : bank->boundsX;
int top = (y < bank->boundsY) ? y : bank->boundsY;
int right = bank->boundsX + bank->boundsW;
int bottom = bank->boundsY + bank->boundsH;
if (x + w > right) right = x + w;
if (y + h > bottom) bottom = y + h;
bank->boundsX = left;
bank->boundsY = top;
bank->boundsW = right - left;
bank->boundsH = bottom - top;
}
}
//###########################################################################
void
BTRioBankMetricsFor(int displayW, int displayH, BTRioBankMetrics *out)
{
if (out == 0) return;
int shortAxis = (displayW < displayH) ? displayW : displayH;
int strip = Scaled(nativeStrip, shortAxis, nativeShortAxis);
if (strip < minimumStrip) strip = minimumStrip;
out->strip = strip;
out->gap = defaultGap;
// The cells with no glass behind them (the flight bank, the map's foot
// row) are drawn in full, so they get a plain readable size scaled off
// the same axis rather than a derived depth.
out->cellW = Scaled(58, shortAxis, nativeShortAxis);
out->cellH = Scaled(28, shortAxis, nativeShortAxis);
if (out->cellW < 12) out->cellW = 12;
if (out->cellH < 12) out->cellH = 12;
}
//###########################################################################
void
BTRioBankLayout(
BTRioBankStyle style,
int originX,
int originY,
int displayW,
int displayH,
int anchorA,
int anchorB,
const BTRioBankMetrics *metrics,
const int *bottomAddrs,
BTRioBank *out
)
{
if (out == 0 || metrics == 0) return;
memset(out, 0, sizeof(*out));
const int strip = metrics->strip;
const int gap = metrics->gap;
// the glass itself is always inside the bounds
GrowBounds(out, originX, originY, displayW, displayH);
if (style == BTRioBankMfd)
{
//---------------------------------------------------------------
// 4 above the glass, 4 below. Addresses descend from the anchor
// row-major (vRIO CockpitLayout::Mfd): top = anchorA-i, bottom =
// anchorA-4-i. Each button reaches HALF THE GLASS in and clears
// the edge by `strip`, so the two banks meet in the middle and
// every pixel of the display is some button's press target.
//---------------------------------------------------------------
int depth = displayH / 2;
if (depth < strip) depth = strip;
int slotW = displayW / 4;
for (int i = 0; i < 4; ++i)
{
int dx = Scaled(mfdColumnDX[i], displayW, nativeMfdW);
int x = originX + i * slotW + dx;
int w = slotW - gap;
int topY = originY - strip;
int botY = originY + displayH - depth;
Push(out, anchorA - i, x, topY, w, depth + strip, 0, 0, 0);
Push(out, anchorA - 4 - i, x, botY, w, depth + strip, 0, 0, 0);
GrowBounds(out, x, topY, w, depth + strip);
GrowBounds(out, x, botY, w, depth + strip);
}
}
else if (style == BTRioBankRadar)
{
//---------------------------------------------------------------
// The portrait map: 6 buttons down each side (anchorA left,
// anchorB right) plus an optional 4 along the foot. Same rule
// turned on its side -- each column reaches half the map in and
// leaves `strip` clearing the edge.
//
// The FOOT ROW IS PUSHED FIRST, deliberately: the columns now
// reach half the width each, so they cover the foot band too,
// and the hit test takes the first match. Foot-first keeps
// those four addresses reachable.
//---------------------------------------------------------------
int depth = displayW / 2;
if (depth < strip) depth = strip;
if (bottomAddrs != 0)
{
int bandH = metrics->cellH;
int bandY = originY + displayH - bandH;
int bandW = displayW / 4;
for (int i = 0; i < 4; ++i)
{
int x = originX + i * bandW;
Push(out, bottomAddrs[i], x, bandY, bandW - gap,
bandH + strip, 1, 0, 0);
GrowBounds(out, x, bandY, bandW - gap, bandH + strip);
}
}
//---------------------------------------------------------------
// Six cells down the height, on the map's MEASURED legend grid
// (see mapLegend* above). Top and bottom are scaled SEPARATELY
// and subtracted rather than scaling a height directly: rounding
// a height would let the buttons drift out of step with the
// labels a display or two down the column.
//---------------------------------------------------------------
int leftX = originX - strip;
int rightX = originX + displayW - depth;
for (int i = 0; i < 6; ++i)
{
int cellTop = mapLegendTop + i * mapLegendPitch;
int top = (cellTop * displayH) / mapLegendSpan;
int bottom = ((cellTop + mapLegendCell) * displayH) / mapLegendSpan;
int h = bottom - top;
if (h < 1) h = 1;
int y = originY + top;
Push(out, anchorA + i, leftX, y, depth + strip, h, 1, 0, 0);
Push(out, anchorB + i, rightX, y, depth + strip, h, 1, 0, 0);
GrowBounds(out, leftX, y, depth + strip, h);
GrowBounds(out, rightX, y, depth + strip, h);
}
}
}
//###########################################################################
void
BTRioBankFlightGrid(
int originX,
int originY,
int baseAddress,
int count,
int columns,
const char *const *labels,
const int *inert,
const BTRioBankMetrics *metrics,
BTRioBank *out
)
{
if (out == 0 || metrics == 0 || columns <= 0) return;
const int cw = metrics->cellW;
const int ch = metrics->cellH;
const int gap = metrics->gap;
for (int i = 0; i < count; ++i)
{
int column = i % columns;
int row = i / columns;
int x = originX + column * (cw + gap);
int y = originY + row * (ch + gap);
// Flight blocks are blue (colorClass 2), EXCEPT the Panic/Eject at 0x3D:
// red (colorClass 0) so the eject stands out on the blue panel.
int cc = (baseAddress + i == 0x3D) ? 0 : 2;
Push(out, baseAddress + i, x, y, cw, ch, cc,
(inert != 0) ? inert[i] : 0,
(labels != 0) ? labels[i] : 0);
GrowBounds(out, x, y, cw, ch);
}
}
//###########################################################################
void
BTRioBankDump(const char *tag, const BTRioBank *bank)
{
static int enabled = -1;
static int printed = 0;
if (enabled < 0)
{
const char *e = getenv("BT_RIOBANK_LOG");
enabled = (e != 0 && e[0] != '0') ? 1 : 0;
}
if (!enabled || bank == 0 || printed >= 16) return;
++printed;
DEBUG_STREAM << "[riobank] " << (tag ? tag : "?")
<< " bounds=(" << bank->boundsX << "," << bank->boundsY << ","
<< bank->boundsW << "," << bank->boundsH << ") "
<< bank->buttonCount << " buttons\n";
for (int i = 0; i < bank->buttonCount; ++i)
{
const BTRioButton &b = bank->buttons[i];
DEBUG_STREAM << "[riobank] addr=0x" << std::hex << b.address << std::dec
<< " rect=(" << b.x << "," << b.y << "," << b.w << "," << b.h << ")"
<< " class=" << b.colorClass << "\n";
}
DEBUG_STREAM << std::flush;
}