Patient walk-up: a pod at a dead relay WAITS for the session
User request after the first internet join attempt: 'should we modify
the program so it sits and retries with a waiting-for-session?' The
arcade model is a pod that waits for the operator, not one that
aborts.
- RelayRequestSeat: retry loop (2s dials, 30 min cap) with live status
into the join.bat console window (BTRelayWaitStatus: AttachConsole to
the parent cmd; silent without one) -- banner + progress dots, a
distinct 'game is FULL, waiting for a free seat' state, and 'seat
assigned -- joining!' on success. The unreachable MessageBox now
only fires at the 30-minute give-up.
- BT_RELAY=auto discovery: same patient loop ('searching for a game on
this network...'), LAN-only guidance in the give-up box.
Verified live: pod launched against a dead relay, waited 20+s, relay
started, pod seat-requested/registered/UDP-up automatically with no
user action.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
52ce7c58c8
commit
f9f230c62b
+171
-69
@@ -107,6 +107,31 @@ struct RelayUdpEnvelope
|
||||
unsigned int sequence;
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// BTRelayWaitStatus -- progress text into the join.bat window during the
|
||||
// patient walk-up wait. The exe is a GUI app with no window yet at seat-
|
||||
// request time; attach to the parent cmd console so the player can see what
|
||||
// the wait is doing. Quiet no-op without a parent console (double-clicked
|
||||
// exe).
|
||||
//
|
||||
void BTRelayWaitStatus(const char *text)
|
||||
{
|
||||
static HANDLE s_out = INVALID_HANDLE_VALUE;
|
||||
static int s_tried = 0;
|
||||
if (!s_tried)
|
||||
{
|
||||
s_tried = 1;
|
||||
AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
s_out = CreateFileA("CONOUT$", GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
||||
}
|
||||
if (s_out != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DWORD written = 0;
|
||||
WriteFile(s_out, text, (DWORD)strlen(text), &written, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
char GlobalEggFileName[80]; // NASTY HACK !!! should suffice till we get console up though
|
||||
|
||||
//struct RMREG //real mode registers structure
|
||||
@@ -352,20 +377,48 @@ L4NetworkManager::L4NetworkManager():
|
||||
// Zero-config for anyone on the operator's LAN (the 90s
|
||||
// arcade model: cabinets find the operator station).
|
||||
//
|
||||
if (!RelayDiscover(&relayConsoleAddress))
|
||||
//
|
||||
// PATIENT WALK-UP: keep probing until the operator starts
|
||||
// the session (status in the join_lan.bat window), 30 min cap.
|
||||
//
|
||||
{
|
||||
// Release Fail() is a bare abort() -- without this box a
|
||||
// player just sees a "crash" (field report 2026-07-18)
|
||||
MessageBoxA(NULL,
|
||||
"No game server answered on this network.\n\n"
|
||||
"join_lan.bat only works on the SAME network as the\n"
|
||||
"operator's machine. Joining over the internet?\n"
|
||||
"Use join.bat instead.\n\n"
|
||||
"On the operator's own network: make sure the\n"
|
||||
"operator has pressed Start Session, then try again.",
|
||||
"BattleTech -- can't find the game server",
|
||||
MB_OK | MB_ICONERROR);
|
||||
Fail("BT_RELAY=auto: no relay answered on this LAN\n");
|
||||
extern void BTRelayWaitStatus(const char *); // below
|
||||
Logical found = False;
|
||||
unsigned long give_up =
|
||||
GetTickCount() + 30UL * 60UL * 1000UL;
|
||||
int shown = 0;
|
||||
while (GetTickCount() < give_up)
|
||||
{
|
||||
if (RelayDiscover(&relayConsoleAddress))
|
||||
{
|
||||
found = True;
|
||||
break;
|
||||
}
|
||||
if (!shown)
|
||||
{
|
||||
BTRelayWaitStatus(
|
||||
"\nSearching for a game on this network ...\n"
|
||||
"(joins automatically when the operator "
|
||||
"starts the session;\n close this window to "
|
||||
"give up. Joining over the INTERNET? Use "
|
||||
"join.bat.)\n");
|
||||
shown = 1;
|
||||
}
|
||||
BTRelayWaitStatus(".");
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
// Release Fail() is a bare abort() -- without this
|
||||
// box a player just sees a "crash"
|
||||
MessageBoxA(NULL,
|
||||
"No game server answered on this network.\n\n"
|
||||
"join_lan.bat only works on the SAME network as\n"
|
||||
"the operator's machine. Joining over the\n"
|
||||
"internet? Use join.bat instead.",
|
||||
"BattleTech -- can't find the game server",
|
||||
MB_OK | MB_ICONERROR);
|
||||
Fail("BT_RELAY=auto: no relay answered on this LAN\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1384,72 +1437,121 @@ void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* H
|
||||
//
|
||||
Logical L4NetworkManager::RelayRequestSeat()
|
||||
{
|
||||
SOCKET seat_socket = RelayDialTcp(relayGameAddress, 60);
|
||||
if (seat_socket == INVALID_SOCKET)
|
||||
//
|
||||
// PATIENT WALK-UP (user request 2026-07-18): one bounded dial + abort
|
||||
// read as "it just unceremoniously crashes" when the operator had not
|
||||
// started the session yet. The arcade model is a pod that WAITS: keep
|
||||
// dialing with visible status until the session appears or a seat frees
|
||||
// up; the player gives up by closing the join.bat window. 30 min cap
|
||||
// so an abandoned window cannot camp a future session's seat forever.
|
||||
//
|
||||
char banner[256];
|
||||
sprintf(banner,
|
||||
"\nWaiting for the operator's session at %s:%d ...\n"
|
||||
"(this joins automatically when the session starts;\n"
|
||||
" close this window to give up)\n",
|
||||
inet_ntoa(relayGameAddress.sin_addr),
|
||||
(int)ntohs(relayGameAddress.sin_port) - 1);
|
||||
unsigned long give_up = GetTickCount() + 30UL * 60UL * 1000UL;
|
||||
int banner_shown = 0, full_shown = 0, dots = 0;
|
||||
|
||||
while (GetTickCount() < give_up)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
Logical got_seat = False;
|
||||
RelayTcpEnvelope request;
|
||||
request.route = RELAY_ROUTE_SEAT_REQUEST;
|
||||
request.length = 0;
|
||||
if (RelaySendAll(seat_socket, (const char *)&request, sizeof(request)))
|
||||
{
|
||||
char reply[sizeof(RelayTcpEnvelope) + 80];
|
||||
int have = 0;
|
||||
unsigned long deadline = GetTickCount() + 10000UL;
|
||||
while (GetTickCount() < deadline && !got_seat)
|
||||
SOCKET seat_socket = RelayDialTcp(relayGameAddress, 2);
|
||||
if (seat_socket == INVALID_SOCKET)
|
||||
{
|
||||
fd_set read_set;
|
||||
FD_ZERO(&read_set);
|
||||
FD_SET(seat_socket, &read_set);
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 200000;
|
||||
if (select(0, &read_set, NULL, NULL, &tv) != 1)
|
||||
if (!banner_shown)
|
||||
{
|
||||
continue;
|
||||
BTRelayWaitStatus(banner);
|
||||
banner_shown = 1;
|
||||
}
|
||||
int received = recv(seat_socket, reply + have,
|
||||
(int)sizeof(reply) - have, 0);
|
||||
if (received <= 0)
|
||||
BTRelayWaitStatus(".");
|
||||
if ((++dots % 30) == 0)
|
||||
{
|
||||
break;
|
||||
BTRelayWaitStatus("\n");
|
||||
}
|
||||
have += received;
|
||||
if (have < (int)sizeof(RelayTcpEnvelope))
|
||||
Sleep(2000);
|
||||
continue;
|
||||
}
|
||||
Logical got_seat = False;
|
||||
Logical roster_full = False;
|
||||
RelayTcpEnvelope request;
|
||||
request.route = RELAY_ROUTE_SEAT_REQUEST;
|
||||
request.length = 0;
|
||||
if (RelaySendAll(seat_socket, (const char *)&request, sizeof(request)))
|
||||
{
|
||||
char reply[sizeof(RelayTcpEnvelope) + 80];
|
||||
int have = 0;
|
||||
unsigned long deadline = GetTickCount() + 10000UL;
|
||||
while (GetTickCount() < deadline && !got_seat)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
RelayTcpEnvelope *envelope = (RelayTcpEnvelope *)reply;
|
||||
if (envelope->route == RELAY_ROUTE_SEAT_FULL)
|
||||
{
|
||||
DEBUG_STREAM << "[relay] seat request: ROSTER FULL\n"
|
||||
<< std::flush;
|
||||
break;
|
||||
}
|
||||
if (envelope->route != RELAY_ROUTE_SEAT_ASSIGN)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (have < (int)(sizeof(RelayTcpEnvelope) + envelope->length))
|
||||
{
|
||||
continue; // partial payload; keep reading
|
||||
}
|
||||
const char *tag = reply + sizeof(RelayTcpEnvelope) + 4;
|
||||
int tag_max = (int)envelope->length - 4;
|
||||
if (tag_max > 0 && tag_max < (int)sizeof(relaySelf))
|
||||
{
|
||||
memcpy(relaySelf, tag, tag_max);
|
||||
relaySelf[tag_max] = '\0';
|
||||
DEBUG_STREAM << "[relay] seat ASSIGNED: '" << relaySelf
|
||||
<< "'\n" << std::flush;
|
||||
got_seat = True;
|
||||
fd_set read_set;
|
||||
FD_ZERO(&read_set);
|
||||
FD_SET(seat_socket, &read_set);
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 200000;
|
||||
if (select(0, &read_set, NULL, NULL, &tv) != 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int received = recv(seat_socket, reply + have,
|
||||
(int)sizeof(reply) - have, 0);
|
||||
if (received <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
have += received;
|
||||
if (have < (int)sizeof(RelayTcpEnvelope))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
RelayTcpEnvelope *envelope = (RelayTcpEnvelope *)reply;
|
||||
if (envelope->route == RELAY_ROUTE_SEAT_FULL)
|
||||
{
|
||||
DEBUG_STREAM << "[relay] seat request: ROSTER FULL\n"
|
||||
<< std::flush;
|
||||
roster_full = True;
|
||||
break;
|
||||
}
|
||||
if (envelope->route != RELAY_ROUTE_SEAT_ASSIGN)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (have < (int)(sizeof(RelayTcpEnvelope) + envelope->length))
|
||||
{
|
||||
continue; // partial payload; keep reading
|
||||
}
|
||||
const char *tag = reply + sizeof(RelayTcpEnvelope) + 4;
|
||||
int tag_max = (int)envelope->length - 4;
|
||||
if (tag_max > 0 && tag_max < (int)sizeof(relaySelf))
|
||||
{
|
||||
memcpy(relaySelf, tag, tag_max);
|
||||
relaySelf[tag_max] = '\0';
|
||||
DEBUG_STREAM << "[relay] seat ASSIGNED: '" << relaySelf
|
||||
<< "'\n" << std::flush;
|
||||
got_seat = True;
|
||||
}
|
||||
}
|
||||
}
|
||||
closesocket(seat_socket);
|
||||
if (got_seat)
|
||||
{
|
||||
char joined[128];
|
||||
sprintf(joined, "\nSeat assigned (%s) -- joining!\n", relaySelf);
|
||||
BTRelayWaitStatus(joined);
|
||||
return True;
|
||||
}
|
||||
if (roster_full && !full_shown)
|
||||
{
|
||||
BTRelayWaitStatus(
|
||||
"\nThe game is FULL -- waiting for a seat to free up ...\n");
|
||||
full_shown = 1;
|
||||
banner_shown = 1; // suppress the banner after this
|
||||
}
|
||||
Sleep(3000);
|
||||
}
|
||||
closesocket(seat_socket);
|
||||
return got_seat;
|
||||
return False;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Reference in New Issue
Block a user