Menu (btl4fe, modeled on RP412 RP_L4/RPL4FE.cpp): full catalogs as always-visible clickable LISTS in three columns with friendly names -- 8 maps, the canonical 8 certified mechs, colors, times, weather, lengths, modes -- bright/dim green with a selection chevron, double-buffered paint, real EDIT controls for pilot name + LAN peers (green-on-black via WM_CTLCOLOREDIT), framed LAUNCH / HOST STEAM / JOIN STEAM buttons, and the loadout persisted across the per-mission relaunch (fe_last.ini, gitignored implicitly by *.ini? no -- plain file beside the eggs). ENTER/ESC kept. Lobby room (RP412 RPL4LOBBY look): pilot roster with loadouts (mech, color), framed L A U N C H M I S S I O N (owner) + LEAVE LOBBY buttons, clickable. Dist: FLAT layout (the RP412 convention) -- ONE btl4.exe entry point at the folder root beside the content tree (the shape TeslaConsole manages), DLLs + launchers at root, content flattened from git-tracked files. Verified: the staged flat tree boots the Mission Console zero-arg from its own root; the click-driven menu launches a full marshaled mission (row click -> length=60 in the egg, LAUNCH click -> ladder -> 41+ ticks, fe_last.ini written). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1075 lines
31 KiB
C++
1075 lines
31 KiB
C++
//###########################################################################
|
|
// btl4fe -- the miniconsole front end (BT_GLASS only; compiled only when
|
|
// the gate is on -- see CMakeLists.txt). Design: btl4fe.hpp.
|
|
//###########################################################################
|
|
|
|
#include "btl4fe.hpp"
|
|
#ifdef BT_STEAM
|
|
#include "btl4lobby.hpp"
|
|
#endif
|
|
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
//###########################################################################
|
|
// Catalogs -- the verified content values (context/content-archives.md map
|
|
// sweep; vehicle/color values from the shipped eggs). The egg writer
|
|
// accepts anything; these are the menu's cycle sets.
|
|
//###########################################################################
|
|
|
|
|
|
//###########################################################################
|
|
// The 4 static rank-place ordinal bitmaps -- byte-identical to the console
|
|
// eggs (content/MP.EGG [ordinals] block: "1st" "2nd" "3rd" "4th" as 128x32
|
|
// plasma pages).
|
|
//###########################################################################
|
|
|
|
static const char *kOrdinalBlock =
|
|
"[ordinals]\n"
|
|
"bitmap=Ordinal::BitMap::1\n"
|
|
"bitmap=Ordinal::BitMap::2\n"
|
|
"bitmap=Ordinal::BitMap::3\n"
|
|
"bitmap=Ordinal::BitMap::4\n"
|
|
"[Ordinal::BitMap::1]\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=000038000003C000001FF00000000F00\n"
|
|
"bitmap=0000F8000003C000003FF80000000F00\n"
|
|
"bitmap=0001F8000003C00000707C0000000F00\n"
|
|
"bitmap=0001F8000003C00000603C0000000F00\n"
|
|
"bitmap=00007801FC0FF00000003C3DF807FF00\n"
|
|
"bitmap=00007803FE0FF00000003C3FFC0FFF00\n"
|
|
"bitmap=00007803C703C00000003C3E3C1F0F00\n"
|
|
"bitmap=000078078303C0000000783C1E1E0F00\n"
|
|
"bitmap=000078078003C0000000783C1E1E0F00\n"
|
|
"bitmap=00007807C003C0000000F03C1E1E0F00\n"
|
|
"bitmap=00007807F003C0000001E03C1E1E0F00\n"
|
|
"bitmap=00007803FC03C0000003C03C1E1E0F00\n"
|
|
"bitmap=00007801FE03C0000007803C1E1E0F00\n"
|
|
"bitmap=000078007F03C000000F003C1E1E0F00\n"
|
|
"bitmap=000078001F03C000001E003C1E1E0F00\n"
|
|
"bitmap=000078000F03C000003C003C1E1E0F00\n"
|
|
"bitmap=000078060F03C0000078003C1E1E0F00\n"
|
|
"bitmap=000078071E03E0000078003C1E1F1F00\n"
|
|
"bitmap=00007803FE01F000007FFC3C1E0FFF00\n"
|
|
"bitmap=00007801FC00F000007FFC3C1E07EF00\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"x=128\n"
|
|
"y=32\n"
|
|
"width=8\n"
|
|
"[Ordinal::BitMap::2]\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=001FFF0000003C0000003C03C0780000\n"
|
|
"bitmap=001FFF0000003C0000007C03C0780000\n"
|
|
"bitmap=00000E0000003C000000FC03C0780000\n"
|
|
"bitmap=00003C0000003C000001BC03C0780000\n"
|
|
"bitmap=0000700F3E1FFC000003BC0FF07BF000\n"
|
|
"bitmap=0001E00F7E3FFC0000073C0FF07FF800\n"
|
|
"bitmap=0003800FFE7C3C0000063C03C07C7800\n"
|
|
"bitmap=0007F80FFE783C00000C3C03C0783C00\n"
|
|
"bitmap=0007FE0F80783C0000183C03C0783C00\n"
|
|
"bitmap=00001E0F00783C0000383C03C0783C00\n"
|
|
"bitmap=00000F0F00783C0000703C03C0783C00\n"
|
|
"bitmap=00000F0F00783C00007FFF03C0783C00\n"
|
|
"bitmap=00000F0F00783C00007FFF03C0783C00\n"
|
|
"bitmap=00000F0F00783C0000003C03C0783C00\n"
|
|
"bitmap=00000F0F00783C0000003C03C0783C00\n"
|
|
"bitmap=00000F0F00783C0000003C03C0783C00\n"
|
|
"bitmap=00180F0F00783C0000003C03C0783C00\n"
|
|
"bitmap=001C1F0F007C7C0000003C03E0783C00\n"
|
|
"bitmap=000FFE0F003FFC0000003C01F0783C00\n"
|
|
"bitmap=0007FC0F001FBC0000003C00F0783C00\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"x=128\n"
|
|
"y=32\n"
|
|
"width=8\n"
|
|
"[Ordinal::BitMap::3]\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=001FFF03C07800000000FC03C0780000\n"
|
|
"bitmap=001FFF03C07800000003FC03C0780000\n"
|
|
"bitmap=001E0003C07800000007C003C0780000\n"
|
|
"bitmap=001E0003C0780000000F0003C0780000\n"
|
|
"bitmap=001E000FF07BF000000F000FF07BF000\n"
|
|
"bitmap=001E000FF07FF800001E000FF07FF800\n"
|
|
"bitmap=001E0003C07C7800001E0003C07C7800\n"
|
|
"bitmap=001FFC03C0783C00001EFC03C0783C00\n"
|
|
"bitmap=001FFE03C0783C00001FFE03C0783C00\n"
|
|
"bitmap=00001F03C0783C00001F1F03C0783C00\n"
|
|
"bitmap=00000F03C0783C00001E0F03C0783C00\n"
|
|
"bitmap=00000F03C0783C00001E0F03C0783C00\n"
|
|
"bitmap=00000F03C0783C00001E0F03C0783C00\n"
|
|
"bitmap=00000F03C0783C00001E0F03C0783C00\n"
|
|
"bitmap=00000F03C0783C00001E0F03C0783C00\n"
|
|
"bitmap=00000F03C0783C00001E0F03C0783C00\n"
|
|
"bitmap=00180F03C0783C00001E0F03C0783C00\n"
|
|
"bitmap=001C1F03E0783C00001F1F03E0783C00\n"
|
|
"bitmap=000FFE01F0783C00000FFE01F0783C00\n"
|
|
"bitmap=0007FC00F0783C000007FC00F0783C00\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"x=128\n"
|
|
"y=32\n"
|
|
"width=8\n"
|
|
"[Ordinal::BitMap::4]\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=001FFF03C07800000007FC03C0780000\n"
|
|
"bitmap=001FFF03C0780000000FFE03C0780000\n"
|
|
"bitmap=00000F03C0780000001F1F03C0780000\n"
|
|
"bitmap=00000F03C0780000001E0F03C0780000\n"
|
|
"bitmap=00000F0FF07BF000001E0F0FF07BF000\n"
|
|
"bitmap=00001F0FF07FF800001E0F0FF07FF800\n"
|
|
"bitmap=00001E03C07C7800001E0F03C07C7800\n"
|
|
"bitmap=00003E03C0783C00001E0F03C0783C00\n"
|
|
"bitmap=00003C03C0783C00000F1E03C0783C00\n"
|
|
"bitmap=00003C03C0783C000007FC03C0783C00\n"
|
|
"bitmap=00007803C0783C000007FC03C0783C00\n"
|
|
"bitmap=00007803C0783C00000F1E03C0783C00\n"
|
|
"bitmap=00007803C0783C00001E0F03C0783C00\n"
|
|
"bitmap=0000F003C0783C00001E0F03C0783C00\n"
|
|
"bitmap=0000F003C0783C00001E0F03C0783C00\n"
|
|
"bitmap=0000F003C0783C00001E0F03C0783C00\n"
|
|
"bitmap=0000F003C0783C00001E0F03C0783C00\n"
|
|
"bitmap=0000F003E0783C00000F1E03E0783C00\n"
|
|
"bitmap=0000F001F0783C00000FFE01F0783C00\n"
|
|
"bitmap=0000F000F0783C000007FC00F0783C00\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"bitmap=00000000000000000000000000000000\n"
|
|
"x=128\n"
|
|
"y=32\n"
|
|
"width=8\n";
|
|
|
|
//###########################################################################
|
|
// GDI-rendered pilot-name plasma bitmaps.
|
|
//
|
|
// The console rendered each pilot's name into 128x32 (large) and 64x16
|
|
// (small) 1bpp plasma pages. We render with GDI into a mono bitmap and
|
|
// hex-encode: `bitmap=` lines carry 16 BYTES each (32 hex chars) -- one
|
|
// line per row for the large page (16-byte rows), TWO 8-byte rows per
|
|
// line for the small page -- byte-identical framing to MP.EGG.
|
|
//###########################################################################
|
|
|
|
static int
|
|
RenderNameBits(const char *name, int width, int height,
|
|
int font_height, unsigned char *bits /* width/8*height bytes */)
|
|
{
|
|
int stride = width / 8; // mono GDI stride for 64/128 px
|
|
HDC screen = GetDC(NULL);
|
|
HDC dc = CreateCompatibleDC(screen);
|
|
ReleaseDC(NULL, screen);
|
|
if (dc == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
HBITMAP bitmap = CreateBitmap(width, height, 1, 1, NULL);
|
|
HBITMAP old_bitmap = (HBITMAP)SelectObject(dc, bitmap);
|
|
HFONT font = CreateFontW(-font_height, 0, 0, 0, FW_BOLD, 0, 0, 0,
|
|
DEFAULT_CHARSET, 0, 0, NONANTIALIASED_QUALITY, 0, L"Arial");
|
|
HFONT old_font = (HFONT)SelectObject(dc, font);
|
|
|
|
RECT r = { 0, 0, width, height };
|
|
SetBkColor(dc, RGB(0, 0, 0));
|
|
SetTextColor(dc, RGB(255, 255, 255));
|
|
HBRUSH black = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
FillRect(dc, &r, black);
|
|
|
|
WCHAR wide[64];
|
|
int n = 0;
|
|
for (const char *s = name; *s && n < 63; ++s)
|
|
{
|
|
wide[n++] = (WCHAR)*s;
|
|
}
|
|
wide[n] = 0;
|
|
DrawTextW(dc, wide, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
|
|
GdiFlush();
|
|
int got = GetBitmapBits(bitmap, stride * height, bits);
|
|
|
|
SelectObject(dc, old_font);
|
|
DeleteObject(font);
|
|
SelectObject(dc, old_bitmap);
|
|
DeleteObject(bitmap);
|
|
DeleteDC(dc);
|
|
return (got == stride * height) ? 0 : -1;
|
|
}
|
|
|
|
static void
|
|
WriteBitmapPage(FILE *f, const char *page_name, const char *pilot_name,
|
|
int width, int height, int font_height)
|
|
{
|
|
unsigned char bits[16 * 32]; // worst case 128x32
|
|
memset(bits, 0, sizeof(bits));
|
|
RenderNameBits(pilot_name, width, height, font_height, bits);
|
|
|
|
fprintf(f, "[%s]\n", page_name);
|
|
int total = (width / 8) * height;
|
|
for (int off = 0; off < total; off += 16)
|
|
{
|
|
fprintf(f, "bitmap=");
|
|
for (int b = 0; b < 16; ++b)
|
|
{
|
|
fprintf(f, "%02X", bits[off + b]);
|
|
}
|
|
fprintf(f, "\n");
|
|
}
|
|
fprintf(f, "x=%d\ny=%d\nwidth=%d\n", width, height, width / 16);
|
|
}
|
|
|
|
//###########################################################################
|
|
// The egg writer
|
|
//###########################################################################
|
|
|
|
int
|
|
BTFeMission_WriteEgg(const BTFeMission *mission, const char *path)
|
|
{
|
|
FILE *f = fopen(path, "wt");
|
|
if (f == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
fprintf(f,
|
|
"[mission]\n"
|
|
"adventure=BattleTech\n"
|
|
"map=%s\n"
|
|
"scenario=freeforall\n"
|
|
"time=%s\n"
|
|
"weather=%s\n"
|
|
"temperature=%d\n"
|
|
"length=%d\n",
|
|
mission->map, mission->time, mission->weather,
|
|
mission->temperature, mission->lengthSeconds);
|
|
|
|
fputs(kOrdinalBlock, f);
|
|
|
|
fprintf(f, "[pilots]\n");
|
|
for (int p = 0; p < mission->pilotCount; ++p)
|
|
{
|
|
fprintf(f, "pilot=%s\n", mission->pilots[p].address);
|
|
}
|
|
for (int p = 0; p < mission->pilotCount; ++p)
|
|
{
|
|
const BTFePilot &pilot = mission->pilots[p];
|
|
fprintf(f,
|
|
"[%s]\n"
|
|
"hostType=0\n"
|
|
"advancedDamage=1\n"
|
|
"loadzones=1\n"
|
|
"name=%s\n"
|
|
"bitmapindex=%d\n"
|
|
"experience=expert\n"
|
|
"badge=VGL\n"
|
|
"patch=Yellow\n"
|
|
"role=Role::Default\n"
|
|
"dropzone=one\n"
|
|
"vehicle=%s\n"
|
|
"vehicleValue=1000\n"
|
|
"color=%s\n",
|
|
pilot.address, pilot.name, p + 1,
|
|
pilot.vehicle, pilot.color);
|
|
}
|
|
|
|
//
|
|
// Name plasma pages. The shipped eggs carry ONE large/small pair (the
|
|
// console personalized per pod); list every pilot's pair -- the engine
|
|
// resolves by name.
|
|
//
|
|
fprintf(f, "[largebitmap]\n");
|
|
for (int p = 0; p < mission->pilotCount; ++p)
|
|
{
|
|
fprintf(f, "bitmap=BitMap::Large::%s\n", mission->pilots[p].name);
|
|
}
|
|
for (int p = 0; p < mission->pilotCount; ++p)
|
|
{
|
|
char page[64];
|
|
sprintf(page, "BitMap::Large::%s", mission->pilots[p].name);
|
|
WriteBitmapPage(f, page, mission->pilots[p].name, 128, 32, 24);
|
|
}
|
|
fprintf(f, "[smallbitmap]\n");
|
|
for (int p = 0; p < mission->pilotCount; ++p)
|
|
{
|
|
fprintf(f, "bitmap=BitMap::Small::%s\n", mission->pilots[p].name);
|
|
}
|
|
for (int p = 0; p < mission->pilotCount; ++p)
|
|
{
|
|
char page[64];
|
|
sprintf(page, "BitMap::Small::%s", mission->pilots[p].name);
|
|
WriteBitmapPage(f, page, mission->pilots[p].name, 64, 16, 12);
|
|
}
|
|
|
|
fprintf(f,
|
|
"[Role::Default]\n"
|
|
"model=dfltrole\n"
|
|
"[Role::NoReturn]\n"
|
|
"model=noretun\n");
|
|
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
//###########################################################################
|
|
// The menu -- the RP412 front-end pattern (RP_L4/RPL4FE.cpp, per Cyd):
|
|
// full catalogs as always-visible clickable LISTS in three columns with
|
|
// friendly display names -- bright green = selected, dim green = the rest --
|
|
// a real EDIT control for the pilot name (and one for LAN peers), framed
|
|
// LAUNCH / HOST STEAM / JOIN STEAM buttons, double-buffered paint, and the
|
|
// player's loadout persisted across runs (fe_last.ini beside the eggs; our
|
|
// per-mission process relaunch would otherwise forget it).
|
|
// ENTER = launch, ESC = quit.
|
|
//###########################################################################
|
|
|
|
struct CatalogEntry
|
|
{
|
|
const char *key;
|
|
const char *name;
|
|
};
|
|
|
|
static const CatalogEntry kMaps[] =
|
|
{
|
|
{ "grass", "Grasslands" },
|
|
{ "cavern", "The Cavern" },
|
|
{ "rav", "The Ravine" },
|
|
{ "polar3", "Polar Station 3" },
|
|
{ "polar4", "Polar Station 4" },
|
|
{ "arena1", "Arena One" },
|
|
{ "arena2", "Arena Two" },
|
|
{ "dbase", "Desert Base" },
|
|
};
|
|
|
|
// The canonical 8 (ALL 18 ModelList names certified 2026-07-18; the menu
|
|
// offers the full mechs -- variants remain reachable via hand-built eggs).
|
|
static const CatalogEntry kVehicles[] =
|
|
{
|
|
{ "avatar", "Avatar" },
|
|
{ "blkhawk", "Black Hawk" },
|
|
{ "loki", "Loki" },
|
|
{ "madcat", "Mad Cat" },
|
|
{ "owens", "Owens" },
|
|
{ "sunder", "Sunder" },
|
|
{ "thor", "Thor" },
|
|
{ "vulture", "Vulture" },
|
|
};
|
|
|
|
static const CatalogEntry kColors[] =
|
|
{
|
|
{ "White", "White" }, { "Black", "Black" }, { "Crimson", "Crimson" },
|
|
};
|
|
|
|
static const CatalogEntry kTimes[] =
|
|
{
|
|
{ "day", "Day" }, { "night", "Night" },
|
|
{ "morning", "Morning" }, { "dusk", "Dusk" },
|
|
};
|
|
|
|
static const CatalogEntry kWeathers[] =
|
|
{
|
|
{ "clear", "Clear" }, { "fog", "Light Fog" }, { "soup", "Heavy Fog" },
|
|
};
|
|
|
|
struct LengthEntry
|
|
{
|
|
int seconds;
|
|
const char *name;
|
|
};
|
|
static const LengthEntry kLengths[] =
|
|
{
|
|
{ 60, "1:00" }, { 300, "5:00" }, { 600, "10:00" }, { 1200, "20:00" },
|
|
};
|
|
|
|
static const CatalogEntry kModes[] =
|
|
{
|
|
{ "solo", "SOLO MISSION" },
|
|
{ "raw", "RAW SOLO (endless)" },
|
|
{ "hostlan", "HOST LAN" },
|
|
{ "joinlan", "JOIN LAN" },
|
|
};
|
|
|
|
static const int kPorts[] = { 1501, 1601, 1701, 1801 };
|
|
|
|
#define COUNT(a) ((int)(sizeof(a)/sizeof((a)[0])))
|
|
|
|
enum MenuGroup
|
|
{
|
|
GroupMap = 0,
|
|
GroupTime,
|
|
GroupWeather,
|
|
GroupVehicle,
|
|
GroupLength,
|
|
GroupMode,
|
|
GroupColor,
|
|
GroupPort,
|
|
GroupLaunch,
|
|
GroupSteamHost,
|
|
GroupSteamJoin,
|
|
GroupCount
|
|
};
|
|
|
|
struct MenuItem
|
|
{
|
|
int group;
|
|
int index;
|
|
RECT rect;
|
|
};
|
|
|
|
struct MenuState
|
|
{
|
|
int selection[GroupCount];
|
|
MenuItem items[96];
|
|
int itemCount;
|
|
HWND window;
|
|
HWND nameEdit;
|
|
HWND peersEdit;
|
|
HFONT textFont;
|
|
HFONT titleFont;
|
|
HBRUSH editBrush;
|
|
int result; // 0 pending, 1 launch, -1 quit
|
|
int steamAction; // 0 none, 1 host lobby, 2 join lobby
|
|
};
|
|
|
|
static MenuState menu;
|
|
|
|
static const COLORREF kGreenBright = RGB(64, 255, 64);
|
|
static const COLORREF kGreenDim = RGB(24, 140, 24);
|
|
|
|
static int
|
|
GroupSize(int group)
|
|
{
|
|
switch (group)
|
|
{
|
|
case GroupMap: return COUNT(kMaps);
|
|
case GroupTime: return COUNT(kTimes);
|
|
case GroupWeather: return COUNT(kWeathers);
|
|
case GroupVehicle: return COUNT(kVehicles);
|
|
case GroupLength: return COUNT(kLengths);
|
|
case GroupMode: return COUNT(kModes);
|
|
case GroupColor: return COUNT(kColors);
|
|
case GroupPort: return COUNT(kPorts);
|
|
default: return 1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
GroupRowText(int group, int index, char *out /*64*/)
|
|
{
|
|
switch (group)
|
|
{
|
|
case GroupMap: strcpy(out, kMaps[index].name); break;
|
|
case GroupTime: strcpy(out, kTimes[index].name); break;
|
|
case GroupWeather: strcpy(out, kWeathers[index].name); break;
|
|
case GroupVehicle: strcpy(out, kVehicles[index].name); break;
|
|
case GroupLength: strcpy(out, kLengths[index].name); break;
|
|
case GroupMode: strcpy(out, kModes[index].name); break;
|
|
case GroupColor: strcpy(out, kColors[index].name); break;
|
|
case GroupPort: sprintf(out, "%d", kPorts[index]); break;
|
|
default: out[0] = 0; break;
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
GroupHeader(int group)
|
|
{
|
|
switch (group)
|
|
{
|
|
case GroupMap: return "MAP";
|
|
case GroupTime: return "TIME";
|
|
case GroupWeather: return "WEATHER";
|
|
case GroupVehicle: return "MECH";
|
|
case GroupLength: return "LENGTH";
|
|
case GroupMode: return "MODE";
|
|
case GroupColor: return "COLOR";
|
|
case GroupPort: return "NET PORT";
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
//
|
|
// Layout: three catalog columns + the button column.
|
|
// col1: MAP, TIME, WEATHER col2: MECH, LENGTH
|
|
// col3: MODE, COLOR, NET PORT col4: name/peers edits + buttons
|
|
//
|
|
enum
|
|
{
|
|
MenuClientW = 960,
|
|
MenuClientH = 560,
|
|
MenuRowH = 22,
|
|
MenuColW = 190,
|
|
MenuCol1X = 28,
|
|
MenuCol2X = 254,
|
|
MenuCol3X = 480,
|
|
MenuCol4X = 706,
|
|
MenuTopY = 64
|
|
};
|
|
|
|
static void
|
|
AddGroup(int group, int x, int *y)
|
|
{
|
|
*y += MenuRowH; // the header row
|
|
int count = GroupSize(group);
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
MenuItem *item = &menu.items[menu.itemCount++];
|
|
item->group = group;
|
|
item->index = i;
|
|
item->rect.left = x;
|
|
item->rect.top = *y;
|
|
item->rect.right = x + MenuColW;
|
|
item->rect.bottom = *y + MenuRowH;
|
|
*y += MenuRowH;
|
|
}
|
|
*y += MenuRowH / 2; // gap below the group
|
|
}
|
|
|
|
static void
|
|
AddButton(int group, int x, int y, int width, int height)
|
|
{
|
|
MenuItem *item = &menu.items[menu.itemCount++];
|
|
item->group = group;
|
|
item->index = 0;
|
|
item->rect.left = x;
|
|
item->rect.top = y;
|
|
item->rect.right = x + width;
|
|
item->rect.bottom = y + height;
|
|
}
|
|
|
|
static void
|
|
LayoutMenu()
|
|
{
|
|
menu.itemCount = 0;
|
|
int y;
|
|
y = MenuTopY; AddGroup(GroupMap, MenuCol1X, &y);
|
|
AddGroup(GroupTime, MenuCol1X, &y);
|
|
AddGroup(GroupWeather, MenuCol1X, &y);
|
|
y = MenuTopY; AddGroup(GroupVehicle, MenuCol2X, &y);
|
|
AddGroup(GroupLength, MenuCol2X, &y);
|
|
y = MenuTopY; AddGroup(GroupMode, MenuCol3X, &y);
|
|
AddGroup(GroupColor, MenuCol3X, &y);
|
|
AddGroup(GroupPort, MenuCol3X, &y);
|
|
AddButton(GroupLaunch, MenuCol4X, MenuClientH - 190, 220, 52);
|
|
#ifdef BT_STEAM
|
|
AddButton(GroupSteamHost, MenuCol4X, MenuClientH - 120, 220, 34);
|
|
AddButton(GroupSteamJoin, MenuCol4X, MenuClientH - 76, 220, 34);
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// fe_last.ini -- the persisted loadout (our process-relaunch model would
|
|
// otherwise forget it between missions; RP412 persists in-process).
|
|
//
|
|
static void
|
|
LoadPersisted(char *pilot_name, int name_capacity,
|
|
char *peers, int peers_capacity)
|
|
{
|
|
FILE *f = fopen("fe_last.ini", "rt");
|
|
if (f == NULL)
|
|
{
|
|
return;
|
|
}
|
|
char line[160];
|
|
while (fgets(line, sizeof(line), f))
|
|
{
|
|
char *newline = strchr(line, '\n');
|
|
if (newline) *newline = 0;
|
|
char *equals = strchr(line, '=');
|
|
if (equals == NULL) continue;
|
|
*equals = 0;
|
|
const char *value = equals + 1;
|
|
for (int g = 0; g < GroupCount; ++g)
|
|
{
|
|
if (_stricmp(line, GroupHeader(g)) == 0)
|
|
{
|
|
int v = atoi(value);
|
|
if (v >= 0 && v < GroupSize(g)) menu.selection[g] = v;
|
|
}
|
|
}
|
|
if (_stricmp(line, "NAME") == 0 && value[0])
|
|
{
|
|
strncpy(pilot_name, value, name_capacity - 1);
|
|
pilot_name[name_capacity - 1] = 0;
|
|
}
|
|
if (_stricmp(line, "PEERS") == 0)
|
|
{
|
|
strncpy(peers, value, peers_capacity - 1);
|
|
peers[peers_capacity - 1] = 0;
|
|
}
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
static void
|
|
SavePersisted(const char *pilot_name, const char *peers)
|
|
{
|
|
FILE *f = fopen("fe_last.ini", "wt");
|
|
if (f == NULL)
|
|
{
|
|
return;
|
|
}
|
|
for (int g = 0; g <= GroupPort; ++g)
|
|
{
|
|
fprintf(f, "%s=%d\n", GroupHeader(g), menu.selection[g]);
|
|
}
|
|
fprintf(f, "NAME=%s\nPEERS=%s\n", pilot_name, peers);
|
|
fclose(f);
|
|
}
|
|
|
|
static void
|
|
DrawMenuText(HDC dc, const char *text, RECT *rect, COLORREF color,
|
|
UINT format)
|
|
{
|
|
WCHAR wide[128];
|
|
int n = 0;
|
|
for (const char *s = text; *s && n < 127; ++s) wide[n++] = (WCHAR)*s;
|
|
wide[n] = 0;
|
|
SetTextColor(dc, color);
|
|
DrawTextW(dc, wide, -1, rect, format);
|
|
}
|
|
|
|
static void
|
|
PaintMenu(HWND window)
|
|
{
|
|
PAINTSTRUCT paint;
|
|
HDC screen = BeginPaint(window, &paint);
|
|
RECT client;
|
|
GetClientRect(window, &client);
|
|
|
|
// double-buffered (the RP412 pattern)
|
|
HDC dc = CreateCompatibleDC(screen);
|
|
HBITMAP backing = CreateCompatibleBitmap(screen, client.right, client.bottom);
|
|
HBITMAP old_backing = (HBITMAP)SelectObject(dc, backing);
|
|
|
|
HBRUSH background = CreateSolidBrush(RGB(0, 0, 0));
|
|
FillRect(dc, &client, background);
|
|
DeleteObject(background);
|
|
SetBkMode(dc, TRANSPARENT);
|
|
|
|
HFONT old_font = (HFONT)SelectObject(dc, menu.titleFont);
|
|
RECT title_rect = { MenuCol1X, 14, client.right - 24, 54 };
|
|
DrawMenuText(dc, "BATTLETECH -- MISSION CONSOLE", &title_rect,
|
|
kGreenBright, DT_LEFT | DT_TOP | DT_SINGLELINE);
|
|
|
|
SelectObject(dc, menu.textFont);
|
|
|
|
//
|
|
// Group headers (above each group's first row).
|
|
//
|
|
for (int i = 0; i < menu.itemCount; ++i)
|
|
{
|
|
const MenuItem &item = menu.items[i];
|
|
if (item.index != 0 || item.group > GroupPort)
|
|
{
|
|
continue;
|
|
}
|
|
RECT header = item.rect;
|
|
header.top -= MenuRowH;
|
|
header.bottom = item.rect.top;
|
|
DrawMenuText(dc, GroupHeader(item.group), &header, kGreenDim,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
|
|
//
|
|
// Catalog rows: bright = selected, dim = not.
|
|
//
|
|
for (int i = 0; i < menu.itemCount; ++i)
|
|
{
|
|
const MenuItem &item = menu.items[i];
|
|
if (item.group > GroupPort)
|
|
{
|
|
continue;
|
|
}
|
|
char text[64];
|
|
GroupRowText(item.group, item.index, text);
|
|
int selected = (menu.selection[item.group] == item.index);
|
|
RECT row = item.rect;
|
|
row.left += 14;
|
|
if (selected)
|
|
{
|
|
RECT marker = item.rect;
|
|
marker.right = marker.left + 14;
|
|
DrawMenuText(dc, ">", &marker, kGreenBright,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
DrawMenuText(dc, text, &row,
|
|
selected ? kGreenBright : kGreenDim,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
|
|
//
|
|
// Edit labels (the controls themselves are child windows).
|
|
//
|
|
RECT label = { MenuCol4X, MenuTopY, MenuCol4X + 220, MenuTopY + MenuRowH };
|
|
DrawMenuText(dc, "PILOT NAME", &label, kGreenDim,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
label.top = MenuTopY + 3 * MenuRowH;
|
|
label.bottom = label.top + MenuRowH;
|
|
DrawMenuText(dc, "LAN PEERS (host: ip:port,...)", &label, kGreenDim,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
|
|
//
|
|
// Buttons (framed, RP412 style).
|
|
//
|
|
HBRUSH frame = CreateSolidBrush(kGreenBright);
|
|
for (int i = 0; i < menu.itemCount; ++i)
|
|
{
|
|
const MenuItem &item = menu.items[i];
|
|
const char *text =
|
|
(item.group == GroupLaunch) ? "L A U N C H" :
|
|
(item.group == GroupSteamHost) ? "HOST STEAM LOBBY" :
|
|
(item.group == GroupSteamJoin) ? "JOIN STEAM LOBBY" : NULL;
|
|
if (text == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
RECT box = item.rect;
|
|
FrameRect(dc, &box, frame);
|
|
DrawMenuText(dc, text, &box, kGreenBright,
|
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
DeleteObject(frame);
|
|
|
|
RECT help = { MenuCol1X, client.bottom - 30, client.right - 24, client.bottom - 6 };
|
|
DrawMenuText(dc, "click to select ENTER = launch ESC = quit",
|
|
&help, kGreenDim, DT_LEFT | DT_TOP | DT_SINGLELINE);
|
|
|
|
SelectObject(dc, old_font);
|
|
BitBlt(screen, 0, 0, client.right, client.bottom, dc, 0, 0, SRCCOPY);
|
|
SelectObject(dc, old_backing);
|
|
DeleteObject(backing);
|
|
DeleteDC(dc);
|
|
EndPaint(window, &paint);
|
|
}
|
|
|
|
static LRESULT CALLBACK
|
|
MenuWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
switch (message)
|
|
{
|
|
case WM_PAINT:
|
|
PaintMenu(window);
|
|
return 0;
|
|
|
|
case WM_CTLCOLOREDIT:
|
|
//
|
|
// The edits render green-on-black like everything else.
|
|
//
|
|
SetTextColor((HDC)wparam, kGreenBright);
|
|
SetBkColor((HDC)wparam, RGB(6, 20, 6));
|
|
return (LRESULT)menu.editBrush;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
{
|
|
int x = (int)(short)LOWORD(lparam);
|
|
int y = (int)(short)HIWORD(lparam);
|
|
for (int i = 0; i < menu.itemCount; ++i)
|
|
{
|
|
const MenuItem &item = menu.items[i];
|
|
if (x < item.rect.left || x >= item.rect.right ||
|
|
y < item.rect.top || y >= item.rect.bottom)
|
|
{
|
|
continue;
|
|
}
|
|
if (item.group == GroupLaunch)
|
|
{
|
|
menu.result = 1;
|
|
}
|
|
else if (item.group == GroupSteamHost)
|
|
{
|
|
menu.steamAction = 1;
|
|
menu.result = 1;
|
|
}
|
|
else if (item.group == GroupSteamJoin)
|
|
{
|
|
menu.steamAction = 2;
|
|
menu.result = 1;
|
|
}
|
|
else
|
|
{
|
|
menu.selection[item.group] = item.index;
|
|
InvalidateRect(window, NULL, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
SetFocus(window); // clicks outside the edits take focus back
|
|
}
|
|
return 0;
|
|
|
|
case WM_KEYDOWN:
|
|
if (wparam == VK_RETURN)
|
|
{
|
|
menu.result = 1;
|
|
}
|
|
else if (wparam == VK_ESCAPE)
|
|
{
|
|
menu.result = -1;
|
|
}
|
|
return 0;
|
|
|
|
case WM_CLOSE:
|
|
menu.result = -1;
|
|
return 0;
|
|
}
|
|
return DefWindowProcW(window, message, wparam, lparam);
|
|
}
|
|
|
|
int
|
|
BTFrontEnd_Run(BTFeLaunchSpec *spec)
|
|
{
|
|
memset(spec, 0, sizeof(*spec));
|
|
memset(&menu, 0, sizeof(menu));
|
|
menu.selection[GroupLength] = 2; // 10:00, the console default
|
|
|
|
char pilot_name[32] = "Dev";
|
|
char peers[128] = "";
|
|
LoadPersisted(pilot_name, sizeof(pilot_name), peers, sizeof(peers));
|
|
LayoutMenu();
|
|
|
|
WNDCLASSW window_class;
|
|
memset(&window_class, 0, sizeof(window_class));
|
|
window_class.lpfnWndProc = MenuWndProc;
|
|
window_class.hInstance = GetModuleHandleW(NULL);
|
|
window_class.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
|
|
window_class.lpszClassName = L"BTFrontEndWnd";
|
|
RegisterClassW(&window_class);
|
|
|
|
RECT frame_rect = { 0, 0, MenuClientW, MenuClientH };
|
|
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
|
AdjustWindowRect(&frame_rect, style, FALSE);
|
|
menu.window = CreateWindowW(
|
|
L"BTFrontEndWnd", L"BattleTech - Mission Console",
|
|
style, 100, 80,
|
|
frame_rect.right - frame_rect.left, frame_rect.bottom - frame_rect.top,
|
|
NULL, NULL, GetModuleHandleW(NULL), NULL);
|
|
if (menu.window == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
menu.textFont = CreateFontW(-16, 0, 0, 0, FW_NORMAL, 0, 0, 0,
|
|
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, FIXED_PITCH, L"Consolas");
|
|
menu.titleFont = CreateFontW(-24, 0, 0, 0, FW_BOLD, 0, 0, 0,
|
|
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, FIXED_PITCH, L"Consolas");
|
|
menu.editBrush = CreateSolidBrush(RGB(6, 20, 6));
|
|
|
|
//
|
|
// The two edit controls (name; LAN peers for host mode).
|
|
//
|
|
menu.nameEdit = CreateWindowW(L"EDIT", L"",
|
|
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
|
|
MenuCol4X, MenuTopY + MenuRowH, 220, MenuRowH + 4,
|
|
menu.window, NULL, GetModuleHandleW(NULL), NULL);
|
|
menu.peersEdit = CreateWindowW(L"EDIT", L"",
|
|
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
|
|
MenuCol4X, MenuTopY + 4 * MenuRowH, 220, MenuRowH + 4,
|
|
menu.window, NULL, GetModuleHandleW(NULL), NULL);
|
|
SendMessageW(menu.nameEdit, WM_SETFONT, (WPARAM)menu.textFont, TRUE);
|
|
SendMessageW(menu.peersEdit, WM_SETFONT, (WPARAM)menu.textFont, TRUE);
|
|
SetWindowTextA(menu.nameEdit, pilot_name);
|
|
SetWindowTextA(menu.peersEdit, peers);
|
|
|
|
ShowWindow(menu.window, SW_SHOW);
|
|
SetForegroundWindow(menu.window);
|
|
|
|
MSG message;
|
|
while (menu.result == 0 && GetMessageW(&message, NULL, 0, 0))
|
|
{
|
|
//
|
|
// ENTER/ESC work with an edit focused too.
|
|
//
|
|
if (message.message == WM_KEYDOWN &&
|
|
(message.wParam == VK_RETURN || message.wParam == VK_ESCAPE))
|
|
{
|
|
menu.result = (message.wParam == VK_RETURN) ? 1 : -1;
|
|
continue;
|
|
}
|
|
TranslateMessage(&message);
|
|
DispatchMessageW(&message);
|
|
}
|
|
|
|
GetWindowTextA(menu.nameEdit, pilot_name, sizeof(pilot_name));
|
|
GetWindowTextA(menu.peersEdit, peers, sizeof(peers));
|
|
|
|
DestroyWindow(menu.window);
|
|
DeleteObject(menu.textFont);
|
|
DeleteObject(menu.titleFont);
|
|
DeleteObject(menu.editBrush);
|
|
|
|
if (menu.result != 1)
|
|
{
|
|
return 1; // quit
|
|
}
|
|
SavePersisted(pilot_name[0] ? pilot_name : "Dev", peers);
|
|
|
|
//
|
|
// Resolve the launch spec + write the egg.
|
|
//
|
|
int console_port = kPorts[menu.selection[GroupPort]];
|
|
BTFeMission mission;
|
|
memset(&mission, 0, sizeof(mission));
|
|
strcpy(mission.map, kMaps[menu.selection[GroupMap]].key);
|
|
strcpy(mission.time, kTimes[menu.selection[GroupTime]].key);
|
|
strcpy(mission.weather, kWeathers[menu.selection[GroupWeather]].key);
|
|
mission.temperature = 27;
|
|
mission.lengthSeconds = kLengths[menu.selection[GroupLength]].seconds;
|
|
mission.pilotCount = 1;
|
|
BTFePilot &self = mission.pilots[0];
|
|
strcpy(self.name, pilot_name[0] ? pilot_name : "Dev");
|
|
strcpy(self.vehicle, kVehicles[menu.selection[GroupVehicle]].key);
|
|
strcpy(self.color, kColors[menu.selection[GroupColor]].key);
|
|
|
|
spec->consolePort = console_port;
|
|
spec->missionSeconds = mission.lengthSeconds;
|
|
strcpy(spec->eggPath, "frontend.egg");
|
|
|
|
int mode = menu.selection[GroupMode];
|
|
#ifdef BT_STEAM
|
|
if (menu.steamAction == 1)
|
|
{
|
|
BTLobbyRoster roster;
|
|
if (BTLobby_HostAndRoom(self.name, self.vehicle, self.color,
|
|
&roster, spec->steamMyToken, sizeof(spec->steamMyToken),
|
|
spec->steamMap, sizeof(spec->steamMap)) != 0 ||
|
|
roster.memberCount == 0)
|
|
{
|
|
return 1; // cancelled / Steam unavailable
|
|
}
|
|
spec->mode = BTFeLaunchHostSteam;
|
|
mission.pilotCount = 0;
|
|
sprintf(spec->podList, "127.0.0.1:%d", console_port);
|
|
for (int m = 0; m < roster.memberCount && m < 8; ++m)
|
|
{
|
|
const BTLobbyMember &member = roster.members[m];
|
|
BTFePilot &pilot = mission.pilots[mission.pilotCount++];
|
|
strncpy(pilot.name, member.name, sizeof(pilot.name) - 1);
|
|
strncpy(pilot.vehicle,
|
|
member.vehicle[0] ? member.vehicle : kVehicles[0].key,
|
|
sizeof(pilot.vehicle) - 1);
|
|
strncpy(pilot.color,
|
|
member.color[0] ? member.color : kColors[0].key,
|
|
sizeof(pilot.color) - 1);
|
|
sprintf(pilot.address, "%s:%d",
|
|
member.fakeAddress, member.gamePort);
|
|
if (!member.isSelf)
|
|
{
|
|
char entry[64];
|
|
sprintf(entry, ",%s:%d",
|
|
member.fakeAddress, member.consolePort);
|
|
strcat(spec->podList, entry);
|
|
}
|
|
}
|
|
if (BTFeMission_WriteEgg(&mission, spec->eggPath) != 0)
|
|
{
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
if (menu.steamAction == 2)
|
|
{
|
|
if (BTLobby_JoinAndWait(self.name, self.vehicle, self.color,
|
|
spec->steamMyToken, sizeof(spec->steamMyToken),
|
|
spec->steamMap, sizeof(spec->steamMap)) != 0)
|
|
{
|
|
return 1;
|
|
}
|
|
spec->mode = BTFeLaunchJoinSteam;
|
|
spec->eggPath[0] = 0;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
switch (mode)
|
|
{
|
|
case 0: // SOLO MISSION: networked 1-pod, marshal feeds self
|
|
spec->mode = BTFeLaunchSolo;
|
|
sprintf(self.address, "127.0.0.1:%d", console_port + 1);
|
|
sprintf(spec->podList, "127.0.0.1:%d", console_port);
|
|
break;
|
|
|
|
case 1: // RAW SOLO: the plain -egg endless path
|
|
spec->mode = BTFeLaunchRawSolo;
|
|
strcpy(self.address, "127.0.0.1");
|
|
break;
|
|
|
|
case 2: // HOST LAN: marshal feeds self + the peers list
|
|
{
|
|
spec->mode = BTFeLaunchHostLan;
|
|
sprintf(self.address, "127.0.0.1:%d", console_port + 1);
|
|
sprintf(spec->podList, "127.0.0.1:%d", console_port);
|
|
char peers_copy[128];
|
|
strcpy(peers_copy, peers);
|
|
char *cursor = strtok(peers_copy, ", ");
|
|
while (cursor != NULL && mission.pilotCount < 8)
|
|
{
|
|
char host[48];
|
|
int port = 0;
|
|
const char *colon = strrchr(cursor, ':');
|
|
if (colon != NULL && (port = atoi(colon + 1)) > 0 &&
|
|
(int)(colon - cursor) < (int)sizeof(host))
|
|
{
|
|
memcpy(host, cursor, colon - cursor);
|
|
host[colon - cursor] = 0;
|
|
BTFePilot &peer = mission.pilots[mission.pilotCount];
|
|
sprintf(peer.name, "Pilot%d", mission.pilotCount + 1);
|
|
strcpy(peer.vehicle, kVehicles[
|
|
(menu.selection[GroupVehicle] + mission.pilotCount) % COUNT(kVehicles)].key);
|
|
strcpy(peer.color, kColors[
|
|
(menu.selection[GroupColor] + mission.pilotCount) % COUNT(kColors)].key);
|
|
sprintf(peer.address, "%s:%d", host, port + 1);
|
|
++mission.pilotCount;
|
|
char entry[64];
|
|
sprintf(entry, ",%s:%d", host, port);
|
|
strcat(spec->podList, entry);
|
|
}
|
|
cursor = strtok(NULL, ", ");
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: // JOIN LAN: plain -net pod; the host's marshal feeds us
|
|
spec->mode = BTFeLaunchJoinLan;
|
|
spec->eggPath[0] = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (BTFeMission_WriteEgg(&mission, spec->eggPath) != 0)
|
|
{
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|