Members get the score sheet: owner publishes results through the lobby

Round seven was the first complete Steam race: three machines, five
minutes, wall deaths respawning, the console ending the race on time,
and rematches launched from the same lobby. The one gap the run showed:
only the owner saw scores - members went straight back to the room.

The owner now publishes its console results (host, score, name, keyed
by the race nonce) into lobby data right after teardown; members pull
the sheet (waiting up to 8s for it to land), inject it into the local
results intake, and the same RACE RESULTS screen shows on every
machine before the room.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-13 00:34:34 -05:00
co-authored by Claude Fable 5
parent a1b524da29
commit 18c6f05cf1
5 changed files with 158 additions and 0 deletions
+105
View File
@@ -15,10 +15,13 @@ Logical RPL4Lobby_InRoom() { return False; }
int RPL4Lobby_Host(HINSTANCE, HWND) { return LobbyRoomLeft; }
int RPL4Lobby_Join(HINSTANCE, HWND) { return LobbyRoomLeft; }
int RPL4Lobby_Room(HINSTANCE, HWND) { return LobbyRoomLeft; }
void RPL4Lobby_PushRaceResults() { }
void RPL4Lobby_PullRaceResults() { }
#else
#include "rpl4fe.h"
#include "rpl4console.h"
#include "..\munga_l4\l4steamtransport.h"
#pragma pack(push, 8)
@@ -40,6 +43,8 @@ namespace
CSteamID gLobby;
Logical gInLobby = False;
int gLastGoNonce = 0; // launches we already answered
int gShownResultsNonce = 0; // score sheets we already displayed
const char kResultsKey[] = "res";
// async call-result plumbing
Logical gCallDone = False;
@@ -696,4 +701,104 @@ int
return RunRoom(instance, main_window);
}
void
RPL4Lobby_PushRaceResults()
{
if (!gInLobby || !IsOwner() || RPL4LocalConsole_ResultCount() == 0)
{
return;
}
SteamAPI_RunCallbacks();
char sheet[600];
sprintf(sheet, "%d:", gLastGoNonce);
for (int i = 0; i < RPL4LocalConsole_ResultCount(); ++i)
{
int host_ID, score;
if (!RPL4LocalConsole_GetResult(i, &host_ID, &score))
{
continue;
}
const char *name = RPL4LocalConsole_GetResultName(host_ID);
char entry[80];
sprintf(entry, "%d|%d|%.30s;", host_ID, score,
(name != NULL) ? name : "");
if (strlen(sheet) + strlen(entry) < sizeof(sheet))
{
strcat(sheet, entry);
}
}
SteamMatchmaking()->SetLobbyData(gLobby, kResultsKey, sheet);
gShownResultsNonce = gLastGoNonce; // the owner sees its own screen
DEBUG_STREAM << "Lobby: results published (" << sheet << ")\n" << std::flush;
}
void
RPL4Lobby_PullRaceResults()
{
if (!gInLobby || IsOwner() || gLastGoNonce == gShownResultsNonce)
{
return;
}
//
// The owner publishes right after its race teardown - ours may
// finish first, so give the sheet a moment to arrive
//
const char *sheet = NULL;
DWORD deadline = GetTickCount() + 8 * 1000;
for (;;)
{
SteamAPI_RunCallbacks();
sheet = SteamMatchmaking()->GetLobbyData(gLobby, kResultsKey);
if (sheet != NULL && sheet[0] != '\0' && atoi(sheet) == gLastGoNonce)
{
break;
}
if ((LONG)(GetTickCount() - deadline) >= 0)
{
return; // no sheet for this race - skip the screen
}
Sleep(100);
}
gShownResultsNonce = gLastGoNonce;
RPL4LocalConsole_ClearResults();
const char *cursor = strchr(sheet, ':');
cursor = (cursor != NULL) ? cursor + 1 : sheet;
while (*cursor != '\0')
{
int host_ID = atoi(cursor);
int score = 0;
char name[32] = "";
while (*cursor != '\0' && *cursor != '|' && *cursor != ';') ++cursor;
if (*cursor == '|')
{
score = atoi(++cursor);
while (*cursor != '\0' && *cursor != '|' && *cursor != ';') ++cursor;
}
if (*cursor == '|')
{
++cursor;
int n = 0;
while (cursor[n] != '\0' && cursor[n] != ';' &&
n < (int) sizeof(name) - 1)
{
name[n] = cursor[n];
++n;
}
name[n] = '\0';
cursor += n;
}
while (*cursor != '\0' && *cursor != ';') ++cursor;
if (*cursor == ';') ++cursor;
if (host_ID > 0)
{
RPL4LocalConsole_InjectResult(host_ID, score, name);
}
}
DEBUG_STREAM << "Lobby: results pulled for race " << gLastGoNonce << "\n" << std::flush;
}
#endif // RP412_STEAM