Network races: the local console marshals remote pods over the wire
The lobby-owner-as-console architecture, in-engine. RPL4CONSOLE gains RPL4LocalConsole_InstallNetworkRace: the owner pod switches to network mode and meshes like any pod (egg fed locally via FeedLocalEgg, which now opens the ConsoleOnly state gate), while the console tick also marshals REMOTE pods over NetTransport speaking the exact arcade protocol - egg chunks with 5s-retry-until-ACK, 1Hz state polling, RunMission once every pod stages at WaitingForLaunch, StopMission at expiry (remotes first, local pod holds until their EndMission scores land), score intake labeled with [pilots]-order names on the results screen. The front end builds the multi-pilot egg: RP412HOSTPODS lists member console channels (lobby stand-in; the Steam lobby feeds the same path), RP412HOSTPORT/RP412HOSTADDR set the owner side. Winsock Connect now redials with a fresh socket per attempt (a refused TCP socket is dead; the old loop reused it) bounded at 120s - needed whenever a peer boots after the caller, which is the normal Steam lobby launch order. Verified on loopback: member pod in -net, owner hosting from its menu; mesh completed both sides, 30s race, remote score collected over the wire (host 3), local stop after the drain, results screen shows both pilots by name in one process that returns to the menu. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+168
-8
@@ -161,6 +161,87 @@ namespace
|
||||
Logical gHavePersist = False;
|
||||
char gLastPilotName[24] = "Pilot";
|
||||
|
||||
// [pilots]-order names of the last launched race (owner first),
|
||||
// comma separated - the network console labels results with them
|
||||
char gLastPilotNamesCsv[256] = "";
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Multiplayer host mode (lobby stand-in): RP412HOSTPODS lists the
|
||||
// member pods' console channels ("ip[:port]" comma separated) and
|
||||
// the egg gains one pilot per member. RP412HOSTPORT is the
|
||||
// owner's console port (default 1501; game port is +1) and
|
||||
// RP412HOSTADDR the owner's mesh address (default 127.0.0.1 -
|
||||
// the Steam lobby supplies the FakeIP instead).
|
||||
//---------------------------------------------------------------
|
||||
enum { maxExtraPilots = 8 };
|
||||
struct ExtraPilot
|
||||
{
|
||||
char address[64]; // mesh (game port) address for [pilots]
|
||||
char name[32];
|
||||
const char *vehicle;
|
||||
const char *color;
|
||||
};
|
||||
|
||||
int CollectExtraPilots(const FEState *fe, ExtraPilot *extras, char *owner_address)
|
||||
{
|
||||
const char *host_pods = getenv("RP412HOSTPODS");
|
||||
if (host_pods == NULL || host_pods[0] == '\0')
|
||||
{
|
||||
return -1; // single player: standalone address
|
||||
}
|
||||
|
||||
int host_port = 1501;
|
||||
const char *port_override = getenv("RP412HOSTPORT");
|
||||
if (port_override != NULL && atoi(port_override) > 0)
|
||||
{
|
||||
host_port = atoi(port_override);
|
||||
}
|
||||
const char *host_address = getenv("RP412HOSTADDR");
|
||||
if (host_address == NULL || host_address[0] == '\0')
|
||||
{
|
||||
host_address = "127.0.0.1";
|
||||
}
|
||||
sprintf(owner_address, "%s:%d", host_address, host_port + 1);
|
||||
|
||||
int extra_count = 0;
|
||||
const char *cursor = host_pods;
|
||||
while (*cursor != '\0' && extra_count < maxExtraPilots)
|
||||
{
|
||||
char entry[64];
|
||||
int length = 0;
|
||||
while (cursor[length] != '\0' && cursor[length] != ',' &&
|
||||
length < (int) sizeof(entry) - 1)
|
||||
{
|
||||
entry[length] = cursor[length];
|
||||
++length;
|
||||
}
|
||||
entry[length] = '\0';
|
||||
cursor += length;
|
||||
if (*cursor == ',')
|
||||
{
|
||||
++cursor;
|
||||
}
|
||||
|
||||
int console_port = 1501;
|
||||
char *colon = strrchr(entry, ':');
|
||||
if (colon != NULL)
|
||||
{
|
||||
*colon = '\0';
|
||||
console_port = atoi(colon + 1);
|
||||
}
|
||||
|
||||
ExtraPilot *pilot = &extras[extra_count];
|
||||
sprintf(pilot->address, "%s:%d", entry, console_port + 1);
|
||||
sprintf(pilot->name, "PILOT %d", extra_count + 2);
|
||||
pilot->vehicle = kVehicles[
|
||||
(fe->selection[GroupVehicle] + extra_count + 1) % FE_COUNT(kVehicles)].key;
|
||||
pilot->color = kColors[
|
||||
(fe->selection[GroupColor] + extra_count + 1) % FE_COUNT(kColors)].key;
|
||||
++extra_count;
|
||||
}
|
||||
return extra_count;
|
||||
}
|
||||
|
||||
const COLORREF kGreenBright = RGB(64, 255, 64);
|
||||
const COLORREF kGreenDim = RGB(24, 140, 24);
|
||||
const COLORREF kBlack = RGB(0, 0, 0);
|
||||
@@ -518,13 +599,23 @@ namespace
|
||||
extern const char *kOrdinalsBlock;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Egg assembly (console RPMission.ToEggString, race scenario,
|
||||
// one local pilot)
|
||||
// Egg assembly (console RPMission.ToEggString, race scenario).
|
||||
// Single player uses the standalone address; a hosted network
|
||||
// race lists the owner first plus one pilot per member pod.
|
||||
//---------------------------------------------------------------
|
||||
void BuildEggText(const FEState *fe, std::string &egg)
|
||||
{
|
||||
char line[256];
|
||||
|
||||
ExtraPilot extras[maxExtraPilots];
|
||||
char owner_address[64];
|
||||
strcpy(owner_address, kLocalPilotAddress);
|
||||
int extra_count = CollectExtraPilots(fe, extras, owner_address);
|
||||
if (extra_count < 0)
|
||||
{
|
||||
extra_count = 0; // single player
|
||||
}
|
||||
|
||||
egg += "[mission]\n";
|
||||
egg += "adventure=Red Planet\n";
|
||||
sprintf(line, "map=%s\n", kMaps[fe->selection[GroupMap]].key);
|
||||
@@ -544,10 +635,15 @@ namespace
|
||||
}
|
||||
|
||||
egg += "[pilots]\n";
|
||||
sprintf(line, "pilot=%s\n", kLocalPilotAddress);
|
||||
sprintf(line, "pilot=%s\n", owner_address);
|
||||
egg += line;
|
||||
for (int p = 0; p < extra_count; ++p)
|
||||
{
|
||||
sprintf(line, "pilot=%s\n", extras[p].address);
|
||||
egg += line;
|
||||
}
|
||||
|
||||
sprintf(line, "[%s]\n", kLocalPilotAddress);
|
||||
sprintf(line, "[%s]\n", owner_address);
|
||||
egg += line;
|
||||
egg += "hostType=0\n";
|
||||
egg += "dropzone=one\n";
|
||||
@@ -562,22 +658,75 @@ namespace
|
||||
sprintf(line, "badge=%s\n", kBadges[fe->selection[GroupBadge]].key);
|
||||
egg += line;
|
||||
|
||||
sprintf(line, "[largebitmap]\nbitmap=BitMap::Large::%s\n", fe->pilotName);
|
||||
for (int p = 0; p < extra_count; ++p)
|
||||
{
|
||||
sprintf(line, "[%s]\n", extras[p].address);
|
||||
egg += line;
|
||||
egg += "hostType=0\n";
|
||||
egg += "dropzone=one\n";
|
||||
sprintf(line, "name=%s\n", extras[p].name);
|
||||
egg += line;
|
||||
sprintf(line, "bitmapindex=%d\n", p + 2);
|
||||
egg += line;
|
||||
egg += "loadzones=1\n";
|
||||
sprintf(line, "vehicle=%s\n", extras[p].vehicle);
|
||||
egg += line;
|
||||
sprintf(line, "color=%s\n", extras[p].color);
|
||||
egg += line;
|
||||
egg += "badge=None\n";
|
||||
}
|
||||
|
||||
egg += "[largebitmap]\n";
|
||||
sprintf(line, "bitmap=BitMap::Large::%s\n", fe->pilotName);
|
||||
egg += line;
|
||||
sprintf(line, "[smallbitmap]\nbitmap=BitMap::Small::%s\n", fe->pilotName);
|
||||
for (int p = 0; p < extra_count; ++p)
|
||||
{
|
||||
sprintf(line, "bitmap=BitMap::Large::%s\n", extras[p].name);
|
||||
egg += line;
|
||||
}
|
||||
egg += "[smallbitmap]\n";
|
||||
sprintf(line, "bitmap=BitMap::Small::%s\n", fe->pilotName);
|
||||
egg += line;
|
||||
for (int p = 0; p < extra_count; ++p)
|
||||
{
|
||||
sprintf(line, "bitmap=BitMap::Small::%s\n", extras[p].name);
|
||||
egg += line;
|
||||
}
|
||||
|
||||
sprintf(line, "[BitMap::Large::%s]\n", fe->pilotName);
|
||||
egg += line;
|
||||
AppendNameBitmap(egg, 128, 32, fe->pilotName);
|
||||
egg += "width=8\n";
|
||||
|
||||
sprintf(line, "[BitMap::Small::%s]\n", fe->pilotName);
|
||||
egg += line;
|
||||
AppendNameBitmap(egg, 64, 16, fe->pilotName);
|
||||
egg += "width=4\n";
|
||||
|
||||
for (int p = 0; p < extra_count; ++p)
|
||||
{
|
||||
sprintf(line, "[BitMap::Large::%s]\n", extras[p].name);
|
||||
egg += line;
|
||||
AppendNameBitmap(egg, 128, 32, extras[p].name);
|
||||
egg += "width=8\n";
|
||||
sprintf(line, "[BitMap::Small::%s]\n", extras[p].name);
|
||||
egg += line;
|
||||
AppendNameBitmap(egg, 64, 16, extras[p].name);
|
||||
egg += "width=4\n";
|
||||
}
|
||||
|
||||
egg += kOrdinalsBlock;
|
||||
|
||||
// [pilots]-order names for the network console's results
|
||||
strcpy(gLastPilotNamesCsv, fe->pilotName);
|
||||
for (int p = 0; p < extra_count; ++p)
|
||||
{
|
||||
if (strlen(gLastPilotNamesCsv) + strlen(extras[p].name) + 2 <
|
||||
sizeof(gLastPilotNamesCsv))
|
||||
{
|
||||
strcat(gLastPilotNamesCsv, ",");
|
||||
strcat(gLastPilotNamesCsv, extras[p].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,6 +893,12 @@ int
|
||||
return gLastMissionSeconds;
|
||||
}
|
||||
|
||||
const char *
|
||||
RPL4FrontEnd_LastPilotNames()
|
||||
{
|
||||
return gLastPilotNamesCsv;
|
||||
}
|
||||
|
||||
//########################################################################
|
||||
//######################## RPL4FrontEnd_ShowResults ######################
|
||||
//########################################################################
|
||||
@@ -915,7 +1070,12 @@ Logical
|
||||
continue;
|
||||
}
|
||||
ResultRow *row = &rs.rows[rs.rowCount++];
|
||||
if (result_count == 1)
|
||||
const char *pilot_name = RPL4LocalConsole_GetResultName(host_ID);
|
||||
if (pilot_name != NULL)
|
||||
{
|
||||
sprintf(row->name, "%.30s", pilot_name);
|
||||
}
|
||||
else if (result_count == 1)
|
||||
{
|
||||
sprintf(row->name, "%s", gLastPilotName);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user