"Race" is Red Planet's term (it's a racing game) and rode in with the
RPL4LOBBY port. In BattleTech the gameplay unit is a MISSION. Sweep it out
of the front-end / lobby / marshal code + the roadmap and steamification
digest: rename the public API BTLobby_Push/PullRaceResults ->
Push/PullMissionResults, the internal PrimeHostedRace -> PrimeHostedMission,
the deferred marshal InstallNetworkRace -> InstallNetworkMission, and the
user-facing strings ("STEAM MISSION LOBBY", "L A U N C H M I S S I O N",
"HOST/JOIN STEAM MISSION"). Comments follow. No behavior change; both gates
(default + BT412_STEAM) still build + link clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
433 lines
18 KiB
C++
433 lines
18 KiB
C++
//===========================================================================//
|
|
// btl4lobby.cpp -- BT412 Steam lobby (see btl4lobby.hpp).
|
|
//
|
|
// Ported from RP412 RPL4LOBBY: the ISteamMatchmaking room, member loadout
|
|
// exchange (FakeIP + ports + persona + mech/color/badge), the launch roster,
|
|
// and the post-mission score sheet. Compiled to stubs without BT412_STEAM.
|
|
//===========================================================================//
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define _WIN32_WINNT 0x0500
|
|
#define WINVER 0x0500
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
|
|
#include "btl4lobby.hpp"
|
|
|
|
#ifndef BT412_STEAM
|
|
|
|
//---------------------------------------------------------------------------//
|
|
// No Steam SDK: the lobby is unavailable; single-player + LAN -net unchanged.
|
|
//---------------------------------------------------------------------------//
|
|
int BTLobby_Available() { return 0; }
|
|
int BTLobby_InRoom() { return 0; }
|
|
int BTLobby_Host(void *, void *) { return LobbyRoomLeft; }
|
|
int BTLobby_Join(void *, void *) { return LobbyRoomLeft; }
|
|
int BTLobby_Room(void *, void *) { return LobbyRoomLeft; }
|
|
void BTLobby_PushMissionResults() { }
|
|
void BTLobby_PullMissionResults() { }
|
|
|
|
#else
|
|
|
|
#include "btl4fe.hpp" // BTFrontEnd_GetLoadout / SetHostedPilots / FEHostedPilot
|
|
#include "btl4console.hpp" // result API for the score sheet
|
|
#include "l4steamtransport.hpp" // SteamNetTransport_* (via the fwd shim)
|
|
|
|
#pragma pack(push, 8)
|
|
#include "steam/steam_api.h"
|
|
#pragma pack(pop)
|
|
|
|
#include <string.h>
|
|
#include <iostream>
|
|
|
|
// The marshal result API (kills/deaths per roster slot; slot 0 = local).
|
|
extern int BTLocalConsole_ResultCount();
|
|
extern int BTLocalConsole_GetResult(int index, int *kills, int *deaths);
|
|
extern void BTLocalConsole_ClearResults();
|
|
extern void BTLocalConsole_InjectResult(int slot, int kills, int deaths, const char *name);
|
|
extern const char *BTLocalConsole_GetResultName(int slot);
|
|
// (The remote-pod network-mission marshal -- collating every pod's scores over
|
|
// the wire -- is deferred; the owner publishes its local snapshot for now.)
|
|
|
|
namespace
|
|
{
|
|
enum { kMaxLobbyMembers = 8 };
|
|
const char kLobbyTagKey[] = "bt412";
|
|
const char kGoKey[] = "go";
|
|
const char kResultsKey[] = "res";
|
|
|
|
CSteamID gLobby;
|
|
bool gInLobby = false;
|
|
int gLastGoNonce = 0;
|
|
int gShownResultsNonce = 0;
|
|
|
|
// async call-result plumbing
|
|
bool gCallDone = false, 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 *r, bool io) {
|
|
gCallOK = !io && r->m_eResult == k_EResultOK;
|
|
gCallLobby = CSteamID(r->m_ulSteamIDLobby); gCallDone = true;
|
|
}
|
|
void OnList(LobbyMatchList_t *r, bool io) {
|
|
gCallOK = !io && r->m_nLobbiesMatching > 0;
|
|
if (gCallOK) gCallLobby = SteamMatchmaking()->GetLobbyByIndex(0);
|
|
gCallDone = true;
|
|
}
|
|
void OnEnter(LobbyEnter_t *r, bool io) {
|
|
gCallOK = !io && r->m_EChatRoomEnterResponse == k_EChatRoomEnterResponseSuccess;
|
|
gCallLobby = CSteamID(r->m_ulSteamIDLobby); gCallDone = true;
|
|
}
|
|
};
|
|
LobbyCalls gCalls;
|
|
|
|
bool WaitForCall(int timeout_ms) {
|
|
DWORD deadline = GetTickCount() + timeout_ms;
|
|
while (!gCallDone) {
|
|
SteamAPI_RunCallbacks();
|
|
if ((LONG)(GetTickCount() - deadline) >= 0) return false;
|
|
Sleep(50);
|
|
}
|
|
return gCallOK;
|
|
}
|
|
|
|
void SanitizeName(const char *in, char *out, int out_size) {
|
|
int n = 0;
|
|
for (int i = 0; in[i] && 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];
|
|
BTFrontEnd_GetLoadout(vehicle, color, badge);
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "vh", vehicle);
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "cl", color);
|
|
SteamMatchmaking()->SetLobbyMemberData(gLobby, "bd", badge);
|
|
}
|
|
|
|
bool IsOwner() {
|
|
return gInLobby && SteamMatchmaking()->GetLobbyOwner(gLobby) == SteamUser()->GetSteamID();
|
|
}
|
|
|
|
struct MemberInfo {
|
|
CSteamID id;
|
|
char ip[32]; int consolePort, gamePort;
|
|
char name[32], vehicle[24], color[16], badge[24];
|
|
bool published;
|
|
};
|
|
|
|
int CollectMembers(MemberInfo *members) {
|
|
int count = SteamMatchmaking()->GetNumLobbyMembers(gLobby);
|
|
if (count > kMaxLobbyMembers) count = kMaxLobbyMembers;
|
|
for (int i = 0; i < count; ++i) {
|
|
MemberInfo *m = &members[i]; memset(m, 0, sizeof(*m));
|
|
m->id = SteamMatchmaking()->GetLobbyMemberByIndex(gLobby, i);
|
|
strncpy(m->ip, SteamMatchmaking()->GetLobbyMemberData(gLobby, m->id, "ip"), sizeof(m->ip)-1);
|
|
m->consolePort = atoi(SteamMatchmaking()->GetLobbyMemberData(gLobby, m->id, "cp"));
|
|
m->gamePort = atoi(SteamMatchmaking()->GetLobbyMemberData(gLobby, m->id, "gp"));
|
|
strncpy(m->name, SteamMatchmaking()->GetLobbyMemberData(gLobby, m->id, "nm"), sizeof(m->name)-1);
|
|
strncpy(m->vehicle, SteamMatchmaking()->GetLobbyMemberData(gLobby, m->id, "vh"), sizeof(m->vehicle)-1);
|
|
strncpy(m->color, SteamMatchmaking()->GetLobbyMemberData(gLobby, m->id, "cl"), sizeof(m->color)-1);
|
|
strncpy(m->badge, SteamMatchmaking()->GetLobbyMemberData(gLobby, m->id, "bd"), sizeof(m->badge)-1);
|
|
m->published = m->ip[0] && m->consolePort > 0 && m->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 the hosted-mission env (BT412HOSTPODS/ADDR/PORT) + the
|
|
// hosted-pilot loadouts the front end feeds into the egg.
|
|
void PrimeHostedMission(MemberInfo *members, int count) {
|
|
static char pods_env[512], addr_env[64], 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 *p = &pilots[pilot_count++]; memset(p, 0, sizeof(*p));
|
|
sprintf(p->address, "%s:1502", members[i].ip);
|
|
strncpy(p->name, members[i].name, sizeof(p->name)-1);
|
|
strncpy(p->vehicle, members[i].vehicle, sizeof(p->vehicle)-1);
|
|
strncpy(p->color, members[i].color, sizeof(p->color)-1);
|
|
strncpy(p->badge, members[i].badge, sizeof(p->badge)-1);
|
|
if (pods[0]) strcat(pods, ",");
|
|
strcat(pods, members[i].ip); strcat(pods, ":1501");
|
|
}
|
|
BTFrontEnd_SetHostedPilots(SteamNetTransport_GetFakeAddressString(), pilots, pilot_count);
|
|
sprintf(pods_env, "BT412HOSTPODS=%s", pods); putenv(pods_env);
|
|
sprintf(addr_env, "BT412HOSTADDR=%s", SteamNetTransport_GetFakeAddressString()); putenv(addr_env);
|
|
strcpy(port_env, "BT412HOSTPORT=1501"); putenv(port_env);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------//
|
|
// The room screen (green-on-black, like the menu).
|
|
//-----------------------------------------------------------------------//
|
|
const COLORREF kGreenBright = RGB(64,255,64), kGreenDim = RGB(24,140,24);
|
|
struct RoomState {
|
|
HWND window; HFONT textFont, titleFont;
|
|
RECT launchRect, leaveRect;
|
|
bool launchClicked, leaveClicked, closed;
|
|
MemberInfo members[kMaxLobbyMembers]; int memberCount;
|
|
};
|
|
RoomState *gRoom = NULL;
|
|
|
|
void PaintRoom(RoomState *room) {
|
|
PAINTSTRUCT ps; HDC hdc = BeginPaint(room->window, &ps);
|
|
RECT cl; GetClientRect(room->window, &cl);
|
|
HDC mem = CreateCompatibleDC(hdc);
|
|
HBITMAP surf = CreateCompatibleBitmap(hdc, cl.right, cl.bottom);
|
|
HBITMAP old = (HBITMAP) SelectObject(mem, surf);
|
|
FillRect(mem, &cl, (HBRUSH) GetStockObject(BLACK_BRUSH)); SetBkMode(mem, TRANSPARENT);
|
|
SelectObject(mem, room->titleFont); SetTextColor(mem, kGreenBright);
|
|
RECT title = cl; title.top = cl.bottom/28; title.bottom = title.top + cl.bottom/10;
|
|
DrawTextA(mem, "STEAM MISSION LOBBY", -1, &title, DT_CENTER|DT_TOP|DT_SINGLELINE);
|
|
SelectObject(mem, room->textFont);
|
|
int row_h = cl.bottom/16, top = cl.bottom/4, left = cl.right/4, right = 3*cl.right/4;
|
|
char text[128];
|
|
for (int i = 0; i < room->memberCount; ++i) {
|
|
MemberInfo *m = &room->members[i];
|
|
RECT row; row.left=left; row.right=right; row.top=top+i*row_h; row.bottom=row.top+row_h;
|
|
bool owner_row = SteamMatchmaking()->GetLobbyOwner(gLobby) == m->id;
|
|
SetTextColor(mem, m->published ? kGreenBright : kGreenDim);
|
|
sprintf(text, "%s%s", m->name[0]?m->name:"(joining...)", owner_row?" [HOST]":"");
|
|
DrawTextA(mem, text, -1, &row, DT_LEFT|DT_VCENTER|DT_SINGLELINE);
|
|
if (m->vehicle[0]) { sprintf(text, "%s / %s", m->vehicle, m->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 M I S S I O N", -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, cl.right, cl.bottom, mem, 0,0, SRCCOPY);
|
|
SelectObject(mem, old); DeleteObject(surf); DeleteDC(mem);
|
|
EndPaint(room->window, &ps);
|
|
}
|
|
|
|
LRESULT CALLBACK RoomWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
|
RoomState *room = gRoom;
|
|
switch (msg) {
|
|
case WM_PAINT: if (room) { PaintRoom(room); return 0; } break;
|
|
case WM_LBUTTONDOWN:
|
|
if (room) { POINT pt = {(int)(short)LOWORD(lp),(int)(short)HIWORD(lp)};
|
|
if (IsOwner() && PtInRect(&room->launchRect, pt)) room->launchClicked = true;
|
|
else if (PtInRect(&room->leaveRect, pt)) room->leaveClicked = true;
|
|
return 0; }
|
|
break;
|
|
case WM_ERASEBKGND: return 1;
|
|
}
|
|
return DefWindowProcA(hwnd, msg, wp, lp);
|
|
}
|
|
|
|
int RunRoom(HINSTANCE instance, HWND main_window) {
|
|
static bool reg = false;
|
|
if (!reg) { reg = true;
|
|
WNDCLASSA wc; memset(&wc,0,sizeof(wc));
|
|
wc.style=CS_HREDRAW|CS_VREDRAW; wc.lpfnWndProc=RoomWndProc; wc.hInstance=instance;
|
|
wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wc.lpszClassName="BTLobbyRoom"; RegisterClassA(&wc);
|
|
}
|
|
int W = 1000, H = 720;
|
|
int sx = (GetSystemMetrics(SM_CXSCREEN)-W)/2, sy = (GetSystemMetrics(SM_CYSCREEN)-H)/2;
|
|
RoomState room; memset(&room, 0, sizeof(room));
|
|
room.window = CreateWindowExA(0, "BTLobbyRoom", "BattleTech",
|
|
WS_OVERLAPPEDWINDOW|WS_VISIBLE, sx<0?0:sx, sy<0?0:sy, W, H, NULL, NULL, instance, NULL);
|
|
if (room.window == NULL) return LobbyRoomClosed;
|
|
RECT cl; GetClientRect(room.window, &cl);
|
|
int th = cl.bottom/40; if (th<16) th=16; if (th>24) th=24;
|
|
room.textFont = CreateFontA(-(th*3/2),0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,DEFAULT_PITCH|FF_MODERN,"Consolas");
|
|
room.titleFont = CreateFontA(-(th*2),0,0,0,FW_BOLD,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,DEFAULT_PITCH|FF_MODERN,"Consolas");
|
|
int row_h = cl.bottom/36; if (row_h<18) row_h=18; if (row_h>30) row_h=30;
|
|
int col_w = cl.right/4;
|
|
room.launchRect.left=(cl.right-col_w)/2; room.launchRect.right=room.launchRect.left+col_w;
|
|
room.launchRect.top=cl.bottom-6*row_h; room.launchRect.bottom=room.launchRect.top+2*row_h;
|
|
room.leaveRect.left=(cl.right-col_w/2)/2; room.leaveRect.right=room.leaveRect.left+col_w/2;
|
|
room.leaveRect.top=cl.bottom-3*row_h; room.leaveRect.bottom=cl.bottom-row_h;
|
|
gRoom = &room;
|
|
PublishMemberData();
|
|
SetForegroundWindow(room.window);
|
|
|
|
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 (room.closed) { outcome = LobbyRoomClosed; break; }
|
|
if (room.leaveClicked) {
|
|
SteamMatchmaking()->LeaveLobby(gLobby); gInLobby = false;
|
|
BTFrontEnd_SetHostedPilots(NULL, NULL, 0);
|
|
static char clr[] = "BT412HOSTPODS="; putenv(clr);
|
|
outcome = LobbyRoomLeft; break;
|
|
}
|
|
if (room.launchClicked) {
|
|
room.launchClicked = false;
|
|
room.memberCount = CollectMembers(room.members);
|
|
bool all_pub = true;
|
|
for (int i = 0; i < room.memberCount; ++i) if (!room.members[i].published) all_pub = false;
|
|
if (all_pub && room.memberCount >= 1) {
|
|
++gLastGoNonce;
|
|
char go[900]; sprintf(go, "%d:", gLastGoNonce);
|
|
for (int i = 0; i < room.memberCount; ++i) {
|
|
char e[96]; sprintf(e, "%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(e) < sizeof(go)) strcat(go, e);
|
|
}
|
|
SteamMatchmaking()->SetLobbyData(gLobby, kGoKey, go);
|
|
RegisterRoster(room.members, room.memberCount);
|
|
PrimeHostedMission(room.members, room.memberCount);
|
|
outcome = LobbyLaunchHost; break;
|
|
}
|
|
}
|
|
if (!IsOwner()) {
|
|
const char *go = SteamMatchmaking()->GetLobbyData(gLobby, kGoKey);
|
|
if (go && go[0]) {
|
|
int nonce = atoi(go);
|
|
if (nonce > gLastGoNonce) {
|
|
gLastGoNonce = nonce;
|
|
SteamNetTransport_SetEnginePorts(1501);
|
|
const char *c = strchr(go, ':'); c = c ? c+1 : go;
|
|
while (*c) {
|
|
char ip[32]; int cpp=0, gpp=0; unsigned __int64 sid=0; int n=0;
|
|
while (*c && *c!='|' && n<(int)sizeof(ip)-1) ip[n++]=*c++;
|
|
ip[n]='\0';
|
|
if (*c=='|') { cpp=atoi(++c); while (*c && *c!='|') ++c; }
|
|
if (*c=='|') { gpp=atoi(++c); while (*c && *c!='|' && *c!=';') ++c; }
|
|
if (*c=='|') sid=_strtoui64(++c, NULL, 10);
|
|
while (*c && *c!=';') ++c; if (*c==';') ++c;
|
|
if (ip[0] && cpp>0 && gpp>0) SteamNetTransport_RegisterPeer(ip, cpp, gpp, sid);
|
|
}
|
|
outcome = LobbyLaunchMember; break;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
} // namespace
|
|
|
|
//===========================================================================//
|
|
// Public API
|
|
//===========================================================================//
|
|
int BTLobby_Available() { return SteamNetTransport_GetFakeAddressString()[0] != '\0'; }
|
|
int BTLobby_InRoom() { return gInLobby ? 1 : 0; }
|
|
|
|
int BTLobby_Host(void *instance, void *main_window) {
|
|
gCallDone = false;
|
|
SteamAPICall_t call = SteamMatchmaking()->CreateLobby(k_ELobbyTypePublic, kMaxLobbyMembers);
|
|
gCalls.createResult.Set(call, &gCalls, &LobbyCalls::OnCreated);
|
|
if (!WaitForCall(15000)) return LobbyRoomLeft;
|
|
gLobby = gCallLobby; gInLobby = true; gLastGoNonce = 0;
|
|
SteamMatchmaking()->SetLobbyData(gLobby, kLobbyTagKey, "1");
|
|
SteamMatchmaking()->SetLobbyData(gLobby, kGoKey, "");
|
|
return RunRoom((HINSTANCE)instance, (HWND)main_window);
|
|
}
|
|
|
|
int BTLobby_Join(void *instance, void *main_window) {
|
|
gCallDone = false;
|
|
SteamMatchmaking()->AddRequestLobbyListStringFilter(kLobbyTagKey, "1", k_ELobbyComparisonEqual);
|
|
SteamMatchmaking()->AddRequestLobbyListDistanceFilter(k_ELobbyDistanceFilterWorldwide);
|
|
SteamAPICall_t call = SteamMatchmaking()->RequestLobbyList();
|
|
gCalls.listResult.Set(call, &gCalls, &LobbyCalls::OnList);
|
|
if (!WaitForCall(15000)) return LobbyRoomLeft;
|
|
CSteamID target = gCallLobby;
|
|
gCallDone = false;
|
|
call = SteamMatchmaking()->JoinLobby(target);
|
|
gCalls.enterResult.Set(call, &gCalls, &LobbyCalls::OnEnter);
|
|
if (!WaitForCall(15000)) return LobbyRoomLeft;
|
|
gLobby = gCallLobby; gInLobby = true;
|
|
const char *go = SteamMatchmaking()->GetLobbyData(gLobby, kGoKey);
|
|
gLastGoNonce = go ? atoi(go) : 0;
|
|
return RunRoom((HINSTANCE)instance, (HWND)main_window);
|
|
}
|
|
|
|
int BTLobby_Room(void *instance, void *main_window) {
|
|
if (!gInLobby) return LobbyRoomLeft;
|
|
return RunRoom((HINSTANCE)instance, (HWND)main_window);
|
|
}
|
|
|
|
void BTLobby_PushMissionResults() {
|
|
if (!gInLobby || !IsOwner() || BTLocalConsole_ResultCount() == 0) return;
|
|
SteamAPI_RunCallbacks();
|
|
char sheet[700]; sprintf(sheet, "%d:", gLastGoNonce);
|
|
for (int i = 0; i < BTLocalConsole_ResultCount(); ++i) {
|
|
int kills = 0, deaths = 0; BTLocalConsole_GetResult(i, &kills, &deaths);
|
|
const char *name = BTLocalConsole_GetResultName(i);
|
|
char e[96]; sprintf(e, "%d|%d|%d|%.30s;", i, kills, deaths, name ? name : "");
|
|
if (strlen(sheet)+strlen(e) < sizeof(sheet)) strcat(sheet, e);
|
|
}
|
|
SteamMatchmaking()->SetLobbyData(gLobby, kResultsKey, sheet);
|
|
gShownResultsNonce = gLastGoNonce;
|
|
}
|
|
|
|
void BTLobby_PullMissionResults() {
|
|
if (!gInLobby || IsOwner() || gLastGoNonce == gShownResultsNonce) return;
|
|
const char *sheet = NULL; DWORD deadline = GetTickCount() + 8000;
|
|
for (;;) {
|
|
SteamAPI_RunCallbacks();
|
|
sheet = SteamMatchmaking()->GetLobbyData(gLobby, kResultsKey);
|
|
if (sheet && sheet[0] && atoi(sheet) == gLastGoNonce) break;
|
|
if ((LONG)(GetTickCount() - deadline) >= 0) return;
|
|
Sleep(100);
|
|
}
|
|
gShownResultsNonce = gLastGoNonce;
|
|
BTLocalConsole_ClearResults();
|
|
const char *c = strchr(sheet, ':'); c = c ? c+1 : sheet;
|
|
while (*c) {
|
|
int slot = atoi(c), kills = 0, deaths = 0; char name[32] = "";
|
|
while (*c && *c!='|' && *c!=';') ++c;
|
|
if (*c=='|') { kills=atoi(++c); while (*c && *c!='|' && *c!=';') ++c; }
|
|
if (*c=='|') { deaths=atoi(++c); while (*c && *c!='|' && *c!=';') ++c; }
|
|
if (*c=='|') { ++c; int n=0; while (*c && *c!=';' && n<(int)sizeof(name)-1) name[n++]=*c++; name[n]='\0'; }
|
|
while (*c && *c!=';') ++c; if (*c==';') ++c;
|
|
BTLocalConsole_InjectResult(slot, kills, deaths, name);
|
|
}
|
|
}
|
|
|
|
#endif // BT412_STEAM
|