Net: Steam lobby (btl4lobby) + launch-mode wiring (Workstream C.2)
Port RP412's RPL4LOBBY to BT: an ISteamMatchmaking room stands in for the arcade Site-Management screen. Compiled both gates -- stubs without BT412_STEAM, the full room under it (both configs build + link clean). Lobby (game/reconstructed/btl4lobby.*): - Room screen (green-on-black, like the menu), owner = console. - Member data: FakeIP + fake console/game ports + persona + mech/color/badge (ip/cp/gp/nm/vh/cl/bd keys). - Nonced "go" launch roster -> SteamNetTransport_RegisterPeer for every peer. - Push/PullRaceResults over lobby data rebuild the shared score sheet. Front end (btl4fe.*): HOST/JOIN buttons when BTLobby_Available() (Steam transport up); the menu loop exits on a steam action and BTFrontEnd_Run routes the lobby outcome -- host builds the egg with every member as a [pilots] mesh entry (BTFrontEnd_SetHostedPilots), member returns launch mode 2. Results screen prefers the marshal's collated result names. Marshal (btl4console.*): add GetResultName / ClearResults / InjectResult so the lobby owner can refill the sheet from the collated wire scores; Result gains a name field. WinMain (btl4main.cpp): install the Steam transport on BT412STEAM env (before the front end, so the lobby is offered); branch on BTFrontEnd_LastLaunchMode() -- host owns the marshal clock + joins the mesh, member enters as a network pod on :1501 (SetNetworkCommonFlatAddress + gConsoleLossEndsMission); Push (host) / Pull (member) results, then relaunch. CMake: btl4lobby.cpp joins bt410_l4; that lib gets BT412_STEAM + the Steamworks include under the gate. build-steam/ gitignored. Deferred (untestable here -- needs Steam + multiple machines): the host->member wire egg-feed marshal (InstallNetworkRace); a member currently waits for a console connection nothing supplies, so a live host+member race is blocked on it. The lobby object does not survive the per-mission relaunch. docs/STEAM-3-MACHINE-TEST.md for BT not yet authored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,11 +18,42 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "btl4fe.hpp"
|
||||
#include "btl4lobby.hpp" // BTLobby_Host/Join + LobbyOutcome
|
||||
#include "l4app.hpp" // L4Application::SetEggNotationFileName
|
||||
|
||||
// Mission length (seconds) chosen on the last LAUNCH; WinMain arms the marshal.
|
||||
static int gLastMissionSeconds = 300;
|
||||
static char gLastPilotName[24] = "Pilot";
|
||||
static char gLastVehicle[16] = "bhk1";
|
||||
static char gLastColor[16] = "White";
|
||||
static char gLastBadge[8] = "VGL";
|
||||
|
||||
// Lobby-fed hosted pilots (the owner's egg gains one [pilots] entry each).
|
||||
static FEHostedPilot gHostedPilots[8];
|
||||
static int gHostedPilotCount = 0;
|
||||
static char gHostedOwnerAddress[64] = "";
|
||||
|
||||
// Launch mode from the last LAUNCH: 0 single, 1 host (lobby), 2 member (lobby).
|
||||
static int gLastLaunchMode = 0;
|
||||
|
||||
void BTFrontEnd_GetLoadout(char *vehicle, char *color, char *badge)
|
||||
{
|
||||
strcpy(vehicle, gLastVehicle);
|
||||
strcpy(color, gLastColor);
|
||||
strcpy(badge, gLastBadge);
|
||||
}
|
||||
|
||||
void BTFrontEnd_SetHostedPilots(const char *owner_address,
|
||||
const FEHostedPilot *pilots, int count)
|
||||
{
|
||||
if (owner_address == 0 || count <= 0) { gHostedPilotCount = 0; gHostedOwnerAddress[0] = '\0'; return; }
|
||||
strncpy(gHostedOwnerAddress, owner_address, sizeof(gHostedOwnerAddress) - 1);
|
||||
if (count > 8) count = 8;
|
||||
for (int i = 0; i < count; ++i) gHostedPilots[i] = pilots[i];
|
||||
gHostedPilotCount = count;
|
||||
}
|
||||
|
||||
int BTFrontEnd_LastLaunchMode() { return gLastLaunchMode; }
|
||||
|
||||
//---------------------------------------------------------------------------//
|
||||
// Catalogs -- the BattleTech content the console's RPConfig.xml named.
|
||||
@@ -370,7 +401,7 @@ const LenEntry kMenuLengths[] = { { 180,"3:00" }, { 300,"5:00" }, { 600,"10:00"
|
||||
|
||||
#define MENU_COUNT(t) ((int)(sizeof(t)/sizeof((t)[0])))
|
||||
|
||||
enum MenuGroup { GMap=0, GMech, GColor, GTime, GWeather, GLength, GLaunch, GCount };
|
||||
enum MenuGroup { GMap=0, GMech, GColor, GTime, GWeather, GLength, GLaunch, GHost, GJoin, GCount };
|
||||
|
||||
const char *GroupTitle(int g) {
|
||||
switch (g) {
|
||||
@@ -393,6 +424,8 @@ const char *ItemName(int g, int i) {
|
||||
case GColor: return kMenuColors[i].name; case GTime: return kMenuTimes[i].name;
|
||||
case GWeather: return kMenuWeather[i].name; case GLength: return kMenuLengths[i].name;
|
||||
case GLaunch: return "L A U N C H";
|
||||
case GHost: return "HOST STEAM RACE";
|
||||
case GJoin: return "JOIN STEAM RACE";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -406,6 +439,7 @@ struct MenuState {
|
||||
HFONT textFont, titleFont;
|
||||
HBRUSH editBrush;
|
||||
int launched, closed;
|
||||
int steamAction; // 0 none, 1 host, 2 join
|
||||
};
|
||||
MenuState *gMenu = NULL;
|
||||
|
||||
@@ -446,6 +480,20 @@ void LayoutMenu(MenuState *m, int cw, int ch) {
|
||||
launch->rect.left = col3; launch->rect.top = ch - 3 * row_h;
|
||||
launch->rect.right = col3 + col_w; launch->rect.bottom = ch - row_h;
|
||||
|
||||
// Steam HOST / JOIN buttons (only when the Steam transport is up).
|
||||
// (BTLobby_Available is declared globally in btl4lobby.hpp -- a block-scope
|
||||
// `extern` HERE would bind it to this anonymous namespace instead.)
|
||||
if (BTLobby_Available()) {
|
||||
MenuItem *host = &m->items[m->itemCount++];
|
||||
host->group = GHost; host->index = 0;
|
||||
host->rect.left = col3; host->rect.top = ch - 6 * row_h;
|
||||
host->rect.right = col3 + col_w; host->rect.bottom = ch - 5 * row_h;
|
||||
MenuItem *join = &m->items[m->itemCount++];
|
||||
join->group = GJoin; join->index = 0;
|
||||
join->rect.left = col3; join->rect.top = ch - (9 * row_h) / 2;
|
||||
join->rect.right = col3 + col_w; join->rect.bottom = ch - (7 * row_h) / 2;
|
||||
}
|
||||
|
||||
if (m->nameEdit != NULL)
|
||||
MoveWindow(m->nameEdit, col3, top - row_h / 4, col_w, row_h, TRUE);
|
||||
}
|
||||
@@ -476,7 +524,7 @@ void PaintMenu(MenuState *m) {
|
||||
DrawTextA(mem, GroupTitle(it->group), -1, &h, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
RECT row = it->rect;
|
||||
if (it->group == GLaunch) {
|
||||
if (it->group >= GLaunch) { // LAUNCH / HOST / JOIN -- framed buttons
|
||||
HBRUSH b = CreateSolidBrush(kGreenBright); FrameRect(mem, &row, b); DeleteObject(b);
|
||||
SetTextColor(mem, kGreenBright);
|
||||
DrawTextA(mem, ItemName(it->group, it->index), -1, &row, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
@@ -509,6 +557,9 @@ void MenuClick(MenuState *m, int x, int y) {
|
||||
if (x >= it->rect.left && x < it->rect.right && y >= it->rect.top && y < it->rect.bottom) {
|
||||
if (it->group == GLaunch) {
|
||||
m->launched = 1; PostMessageA(m->window, WM_NULL, 0, 0);
|
||||
} else if (it->group == GHost || it->group == GJoin) {
|
||||
m->steamAction = (it->group == GHost) ? 1 : 2;
|
||||
PostMessageA(m->window, WM_NULL, 0, 0);
|
||||
} else {
|
||||
m->selection[it->group] = it->index;
|
||||
InvalidateRect(m->window, NULL, FALSE);
|
||||
@@ -595,7 +646,7 @@ int BTFrontEnd_Run()
|
||||
SetForegroundWindow(m.window);
|
||||
|
||||
MSG msg;
|
||||
while (!m.launched && !m.closed) {
|
||||
while (!m.launched && !m.closed && !m.steamAction) {
|
||||
BOOL r = GetMessageA(&msg, NULL, 0, 0);
|
||||
if (r <= 0) { m.closed = 1; break; }
|
||||
// Enter = LAUNCH, Escape = quit -- intercepted at the loop level so
|
||||
@@ -615,17 +666,36 @@ int BTFrontEnd_Run()
|
||||
GetWindowTextA(m.nameEdit, pilotName, sizeof(pilotName) - 1);
|
||||
if (pilotName[0] == '\0') strcpy(pilotName, "Pilot");
|
||||
}
|
||||
int launched = m.launched, closed = m.closed;
|
||||
int launched = m.launched, closed = m.closed, steamAction = m.steamAction;
|
||||
int selMap = m.selection[GMap], selMech = m.selection[GMech], selColor = m.selection[GColor];
|
||||
int selTime = m.selection[GTime], selWx = m.selection[GWeather], selLen = m.selection[GLength];
|
||||
|
||||
// Remember the loadout so the lobby can publish it as member data.
|
||||
strncpy(gLastPilotName, pilotName, sizeof(gLastPilotName) - 1);
|
||||
strncpy(gLastVehicle, kMenuMechs[selMech].key, sizeof(gLastVehicle) - 1);
|
||||
strncpy(gLastColor, kMenuColors[selColor].key, sizeof(gLastColor) - 1);
|
||||
|
||||
DestroyWindow(m.window);
|
||||
DeleteObject(m.textFont); DeleteObject(m.titleFont); DeleteObject(m.editBrush);
|
||||
gMenu = NULL;
|
||||
|
||||
if (closed || !launched)
|
||||
if (closed)
|
||||
return 0;
|
||||
|
||||
// Steam HOST / JOIN: run the lobby room; act on its outcome.
|
||||
if (steamAction) {
|
||||
// BTLobby_Host/Join are declared in btl4lobby.hpp (global scope).
|
||||
int outcome = (steamAction == 1) ? BTLobby_Host(instance, 0) : BTLobby_Join(instance, 0);
|
||||
if (outcome == LobbyRoomClosed) return 0;
|
||||
if (outcome == LobbyLaunchMember) { gLastLaunchMode = 2; return 1; } // -net pod (no egg)
|
||||
if (outcome == LobbyLaunchHost) { gLastLaunchMode = 1; } // build the egg below
|
||||
else return BTFrontEnd_Run(); // left the room -> menu again
|
||||
}
|
||||
else {
|
||||
if (!launched) return 0;
|
||||
gLastLaunchMode = 0;
|
||||
}
|
||||
|
||||
// Build the mission from the selections.
|
||||
BTFeMission mission;
|
||||
BTFeMission_Default(&mission);
|
||||
@@ -634,7 +704,27 @@ int BTFrontEnd_Run()
|
||||
strcpy(mission.weather, kMenuWeather[selWx].key);
|
||||
mission.lengthSeconds = kMenuLengths[selLen].seconds;
|
||||
strcpy(mission.pilots[0].name, pilotName);
|
||||
strncpy(gLastPilotName, pilotName, sizeof(gLastPilotName) - 1);
|
||||
|
||||
// Host (lobby): append every lobby member as a [pilots] entry so the
|
||||
// owner's egg carries the whole mesh (drop zones one..eight round-robin).
|
||||
if (gHostedPilotCount > 0) {
|
||||
static const char *const dz[8] = { "one","two","three","four","five","six","seven","eight" };
|
||||
for (int i = 0; i < gHostedPilotCount && mission.pilotCount < 8; ++i) {
|
||||
BTFePilot *p = &mission.pilots[mission.pilotCount];
|
||||
memset(p, 0, sizeof(*p));
|
||||
strncpy(p->address, gHostedPilots[i].address, sizeof(p->address)-1);
|
||||
strncpy(p->name, gHostedPilots[i].name, sizeof(p->name)-1);
|
||||
strncpy(p->vehicle, gHostedPilots[i].vehicle[0]?gHostedPilots[i].vehicle:"bhk1", sizeof(p->vehicle)-1);
|
||||
strncpy(p->color, gHostedPilots[i].color[0]?gHostedPilots[i].color:"Red", sizeof(p->color)-1);
|
||||
strcpy(p->badge, "VGL"); strcpy(p->patch, "Yellow");
|
||||
strncpy(p->dropzone, dz[mission.pilotCount % 8], sizeof(p->dropzone)-1);
|
||||
p->bitmapindex = mission.pilotCount + 1; p->advancedDamage = 1; p->loadzones = 1;
|
||||
mission.pilotCount++;
|
||||
}
|
||||
// the owner is pilot 0; its mesh address is the FakeIP:1502
|
||||
if (gHostedOwnerAddress[0])
|
||||
sprintf(mission.pilots[0].address, "%s:1502", gHostedOwnerAddress);
|
||||
}
|
||||
strcpy(mission.pilots[0].vehicle, kMenuMechs[selMech].key);
|
||||
strcpy(mission.pilots[0].color, kMenuColors[selColor].key);
|
||||
|
||||
@@ -661,6 +751,7 @@ namespace { HWND gResultsWin = NULL; int gResultsDone = 0; }
|
||||
|
||||
extern int BTLocalConsole_ResultCount();
|
||||
extern int BTLocalConsole_GetResult(int index, int *kills, int *deaths);
|
||||
extern const char *BTLocalConsole_GetResultName(int index);
|
||||
|
||||
namespace {
|
||||
LRESULT CALLBACK ResultsWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
@@ -694,7 +785,10 @@ LRESULT CALLBACK ResultsWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
if (n <= 0) n = 1; // always show the local pilot line
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int k = 0, d = 0; BTLocalConsole_GetResult(i, &k, &d);
|
||||
char name[24]; if (i == 0) strncpy(name, gLastPilotName, sizeof(name)-1), name[sizeof(name)-1]=0;
|
||||
char name[24];
|
||||
const char *rn = BTLocalConsole_GetResultName(i); // set when the lobby collated the sheet
|
||||
if (rn && rn[0]) { strncpy(name, rn, sizeof(name)-1); name[sizeof(name)-1]=0; }
|
||||
else if (i == 0) { strncpy(name, gLastPilotName, sizeof(name)-1); name[sizeof(name)-1]=0; }
|
||||
else sprintf(name, "Pilot %d", i + 1);
|
||||
char kb[8], db[8]; sprintf(kb, "%d", k); sprintf(db, "%d", d);
|
||||
SetTextColor(mem, i == 0 ? gB : gD);
|
||||
|
||||
Reference in New Issue
Block a user