//########################################################################### // 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 #include #include #include //########################################################################### // 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. //########################################################################### static const char *kMaps[] = { "grass", "cavern", "rav", "polar3", "polar4", "arena1", "arena2", "dbase" }; static const char *kTimes[] = { "day", "night", "morning", "dusk" }; static const char *kWeathers[] = { "clear", "fog", "rain", "snow" }; static const int kLengths[] = { 300, 600, 1200, 60 }; static const char *kVehicles[] = { "bhk1", "madcat", "ava1" }; static const char *kColors[] = { "White", "Black", "Crimson" }; static const char *kModes[] = { "SOLO MISSION (networked)", "RAW SOLO (-egg, endless)", "HOST LAN", "JOIN LAN", #ifdef BT_STEAM "HOST STEAM LOBBY", "JOIN STEAM LOBBY", #endif }; static const int kPorts[] = { 1501, 1601, 1701, 1801 }; #define COUNT(a) ((int)(sizeof(a)/sizeof((a)[0]))) //########################################################################### // 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 -- a green-on-black GDI catalog, own message loop (runs before // any engine init). Up/Down select, Left/Right (or click) cycle, type // into the text fields, ENTER launches, ESC quits. //########################################################################### enum MenuField { FieldMap = 0, FieldTime, FieldWeather, FieldLength, FieldVehicle, FieldColor, FieldName, FieldMode, FieldPort, FieldPeers, FieldLaunch, FieldCount }; struct MenuState { int mapIndex, timeIndex, weatherIndex, lengthIndex; int vehicleIndex, colorIndex, modeIndex, portIndex; char pilotName[32]; char peers[128]; int selected; int result; // 0 pending, 1 launch, -1 quit }; static MenuState menu; static void CycleField(int field, int direction) { switch (field) { case FieldMap: menu.mapIndex = (menu.mapIndex + direction + COUNT(kMaps)) % COUNT(kMaps); break; case FieldTime: menu.timeIndex = (menu.timeIndex + direction + COUNT(kTimes)) % COUNT(kTimes); break; case FieldWeather: menu.weatherIndex = (menu.weatherIndex + direction + COUNT(kWeathers)) % COUNT(kWeathers); break; case FieldLength: menu.lengthIndex = (menu.lengthIndex + direction + COUNT(kLengths)) % COUNT(kLengths); break; case FieldVehicle: menu.vehicleIndex = (menu.vehicleIndex + direction + COUNT(kVehicles)) % COUNT(kVehicles); break; case FieldColor: menu.colorIndex = (menu.colorIndex + direction + COUNT(kColors)) % COUNT(kColors); break; case FieldMode: menu.modeIndex = (menu.modeIndex + direction + COUNT(kModes)) % COUNT(kModes); break; case FieldPort: menu.portIndex = (menu.portIndex + direction + COUNT(kPorts)) % COUNT(kPorts); break; } } static void FieldLine(int field, char *out /*128*/) { switch (field) { case FieldMap: sprintf(out, "MAP < %s >", kMaps[menu.mapIndex]); break; case FieldTime: sprintf(out, "TIME < %s >", kTimes[menu.timeIndex]); break; case FieldWeather: sprintf(out, "WEATHER < %s >", kWeathers[menu.weatherIndex]); break; case FieldLength: sprintf(out, "LENGTH < %d s >", kLengths[menu.lengthIndex]); break; case FieldVehicle: sprintf(out, "MECH < %s >", kVehicles[menu.vehicleIndex]); break; case FieldColor: sprintf(out, "COLOR < %s >", kColors[menu.colorIndex]); break; case FieldName: sprintf(out, "PILOT [ %s_ ]", menu.pilotName); break; case FieldMode: sprintf(out, "MODE < %s >", kModes[menu.modeIndex]); break; case FieldPort: sprintf(out, "NET PORT < %d >", kPorts[menu.portIndex]); break; case FieldPeers: sprintf(out, "PEERS [ %s_ ] (host: ip:port,...)", menu.peers); break; case FieldLaunch: sprintf(out, " >>> LAUNCH <<<"); break; default: out[0] = 0; break; } } static void PaintMenu(HWND window) { PAINTSTRUCT paint; HDC dc = BeginPaint(window, &paint); RECT client; GetClientRect(window, &client); HBRUSH background = CreateSolidBrush(RGB(4, 12, 4)); FillRect(dc, &client, background); DeleteObject(background); SetBkMode(dc, TRANSPARENT); HFONT font = CreateFontW(-16, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, FIXED_PITCH, L"Consolas"); HFONT old_font = (HFONT)SelectObject(dc, font); SetTextColor(dc, RGB(90, 255, 90)); RECT title_rect = { 24, 16, client.right - 24, 44 }; DrawTextW(dc, L"BATTLETECH -- MISSION CONSOLE", -1, &title_rect, DT_LEFT | DT_TOP | DT_SINGLELINE); for (int field = 0; field < FieldCount; ++field) { char line[160]; FieldLine(field, line); WCHAR wide[160]; int n = 0; for (const char *s = line; *s && n < 159; ++s) { wide[n++] = (WCHAR)*s; } wide[n] = 0; int selected = (field == menu.selected); SetTextColor(dc, selected ? RGB(200, 255, 120) : RGB(70, 200, 70)); RECT row = { 40, 64 + field * 28, client.right - 24, 92 + field * 28 }; if (selected) { RECT marker = { 24, row.top, 40, row.bottom }; DrawTextW(dc, L">", -1, &marker, DT_LEFT | DT_TOP | DT_SINGLELINE); } DrawTextW(dc, wide, -1, &row, DT_LEFT | DT_TOP | DT_SINGLELINE); } SetTextColor(dc, RGB(40, 140, 40)); RECT help_rect = { 24, client.bottom - 52, client.right - 24, client.bottom - 8 }; DrawTextW(dc, L"UP/DOWN select LEFT/RIGHT or click = change type into [ ] fields\n" L"ENTER = launch ESC = quit", -1, &help_rect, DT_LEFT | DT_TOP); SelectObject(dc, old_font); DeleteObject(font); EndPaint(window, &paint); } static void TypeIntoField(int field, WPARAM ch) { char *buffer = (field == FieldName) ? menu.pilotName : (field == FieldPeers) ? menu.peers : NULL; int capacity = (field == FieldName) ? (int)sizeof(menu.pilotName) : (int)sizeof(menu.peers); if (buffer == NULL) { return; } int length = (int)strlen(buffer); if (ch == VK_BACK) { if (length > 0) { buffer[length - 1] = 0; } return; } if (ch >= 32 && ch < 127 && length < capacity - 1) { // // Pilot names become NotationFile page names -- keep them clean. // if (field == FieldName && !((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))) { return; } buffer[length] = (char)ch; buffer[length + 1] = 0; } } static LRESULT CALLBACK MenuWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { switch (message) { case WM_PAINT: PaintMenu(window); return 0; case WM_KEYDOWN: switch (wparam) { case VK_UP: menu.selected = (menu.selected + FieldCount - 1) % FieldCount; break; case VK_DOWN: menu.selected = (menu.selected + 1) % FieldCount; break; case VK_LEFT: CycleField(menu.selected, -1); break; case VK_RIGHT: CycleField(menu.selected, +1); break; case VK_RETURN: menu.result = 1; break; case VK_ESCAPE: menu.result = -1; break; case VK_BACK: TypeIntoField(menu.selected, VK_BACK); break; } InvalidateRect(window, NULL, FALSE); return 0; case WM_CHAR: if (wparam >= 32) { TypeIntoField(menu.selected, wparam); InvalidateRect(window, NULL, FALSE); } return 0; case WM_LBUTTONDOWN: { int y = (int)(short)HIWORD(lparam); int field = (y - 64) / 28; if (field >= 0 && field < FieldCount) { menu.selected = field; if (field == FieldLaunch) { menu.result = 1; } else { CycleField(field, +1); } InvalidateRect(window, NULL, FALSE); } } 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)); strcpy(menu.pilotName, "Dev"); menu.lengthIndex = 1; // 600 s, the console default 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); HWND window = CreateWindowW( L"BTFrontEndWnd", L"BattleTech - Mission Console", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 120, 120, 700, 480, NULL, NULL, GetModuleHandleW(NULL), NULL); if (window == NULL) { return -1; } ShowWindow(window, SW_SHOW); SetForegroundWindow(window); MSG message; while (menu.result == 0 && GetMessageW(&message, NULL, 0, 0)) { TranslateMessage(&message); DispatchMessageW(&message); } DestroyWindow(window); if (menu.result != 1) { return 1; // quit } // // Resolve the launch spec + write the egg. // int console_port = kPorts[menu.portIndex]; BTFeMission mission; memset(&mission, 0, sizeof(mission)); strcpy(mission.map, kMaps[menu.mapIndex]); strcpy(mission.time, kTimes[menu.timeIndex]); strcpy(mission.weather, kWeathers[menu.weatherIndex]); mission.temperature = 27; mission.lengthSeconds = kLengths[menu.lengthIndex]; mission.pilotCount = 1; BTFePilot &self = mission.pilots[0]; strcpy(self.name, menu.pilotName[0] ? menu.pilotName : "Dev"); strcpy(self.vehicle, kVehicles[menu.vehicleIndex]); strcpy(self.color, kColors[menu.colorIndex]); spec->consolePort = console_port; spec->missionSeconds = mission.lengthSeconds; strcpy(spec->eggPath, "frontend.egg"); switch (menu.modeIndex) { 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); // // Each peer "ip:consolePort" joins the roster with its GAME // port (console+1) and the marshal list with its console port. // char peers_copy[128]; strcpy(peers_copy, menu.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.vehicleIndex + mission.pilotCount) % COUNT(kVehicles)]); strcpy(peer.color, kColors[ (menu.colorIndex + mission.pilotCount) % COUNT(kColors)]); 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; case 3: // JOIN LAN: plain -net pod; the host's marshal feeds us spec->mode = BTFeLaunchJoinLan; spec->eggPath[0] = 0; // no egg written; host provides return 0; #ifdef BT_STEAM case 4: // HOST STEAM LOBBY: the room fills the roster (self first) { 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; // // Roster -> egg pilots (FAKE game addresses; self is pilot 1) // + the marshal pod list (self over TCP loopback, members at // their FAKE console addresses over the Steam wire). // 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], sizeof(pilot.vehicle) - 1); strncpy(pilot.color, member.color[0] ? member.color : kColors[0], 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); } } } break; case 5: // JOIN STEAM LOBBY: wait for GO, then a Steam-wire pod 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 default: return 1; } if (BTFeMission_WriteEgg(&mission, spec->eggPath) != 0) { return -1; } return 0; }