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:
@@ -530,6 +530,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
//=======================================================================//
|
||||
extern int BTFrontEnd_Run();
|
||||
extern int BTFrontEnd_LastMissionSeconds();
|
||||
extern void BTFrontEnd_ShowResults();
|
||||
extern void BTLocalConsole_Install(int mission_seconds);
|
||||
extern int BTLocalConsole_MissionCompleted();
|
||||
|
||||
@@ -607,6 +608,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
//-------------------------------------------------------------------//
|
||||
if (front_end_mode && BTLocalConsole_MissionCompleted() && IsWindow(hWnd))
|
||||
{
|
||||
// Scoreboard first (kills/deaths snapshotted at the stop), then relaunch.
|
||||
ShowWindow(hWnd, SW_HIDE);
|
||||
BTFrontEnd_ShowResults();
|
||||
|
||||
wchar_t exePath[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, exePath, MAX_PATH);
|
||||
STARTUPINFOW si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
#include "appmgr.hpp" // gPerFrameHook
|
||||
#include "btl4console.hpp"
|
||||
|
||||
// The pilot roster + score bridges (btl4gau3.cpp / mechmppr.cpp / btplayer.cpp):
|
||||
// the pilotList reads them for the Comm MFD, and the marshal snapshots them at
|
||||
// the stop so the results screen has the final scores.
|
||||
extern void *BTResolveRosterPilot(int slot);
|
||||
extern int BTPilotKills(void *pilot);
|
||||
extern int BTPilotDeaths(void *pilot);
|
||||
|
||||
namespace
|
||||
{
|
||||
enum Phase { PhaseIdle, PhaseWaiting, PhaseRunning, PhaseStopped };
|
||||
@@ -22,6 +29,27 @@ namespace
|
||||
unsigned long gStartTick = 0;
|
||||
Application *gWatched = 0;
|
||||
|
||||
// Final scores snapshotted at the stop (roster order; slot 0 = local).
|
||||
enum { maxResults = 8 };
|
||||
struct Result { int kills, deaths; };
|
||||
Result gResults[maxResults];
|
||||
int gResultCount = 0;
|
||||
|
||||
void SnapshotResults()
|
||||
{
|
||||
gResultCount = 0;
|
||||
for (int slot = 0; slot < maxResults; ++slot)
|
||||
{
|
||||
void *pilot = BTResolveRosterPilot(slot);
|
||||
if (pilot == 0)
|
||||
break; // past the last occupied roster slot
|
||||
gResults[gResultCount].kills = BTPilotKills(pilot);
|
||||
gResults[gResultCount].deaths = BTPilotDeaths(pilot);
|
||||
++gResultCount;
|
||||
}
|
||||
DEBUG_STREAM << "[marshal] snapshot " << gResultCount << " pilot score(s)\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
// The game-thread tick (called every frame while an application is live).
|
||||
//
|
||||
@@ -59,9 +87,11 @@ namespace
|
||||
else if (gMissionSeconds > 0 &&
|
||||
(GetTickCount() - gStartTick) >= (unsigned long)(gMissionSeconds * 1000))
|
||||
{
|
||||
// The console clock expired: stop the race exactly as the
|
||||
// arcade console did (StopMissionMessage on our own pod).
|
||||
// The console clock expired: snapshot the final scores (the
|
||||
// game state is still live here), then stop the race exactly
|
||||
// as the arcade console did (StopMissionMessage on our pod).
|
||||
DEBUG_STREAM << "[marshal] time expired -- stopping mission\n" << std::flush;
|
||||
SnapshotResults();
|
||||
Application::StopMissionMessage message(0);
|
||||
application->Dispatch(&message);
|
||||
gPhase = PhaseStopped;
|
||||
@@ -92,6 +122,20 @@ int BTLocalConsole_MissionCompleted()
|
||||
return (gPhase == PhaseStopped) ? 1 : 0;
|
||||
}
|
||||
|
||||
int BTLocalConsole_ResultCount()
|
||||
{
|
||||
return gResultCount;
|
||||
}
|
||||
|
||||
int BTLocalConsole_GetResult(int index, int *kills, int *deaths)
|
||||
{
|
||||
if (index < 0 || index >= gResultCount)
|
||||
return 0;
|
||||
if (kills) *kills = gResults[index].kills;
|
||||
if (deaths) *deaths = gResults[index].deaths;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void BTLocalConsole_Uninstall()
|
||||
{
|
||||
gPerFrameHook = 0;
|
||||
|
||||
@@ -21,6 +21,10 @@ void BTLocalConsole_Install(int mission_seconds);
|
||||
// other way while armed) -- the WinMain loop cycles back to the menu.
|
||||
int BTLocalConsole_MissionCompleted();
|
||||
|
||||
// Final scores snapshotted at the stop (roster order; slot 0 = local pilot).
|
||||
int BTLocalConsole_ResultCount();
|
||||
int BTLocalConsole_GetResult(int index, int *kills, int *deaths);
|
||||
|
||||
// Drop the marshal + clear the per-frame hook.
|
||||
void BTLocalConsole_Uninstall();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -73,4 +73,7 @@ int BTFrontEnd_Run();
|
||||
// LAUNCH -- WinMain arms the LocalConsole marshal with it.
|
||||
int BTFrontEnd_LastMissionSeconds();
|
||||
|
||||
// Show the post-mission scoreboard (marshal snapshot) until dismissed.
|
||||
void BTFrontEnd_ShowResults();
|
||||
|
||||
#endif // BTL4FE_HPP
|
||||
|
||||
Reference in New Issue
Block a user