From 129b27499d30b154d31868df1026030d387ab6d9 Mon Sep 17 00:00:00 2001 From: arcattack Date: Mon, 20 Jul 2026 20:39:34 -0500 Subject: [PATCH] Cockpit surround: composite the gauges around the 3D view (default desktop layout) Under BT_DEV_GAUGES the DEFAULT is now the pod-faithful cockpit surround: the 3D world CENTERED with the six gauge surfaces composited AROUND it in the single main window at 1/2 native scale, plus clickable RIO button lamps. Iterated as a visual reference with the user, then landed in the engine. - L4VB16.cpp/.h: BTCockpitLayout + BTCockpitComputeLayout (layout single source of truth, computed from the backbuffer canvas); BTDrawCockpitPanels (band fill -> button lamp quads -> 9-entry surface loop, phosphor-green mono / amber palette sec, Eng-sibling slot map -> GDI-atlas flight labels; D3DSBT_ALL state-block isolation = the floating-rocks trap); BTCockpitMouseDown/Up (client->bb hit-test + glass press/release/right-latch contract); BTApplyWorldViewport + DevGaugeDocked cockpit branches; shared BTLampBrightnessOf inline. Hooked at the top of BTDrawGaugeInset (before BT_SHOT, so shots include it). - btl4main.cpp: glass preset (cockpit default, per-display panels opt-in); mode resolution -> gBTGaugeCockpit with work-area-clamped window sizing (-res = view size); WndProc WM_L/RBUTTON routing. - L4VIDEO.cpp: BTWorldAspectOf cockpit branch (view rect on-screen aspect). - L4VIDRND.cpp: CameraShipHUDRenderable::Render made viewport-relative. Buttons = the L4GLASSWIN address banks x0.5 (Heat 0x2F, Mfd2 0x27, Comm 0x37, Mfd1 0x0F, Mfd3 0x07 red; radar rails 0x10-0x1B yellow; flight 0x38-0x47 blue, labeled). Full rect is the hit target; the surface draws over it so only the lamp edge shows. Precedence: BT_GLASS_PANELS=1 stands cockpit down > BT_COCKPIT=1 > _WINDOW/_DOCK opt-out > cockpit default; BT_COCKPIT=0 = dock-bottom. Green tunable via BT_COCKPIT_TINT. Renders in ALL builds; only the PadRIO click/lamp seam is BT_GLASS-gated. Verified (awaiting playtest): both build/ + build-glass/ compile (0 error C); glass BT_SHOT matches the mockup; 500-click storm during a live mission survived (Gitea #18 Receiver-gap fix holds); BT_COCKPIT=0 dock-bottom unregressed; BT_GLASS_PANELS=1 stands cockpit down; pod build renders the panels. Co-Authored-By: Claude Opus 4.8 --- context/gauges-hud.md | 39 ++- docs/GAUGE_COMPOSITE.md | 49 ++++ engine/MUNGA_L4/L4VB16.cpp | 518 ++++++++++++++++++++++++++++++++++- engine/MUNGA_L4/L4VB16.h | 60 ++++ engine/MUNGA_L4/L4VIDEO.cpp | 17 ++ engine/MUNGA_L4/L4VIDRND.cpp | 30 +- game/btl4main.cpp | 121 ++++++-- 7 files changed, 793 insertions(+), 41 deletions(-) diff --git a/context/gauges-hud.md b/context/gauges-hud.md index ea3f3ca..cc35e49 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -72,11 +72,40 @@ TestInstance/BecameActive/Execute. **Gotchas that bite EVERY widget:** (`RecomputeCondenserValves`, FUN_0049f788, was a no-op stub). [T2] ## Dev composite (off-pod) -`BT_DEV_GAUGES` renders the 6 pod [[MFD]] surfaces in a separate 960×384 window (bit-plane masks -over one shared `SVGA16` pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they come from -`SETENV.BAT`/`L4GAUGE` on the real multi-adapter hardware (`FindBestAdapterIndices`/`BuildWindows`, -intact). The `overlay` port (SectorDisplay lives there) shares the `sec` physical surface via a -different bit-plane (0x00C0). [T2] +`BT_DEV_GAUGES` renders the 6 pod [[MFD]] surfaces (bit-plane masks over one shared `SVGA16` +pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they come from `SETENV.BAT`/`L4GAUGE` on the +real multi-adapter hardware (`FindBestAdapterIndices`/`BuildWindows`, intact). The `overlay` port +(SectorDisplay lives there) shares the `sec` physical surface via a different bit-plane (0x00C0). [T2] + +## Cockpit surround (the DEFAULT desktop layout, 2026-07-20) [T2] +Under `BT_DEV_GAUGES`, the DEFAULT is now the **cockpit surround** (`L4VB16.cpp` +`BTDrawCockpitPanels`): the 3D world view CENTERED with the six gauge surfaces composited AROUND +it in the SINGLE main window at **½ native scale** (MFD 320×240, radar 240×320 portrait), plus +clickable RIO button lamps — the pod-faithful arrangement (Coolant UL, Mfd2 upper-center, Comm/Hot +Box UR, Mfd1/Mfd3 lower flanks, secondary/radar flush below). The pod monitors physically clip the +eyeport, so the corner MFDs + top-center overlap the view edges. Mono MFDs are tinted **phosphor +green** (radar keeps its **amber** palette); the world viewport is the centered view rect +(`BTApplyWorldViewport` cockpit branch) so the reticle/HUD follow for free (dpl2d maps through the +viewport), and `gWindowAspect` = the view rect's on-screen aspect (`BTWorldAspectOf` cockpit branch). +- **Layout = single source of truth**: `BTCockpitLayout` / `BTCockpitComputeLayout(canvasW,canvasH)` + in `l4vb16.h`/`L4VB16.cpp`, computed FROM the backbuffer (canvas) size — consumed by the window + sizing (`btl4main.cpp`), the world viewport, the panel/button draw, the aspect, and the mouse + hit-test. Constants: `SCALE=0.5, OVL=44 (corner overlap), LAMP=16 (protruding lamp edge), + REDCELL=64 (hidden hit depth), RAILW=26`; `canvas = view + (552, 548)`. +- **Buttons** = the L4GLASSWIN geometry ×0.5 with the same address banks (Heat 0x2F, Mfd2 0x27, + Comm 0x37, Mfd1 0x0F, Mfd3 0x07 red 8-btn; radar rails 0x10-0x15/0x18-0x1D + bottom + {0x16,0x17,0x1F,0x1E} yellow; flight 0x38-0x3F/0x40-0x47 blue, labeled). Full rect = hit target; + the surface draws OVER it so only the lamp edge shows (the PaintGlass painter trick). Mouse: + main WndProc `WM_L/RBUTTON` → `BTCockpitMouseDown/Up` (client→bb map, glass press/release/right- + latch contract) → `PadRIO::SetScreenButton` (`#ifdef BT_GLASS`; dim/no-op in pod builds). Lamp + brightness = `BTLampBrightnessOf` (shared inline in l4vb16.h) over `PadRIO::GetLampState`. +- **Env / precedence** (resolved once in btl4main → `gBTGaugeCockpit`): `BT_GLASS_PANELS=1` + (Cyd's per-display windows) stands cockpit down > explicit `BT_COCKPIT=1` > `BT_DEV_GAUGES_WINDOW` + (separate window) / `BT_DEV_GAUGES_DOCK` (legacy inset) opt-out > **cockpit default**. + `BT_COCKPIT=0` forces the dock-bottom strip. `-res W H` = the WORLD VIEW size (canvas clamped to + the work area). Green tint tunable via `BT_COCKPIT_TINT=RRGGBB` (default `0x27E8`). Labels are a + lazy GDI-baked MANAGED atlas (survives device reset). Renders in ALL builds; only the PadRIO + click/lamp seam is BT_GLASS-gated. Full detail: `docs/GAUGE_COMPOSITE.md`. ## MP gauge-window FREEZE = dangling bindings + permanent SEH disable (Gitea #12, 2026-07-19) [T2 log-convicted] The live-MP "dev-gauges window froze entirely mid-session" incident (issue #12) is NOT a diff --git a/docs/GAUGE_COMPOSITE.md b/docs/GAUGE_COMPOSITE.md index 4c6e595..8cf8c4f 100644 --- a/docs/GAUGE_COMPOSITE.md +++ b/docs/GAUGE_COMPOSITE.md @@ -807,3 +807,52 @@ screenshots silently omitted the gauge panel (audit runs 1-2 captured cockpit-on Moved the capture after the inset (L4VIDEO.cpp) — dock shots again show what the screen shows. Also noted: BT_DEV_GAUGES_DOCK alone does NOT enable the composite — DevGaugeComposite() gates on BT_DEV_GAUGES (L4VB16.cpp:105); set BOTH. + +--- + +## Cockpit surround — the default desktop layout (2026-07-20) + +Replaces dock-bottom as the DEFAULT under `BT_DEV_GAUGES`: the 3D world CENTERED with the six +gauge surfaces composited AROUND it in the single main window at ½ native scale, plus clickable +RIO button lamps. Iterated as a visual reference with the user (scratchpad/cockpit_pod.py) then +landed in the engine. + +**Files.** +- `engine/MUNGA_L4/l4vb16.h` — `BTCockpitLayout`/`BTCockpitBtn` structs, the compute/draw/mouse + declarations, and `BTLampBrightnessOf` (shared inline lamp decode, dedup of L4GLASSWIN's). +- `engine/MUNGA_L4/L4VB16.cpp` — `gBTGaugeCockpit`/`gBTCockpitCanvasW/H`; `BTCockpitCanvasFor` + + `BTCockpitComputeLayout` (layout single source of truth); `BTDrawCockpitPanels` (band fill → + button lamp quads → 9-entry surface loop with green tint / palette sec + Eng-sibling slot map → + GDI-atlas flight labels; wrapped in a `D3DSBT_ALL` state block = the floating-rocks isolation); + `BTCockpitMouseDown/Up` (client→bb hit-test + glass press/release/right-latch contract); + `BTApplyWorldViewport` + `DevGaugeDocked` cockpit branches. Hooked at the top of + `BTDrawGaugeInset` (before BT_SHOT, so shots include it). +- `game/btl4main.cpp` — glass preset (cockpit default, panels opt-in), mode resolution + + work-area-clamped window sizing (`-res` = the view size), WndProc `WM_L/RBUTTON` routing. +- `engine/MUNGA_L4/L4VIDEO.cpp` — `BTWorldAspectOf` cockpit branch (view rect on-screen aspect). +- `engine/MUNGA_L4/L4VIDRND.cpp` — `CameraShipHUDRenderable::Render` made viewport-relative + (camera-seat banner + ranking window land over the view, not clipped into the surround bands). + +**Layout math.** `SCALE=0.5; MFD 320×240, radar 240×320; OVL=44 (corner overlap into the view); +LAMP=16 (visible lamp edge, user asked for larger than glass's 10); REDCELL=64 (hidden hit depth); +RAILW=26; sideBand=276, topBand=212, bottomBand=336; canvas=(viewW+552, viewH+548).` Corner MFDs +overlap the view corner by OVL both axes; Mfd2 drops 25% of its height into the top; sec flush +below (view bottom clean). Buttons: the full rect is the click target, the surface draws over it so +only the LAMP edge shows (PaintGlass painter trick). Address banks = L4GLASSWIN +BuildMfd/BuildRadar/BuildFlight. + +**Gating precedence** (resolved once → `gBTGaugeCockpit`): `BTGlassPanelsActive` (BT_GLASS_PANELS) +> `BT_COCKPIT=1` > `BT_DEV_GAUGES_WINDOW` / `BT_DEV_GAUGES_DOCK` > **cockpit default**. `BT_COCKPIT=0` += dock-bottom. `DevGaugeDocked()` includes cockpit so the separate window stands down. Green tint +`BT_COCKPIT_TINT=RRGGBB` (default 0x27E8 ≈ rgb(33,255,66)); radar stays palette amber. + +**Pod-build seam.** Panels + dim lamps render in ALL builds; only `PadRIO::SetScreenButton/ +GetLampState` are `#ifdef BT_GLASS` (via `CkEmit`/`CkLampState`). Pod build: cockpit draws, clicks +no-op. Labels: lazy GDI DIB → MANAGED A8R8G8B8 texture cache (survives device reset). + +**Verified (2026-07-20, awaiting human playtest):** both build/ (BT_GLASS off) and build-glass/ +compile (0 error C). Glass BT_SHOT matches the mockup (green MFDs, amber radar, red/yellow/blue +lamps, labeled flight blocks, reticle centered on target). 500-click PostMessage storm during a +live mission SURVIVED (no AV — the Gitea #18 Receiver-gap fix holds); 20 hits logged with correct +addresses/coords. BT_COCKPIT=0 dock-bottom pixel-unregressed. BT_GLASS_PANELS=1 stands cockpit +down (Cyd's windows up). Pod build renders the panels. diff --git a/engine/MUNGA_L4/L4VB16.cpp b/engine/MUNGA_L4/L4VB16.cpp index 603cd7c..7fe1282 100644 --- a/engine/MUNGA_L4/L4VB16.cpp +++ b/engine/MUNGA_L4/L4VB16.cpp @@ -8,6 +8,7 @@ #ifdef BT_GLASS #include "l4glasswin.h" // BTGlassPanelsActive(): the per-display windows own the surfaces +#include "l4padrio.h" // PadRIO::GetLampState / SetScreenButton (cockpit lamps + clicks) #endif // BTResolveGaugeRenderer()/BTResolveMainWindow() (l4vb16.h) are DEFINED in // game/btl4main.cpp off the real app/window pointers -- NOT here: an engine TU @@ -139,12 +140,25 @@ int BTGaugeStripHeightFor(int width) // Called by the main renderer right after BeginScene; no-op unless dock-bottom. void BTApplyWorldViewport(LPDIRECT3DDEVICE9 device) { - if (!gBTGaugeDockBottom || device == NULL) return; + if (device == NULL) return; + if (!gBTGaugeDockBottom && !gBTGaugeCockpit) return; IDirect3DSurface9 *rt = NULL; if (FAILED(device->GetRenderTarget(0, &rt)) || rt == NULL) return; D3DSURFACE_DESC d; rt->GetDesc(&d); rt->Release(); + if (gBTGaugeCockpit) + { + // COCKPIT: the world renders in the CENTERED view rect; the surround + // bands + panels draw on top afterwards (BTDrawCockpitPanels). The + // reticle/HUD dpl2d pass maps through this viewport for free. + BTCockpitLayout L; + BTCockpitComputeLayout((int)d.Width, (int)d.Height, &L); + if (L.viewW <= 0 || L.viewH <= 0) return; + D3DVIEWPORT9 vp = { (DWORD)L.viewX, (DWORD)L.viewY, (DWORD)L.viewW, (DWORD)L.viewH, 0.0f, 1.0f }; + device->SetViewport(&vp); + return; + } DWORD stripH = (DWORD)BTGaugeStripHeightFor((int)d.Width); if (stripH == 0 || stripH >= d.Height) return; D3DVIEWPORT9 vp = { 0, 0, d.Width, d.Height - stripH, 0.0f, 1.0f }; @@ -158,7 +172,7 @@ static bool DevGaugeDocked() { static int v = -1; if (v < 0) v = (getenv("BT_DEV_GAUGES_DOCK") != NULL) ? 1 : 0; - return v != 0 || gBTGaugeDockBottom != 0; + return v != 0 || gBTGaugeDockBottom != 0 || gBTGaugeCockpit != 0; } // GLASS per-display windows (L4GLASSWIN) own the instrument surfaces in their OWN @@ -173,6 +187,147 @@ static bool GlassPanelsOwnSurfaces() #endif } +//===========================================================================// +// COCKPIT SURROUND (BT_COCKPIT) -- layout single source of truth. +//===========================================================================// +int gBTGaugeCockpit = 0; // mode select (resolved once by btl4main) +int gBTCockpitCanvasW = 0; // backbuffer canvas dims (set by btl4main; the +int gBTCockpitCanvasH = 0; // aspect calc needs them after a client resize) + +// Layout constants (backbuffer px). MFD = 640x480 native x 0.5; radar = 480x640 +// portrait x 0.5. The lamp EDGE (kCkLamp) protrudes past the surface; the FULL +// button rect reaches kCkRedCell INTO the display (hit-only, covered by the +// surface). User asked for larger/more-clickable buttons than the glass windows' +// 10px edge -> kCkLamp 16. +enum { + kCkMFDW = 320, kCkMFDH = 240, + kCkRADW = 240, kCkRADH = 320, + kCkOVL = 44, // (int)(0.14f * 320): corner overlap into the view + kCkLamp = 16, // visible protruding lamp edge + kCkRedCell = 64, // glass RedCellH(128) * 0.5: hidden hit depth into the MFD + kCkRailW = 26, // radar side-rail width + kCkGap = 2, + kCkSideBand = kCkMFDW - kCkOVL, // 276 + kCkTopBand = (kCkMFDH - kCkOVL) + kCkLamp, // 212 + kCkBotBand = kCkRADH + kCkLamp // 336 (radar flush below view; lamps protrude) +}; + +// Canvas = view region + the surround bands. +void BTCockpitCanvasFor(int viewW, int viewH, int *canvasW, int *canvasH) +{ + if (canvasW) *canvasW = viewW + 2 * kCkSideBand; + if (canvasH) *canvasH = viewH + kCkTopBand + kCkBotBand; +} + +// The five MFD banks: {cockpit surface slot, high address}. Top row = bankHi-i +// (descending), bottom row = bankHi-4-i (L4GLASSWIN BuildMfd order + the +// BTGlassPanels_Create address assignment). +static const struct { int slot, bankHi; } kCkMfdBank[5] = +{ + { 0, 0x2F }, // Heat (UL) + { 1, 0x27 }, // Mfd2 (UC) + { 5, 0x37 }, // Comm (UR) + { 2, 0x0F }, // Mfd1 (LL) + { 3, 0x07 }, // Mfd3 (LR) +}; + +static void CkPushBtn(BTCockpitLayout *L, int addr, int x, int y, int w, int h, + int colorClass, int inert, const char *label) +{ + if (L->buttonCount >= (int)(sizeof(L->buttons) / sizeof(L->buttons[0]))) return; + BTCockpitBtn &b = L->buttons[L->buttonCount++]; + b.address = addr; b.x = x; b.y = y; b.w = w; b.h = h; + b.colorClass = colorClass; b.inert = inert; b.label = label; +} + +void BTCockpitComputeLayout(int canvasW, int canvasH, BTCockpitLayout *out) +{ + BTCockpitLayout *L = out; + memset(L, 0, sizeof(*L)); + L->canvasW = canvasW; + L->canvasH = canvasH; + int viewW = canvasW - 2 * kCkSideBand; + int viewH = canvasH - kCkTopBand - kCkBotBand; + if (viewW < 64) viewW = 64; + if (viewH < 64) viewH = 64; + int vx = kCkSideBand, vy = kCkTopBand; + L->viewX = vx; L->viewY = vy; L->viewW = viewW; L->viewH = viewH; + + // Surface dest rects (slots: 0 Heat, 1 Mfd2, 2 Mfd1, 3 Mfd3, 4 sec, 5 Comm). + // Corner MFDs overlap the view corner by kCkOVL both axes; Mfd2 drops 25% of + // its height into the top; sec is flush below (view bottom edge clean). + int cx = vx + viewW / 2; + // Heat TL + L->surfX[0] = vx - kCkMFDW + kCkOVL; L->surfY[0] = vy - kCkMFDH + kCkOVL; + L->surfW[0] = kCkMFDW; L->surfH[0] = kCkMFDH; + // Mfd2 top-center (25% into view) + L->surfX[1] = cx - kCkMFDW / 2; L->surfY[1] = vy - (int)(0.75f * kCkMFDH); + L->surfW[1] = kCkMFDW; L->surfH[1] = kCkMFDH; + // Mfd1 BL + L->surfX[2] = vx - kCkMFDW + kCkOVL; L->surfY[2] = vy + viewH - kCkOVL; + L->surfW[2] = kCkMFDW; L->surfH[2] = kCkMFDH; + // Mfd3 BR + L->surfX[3] = vx + viewW - kCkOVL; L->surfY[3] = vy + viewH - kCkOVL; + L->surfW[3] = kCkMFDW; L->surfH[3] = kCkMFDH; + // sec center, flush below + L->surfX[4] = cx - kCkRADW / 2; L->surfY[4] = vy + viewH; + L->surfW[4] = kCkRADW; L->surfH[4] = kCkRADH; + // Comm TR + L->surfX[5] = vx + viewW - kCkOVL; L->surfY[5] = vy - kCkMFDH + kCkOVL; + L->surfW[5] = kCkMFDW; L->surfH[5] = kCkMFDH; + + // --- MFD red buttons: 4 top (bankHi-i) + 4 bottom (bankHi-4-i) --- + int slotW = kCkMFDW / 4; // 80 + for (int m = 0; m < 5; m++) + { + int s = kCkMfdBank[m].slot, hi = kCkMfdBank[m].bankHi; + int sx = L->surfX[s], sy = L->surfY[s]; + for (int i = 0; i < 4; i++) + { + int bx = sx + i * slotW; + CkPushBtn(L, hi - i, bx, sy - kCkLamp, slotW - kCkGap, kCkRedCell + kCkLamp, 0, 0, 0); + CkPushBtn(L, hi - 4 - i, bx, sy + kCkMFDH - kCkRedCell, slotW - kCkGap, kCkRedCell + kCkLamp, 0, 0, 0); + } + } + + // --- radar yellow rails (6/side) + 4 bottom cells --- + { + int rx = L->surfX[4], ry = L->surfY[4]; + int slotH = kCkRADH / 6; + for (int i = 0; i < 6; i++) + { + int y = ry + i * slotH; + CkPushBtn(L, 0x10 + i, rx - kCkLamp, y, kCkLamp + kCkRailW, slotH - kCkGap, 1, 0, 0); + CkPushBtn(L, 0x18 + i, rx + kCkRADW - kCkRailW, y, kCkRailW + kCkLamp, slotH - kCkGap, 1, 0, 0); + } + static const int bottomAddr[4] = { 0x16, 0x17, 0x1F, 0x1E }; + int botLeft = rx + kCkRailW, botSpan = kCkRADW - 2 * kCkRailW, botW = botSpan / 4; + for (int i = 0; i < 4; i++) + CkPushBtn(L, bottomAddr[i], botLeft + i * botW, ry + kCkRADH - 14, botW - kCkGap, 14 + kCkLamp, 1, 0, 0); + } + + // --- flight blue blocks: THROTTLE/AUX (0x38-0x3F) under Mfd1, JOYSTICK + // (0x40-0x47) under Mfd3 -- two 4x2 grids, fully visible + labeled --- + { + static const char *thrLabel[8] = { 0, 0, 0, 0, 0, "Panic", 0, "Throttle" }; // 0x38..0x3F + static const int thrInert[8] = { 1, 1, 1, 1, 1, 0, 1, 0 }; + static const char *joyLabel[8] = { "Main", "Hat Bk", "Hat Up", "Hat R", + "Hat L", "Pinky", "Middle", "Upper" }; // 0x40..0x47 + const int cw = 68, ch = 30, g = 4; + int blockW = 4 * cw + 3 * g; // 284 + int thrX = L->surfX[2], thrY = L->surfY[2] + kCkMFDH + 6; + int joyX = L->surfX[3] + kCkMFDW - blockW, joyY = L->surfY[3] + kCkMFDH + 6; + for (int i = 0; i < 8; i++) + { + int c = i % 4, r = i / 4; + CkPushBtn(L, 0x38 + i, thrX + c * (cw + g), thrY + r * (ch + g), cw, ch, 2, + thrInert[i], thrLabel[i] ? thrLabel[i] : ""); + CkPushBtn(L, 0x40 + i, joyX + c * (cw + g), joyY + r * (ch + g), cw, ch, 2, + 0, joyLabel[i]); + } + } +} + //===========================================================================// // DEV-COMPOSITE (option B) -- draw the woken gauge renderer's SECONDARY/radar // surface as an inset in the MAIN window. The gauge widgets have already @@ -495,6 +650,358 @@ void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw, } } +//===========================================================================// +// COCKPIT SURROUND -- draw the surround bands + gauge panels + button lamps over +// the CENTERED world view. Hooked into BTDrawGaugeInset (before BT_SHOT), so it +// draws into the main backbuffer and screenshots include it. Mirrors +// BTDrawGaugeSurfaces' device-state isolation (the "floating cavern rocks" trap). +//===========================================================================// + +// Shared click state (also written by BTCockpitMouseDown/Up below). +static int gCkPressed = -1; +static unsigned char gCkLatched[128] = { 0 }; + +// The mono MFD phosphor tint (R5G6B5); default green, env BT_COCKPIT_TINT=RRGGBB. +static int CkTint() +{ + static int t = -1; + if (t < 0) + { + const char *e = getenv("BT_COCKPIT_TINT"); + if (e != NULL && strlen(e) >= 6) + { + unsigned int rgb = (unsigned int)strtoul(e, NULL, 16); + int r = (rgb >> 16) & 0xFF, g = (rgb >> 8) & 0xFF, b = rgb & 0xFF; + t = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + } + else + t = 0x27E8; // ~rgb(33,255,66) phosphor green + } + return t; +} + +// Glass-only RIO seam: lamp state feeds the draw; a click emits a button. No-op +// (dim / ignored) in a pod build, where PadRIO isn't compiled. +static int CkLampState(int addr) +{ +#ifdef BT_GLASS + return PadRIO::GetLampState(addr); +#else + (void)addr; return 0; +#endif +} + +// Colored-quad (untextured DIFFUSE) draw state -- set once, then CkFill per rect. +static void CkColorState(LPDIRECT3DDEVICE9 dev) +{ + dev->SetTexture(0, NULL); + dev->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE); + dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE); + dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE); + dev->SetRenderState(D3DRS_ZENABLE, FALSE); + dev->SetRenderState(D3DRS_LIGHTING, FALSE); + dev->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); +} + +static void CkFill(LPDIRECT3DDEVICE9 dev, int x, int y, int w, int h, D3DCOLOR c) +{ + if (w <= 0 || h <= 0) return; + struct V { float x, y, z, rhw; D3DCOLOR c; }; + float fx = (float)x, fy = (float)y, fw = (float)w, fh = (float)h; + V q[4] = { + { fx, fy, 0.0f, 1.0f, c }, + { fx + fw, fy, 0.0f, 1.0f, c }, + { fx + fw, fy + fh, 0.0f, 1.0f, c }, + { fx, fy + fh, 0.0f, 1.0f, c }, + }; + dev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, q, sizeof(V)); +} + +// Lamp fill + border for a button, from the glass PaintGlass color ramps. +static void CkLampColors(int colorClass, int shade, int inert, D3DCOLOR *fill, D3DCOLOR *border) +{ + if (colorClass == 1) // yellow (radar) + { + *border = D3DCOLOR_XRGB(245, 210, 60); + *fill = (shade == 3) ? D3DCOLOR_XRGB(245, 210, 60) + : (shade != 0) ? D3DCOLOR_XRGB(140, 118, 38) + : D3DCOLOR_XRGB(70, 60, 24); + } + else if (colorClass == 2) // blue (flight) + { + if (inert) + { + *border = D3DCOLOR_XRGB(90, 100, 122); + *fill = D3DCOLOR_XRGB(44, 48, 58); + return; + } + *border = D3DCOLOR_XRGB(205, 228, 255); + *fill = (shade == 3) ? D3DCOLOR_XRGB(205, 228, 255) + : (shade != 0) ? D3DCOLOR_XRGB(110, 135, 180) + : D3DCOLOR_XRGB(70, 86, 120); + } + else // red (MFD) + { + *border = D3DCOLOR_XRGB(230, 70, 70); + *fill = (shade == 3) ? D3DCOLOR_XRGB(230, 70, 70) + : (shade != 0) ? D3DCOLOR_XRGB(120, 50, 50) + : D3DCOLOR_XRGB(64, 40, 40); + } +} + +// Lazy GDI-baked label textures (MANAGED A8R8G8B8, survive device reset) for the +// blue flight blocks -- keyed by string. ~16 short strings. +struct CkLabelTex { char str[16]; LPDIRECT3DTEXTURE9 tex; int w, h; }; +static CkLabelTex gCkLabels[32]; +static int gCkLabelCount = 0; +static HFONT gCkFont = NULL; + +static LPDIRECT3DTEXTURE9 CkGetLabel(LPDIRECT3DDEVICE9 dev, const char *str, int *ow, int *oh) +{ + for (int i = 0; i < gCkLabelCount; i++) + if (strcmp(gCkLabels[i].str, str) == 0) + { if (ow) *ow = gCkLabels[i].w; if (oh) *oh = gCkLabels[i].h; return gCkLabels[i].tex; } + if (gCkLabelCount >= 32) return NULL; + + if (gCkFont == NULL) + gCkFont = CreateFontA(-11, 0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET, + 0, 0, ANTIALIASED_QUALITY, 0, "Segoe UI"); + HDC dc = CreateCompatibleDC(NULL); + HFONT oldFont = (HFONT)SelectObject(dc, gCkFont); + SIZE sz; GetTextExtentPoint32A(dc, str, (int)strlen(str), &sz); + int tw = sz.cx + 2, th = sz.cy + 2; + if (tw < 1) tw = 1; + if (th < 1) th = 1; + + BITMAPINFO bmi; memset(&bmi, 0, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = tw; bmi.bmiHeader.biHeight = -th; // top-down + bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; + void *bits = NULL; + HBITMAP dib = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); + HBITMAP oldBm = (HBITMAP)SelectObject(dc, dib); + RECT r = { 0, 0, tw, th }; + FillRect(dc, &r, (HBRUSH)GetStockObject(BLACK_BRUSH)); + SetBkMode(dc, TRANSPARENT); + SetTextColor(dc, RGB(255, 255, 255)); + TextOutA(dc, 1, 1, str, (int)strlen(str)); + GdiFlush(); + + LPDIRECT3DTEXTURE9 tex = NULL; + dev->CreateTexture(tw, th, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &tex, NULL); + if (tex != NULL && bits != NULL) + { + D3DLOCKED_RECT lr; + if (SUCCEEDED(tex->LockRect(0, &lr, NULL, 0))) + { + for (int y = 0; y < th; y++) + { + unsigned long *s = (unsigned long*)bits + y * tw; + unsigned long *d = (unsigned long*)((char*)lr.pBits + y * lr.Pitch); + for (int x = 0; x < tw; x++) + d[x] = ((s[x] & 0xFF) << 24) | 0x00FFFFFF; // A = text luminance, RGB white + } + tex->UnlockRect(0); + } + } + SelectObject(dc, oldBm); + DeleteObject(dib); + SelectObject(dc, oldFont); + DeleteDC(dc); + + CkLabelTex &e = gCkLabels[gCkLabelCount++]; + strncpy(e.str, str, 15); e.str[15] = 0; e.tex = tex; e.w = tw; e.h = th; + if (ow) *ow = tw; if (oh) *oh = th; + return tex; +} + +static void CkDrawLabel(LPDIRECT3DDEVICE9 dev, int cx, int cy, const char *str, D3DCOLOR color) +{ + if (str == NULL || str[0] == 0) return; + int tw = 0, th = 0; + LPDIRECT3DTEXTURE9 tex = CkGetLabel(dev, str, &tw, &th); + if (tex == NULL) return; + float x = (float)(cx - tw / 2), y = (float)(cy - th / 2); + struct V { float x, y, z, rhw; D3DCOLOR c; float u, v; }; + V q[4] = { + { x, y, 0.0f, 1.0f, color, 0.0f, 0.0f }, + { x + (float)tw, y, 0.0f, 1.0f, color, 1.0f, 0.0f }, + { x + (float)tw, y + (float)th, 0.0f, 1.0f, color, 1.0f, 1.0f }, + { x, y + (float)th, 0.0f, 1.0f, color, 0.0f, 1.0f }, + }; + dev->SetTexture(0, tex); + dev->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); + dev->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + dev->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE); // label color + dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); // text mask + dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + dev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + dev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + dev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, q, sizeof(V)); +} + +void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device) +{ + if (device == NULL) return; + IDirect3DSurface9 *rt = NULL; + if (FAILED(device->GetRenderTarget(0, &rt)) || rt == NULL) return; + D3DSURFACE_DESC dsc; rt->GetDesc(&dsc); rt->Release(); + BTCockpitLayout L; + BTCockpitComputeLayout((int)dsc.Width, (int)dsc.Height, &L); + + // DEVICE-STATE ISOLATION (floating cavern rocks): snapshot + restore. + IDirect3DStateBlock9 *sb = NULL; + device->CreateStateBlock(D3DSBT_ALL, &sb); + + // Full-target viewport (the world viewport was centered; our quads span the canvas). + D3DVIEWPORT9 vp = { 0, 0, dsc.Width, dsc.Height, 0.0f, 1.0f }; + device->SetViewport(&vp); + + // 1) Surround bands (dark) AROUND the view -- the engine Clear never covers + // them, and the gauge renderer may not be up yet, so fill unconditionally. + CkColorState(device); + D3DCOLOR band = D3DCOLOR_XRGB(6, 7, 9); + CkFill(device, 0, 0, L.canvasW, L.viewY, band); // top + CkFill(device, 0, L.viewY + L.viewH, L.canvasW, L.canvasH - (L.viewY + L.viewH), band); // bottom + CkFill(device, 0, L.viewY, L.viewX, L.viewH, band); // left + CkFill(device, L.viewX + L.viewW, L.viewY, L.canvasW - (L.viewX + L.viewW), L.viewH, band); // right + + // 2) Button lamp faces (UNDER the surfaces -- only the protruding edge shows). + unsigned long tick = GetTickCount(); + for (int i = 0; i < L.buttonCount; i++) + { + const BTCockpitBtn &b = L.buttons[i]; + int shade = BTLampBrightnessOf(CkLampState(b.address), tick); + if (gCkPressed == b.address || gCkLatched[b.address & 0x7F]) shade = 3; + D3DCOLOR fill, border; + CkLampColors(b.colorClass, shade, b.inert, &fill, &border); + CkFill(device, b.x, b.y, b.w, b.h, border); + CkFill(device, b.x + 1, b.y + 1, b.w - 2, b.h - 2, fill); + } + + // 3) Gauge surfaces on top (green-tinted mono / palette sec). Reuses the + // 9-entry loop shape + BlankColor skip of BTDrawGaugeSurfaces; the Eng1-3 + // planes share their sibling's dest cell (ckSlotOf). + GaugeRenderer *gr = BTResolveGaugeRenderer(); + if (gr != NULL) + { + SVGA16 *svga = NULL; + for (int i = 0; i < 6 && svga == NULL; i++) + { + L4GraphicsPort *p = static_cast(gr->GetGraphicsPort(kBTGaugeSurfaces[i].portName)); + if (p != NULL) svga = (SVGA16*) p->graphicsDisplay; + } + if (svga != NULL) + { + static const int ckSlotOf[9] = { 0, 1, 2, 3, 4, 5, 1, 2, 3 }; + int tint = CkTint(); + for (int i = 0; i < 9; i++) + { + const BTGaugeSurfaceDesc &d = kBTGaugeSurfaces[i]; + L4GraphicsPort *port = static_cast(gr->GetGraphicsPort(d.portName)); + if (port == NULL) continue; + if (d.monoTint >= 0 && port->GetEnableID() == L4GraphicsPort::BlankColor) continue; + int rot = d.rotateCCW; + if (rot != 0) + { + static int s_secRot = -1; + if (s_secRot < 0) { const char *rv = getenv("BT_GAUGE_SEC_ROT"); s_secRot = (rv != NULL && rv[0] == '1') ? 1 : 3; } + rot = s_secRot; + } + int surf = ckSlotOf[i]; + int useTint = (d.monoTint < 0) ? -1 : tint; + svga->DrawDevSurface(device, i, port->GetBitMask(), port->paletteID, useTint, + (float)L.surfX[surf], (float)L.surfY[surf], (float)L.surfW[surf], (float)L.surfH[surf], rot); + } + } + } + + // 4) Flight-block labels (on top; the blue faces aren't covered by surfaces). + for (int i = 0; i < L.buttonCount; i++) + { + const BTCockpitBtn &b = L.buttons[i]; + if (b.label == NULL || b.label[0] == 0) continue; + D3DCOLOR lc = b.inert ? D3DCOLOR_ARGB(255, 120, 128, 145) : D3DCOLOR_ARGB(255, 0, 0, 0); + CkDrawLabel(device, b.x + b.w / 2, b.y + b.h / 2, b.label, lc); + } + + if (sb != NULL) { sb->Apply(); sb->Release(); } +} + +// Glass-only RIO emit: a cockpit click injects a button edge through PadRIO's +// static entry (safe no-op if no PadRIO -- pod build never links it). +static void CkEmit(int addr, int pressed) +{ +#ifdef BT_GLASS + PadRIO::SetScreenButton(addr, pressed); +#else + (void)addr; (void)pressed; +#endif +} + +// Mouse routing (main-window WndProc -> here). Client coords are mapped into the +// fixed backbuffer canvas; hit-test the FULL button rects; mirror the L4GLASSWIN +// GlassWndProc contract (left = press / unlatch-if-latched; right = latch toggle). +// Returns 1 when a button was hit (the caller SetCaptures on a left press). +int BTCockpitMouseDown(int cx, int cy, int clientW, int clientH, int rightButton) +{ + if (!gBTGaugeCockpit) return 0; + int bbW = gBTCockpitCanvasW, bbH = gBTCockpitCanvasH; + if (bbW <= 0 || bbH <= 0 || clientW <= 0 || clientH <= 0) return 0; + int bx = cx * bbW / clientW; + int by = cy * bbH / clientH; + + BTCockpitLayout L; + BTCockpitComputeLayout(bbW, bbH, &L); + int a = -1; + for (int i = 0; i < L.buttonCount; i++) + { + const BTCockpitBtn &b = L.buttons[i]; + if (bx >= b.x && bx < b.x + b.w && by >= b.y && by < b.y + b.h) { a = b.address; break; } + } + if (a < 0) return 0; + + // Crash forensics (always-on, flushed BEFORE dispatch): a click that kills the + // process leaves its address as the last log line (the Gitea #18 pattern). + DEBUG_STREAM << "[cockpit] CLICK addr=0x" << std::hex << a << std::dec + << " at(" << bx << "," << by << ")" + << (rightButton ? " latch-toggle" + : (gCkLatched[a & 0x7F] ? " unlatch" : " press")) + << "\n" << std::flush; + + if (rightButton) + { + gCkLatched[a & 0x7F] = gCkLatched[a & 0x7F] ? 0 : 1; + CkEmit(a, gCkLatched[a & 0x7F]); + return 1; + } + if (gCkLatched[a & 0x7F]) + { + gCkLatched[a & 0x7F] = 0; + CkEmit(a, 0); + return 0; // released -- no capture needed + } + gCkPressed = a; + CkEmit(a, 1); + return 1; // pressed -- caller SetCaptures for release-outside +} + +void BTCockpitMouseUp(void) +{ + if (gCkPressed >= 0) + { + CkEmit(gCkPressed, 0); + gCkPressed = -1; + } +} + // // Free entry point the MAIN renderer calls (L4VIDEO DPLRenderer::ExecuteImplementation, // just before EndScene). No-op unless BT_DEV_GAUGES is set. Docks the 6-surface panel @@ -505,6 +1012,13 @@ void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device) { if (!DevGaugeComposite() || !DevGaugeDocked() || device == NULL) return; if (GlassPanelsOwnSurfaces()) return; // the per-display glass windows draw the surfaces + if (gBTGaugeCockpit) + { + // COCKPIT SURROUND: draw the surround bands + panels + button lamps over + // the centered world view (captured before BT_SHOT, so shots include it). + BTDrawCockpitPanels(device); + return; + } if (gBTGaugeDockBottom) { // DOCK-BOTTOM: the strip owns the bottom band of the target (the world diff --git a/engine/MUNGA_L4/L4VB16.h b/engine/MUNGA_L4/L4VB16.h index d8670ce..229ed0e 100644 --- a/engine/MUNGA_L4/L4VB16.h +++ b/engine/MUNGA_L4/L4VB16.h @@ -17,6 +17,66 @@ class GaugeRenderer; GaugeRenderer *BTResolveGaugeRenderer(); void *BTResolveMainWindow(); // HWND (kept void* -- no windows.h dependency) +//######################################################################## +//#################### COCKPIT SURROUND (BT_COCKPIT) ##################### +//######################################################################## +// +// The pod-faithful single-window layout: the 3D world view CENTERED, with the +// six live gauge surfaces composited AROUND it at 1/2 native scale (the pod +// monitors physically clip the eyeport, so the corner MFDs / top-center / etc. +// overlap the view edges) and clickable RIO button lamps. DEFAULT under +// BT_DEV_GAUGES (BT_COCKPIT=0 falls back to the dock-bottom strip). +// +// The layout is the SINGLE SOURCE OF TRUTH -- computed from the backbuffer +// (canvas) size and consumed by the window sizing, the world viewport, the +// panel/button draw, the aspect calc, and the mouse hit-test. See L4VB16.cpp. +// +struct BTCockpitBtn +{ + int address; // RIO buttonGroup address (0x00-0x47) + int x, y, w, h; // backbuffer-space rect (full hit target; only the + // protruding lamp edge shows after the surface draws over) + int colorClass; // 0 = red (MFD), 1 = yellow (radar), 2 = blue (flight) + int inert; // 1 = no authored .CTL mapping -> drawn greyed + const char *label; // non-NULL (blue flight block) -> face is drawn + labeled +}; + +struct BTCockpitLayout +{ + int canvasW, canvasH; + int viewX, viewY, viewW, viewH; // centered world view + int surfX[6], surfY[6], surfW[6], surfH[6]; // Heat,Mfd2,Mfd1,Mfd3,sec,Comm dest rects + BTCockpitBtn buttons[80]; // 5*8 red + 16 yellow + 16 blue = 72 + int buttonCount; +}; + +extern int gBTGaugeCockpit; // mode select; set once by btl4main +extern int gBTCockpitCanvasW, gBTCockpitCanvasH; // backbuffer canvas dims (for the aspect calc) + +void BTCockpitCanvasFor(int viewW, int viewH, int *canvasW, int *canvasH); +void BTCockpitComputeLayout(int canvasW, int canvasH, BTCockpitLayout *out); +void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device); +int BTCockpitMouseDown(int cx, int cy, int clientW, int clientH, int rightButton); // 1 = consumed +void BTCockpitMouseUp(void); + +// Lamp brightness decode (RIOBase::LampState) -- shared by the cockpit draw and +// the glass per-display windows. level = max of the two 2-bit fields; the low 2 +// bits pick a flash half-period (500/250/125 ms). Returns 0..3. +inline int BTLampBrightnessOf(int state, unsigned long tick) +{ + int level1 = (state >> 2) & 0x3; + int level2 = (state >> 4) & 0x3; + int level = (level1 > level2) ? level1 : level2; + if (level == 0) return 0; + int flash = state & 0x3; + if (flash != 0) + { + unsigned long half = (flash == 1) ? 500 : (flash == 2) ? 250 : 125; + if ((tick / half) & 1) return 0; + } + return level; +} + //######################################################################## //######################### Video16BitBuffered ########################### //######################################################################## diff --git a/engine/MUNGA_L4/L4VIDEO.cpp b/engine/MUNGA_L4/L4VIDEO.cpp index 1ff5150..d7e77e9 100644 --- a/engine/MUNGA_L4/L4VIDEO.cpp +++ b/engine/MUNGA_L4/L4VIDEO.cpp @@ -6,6 +6,7 @@ #include "..\munga\eyecandy.h" #include "l4vidrnd.h" #include "l4video.h" +#include "l4vb16.h" // BTCockpitLayout / BTCockpitComputeLayout (cockpit aspect) #include "..\munga\matrix.h" #include "..\munga\mover.h" #include "..\munga\jmover.h" @@ -9384,6 +9385,22 @@ static float BTWorldAspectOf(int client_w, int client_h) { extern int gBTGaugeDockBottom; extern int BTGaugeStripHeightFor(int width); + // COCKPIT: the world occupies the CENTERED view sub-rect of the fixed-size + // backbuffer (canvas), which D3D stretches into the client area. The view's + // ON-SCREEN aspect = (viewW/canvasW * client_w) / (viewH/canvasH * client_h). + extern int gBTGaugeCockpit; + extern int gBTCockpitCanvasW, gBTCockpitCanvasH; + if (gBTGaugeCockpit && gBTCockpitCanvasW > 0 && gBTCockpitCanvasH > 0) + { + BTCockpitLayout L; + BTCockpitComputeLayout(gBTCockpitCanvasW, gBTCockpitCanvasH, &L); + if (L.viewW > 0 && L.viewH > 0) + { + float w = (float)L.viewW * (float)client_w / (float)gBTCockpitCanvasW; + float h = (float)L.viewH * (float)client_h / (float)gBTCockpitCanvasH; + if (h > 0.0f) return w / h; + } + } float world_h = (float)client_h; if (gBTGaugeDockBottom) { diff --git a/engine/MUNGA_L4/L4VIDRND.cpp b/engine/MUNGA_L4/L4VIDRND.cpp index e7c8f57..047c3f2 100644 --- a/engine/MUNGA_L4/L4VIDRND.cpp +++ b/engine/MUNGA_L4/L4VIDRND.cpp @@ -3102,16 +3102,26 @@ void CameraShipHUDRenderable::Render(int pass, const D3DXMATRIX *viewTransform) device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + // COCKPIT/DOCK: the world renders in a SUB-RECT viewport, not the whole + // backbuffer -- place the camera-seat HUD relative to the CURRENT viewport + // (== full backbuffer in normal mode) so it lands over the world view, not in + // the surround bands (where pretransformed verts would be viewport-clipped). + D3DVIEWPORT9 cvp; + device->GetViewport(&cvp); + const float refX = (float)cvp.X, refY = (float)cvp.Y; + const float refW = (float)cvp.Width, refH = (float)cvp.Height; + // - // The followed player's callsign banner (bottom center). Execute already - // converts the 1-based goalPlayerIndex to the 0-based texture slot. + // The followed player's callsign banner (bottom center of the view). Execute + // already converts the 1-based goalPlayerIndex to the 0-based texture slot. + // (Drawn per-frame in viewport space -- the ctor mVB was backbuffer-fixed.) // if (oldFollowedPlayerIndex >= 0 && oldFollowedPlayerIndex < MAX_PLAYER_NAMES) { - device->SetStreamSource(0, mVB, 0, sizeof(L4VERTEX_2D_TEX)); - device->SetTexture(0, - myRenderer->GetNameTexture(oldFollowedPlayerIndex)); - device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + float nameW = (refW * 0.32f) / 2.0f; + float nameH = refH * 0.10f; + CameraHUDDrawQuad(device, myRenderer->GetNameTexture(oldFollowedPlayerIndex), + refX + refW / 2.0f - nameW, refY + refH - nameH * 2.0f, nameW * 2.0f, nameH); } // @@ -3135,14 +3145,14 @@ void CameraShipHUDRenderable::Render(int pass, const D3DXMATRIX *viewTransform) if (displayRankingWindow != NULL && *displayRankingWindow && playerRank != NULL && playerCount > 0) { - const float width = myRenderer->GetWidth(); - const float height = myRenderer->GetHeight(); + const float width = refW; + const float height = refH; const float nameW = width * 0.20f; const float ordW = width * 0.10f; const float rowH = nameW * (32.0f / 128.0f); // bitmap aspect const float pad = width * 0.01f; - const float x0 = width * 0.55f; - const float y0 = height * 0.16f; + const float x0 = refX + width * 0.55f; + const float y0 = refY + height * 0.16f; for (int bmp = 0; bmp < playerCount && bmp < MAX_PLAYER_NAMES; ++bmp) { diff --git a/game/btl4main.cpp b/game/btl4main.cpp index 935a4a5..c6fe8d6 100644 --- a/game/btl4main.cpp +++ b/game/btl4main.cpp @@ -106,6 +106,32 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) // GetAsyncKeyState per frame instead (with a foreground guard), which // is immune to message stealing. return 0; + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + // COCKPIT SURROUND: route clicks on the composited panel buttons to the + // RIO (glass build) via the layout hit-test. No-op in other modes. + { + extern int gBTGaugeCockpit; + extern int BTCockpitMouseDown(int, int, int, int, int); + if (gBTGaugeCockpit) + { + RECT rc; GetClientRect(hWnd, &rc); + int consumed = BTCockpitMouseDown( + (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), + (int)(rc.right - rc.left), (int)(rc.bottom - rc.top), + uMsg == WM_RBUTTONDOWN); + if (consumed && uMsg == WM_LBUTTONDOWN) + SetCapture(hWnd); // a release outside the button still clears it + } + } + return 0; + case WM_LBUTTONUP: + { + extern int gBTGaugeCockpit; + extern void BTCockpitMouseUp(void); + if (gBTGaugeCockpit) { BTCockpitMouseUp(); ReleaseCapture(); } + } + return 0; case WM_SIZE: // Window-resize aspect fix (task #20): rebuild the projection for the // new client aspect so the scene doesn't stretch fat/skinny (the D3D9 @@ -330,19 +356,27 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine { // GLASS profile (dev tooling, step 2e): the desktop cockpit -- // PadRIO input (XInput+keyboard, content\bindings.txt), the - // on-screen clickable button panel, the dev-composited MFD - // surfaces (dock-bottom default; BT_DEV_GAUGES_WINDOW=1 separate - // window / BT_DEV_GAUGES_DOCK=1 overlay inset), and the desktop - // plasma window. Each only when not already set, so a developer - // env overrides the preset. + // dev-composited MFD surfaces, and the desktop plasma window. + // Each only when not already set, so a developer env overrides. if (getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=PAD,KEYBOARD"); if (getenv("BT_DEV_GAUGES") == NULL) putenv("BT_DEV_GAUGES=1"); if (getenv("L4PLASMA") == NULL) putenv("L4PLASMA=SCREEN"); - if (getenv("BT_PAD_PANEL") == NULL) putenv("BT_PAD_PANEL=1"); - // Break the secondary displays into per-display windows (surface + its RIO - // bank around it). Set BT_GLASS_PANELS=0 to fall back to the single - // combined pad panel + docked gauge strip. - if (getenv("BT_GLASS_PANELS") == NULL) putenv("BT_GLASS_PANELS=1"); + + // DEFAULT LAYOUT = the COCKPIT SURROUND (single window: the six gauge + // surfaces + clickable button lamps composited AROUND the centered 3D + // view). Opt out with BT_COCKPIT=0 (dock-bottom strip) or + // BT_GLASS_PANELS=1 (the per-display windows). When cockpit is the + // default the buttons live IN the main window, so no separate pad/panel + // window is created (leave BT_PAD_PANEL / BT_GLASS_PANELS unset). + int cockpitDefault = 1; + if (getenv("BT_COCKPIT") && getenv("BT_COCKPIT")[0] == '0') cockpitDefault = 0; + if (getenv("BT_GLASS_PANELS") && getenv("BT_GLASS_PANELS")[0] != '0') cockpitDefault = 0; + if (getenv("BT_DEV_GAUGES_WINDOW") || getenv("BT_DEV_GAUGES_DOCK")) cockpitDefault = 0; + if (!cockpitDefault) + { + if (getenv("BT_PAD_PANEL") == NULL) putenv("BT_PAD_PANEL=1"); + if (getenv("BT_GLASS_PANELS") == NULL) putenv("BT_GLASS_PANELS=1"); + } } #endif else @@ -562,29 +596,68 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine // restores the old separate MFD window. { extern int gBTGaugeDockBottom; + extern int gBTGaugeCockpit; + extern int gBTCockpitCanvasW, gBTCockpitCanvasH; extern int BTGaugeStripHeightFor(int width); + extern void BTCockpitCanvasFor(int viewW, int viewH, int *cw, int *ch); // BT_GLASS_PANELS breaks the gauges into their own per-display windows, so - // the world window keeps its normal size (no bottom strip). + // the world window keeps its normal size (no cockpit / bottom strip). int glassOwnsGauges = 0; #ifdef BT_GLASS { extern int BTGlassPanelsActive(); glassOwnsGauges = BTGlassPanelsActive(); } #endif - if (!glassOwnsGauges && - getenv("BT_DEV_GAUGES") != 0 && getenv("BT_DEV_GAUGES_WINDOW") == 0) + if (!glassOwnsGauges && getenv("BT_DEV_GAUGES") != 0) { - gBTGaugeDockBottom = 1; - // Readability default (user-reported: the strip at 800 wide is a - // 0.6x downscale, hard to read): a WIDER world region (the - // projection is aspect-correct for any shape) buys strip pixels -- - // 1100x600 world + 1100x400 strip = a 1000-tall window that fits a - // 1080p desktop with the panel at 83% of design size. -res still - // overrides both. - if (strstr(lpCmdLine ? lpCmdLine : "", "-res") == 0) + // Mode resolution (precedence): explicit BT_COCKPIT > separate-window / + // legacy-inset opt-out > COCKPIT SURROUND default under BT_DEV_GAUGES. + int cockpitOn; + const char *ck = getenv("BT_COCKPIT"); + if (ck != NULL) + cockpitOn = (ck[0] != '0'); + else + cockpitOn = (getenv("BT_DEV_GAUGES_WINDOW") == 0 && + getenv("BT_DEV_GAUGES_DOCK") == 0); + + if (cockpitOn) { - winW = 1100; - winH = 600; + gBTGaugeCockpit = 1; + // -res W H = the WORLD VIEW size in cockpit mode; default 900x500. + int viewW = 900, viewH = 500; + if (strstr(lpCmdLine ? lpCmdLine : "", "-res") != 0) { viewW = winW; viewH = winH; } + // Clamp the CANVAS to the work area (minus the window frame): shrink + // viewH (floor 400) then viewW (floor 640) until it fits (1080p tight). + RECT wa = { 0, 0, 1920, 1080 }; + SystemParametersInfo(SPI_GETWORKAREA, 0, &wa, 0); + RECT fr = { 0, 0, 1000, 1000 }; + AdjustWindowRect(&fr, WS_OVERLAPPEDWINDOW, FALSE); + int availW = (wa.right - wa.left) - ((fr.right - fr.left) - 1000); + int availH = (wa.bottom - wa.top) - ((fr.bottom - fr.top) - 1000); + int cw, ch; + BTCockpitCanvasFor(viewW, viewH, &cw, &ch); + while (ch > availH && viewH > 400) { viewH -= 20; BTCockpitCanvasFor(viewW, viewH, &cw, &ch); } + while (cw > availW && viewW > 640) { viewW -= 20; BTCockpitCanvasFor(viewW, viewH, &cw, &ch); } + winW = cw; winH = ch; + gBTCockpitCanvasW = winW; // windowed backbuffer = client canvas + gBTCockpitCanvasH = winH; + std::cout << "[cockpit] view " << viewW << "x" << viewH + << " canvas " << winW << "x" << winH << std::endl << std::flush; + } + else if (getenv("BT_DEV_GAUGES_WINDOW") == 0) + { + gBTGaugeDockBottom = 1; + // Readability default (user-reported: the strip at 800 wide is a + // 0.6x downscale, hard to read): a WIDER world region (the + // projection is aspect-correct for any shape) buys strip pixels -- + // 1100x600 world + 1100x400 strip = a 1000-tall window that fits a + // 1080p desktop with the panel at 83% of design size. -res still + // overrides both. + if (strstr(lpCmdLine ? lpCmdLine : "", "-res") == 0) + { + winW = 1100; + winH = 600; + } + winH += BTGaugeStripHeightFor(winW); } - winH += BTGaugeStripHeightFor(winW); } } RECT wr = { 0, 0, winW, winH };