diff --git a/RP_L4/RPL4.CPP b/RP_L4/RPL4.CPP index dd822ed..09e703b 100644 --- a/RP_L4/RPL4.CPP +++ b/RP_L4/RPL4.CPP @@ -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]; if (!RPL4FrontEnd_Run(hInstance, hWnd, frontend_egg, sizeof(frontend_egg))) { diff --git a/RP_L4/RPL4CONSOLE.cpp b/RP_L4/RPL4CONSOLE.cpp index 9d57975..b95fdc2 100644 --- a/RP_L4/RPL4CONSOLE.cpp +++ b/RP_L4/RPL4CONSOLE.cpp @@ -196,3 +196,21 @@ Logical { 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; +} diff --git a/RP_L4/RPL4CONSOLE.h b/RP_L4/RPL4CONSOLE.h index 7181739..61de503 100644 --- a/RP_L4/RPL4CONSOLE.h +++ b/RP_L4/RPL4CONSOLE.h @@ -25,3 +25,11 @@ void // back to the setup screen for the next race in the same process. Logical 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); diff --git a/RP_L4/RPL4FE.cpp b/RP_L4/RPL4FE.cpp index e8b6d6d..67ee92d 100644 --- a/RP_L4/RPL4FE.cpp +++ b/RP_L4/RPL4FE.cpp @@ -2,6 +2,7 @@ #pragma hdrstop #include "rpl4fe.h" +#include "rpl4console.h" #include #include @@ -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) //######################################################################## diff --git a/RP_L4/RPL4FE.h b/RP_L4/RPL4FE.h index c6d5295..baa9496 100644 --- a/RP_L4/RPL4FE.h +++ b/RP_L4/RPL4FE.h @@ -28,3 +28,13 @@ Logical // successful launch - the local console marshals the mission with it. int 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 + );