Results screen between race and menu
After a console-marshaled race ends, the race loop now shows a RACE RESULTS screen (place / pilot / final score, sorted descending, with a CONTINUE button) before returning to the setup menu. Scores come from the local console's intake; single-player rows carry the pilot's own name, additional pods show their host number until the Steam roster maps IDs to personas. The setup menu also keeps the player's selections and pilot name across races now instead of resetting to defaults each cycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -223,6 +223,12 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// last race's final scores first (no-op on first boot)
|
||||||
|
if (!RPL4FrontEnd_ShowResults(hInstance, hWnd))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
static char frontend_egg[MAX_PATH];
|
static char frontend_egg[MAX_PATH];
|
||||||
if (!RPL4FrontEnd_Run(hInstance, hWnd, frontend_egg, sizeof(frontend_egg)))
|
if (!RPL4FrontEnd_Run(hInstance, hWnd, frontend_egg, sizeof(frontend_egg)))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -196,3 +196,21 @@ Logical
|
|||||||
{
|
{
|
||||||
return gPhase == PhaseStopped;
|
return gPhase == PhaseStopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
RPL4LocalConsole_ResultCount()
|
||||||
|
{
|
||||||
|
return gResultCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical
|
||||||
|
RPL4LocalConsole_GetResult(int index, int *host_ID, int *score)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= gResultCount)
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
*host_ID = gResults[index].hostID;
|
||||||
|
*score = gResults[index].score;
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,3 +25,11 @@ void
|
|||||||
// back to the setup screen for the next race in the same process.
|
// back to the setup screen for the next race in the same process.
|
||||||
Logical
|
Logical
|
||||||
RPL4LocalConsole_MissionCompleted();
|
RPL4LocalConsole_MissionCompleted();
|
||||||
|
|
||||||
|
// The last completed mission's final scores (what each pod reported at
|
||||||
|
// mission end). Valid until the next mission starts running.
|
||||||
|
int
|
||||||
|
RPL4LocalConsole_ResultCount();
|
||||||
|
|
||||||
|
Logical
|
||||||
|
RPL4LocalConsole_GetResult(int index, int *host_ID, int *score);
|
||||||
|
|||||||
+308
-2
@@ -2,6 +2,7 @@
|
|||||||
#pragma hdrstop
|
#pragma hdrstop
|
||||||
|
|
||||||
#include "rpl4fe.h"
|
#include "rpl4fe.h"
|
||||||
|
#include "rpl4console.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -154,6 +155,12 @@ namespace
|
|||||||
FEState *gFE = NULL;
|
FEState *gFE = NULL;
|
||||||
int gLastMissionSeconds = 0;
|
int gLastMissionSeconds = 0;
|
||||||
|
|
||||||
|
// carried across races so cycling back to the menu keeps the
|
||||||
|
// player's loadout (and names the results screen's score rows)
|
||||||
|
int gPersistSelection[GroupCount];
|
||||||
|
Logical gHavePersist = False;
|
||||||
|
char gLastPilotName[24] = "Pilot";
|
||||||
|
|
||||||
const COLORREF kGreenBright = RGB(64, 255, 64);
|
const COLORREF kGreenBright = RGB(64, 255, 64);
|
||||||
const COLORREF kGreenDim = RGB(24, 140, 24);
|
const COLORREF kGreenDim = RGB(24, 140, 24);
|
||||||
const COLORREF kBlack = RGB(0, 0, 0);
|
const COLORREF kBlack = RGB(0, 0, 0);
|
||||||
@@ -588,8 +595,15 @@ Logical
|
|||||||
{
|
{
|
||||||
FEState fe;
|
FEState fe;
|
||||||
memset(&fe, 0, sizeof(fe));
|
memset(&fe, 0, sizeof(fe));
|
||||||
strcpy(fe.pilotName, "Pilot");
|
strcpy(fe.pilotName, gLastPilotName);
|
||||||
fe.selection[GroupLength] = 2; // 5:00
|
if (gHavePersist)
|
||||||
|
{
|
||||||
|
memcpy(fe.selection, gPersistSelection, sizeof(fe.selection));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fe.selection[GroupLength] = 2; // 5:00
|
||||||
|
}
|
||||||
gFE = &fe;
|
gFE = &fe;
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
@@ -688,6 +702,9 @@ Logical
|
|||||||
{
|
{
|
||||||
strcpy(fe.pilotName, "Pilot");
|
strcpy(fe.pilotName, "Pilot");
|
||||||
}
|
}
|
||||||
|
strcpy(gLastPilotName, fe.pilotName);
|
||||||
|
memcpy(gPersistSelection, fe.selection, sizeof(gPersistSelection));
|
||||||
|
gHavePersist = True;
|
||||||
|
|
||||||
std::string egg;
|
std::string egg;
|
||||||
egg.reserve(16384);
|
egg.reserve(16384);
|
||||||
@@ -727,6 +744,295 @@ int
|
|||||||
return gLastMissionSeconds;
|
return gLastMissionSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
//######################## RPL4FrontEnd_ShowResults ######################
|
||||||
|
//########################################################################
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct ResultRow
|
||||||
|
{
|
||||||
|
char name[32];
|
||||||
|
int score;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResultsState
|
||||||
|
{
|
||||||
|
HWND window;
|
||||||
|
HFONT textFont;
|
||||||
|
HFONT titleFont;
|
||||||
|
ResultRow rows[16];
|
||||||
|
int rowCount;
|
||||||
|
RECT continueRect;
|
||||||
|
Logical dismissed;
|
||||||
|
Logical closed;
|
||||||
|
};
|
||||||
|
|
||||||
|
ResultsState *gRS = NULL;
|
||||||
|
|
||||||
|
const char *PlaceName(int place)
|
||||||
|
{
|
||||||
|
static const char *kPlaces[] = { "1ST", "2ND", "3RD", "4TH" };
|
||||||
|
static char other[8];
|
||||||
|
if (place < 4)
|
||||||
|
{
|
||||||
|
return kPlaces[place];
|
||||||
|
}
|
||||||
|
sprintf(other, "%dTH", place + 1);
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaintResults(ResultsState *rs)
|
||||||
|
{
|
||||||
|
PAINTSTRUCT ps;
|
||||||
|
HDC hdc = BeginPaint(rs->window, &ps);
|
||||||
|
|
||||||
|
RECT client;
|
||||||
|
GetClientRect(rs->window, &client);
|
||||||
|
|
||||||
|
HDC mem = CreateCompatibleDC(hdc);
|
||||||
|
HBITMAP surface = CreateCompatibleBitmap(hdc, client.right, client.bottom);
|
||||||
|
HBITMAP old_surface = (HBITMAP) SelectObject(mem, surface);
|
||||||
|
|
||||||
|
FillRect(mem, &client, (HBRUSH) GetStockObject(BLACK_BRUSH));
|
||||||
|
SetBkMode(mem, TRANSPARENT);
|
||||||
|
|
||||||
|
SelectObject(mem, rs->titleFont);
|
||||||
|
SetTextColor(mem, kGreenBright);
|
||||||
|
RECT title = client;
|
||||||
|
title.top = client.bottom / 28;
|
||||||
|
title.bottom = title.top + client.bottom / 10;
|
||||||
|
DrawTextA(mem, "RACE RESULTS", -1, &title,
|
||||||
|
DT_CENTER | DT_TOP | DT_SINGLELINE);
|
||||||
|
|
||||||
|
SelectObject(mem, rs->textFont);
|
||||||
|
int row_h = client.bottom / 16;
|
||||||
|
int top = client.bottom / 4;
|
||||||
|
int left = client.right / 3;
|
||||||
|
int right = 2 * client.right / 3;
|
||||||
|
char text[48];
|
||||||
|
for (int i = 0; i < rs->rowCount; ++i)
|
||||||
|
{
|
||||||
|
RECT row;
|
||||||
|
row.left = left;
|
||||||
|
row.right = right;
|
||||||
|
row.top = top + i * row_h;
|
||||||
|
row.bottom = row.top + row_h;
|
||||||
|
|
||||||
|
SetTextColor(mem, (i == 0) ? kGreenBright : kGreenDim);
|
||||||
|
DrawTextA(mem, PlaceName(i), -1, &row,
|
||||||
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||||
|
|
||||||
|
RECT name = row;
|
||||||
|
name.left += (right - left) / 5;
|
||||||
|
DrawTextA(mem, rs->rows[i].name, -1, &name,
|
||||||
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||||
|
|
||||||
|
sprintf(text, "%d", rs->rows[i].score);
|
||||||
|
DrawTextA(mem, text, -1, &row,
|
||||||
|
DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
HBRUSH frame_brush = CreateSolidBrush(kGreenBright);
|
||||||
|
FrameRect(mem, &rs->continueRect, frame_brush);
|
||||||
|
DeleteObject(frame_brush);
|
||||||
|
SetTextColor(mem, kGreenBright);
|
||||||
|
DrawTextA(mem, "C O N T I N U E", -1, &rs->continueRect,
|
||||||
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||||
|
|
||||||
|
BitBlt(hdc, 0, 0, client.right, client.bottom, mem, 0, 0, SRCCOPY);
|
||||||
|
|
||||||
|
SelectObject(mem, old_surface);
|
||||||
|
DeleteObject(surface);
|
||||||
|
DeleteDC(mem);
|
||||||
|
EndPaint(rs->window, &ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK
|
||||||
|
ResultsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
ResultsState *rs = gRS;
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case WM_PAINT:
|
||||||
|
if (rs != NULL)
|
||||||
|
{
|
||||||
|
PaintResults(rs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
if (rs != NULL)
|
||||||
|
{
|
||||||
|
POINT point = { (int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam) };
|
||||||
|
if (PtInRect(&rs->continueRect, point))
|
||||||
|
{
|
||||||
|
rs->dismissed = True;
|
||||||
|
PostMessageA(rs->window, WM_NULL, 0, 0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_ERASEBKGND:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return DefWindowProcA(hwnd, message, wParam, lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical
|
||||||
|
RPL4FrontEnd_ShowResults(
|
||||||
|
HINSTANCE instance,
|
||||||
|
HWND main_window
|
||||||
|
)
|
||||||
|
{
|
||||||
|
int result_count = RPL4LocalConsole_ResultCount();
|
||||||
|
if (result_count <= 0)
|
||||||
|
{
|
||||||
|
return True; // nothing to show (first boot)
|
||||||
|
}
|
||||||
|
if (!IsWindow(main_window))
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultsState rs;
|
||||||
|
memset(&rs, 0, sizeof(rs));
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Intake, then sort descending by score. Single-player results
|
||||||
|
// carry the pilot's own name; with more pods the roster will map
|
||||||
|
// host IDs to Steam personas (until then: pod number).
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
for (int i = 0; i < result_count && rs.rowCount < 16; ++i)
|
||||||
|
{
|
||||||
|
int host_ID, score;
|
||||||
|
if (!RPL4LocalConsole_GetResult(i, &host_ID, &score))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ResultRow *row = &rs.rows[rs.rowCount++];
|
||||||
|
if (result_count == 1)
|
||||||
|
{
|
||||||
|
sprintf(row->name, "%s", gLastPilotName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(row->name, "POD %d", host_ID);
|
||||||
|
}
|
||||||
|
row->score = score;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < rs.rowCount; ++i)
|
||||||
|
{
|
||||||
|
ResultRow key = rs.rows[i];
|
||||||
|
int j = i - 1;
|
||||||
|
while (j >= 0 && rs.rows[j].score < key.score)
|
||||||
|
{
|
||||||
|
rs.rows[j + 1] = rs.rows[j];
|
||||||
|
--j;
|
||||||
|
}
|
||||||
|
rs.rows[j + 1] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = ResultsWndProc;
|
||||||
|
window_class.hInstance = instance;
|
||||||
|
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
|
window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
|
||||||
|
window_class.lpszClassName = "RPResults";
|
||||||
|
RegisterClassA(&window_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT client;
|
||||||
|
GetClientRect(main_window, &client);
|
||||||
|
|
||||||
|
rs.window = CreateWindowExA(
|
||||||
|
0, "RPResults", "",
|
||||||
|
WS_CHILD | WS_VISIBLE,
|
||||||
|
0, 0, client.right, client.bottom,
|
||||||
|
main_window, NULL, instance, NULL);
|
||||||
|
if (rs.window == NULL)
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
|
||||||
|
int text_h = client.bottom / 40;
|
||||||
|
if (text_h < 16) text_h = 16;
|
||||||
|
if (text_h > 24) text_h = 24;
|
||||||
|
rs.textFont = CreateFontA(-(text_h * 3 / 2), 0, 0, 0, FW_NORMAL,
|
||||||
|
FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
|
||||||
|
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_MODERN,
|
||||||
|
"Consolas");
|
||||||
|
rs.titleFont = CreateFontA(-(text_h * 2), 0, 0, 0, FW_BOLD,
|
||||||
|
FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
|
||||||
|
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_MODERN,
|
||||||
|
"Consolas");
|
||||||
|
|
||||||
|
int row_h = client.bottom / 36;
|
||||||
|
if (row_h < 18) row_h = 18;
|
||||||
|
if (row_h > 30) row_h = 30;
|
||||||
|
int col_w = client.right / 5;
|
||||||
|
rs.continueRect.left = (client.right - col_w) / 2;
|
||||||
|
rs.continueRect.right = rs.continueRect.left + col_w;
|
||||||
|
rs.continueRect.top = client.bottom - 3 * row_h;
|
||||||
|
rs.continueRect.bottom = client.bottom - row_h;
|
||||||
|
|
||||||
|
gRS = &rs;
|
||||||
|
SetFocus(rs.window);
|
||||||
|
InvalidateRect(rs.window, NULL, TRUE);
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Modal until CONTINUE (or any key) - same shape as the menu loop
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
MSG msg;
|
||||||
|
while (!rs.dismissed && !rs.closed)
|
||||||
|
{
|
||||||
|
BOOL result = GetMessageA(&msg, NULL, 0, 0);
|
||||||
|
if (result <= 0)
|
||||||
|
{
|
||||||
|
rs.closed = True;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (msg.message == WM_QUIT ||
|
||||||
|
(msg.message == WM_SYSCOMMAND && msg.wParam == SC_CLOSE))
|
||||||
|
{
|
||||||
|
rs.closed = True;
|
||||||
|
}
|
||||||
|
if (msg.message == WM_CLOSE && msg.hwnd == main_window)
|
||||||
|
{
|
||||||
|
rs.closed = True;
|
||||||
|
}
|
||||||
|
if (msg.message == WM_KEYDOWN &&
|
||||||
|
(msg.wParam == VK_RETURN || msg.wParam == VK_SPACE ||
|
||||||
|
msg.wParam == VK_ESCAPE))
|
||||||
|
{
|
||||||
|
rs.dismissed = True;
|
||||||
|
}
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessageA(&msg);
|
||||||
|
if (!IsWindow(main_window))
|
||||||
|
{
|
||||||
|
rs.closed = True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyWindow(rs.window);
|
||||||
|
DeleteObject(rs.textFont);
|
||||||
|
DeleteObject(rs.titleFont);
|
||||||
|
gRS = NULL;
|
||||||
|
|
||||||
|
return !rs.closed;
|
||||||
|
}
|
||||||
|
|
||||||
//########################################################################
|
//########################################################################
|
||||||
// Ordinal plasma graphics (verbatim from the console / TEST.EGG)
|
// Ordinal plasma graphics (verbatim from the console / TEST.EGG)
|
||||||
//########################################################################
|
//########################################################################
|
||||||
|
|||||||
@@ -28,3 +28,13 @@ Logical
|
|||||||
// successful launch - the local console marshals the mission with it.
|
// successful launch - the local console marshals the mission with it.
|
||||||
int
|
int
|
||||||
RPL4FrontEnd_LastMissionSeconds();
|
RPL4FrontEnd_LastMissionSeconds();
|
||||||
|
|
||||||
|
// The between-races results screen: shows the last mission's final
|
||||||
|
// scores (from the local console) with a CONTINUE button. Returns
|
||||||
|
// immediately when there are no results (first boot). False only if
|
||||||
|
// the player closed the window instead of continuing.
|
||||||
|
Logical
|
||||||
|
RPL4FrontEnd_ShowResults(
|
||||||
|
HINSTANCE instance,
|
||||||
|
HWND main_window
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user