Round four reached one step from the race: eggs delivered, both members ACKed with complete meshes, one member entered LoadingMission. Two failures remained, both now fixed. One: Steam reports incoming callers under locally-allocated ALIAS FakeIPs, not their global ones - the owner accepted both mesh legs but its identity check compared the alias against the egg address and never counted the connections (no Connected to GameMachineHost on the owner). The peer table now carries each member SteamID (lobby go roster gained a field) and Accept resolves the caller identity back to the global FakeIP the egg promised. Two: mission load stalls the game thread for 10-30s with nothing pumping, and Steam default 10s connected-timeout sheared every connection mid-load (end reason 4001, rx ages 11.5-20.5s - right at load duration). Connected timeout is now 90s; TCP never timed out an idle arcade link and races pump every frame once running. Self-test still green (ping, survives listener close). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
700 lines
19 KiB
C++
700 lines
19 KiB
C++
#include "..\munga_l4\mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "rpl4lobby.h"
|
|
|
|
#ifndef RP412_STEAM
|
|
|
|
//########################################################################
|
|
// No Steam SDK in this build: the lobby is unavailable, everything
|
|
// else (single player, LAN -net races) works as always.
|
|
//########################################################################
|
|
|
|
Logical RPL4Lobby_Available() { return False; }
|
|
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; }
|
|
|
|
#else
|
|
|
|
#include "rpl4fe.h"
|
|
#include "..\munga_l4\l4steamtransport.h"
|
|
|
|
#pragma pack(push, 8)
|
|
#include "steam/steam_api.h"
|
|
#pragma pack(pop)
|
|
|
|
#include <stdio.h>
|
|
|
|
//########################################################################
|
|
// Lobby state - one lobby at a time, alive across races
|
|
//########################################################################
|
|
|
|
namespace
|
|
{
|
|
enum { kMaxLobbyMembers = 4 };
|
|
const char kLobbyTagKey[] = "rp412";
|
|
const char kGoKey[] = "go";
|
|
|
|
CSteamID gLobby;
|
|
Logical gInLobby = False;
|
|
int gLastGoNonce = 0; // launches we already answered
|
|
|
|
// async call-result plumbing
|
|
Logical gCallDone = False;
|
|
Logical gCallOK = False;
|
|
CSteamID gCallLobby;
|
|
|
|
class LobbyCalls
|
|
{
|
|
public:
|
|
CCallResult<LobbyCalls, LobbyCreated_t> createResult;
|
|
CCallResult<LobbyCalls, LobbyMatchList_t> listResult;
|
|
CCallResult<LobbyCalls, LobbyEnter_t> enterResult;
|
|
|
|
void OnCreated(LobbyCreated_t *result, bool io_failure)
|
|
{
|
|
gCallOK = !io_failure && result->m_eResult == k_EResultOK;
|
|
gCallLobby = CSteamID(result->m_ulSteamIDLobby);
|
|
gCallDone = True;
|
|
}
|
|
void OnList(LobbyMatchList_t *result, bool io_failure)
|
|
{
|
|
gCallOK = !io_failure && result->m_nLobbiesMatching > 0;
|
|
if (gCallOK)
|
|
{
|
|
gCallLobby = SteamMatchmaking()->GetLobbyByIndex(0);
|
|
}
|
|
gCallDone = True;
|
|
}
|
|
void OnEnter(LobbyEnter_t *result, bool io_failure)
|
|
{
|
|
gCallOK = !io_failure &&
|
|
result->m_EChatRoomEnterResponse == k_EChatRoomEnterResponseSuccess;
|
|
gCallLobby = CSteamID(result->m_ulSteamIDLobby);
|
|
gCallDone = True;
|
|
}
|
|
};
|
|
LobbyCalls gCalls;
|
|
|
|
Logical WaitForCall(int timeout_ms)
|
|
{
|
|
DWORD deadline = GetTickCount() + timeout_ms;
|
|
while (!gCallDone)
|
|
{
|
|
SteamAPI_RunCallbacks();
|
|
if ((LONG)(GetTickCount() - deadline) >= 0)
|
|
{
|
|
return False;
|
|
}
|
|
Sleep(50);
|
|
}
|
|
return gCallOK;
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Member data: what every pod publishes on entering the room
|
|
//---------------------------------------------------------------
|
|
void SanitizeName(const char *in, char *out, int out_size)
|
|
{
|
|
int n = 0;
|
|
for (int i = 0; in[i] != '\0' && n < out_size - 1; ++i)
|
|
{
|
|
char c = in[i];
|
|
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
|
(c >= '0' && c <= '9') || c == ' ' || c == '-' || c == '_')
|
|
{
|
|
out[n++] = c;
|
|
}
|
|
}
|
|
out[n] = '\0';
|
|
if (n == 0)
|
|
{
|
|
strcpy(out, "Pilot");
|
|
}
|
|
}
|
|
|
|
void PublishMemberData()
|
|
{
|
|
char value[64];
|
|
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "ip",
|
|
SteamNetTransport_GetFakeAddressString());
|
|
sprintf(value, "%d", SteamNetTransport_GetFakeConsolePort());
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "cp", value);
|
|
sprintf(value, "%d", SteamNetTransport_GetFakeGamePort());
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "gp", value);
|
|
|
|
char name[32];
|
|
SanitizeName(SteamFriends()->GetPersonaName(), name, sizeof(name));
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "nm", name);
|
|
|
|
char vehicle[24], color[16], badge[24];
|
|
RPL4FrontEnd_GetLoadout(vehicle, color, badge);
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "vh", vehicle);
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "cl", color);
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "bd", badge);
|
|
}
|
|
|
|
Logical IsOwner()
|
|
{
|
|
return gInLobby &&
|
|
SteamMatchmaking()->GetLobbyOwner(gLobby) == SteamUser()->GetSteamID();
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Launch: roster into lobby data, peers into the transport, and
|
|
// the hosted-race path primed on the owner
|
|
//---------------------------------------------------------------
|
|
struct MemberInfo
|
|
{
|
|
CSteamID id;
|
|
char ip[32];
|
|
int consolePort;
|
|
int gamePort;
|
|
char name[32];
|
|
char vehicle[24];
|
|
char color[16];
|
|
char badge[24];
|
|
Logical published;
|
|
};
|
|
|
|
int CollectMembers(MemberInfo *members)
|
|
{
|
|
int count = SteamMatchmaking()->GetNumLobbyMembers(gLobby);
|
|
if (count > kMaxLobbyMembers)
|
|
{
|
|
count = kMaxLobbyMembers;
|
|
}
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
MemberInfo *member = &members[i];
|
|
memset(member, 0, sizeof(*member));
|
|
member->id = SteamMatchmaking()->GetLobbyMemberByIndex(gLobby, i);
|
|
strncpy(member->ip,
|
|
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "ip"),
|
|
sizeof(member->ip) - 1);
|
|
member->consolePort = atoi(
|
|
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "cp"));
|
|
member->gamePort = atoi(
|
|
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "gp"));
|
|
strncpy(member->name,
|
|
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "nm"),
|
|
sizeof(member->name) - 1);
|
|
strncpy(member->vehicle,
|
|
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "vh"),
|
|
sizeof(member->vehicle) - 1);
|
|
strncpy(member->color,
|
|
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "cl"),
|
|
sizeof(member->color) - 1);
|
|
strncpy(member->badge,
|
|
SteamMatchmaking()->GetLobbyMemberData(gLobby, member->id, "bd"),
|
|
sizeof(member->badge) - 1);
|
|
member->published =
|
|
member->ip[0] != '\0' && member->consolePort > 0 && member->gamePort > 0;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void RegisterRoster(MemberInfo *members, int count)
|
|
{
|
|
SteamNetTransport_SetEnginePorts(1501);
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
if (members[i].published)
|
|
{
|
|
SteamNetTransport_RegisterPeer(
|
|
members[i].ip, members[i].consolePort, members[i].gamePort,
|
|
members[i].id.ConvertToUint64());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Owner side: prime RPL4FE + WinMain's hosted-race path
|
|
void PrimeHostedRace(MemberInfo *members, int count)
|
|
{
|
|
static char pods_env[512];
|
|
static char addr_env[64];
|
|
static char port_env[32];
|
|
|
|
CSteamID self = SteamUser()->GetSteamID();
|
|
FEHostedPilot pilots[kMaxLobbyMembers];
|
|
int pilot_count = 0;
|
|
char pods[512] = "";
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
if (members[i].id == self || !members[i].published)
|
|
{
|
|
continue;
|
|
}
|
|
FEHostedPilot *pilot = &pilots[pilot_count++];
|
|
memset(pilot, 0, sizeof(*pilot));
|
|
sprintf(pilot->address, "%s:1502", members[i].ip);
|
|
strncpy(pilot->name, members[i].name, sizeof(pilot->name) - 1);
|
|
strncpy(pilot->vehicle, members[i].vehicle, sizeof(pilot->vehicle) - 1);
|
|
strncpy(pilot->color, members[i].color, sizeof(pilot->color) - 1);
|
|
strncpy(pilot->badge, members[i].badge, sizeof(pilot->badge) - 1);
|
|
|
|
if (pods[0] != '\0')
|
|
{
|
|
strcat(pods, ",");
|
|
}
|
|
strcat(pods, members[i].ip);
|
|
strcat(pods, ":1501");
|
|
}
|
|
|
|
RPL4FrontEnd_SetHostedPilots(
|
|
SteamNetTransport_GetFakeAddressString(), pilots, pilot_count);
|
|
|
|
sprintf(pods_env, "RP412HOSTPODS=%s", pods);
|
|
putenv(pods_env);
|
|
sprintf(addr_env, "RP412HOSTADDR=%s", SteamNetTransport_GetFakeAddressString());
|
|
putenv(addr_env);
|
|
strcpy(port_env, "RP412HOSTPORT=1501");
|
|
putenv(port_env);
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// The room screen (front-end style: green on black)
|
|
//---------------------------------------------------------------
|
|
const COLORREF kGreenBright = RGB(64, 255, 64);
|
|
const COLORREF kGreenDim = RGB(24, 140, 24);
|
|
|
|
struct RoomState
|
|
{
|
|
HWND window;
|
|
HFONT textFont;
|
|
HFONT titleFont;
|
|
RECT launchRect; // owner only
|
|
RECT leaveRect;
|
|
Logical launchClicked;
|
|
Logical leaveClicked;
|
|
Logical closed;
|
|
MemberInfo members[kMaxLobbyMembers];
|
|
int memberCount;
|
|
};
|
|
|
|
RoomState *gRoom = NULL;
|
|
|
|
void PaintRoom(RoomState *room)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(room->window, &ps);
|
|
|
|
RECT client;
|
|
GetClientRect(room->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, room->titleFont);
|
|
SetTextColor(mem, kGreenBright);
|
|
RECT title = client;
|
|
title.top = client.bottom / 28;
|
|
title.bottom = title.top + client.bottom / 10;
|
|
DrawTextA(mem, "STEAM RACE LOBBY", -1, &title,
|
|
DT_CENTER | DT_TOP | DT_SINGLELINE);
|
|
|
|
SelectObject(mem, room->textFont);
|
|
int row_h = client.bottom / 16;
|
|
int top = client.bottom / 4;
|
|
int left = client.right / 4;
|
|
int right = 3 * client.right / 4;
|
|
char text[128];
|
|
for (int i = 0; i < room->memberCount; ++i)
|
|
{
|
|
MemberInfo *member = &room->members[i];
|
|
RECT row;
|
|
row.left = left;
|
|
row.right = right;
|
|
row.top = top + i * row_h;
|
|
row.bottom = row.top + row_h;
|
|
|
|
Logical is_owner_row =
|
|
SteamMatchmaking()->GetLobbyOwner(gLobby) == member->id;
|
|
SetTextColor(mem, member->published ? kGreenBright : kGreenDim);
|
|
sprintf(text, "%s%s", member->name[0] ? member->name : "(joining...)",
|
|
is_owner_row ? " [HOST]" : "");
|
|
DrawTextA(mem, text, -1, &row, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
|
|
if (member->vehicle[0] != '\0')
|
|
{
|
|
sprintf(text, "%s / %s", member->vehicle, member->color);
|
|
DrawTextA(mem, text, -1, &row, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
}
|
|
|
|
SetTextColor(mem, kGreenDim);
|
|
RECT hint;
|
|
hint.left = left;
|
|
hint.right = right;
|
|
hint.top = top + kMaxLobbyMembers * row_h + row_h / 2;
|
|
hint.bottom = hint.top + row_h;
|
|
DrawTextA(mem,
|
|
IsOwner() ? "You are the console: launch when everyone is in."
|
|
: "Waiting for the host to launch...",
|
|
-1, &hint, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
|
|
HBRUSH frame = CreateSolidBrush(kGreenBright);
|
|
if (IsOwner())
|
|
{
|
|
FrameRect(mem, &room->launchRect, frame);
|
|
SetTextColor(mem, kGreenBright);
|
|
DrawTextA(mem, "L A U N C H R A C E", -1, &room->launchRect,
|
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
FrameRect(mem, &room->leaveRect, frame);
|
|
SetTextColor(mem, kGreenBright);
|
|
DrawTextA(mem, "LEAVE LOBBY", -1, &room->leaveRect,
|
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
DeleteObject(frame);
|
|
|
|
BitBlt(hdc, 0, 0, client.right, client.bottom, mem, 0, 0, SRCCOPY);
|
|
|
|
SelectObject(mem, old_surface);
|
|
DeleteObject(surface);
|
|
DeleteDC(mem);
|
|
EndPaint(room->window, &ps);
|
|
}
|
|
|
|
LRESULT CALLBACK
|
|
RoomWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
RoomState *room = gRoom;
|
|
|
|
switch (message)
|
|
{
|
|
case WM_PAINT:
|
|
if (room != NULL)
|
|
{
|
|
PaintRoom(room);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
if (room != NULL)
|
|
{
|
|
POINT point = { (int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam) };
|
|
if (IsOwner() && PtInRect(&room->launchRect, point))
|
|
{
|
|
room->launchClicked = True;
|
|
}
|
|
else if (PtInRect(&room->leaveRect, point))
|
|
{
|
|
room->leaveClicked = True;
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
}
|
|
return DefWindowProcA(hwnd, message, wParam, lParam);
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// The room loop: pump Steam + Windows, watch for launch/leave
|
|
//---------------------------------------------------------------
|
|
int RunRoom(HINSTANCE instance, HWND main_window)
|
|
{
|
|
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 = RoomWndProc;
|
|
window_class.hInstance = instance;
|
|
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
|
|
window_class.lpszClassName = "RPLobby";
|
|
RegisterClassA(&window_class);
|
|
}
|
|
|
|
RECT client;
|
|
GetClientRect(main_window, &client);
|
|
|
|
RoomState room;
|
|
memset(&room, 0, sizeof(room));
|
|
room.window = CreateWindowExA(
|
|
0, "RPLobby", "",
|
|
WS_CHILD | WS_VISIBLE,
|
|
0, 0, client.right, client.bottom,
|
|
main_window, NULL, instance, NULL);
|
|
if (room.window == NULL)
|
|
{
|
|
return LobbyRoomClosed;
|
|
}
|
|
|
|
int text_h = client.bottom / 40;
|
|
if (text_h < 16) text_h = 16;
|
|
if (text_h > 24) text_h = 24;
|
|
room.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");
|
|
room.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 / 4;
|
|
room.launchRect.left = (client.right - col_w) / 2;
|
|
room.launchRect.right = room.launchRect.left + col_w;
|
|
room.launchRect.top = client.bottom - 6 * row_h;
|
|
room.launchRect.bottom = room.launchRect.top + 2 * row_h;
|
|
room.leaveRect.left = (client.right - col_w / 2) / 2;
|
|
room.leaveRect.right = room.leaveRect.left + col_w / 2;
|
|
room.leaveRect.top = client.bottom - 3 * row_h;
|
|
room.leaveRect.bottom = client.bottom - row_h;
|
|
|
|
gRoom = &room;
|
|
PublishMemberData();
|
|
|
|
int outcome = -1;
|
|
DWORD last_refresh = 0;
|
|
while (outcome < 0)
|
|
{
|
|
MSG msg;
|
|
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
|
|
{
|
|
if (msg.message == WM_QUIT)
|
|
{
|
|
room.closed = True;
|
|
}
|
|
TranslateMessage(&msg);
|
|
DispatchMessageA(&msg);
|
|
}
|
|
SteamAPI_RunCallbacks();
|
|
|
|
if (!IsWindow(main_window) || room.closed)
|
|
{
|
|
outcome = LobbyRoomClosed;
|
|
break;
|
|
}
|
|
if (room.leaveClicked)
|
|
{
|
|
SteamMatchmaking()->LeaveLobby(gLobby);
|
|
gInLobby = False;
|
|
// plain menu launches must not inherit lobby hosting
|
|
RPL4FrontEnd_SetHostedPilots(NULL, NULL, 0);
|
|
static char clear_env[] = "RP412HOSTPODS=";
|
|
putenv(clear_env);
|
|
outcome = LobbyRoomLeft;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Owner launch: roster into lobby data + local prime
|
|
//
|
|
if (room.launchClicked)
|
|
{
|
|
room.launchClicked = False;
|
|
room.memberCount = CollectMembers(room.members);
|
|
Logical all_published = True;
|
|
for (int i = 0; i < room.memberCount; ++i)
|
|
{
|
|
if (!room.members[i].published)
|
|
{
|
|
all_published = False;
|
|
}
|
|
}
|
|
if (all_published && room.memberCount >= 1)
|
|
{
|
|
++gLastGoNonce;
|
|
char go[800];
|
|
sprintf(go, "%d:", gLastGoNonce);
|
|
for (int i = 0; i < room.memberCount; ++i)
|
|
{
|
|
char entry[96];
|
|
sprintf(entry, "%s|%d|%d|%I64u;", room.members[i].ip,
|
|
room.members[i].consolePort, room.members[i].gamePort,
|
|
room.members[i].id.ConvertToUint64());
|
|
if (strlen(go) + strlen(entry) < sizeof(go))
|
|
{
|
|
strcat(go, entry);
|
|
}
|
|
}
|
|
SteamMatchmaking()->SetLobbyData(gLobby, kGoKey, go);
|
|
RegisterRoster(room.members, room.memberCount);
|
|
PrimeHostedRace(room.members, room.memberCount);
|
|
outcome = LobbyLaunchHost;
|
|
break;
|
|
}
|
|
DEBUG_STREAM << "Lobby: not everyone is ready yet\n" << std::flush;
|
|
}
|
|
|
|
//
|
|
// Member: watch for the owner's go signal
|
|
//
|
|
if (!IsOwner())
|
|
{
|
|
const char *go = SteamMatchmaking()->GetLobbyData(gLobby, kGoKey);
|
|
if (go != NULL && go[0] != '\0')
|
|
{
|
|
int nonce = atoi(go);
|
|
if (nonce > gLastGoNonce)
|
|
{
|
|
gLastGoNonce = nonce;
|
|
// register every rostered peer with the transport
|
|
SteamNetTransport_SetEnginePorts(1501);
|
|
const char *cursor = strchr(go, ':');
|
|
cursor = (cursor != NULL) ? cursor + 1 : go;
|
|
while (*cursor != '\0')
|
|
{
|
|
char ip[32];
|
|
int console_port = 0, game_port = 0;
|
|
unsigned __int64 steam_id = 0;
|
|
int n = 0;
|
|
while (cursor[n] != '\0' && cursor[n] != '|' &&
|
|
n < (int) sizeof(ip) - 1)
|
|
{
|
|
ip[n] = cursor[n];
|
|
++n;
|
|
}
|
|
ip[n] = '\0';
|
|
cursor += n;
|
|
if (*cursor == '|')
|
|
{
|
|
console_port = atoi(++cursor);
|
|
while (*cursor != '\0' && *cursor != '|') ++cursor;
|
|
}
|
|
if (*cursor == '|')
|
|
{
|
|
game_port = atoi(++cursor);
|
|
while (*cursor != '\0' && *cursor != '|' && *cursor != ';') ++cursor;
|
|
}
|
|
if (*cursor == '|')
|
|
{
|
|
steam_id = _strtoui64(++cursor, NULL, 10);
|
|
}
|
|
while (*cursor != '\0' && *cursor != ';') ++cursor;
|
|
if (*cursor == ';') ++cursor;
|
|
|
|
if (ip[0] != '\0' && console_port > 0 && game_port > 0)
|
|
{
|
|
SteamNetTransport_RegisterPeer(
|
|
ip, console_port, game_port, steam_id);
|
|
}
|
|
}
|
|
outcome = LobbyLaunchMember;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Refresh the member list once a second
|
|
//
|
|
DWORD now = GetTickCount();
|
|
if ((LONG)(now - last_refresh) >= 1000)
|
|
{
|
|
last_refresh = now;
|
|
room.memberCount = CollectMembers(room.members);
|
|
InvalidateRect(room.window, NULL, FALSE);
|
|
RedrawWindow(room.window, NULL, NULL, RDW_UPDATENOW);
|
|
}
|
|
Sleep(50);
|
|
}
|
|
|
|
DestroyWindow(room.window);
|
|
DeleteObject(room.textFont);
|
|
DeleteObject(room.titleFont);
|
|
gRoom = NULL;
|
|
return outcome;
|
|
}
|
|
}
|
|
|
|
//########################################################################
|
|
// Public API
|
|
//########################################################################
|
|
|
|
Logical
|
|
RPL4Lobby_Available()
|
|
{
|
|
return SteamNetTransport_GetFakeAddressString()[0] != '\0';
|
|
}
|
|
|
|
Logical
|
|
RPL4Lobby_InRoom()
|
|
{
|
|
return gInLobby;
|
|
}
|
|
|
|
int
|
|
RPL4Lobby_Host(HINSTANCE instance, HWND main_window)
|
|
{
|
|
gCallDone = False;
|
|
SteamAPICall_t call =
|
|
SteamMatchmaking()->CreateLobby(k_ELobbyTypePublic, kMaxLobbyMembers);
|
|
gCalls.createResult.Set(call, &gCalls, &LobbyCalls::OnCreated);
|
|
if (!WaitForCall(15000))
|
|
{
|
|
DEBUG_STREAM << "Lobby: CreateLobby failed\n" << std::flush;
|
|
return LobbyRoomLeft;
|
|
}
|
|
gLobby = gCallLobby;
|
|
gInLobby = True;
|
|
gLastGoNonce = 0;
|
|
SteamMatchmaking()->SetLobbyData(gLobby, kLobbyTagKey, "1");
|
|
SteamMatchmaking()->SetLobbyData(gLobby, kGoKey, "");
|
|
DEBUG_STREAM << "Lobby: hosting " << gLobby.ConvertToUint64() << "\n" << std::flush;
|
|
return RunRoom(instance, main_window);
|
|
}
|
|
|
|
int
|
|
RPL4Lobby_Join(HINSTANCE instance, HWND main_window)
|
|
{
|
|
gCallDone = False;
|
|
SteamMatchmaking()->AddRequestLobbyListStringFilter(
|
|
kLobbyTagKey, "1", k_ELobbyComparisonEqual);
|
|
SteamAPICall_t call = SteamMatchmaking()->RequestLobbyList();
|
|
gCalls.listResult.Set(call, &gCalls, &LobbyCalls::OnList);
|
|
if (!WaitForCall(15000))
|
|
{
|
|
DEBUG_STREAM << "Lobby: no open race lobby found\n" << std::flush;
|
|
return LobbyRoomLeft;
|
|
}
|
|
|
|
CSteamID target = gCallLobby;
|
|
gCallDone = False;
|
|
call = SteamMatchmaking()->JoinLobby(target);
|
|
gCalls.enterResult.Set(call, &gCalls, &LobbyCalls::OnEnter);
|
|
if (!WaitForCall(15000))
|
|
{
|
|
DEBUG_STREAM << "Lobby: join failed\n" << std::flush;
|
|
return LobbyRoomLeft;
|
|
}
|
|
gLobby = gCallLobby;
|
|
gInLobby = True;
|
|
// answer only launches newer than anything already in the lobby
|
|
const char *go = SteamMatchmaking()->GetLobbyData(gLobby, kGoKey);
|
|
gLastGoNonce = (go != NULL) ? atoi(go) : 0;
|
|
DEBUG_STREAM << "Lobby: joined " << gLobby.ConvertToUint64() << "\n" << std::flush;
|
|
return RunRoom(instance, main_window);
|
|
}
|
|
|
|
int
|
|
RPL4Lobby_Room(HINSTANCE instance, HWND main_window)
|
|
{
|
|
if (!gInLobby)
|
|
{
|
|
return LobbyRoomLeft;
|
|
}
|
|
return RunRoom(instance, main_window);
|
|
}
|
|
|
|
#endif // RP412_STEAM
|