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:
+308
-2
@@ -2,6 +2,7 @@
|
||||
#pragma hdrstop
|
||||
|
||||
#include "rpl4fe.h"
|
||||
#include "rpl4console.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
@@ -154,6 +155,12 @@ namespace
|
||||
FEState *gFE = NULL;
|
||||
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 kGreenDim = RGB(24, 140, 24);
|
||||
const COLORREF kBlack = RGB(0, 0, 0);
|
||||
@@ -588,8 +595,15 @@ Logical
|
||||
{
|
||||
FEState fe;
|
||||
memset(&fe, 0, sizeof(fe));
|
||||
strcpy(fe.pilotName, "Pilot");
|
||||
fe.selection[GroupLength] = 2; // 5:00
|
||||
strcpy(fe.pilotName, gLastPilotName);
|
||||
if (gHavePersist)
|
||||
{
|
||||
memcpy(fe.selection, gPersistSelection, sizeof(fe.selection));
|
||||
}
|
||||
else
|
||||
{
|
||||
fe.selection[GroupLength] = 2; // 5:00
|
||||
}
|
||||
gFE = &fe;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
@@ -688,6 +702,9 @@ Logical
|
||||
{
|
||||
strcpy(fe.pilotName, "Pilot");
|
||||
}
|
||||
strcpy(gLastPilotName, fe.pilotName);
|
||||
memcpy(gPersistSelection, fe.selection, sizeof(gPersistSelection));
|
||||
gHavePersist = True;
|
||||
|
||||
std::string egg;
|
||||
egg.reserve(16384);
|
||||
@@ -727,6 +744,295 @@ int
|
||||
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)
|
||||
//########################################################################
|
||||
|
||||
Reference in New Issue
Block a user