Front end: post-mission results screen (kills/deaths scoreboard)

Completes the marshal chain: menu -> mission -> timed stop -> results ->
relaunch.  BT had no mission-end score flow; this builds one from the
data the Comm MFD pilotList already tracks.

- btl4console: at the timed stop (game thread, state still live) snapshot
  each roster pilot's kills/deaths via the BTResolveRosterPilot /
  BTPilotKills / BTPilotDeaths bridges; expose BTLocalConsole_ResultCount
  / GetResult.
- btl4fe: BTFrontEnd_ShowResults -- a GDI green-on-black 'MISSION COMPLETE'
  scoreboard (PILOT / KILLS / DEATHS, slot 0 named from the menu), any
  key or ~12s to dismiss; store the last pilot name.
- btl4main: show the scoreboard after a marshaled mission, before the
  relaunch.

Verified: MISSION COMPLETE screen renders with the pilot row and the
snapshotted score (0/0 on a no-combat test run; the data path is the
same one the live Comm MFD reads).

Phase 5 (front end) COMPLETE: menu + egg builder + marshal + timed stop
+ results + single-binary (relaunch) loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-16 22:48:09 -05:00
co-authored by Claude Fable 5
parent 9d660fa50b
commit e74957758b
5 changed files with 161 additions and 3 deletions
+103 -1
View File
@@ -21,7 +21,8 @@
#include "l4app.hpp" // L4Application::SetEggNotationFileName
// Mission length (seconds) chosen on the last LAUNCH; WinMain arms the marshal.
static int gLastMissionSeconds = 300;
static int gLastMissionSeconds = 300;
static char gLastPilotName[24] = "Pilot";
//---------------------------------------------------------------------------//
// Catalogs -- the BattleTech content the console's RPConfig.xml named.
@@ -633,6 +634,7 @@ int BTFrontEnd_Run()
strcpy(mission.weather, kMenuWeather[selWx].key);
mission.lengthSeconds = kMenuLengths[selLen].seconds;
strcpy(mission.pilots[0].name, pilotName);
strncpy(gLastPilotName, pilotName, sizeof(gLastPilotName) - 1);
strcpy(mission.pilots[0].vehicle, kMenuMechs[selMech].key);
strcpy(mission.pilots[0].color, kMenuColors[selColor].key);
@@ -648,3 +650,103 @@ int BTFrontEnd_LastMissionSeconds()
{
return gLastMissionSeconds;
}
//===========================================================================//
// THE RESULTS SCREEN -- shown after a marshaled mission, before the relaunch.
// A GDI green-on-black scoreboard reading the marshal's snapshotted scores
// (kills/deaths per roster slot; slot 0 = the local pilot, named from the
// menu). Dismissed by any key/click or after a timeout.
//===========================================================================//
namespace { HWND gResultsWin = NULL; int gResultsDone = 0; }
extern int BTLocalConsole_ResultCount();
extern int BTLocalConsole_GetResult(int index, int *kills, int *deaths);
namespace {
LRESULT CALLBACK ResultsWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_PAINT: {
PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps);
RECT cl; GetClientRect(hwnd, &cl);
HDC mem = CreateCompatibleDC(hdc);
HBITMAP surf = CreateCompatibleBitmap(hdc, cl.right, cl.bottom);
HBITMAP old = (HBITMAP) SelectObject(mem, surf);
FillRect(mem, &cl, (HBRUSH) GetStockObject(BLACK_BRUSH));
SetBkMode(mem, TRANSPARENT);
int th = cl.bottom / 22; if (th < 16) th = 16; if (th > 28) th = 28;
HFONT big = CreateFontA(-(th*2),0,0,0,FW_BOLD,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,DEFAULT_PITCH|FF_MODERN,"Consolas");
HFONT reg = CreateFontA(-th,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,DEFAULT_PITCH|FF_MODERN,"Consolas");
const COLORREF gB = RGB(64,255,64), gD = RGB(24,140,24);
SelectObject(mem, big); SetTextColor(mem, gB);
RECT t = cl; t.top = cl.bottom/12; t.bottom = t.top + th*3;
DrawTextA(mem, "M I S S I O N C O M P L E T E", -1, &t, DT_CENTER|DT_TOP|DT_SINGLELINE);
SelectObject(mem, reg);
int y = cl.bottom/4; int cxN = cl.right/6, cxK = cl.right/2, cxD = (cl.right*2)/3;
SetTextColor(mem, gB);
RECT h; h.top=y; h.bottom=y+th*2;
h.left=cxN; h.right=cxK; DrawTextA(mem,"PILOT",-1,&h,DT_LEFT|DT_VCENTER|DT_SINGLELINE);
h.left=cxK; h.right=cxD; DrawTextA(mem,"KILLS",-1,&h,DT_LEFT|DT_VCENTER|DT_SINGLELINE);
h.left=cxD; h.right=cl.right; DrawTextA(mem,"DEATHS",-1,&h,DT_LEFT|DT_VCENTER|DT_SINGLELINE);
y += th*2;
int n = BTLocalConsole_ResultCount();
if (n <= 0) n = 1; // always show the local pilot line
for (int i = 0; i < n; ++i) {
int k = 0, d = 0; BTLocalConsole_GetResult(i, &k, &d);
char name[24]; if (i == 0) strncpy(name, gLastPilotName, sizeof(name)-1), name[sizeof(name)-1]=0;
else sprintf(name, "Pilot %d", i + 1);
char kb[8], db[8]; sprintf(kb, "%d", k); sprintf(db, "%d", d);
SetTextColor(mem, i == 0 ? gB : gD);
RECT r; r.top=y; r.bottom=y+th*2;
r.left=cxN; r.right=cxK; DrawTextA(mem,name,-1,&r,DT_LEFT|DT_VCENTER|DT_SINGLELINE);
r.left=cxK; r.right=cxD; DrawTextA(mem,kb,-1,&r,DT_LEFT|DT_VCENTER|DT_SINGLELINE);
r.left=cxD; r.right=cl.right; DrawTextA(mem,db,-1,&r,DT_LEFT|DT_VCENTER|DT_SINGLELINE);
y += th*2;
}
SetTextColor(mem, gD);
RECT f = cl; f.top = cl.bottom - th*3; f.bottom = cl.bottom - th;
DrawTextA(mem, "press any key", -1, &f, DT_CENTER|DT_TOP|DT_SINGLELINE);
BitBlt(hdc, 0,0, cl.right, cl.bottom, mem, 0,0, SRCCOPY);
SelectObject(mem, old); DeleteObject(surf); DeleteDC(mem);
DeleteObject(big); DeleteObject(reg);
EndPaint(hwnd, &ps); return 0; }
case WM_KEYDOWN: case WM_LBUTTONDOWN: gResultsDone = 1; return 0;
case WM_CLOSE: gResultsDone = 1; return 0;
}
return DefWindowProcA(hwnd, msg, wp, lp);
}
} // namespace
// Show the scoreboard until dismissed (or ~12s timeout). Returns always.
void BTFrontEnd_ShowResults()
{
HINSTANCE instance = GetModuleHandleA(NULL);
static int reg = 0;
if (!reg) { reg = 1;
WNDCLASSA wc; memset(&wc,0,sizeof(wc));
wc.lpfnWndProc = ResultsWndProc; wc.hInstance = instance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
wc.lpszClassName = "BTResults";
RegisterClassA(&wc);
}
int W = 800, H = 560;
int sx = (GetSystemMetrics(SM_CXSCREEN) - W) / 2;
int sy = (GetSystemMetrics(SM_CYSCREEN) - H) / 2;
gResultsDone = 0;
gResultsWin = CreateWindowExA(0, "BTResults", "BattleTech",
WS_OVERLAPPEDWINDOW | WS_VISIBLE, sx<0?0:sx, sy<0?0:sy, W, H, NULL, NULL, instance, NULL);
if (gResultsWin == NULL) return;
SetForegroundWindow(gResultsWin);
DWORD deadline = GetTickCount() + 12000;
MSG msg;
while (!gResultsDone && GetTickCount() < deadline) {
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_KEYDOWN || msg.message == WM_QUIT) gResultsDone = 1;
TranslateMessage(&msg); DispatchMessageA(&msg);
}
Sleep(15);
}
DestroyWindow(gResultsWin); gResultsWin = NULL;
}