Steam gate: rejected players now get a MESSAGE BOX, not a silent quit

A failed join returns 1 to the FE, which QUITS the exe -- so every
rejection so far was a log line plus "the game just closed" (#68's exact
complaint). New LobbyNotice() = same text in the day log (flattened, still
greppable) + blocking MessageBox. Wired to every join-side bail:

- BUILD MISMATCH: when the version-filtered search is empty, probe once
  without the version filter; if a lobby IS up, the box names the host's
  build vs ours (lobby data rides the list result -- no join needed).
  Post-entry verify mismatch gets the same box.
- NO LOBBY FOUND: probe empty too -> plain no-lobby box naming our build.
- STEAM UNAVAILABLE: transport install failed.
- LEFT BEHIND: host launched without us (no token in btl4map).

Old exes still exit silently on rejection -- nothing shipped today can
add text to a binary players already have; the host's roster marker +
REJECT log line remain the operator's view of those.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-04 11:42:01 -05:00
co-authored by Claude Fable 5
parent edff8fdb96
commit 648f6b1675
+92 -9
View File
@@ -49,6 +49,50 @@ static void
}
#include <stdarg.h>
//
// A rejection the PLAYER must see goes through LobbyNotice: the same text
// lands in the day log (newlines flattened so the line stays greppable)
// AND in a blocking message box. These paths are synchronous FE flows --
// a bare return here QUITS the exe, and #68 taught us a player bounced
// with only a log line reports "the game just closed".
//
static void
LobbyNotice(const char *format, ...)
{
char text[512];
va_list arguments;
va_start(arguments, format);
_vsnprintf(text, sizeof(text) - 1, format, arguments);
text[sizeof(text) - 1] = 0;
va_end(arguments);
{
char flat[512];
int n = 0;
for (const char *s = text; *s && n < 510; ++s)
{
if (*s == '\n')
{
if (n > 0 && flat[n - 1] == ' ')
continue;
flat[n++] = ' ';
}
else
{
flat[n++] = *s;
}
}
flat[n] = 0;
LobbyLog("NOTICE: %s", flat);
}
WCHAR wide[512];
int n = 0;
for (const char *s = text; *s && n < 511; ++s)
wide[n++] = (WCHAR)*s;
wide[n] = 0;
MessageBoxW(NULL, wide, L"BATTLETECH -- STEAM LOBBY",
MB_OK | MB_ICONWARNING | MB_SETFOREGROUND);
}
//###########################################################################
// Synchronous Steam call-result helper (manual polling -- no callback
// template machinery in this C-style TU).
@@ -458,7 +502,9 @@ int
{
if (BTSteamNet_Install() != 0 || !BTSteamNet_Active())
{
LobbyLog("join: Steam transport unavailable");
LobbyNotice("STEAM UNAVAILABLE\n\n"
"Could not reach Steam.\n"
"Is Steam running and logged in?");
return -1;
}
@@ -477,9 +523,36 @@ int
LobbyMatchList_t::k_iCallback, 15000) != 0 ||
match_list.m_nLobbiesMatching == 0)
{
LobbyLog("join: no btl4 lobby found for build %s "
"(a lobby on a DIFFERENT build is invisible by design -- "
"host and joiners must run the same zip)", BT_VERSION_STRING);
//
// Nothing on OUR build. Probe once WITHOUT the version filter so
// the rejection is specific: "the host is on a different zip"
// beats "no lobby" when one is in fact up. Lobby data rides back
// with the list result, readable without joining. (Filters only
// apply to the next request, so the probe re-adds the game key.)
//
matchmaking->AddRequestLobbyListStringFilter(
"btl4", "1", k_ELobbyComparisonEqual);
call = matchmaking->RequestLobbyList();
memset(&match_list, 0, sizeof(match_list));
if (WaitApiCall(call, &match_list, sizeof(match_list),
LobbyMatchList_t::k_iCallback, 8000) == 0 &&
match_list.m_nLobbiesMatching > 0)
{
const char *host_ver = matchmaking->GetLobbyData(
matchmaking->GetLobbyByIndex(0), "btl4ver");
LobbyNotice("BUILD MISMATCH\n\n"
"A lobby is up, but the host runs build %s\n"
"and this machine runs build %s.\n\n"
"Everyone must run the same zip to play together.",
(host_ver && *host_ver) ? host_ver : "(an older build)",
BT_VERSION_STRING);
}
else
{
LobbyNotice("NO LOBBY FOUND\n\n"
"No BattleTech lobby is up right now.\n"
"(This machine runs build %s.)", BT_VERSION_STRING);
}
return -1;
}
@@ -502,12 +575,14 @@ int
const char *host_ver = matchmaking->GetLobbyData(currentLobby, "btl4ver");
if (host_ver == NULL || strcmp(host_ver, BT_VERSION_STRING) != 0)
{
LobbyLog("join: BUILD MISMATCH -- host runs %s, this machine runs %s; "
"leaving (both sides must run the same zip)",
(host_ver && *host_ver) ? host_ver : "<unstamped/older>",
BT_VERSION_STRING);
matchmaking->LeaveLobby(currentLobby);
currentLobby = CSteamID();
LobbyNotice("BUILD MISMATCH\n\n"
"The host runs build %s\n"
"and this machine runs build %s.\n\n"
"Everyone must run the same zip to play together.",
(host_ver && *host_ver) ? host_ver : "(an older build)",
BT_VERSION_STRING);
return -1;
}
}
@@ -548,7 +623,15 @@ int
LobbyLog("join: my token [%s], map [%s]", my_token_out, steam_map_out);
if (my_token_out[0] == 0)
{
result = -1; // the host's map is missing us
// The host launched without us -- no seat in the mission map.
// (Same-build clients can only hit this via a full lobby or a
// publish race now; version skew is gated before entry.)
LobbyNotice("LEFT BEHIND\n\n"
"The host launched the mission without this machine\n"
"(no seat in the mission map).\n\n"
"Rejoin on the next launch. If this repeats, compare\n"
"builds: this machine runs %s.", BT_VERSION_STRING);
result = -1;
}
}
SteamMatchmaking()->LeaveLobby(currentLobby);