//===========================================================================// // btl4fe.cpp -- BT412 in-game front end: local mission-egg builder. // // See btl4fe.hpp. This module is self-contained (no engine types) so it can // build the egg text before the mission machinery exists; it only writes a // NotationFile the standard -egg path then loads. //===========================================================================// // WIN32_LEAN_AND_MEAN keeps from pulling the legacy winsock.h; // the engine chain (via l4app.hpp) brings winsock2.h, and the two collide. // GDI (used below for the plasma name bitmaps) is unaffected. Same preamble // as game/btl4main.cpp. #define WIN32_LEAN_AND_MEAN #define _WIN32_WINNT 0x0500 #define WINVER 0x0500 #include #include #include #include "btl4fe.hpp" #include "l4app.hpp" // L4Application::SetEggNotationFileName //---------------------------------------------------------------------------// // Catalogs -- the BattleTech content the console's RPConfig.xml named. // Maps: the 8 in BTL4.RES. Mechs: the base 8 + the known variants. //---------------------------------------------------------------------------// const char *const kBTMaps[] = { "cavern", "grass", "rav", "polar3", "polar4", "arena1", "arena2", "dbase", 0 }; const char *const kBTMechs[] = { "avatar", "bhk1", "loki", "madcat", "owens", "sunder", "thor", "vulture", "ava1", "lok1", "lok2", "mad1", "mad2", "own1", "snd1", "thr1", "vul1", "blkhawk", 0 }; const char *const kBTColors[] = { "White", "Red", "Blue", "Green", "Yellow", "Orange", "Purple", "Cyan", 0 }; int BTCatalogCount(const char *const *list) { int n = 0; while (list[n] != 0) ++n; return n; } //---------------------------------------------------------------------------// // The four rank-ordinal plasma bitmaps (1st..4th place), 128x32, MSB-first // hex, 4 pixels per nibble. These are static content -- the same graphics // the console shipped in every egg's [ordinals] block (verified against // content/MP.EGG). The pilot NAME bitmaps are GDI-rendered per mission. //---------------------------------------------------------------------------// namespace { const char *const kOrdinal[4][32] = { { // 1st "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "000038000003C000001FF00000000F00","0000F8000003C000003FF80000000F00", "0001F8000003C00000707C0000000F00","0001F8000003C00000603C0000000F00", "00007801FC0FF00000003C3DF807FF00","00007803FE0FF00000003C3FFC0FFF00", "00007803C703C00000003C3E3C1F0F00","000078078303C0000000783C1E1E0F00", "000078078003C0000000783C1E1E0F00","00007807C003C0000000F03C1E1E0F00", "00007807F003C0000001E03C1E1E0F00","00007803FC03C0000003C03C1E1E0F00", "00007801FE03C0000007803C1E1E0F00","000078007F03C000000F003C1E1E0F00", "000078001F03C000001E003C1E1E0F00","000078000F03C000003C003C1E1E0F00", "000078060F03C0000078003C1E1E0F00","000078071E03E0000078003C1E1F1F00", "00007803FE01F000007FFC3C1E0FFF00","00007801FC00F000007FFC3C1E07EF00", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", }, { // 2nd "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "001FFF0000003C0000003C03C0780000","001FFF0000003C0000007C03C0780000", "00000E0000003C000000FC03C0780000","00003C0000003C000001BC03C0780000", "0000700F3E1FFC000003BC0FF07BF000","0001E00F7E3FFC0000073C0FF07FF800", "0003800FFE7C3C0000063C03C07C7800","0007F80FFE783C00000C3C03C0783C00", "0007FE0F80783C0000183C03C0783C00","00001E0F00783C0000383C03C0783C00", "00000F0F00783C0000703C03C0783C00","00000F0F00783C00007FFF03C0783C00", "00000F0F00783C00007FFF03C0783C00","00000F0F00783C0000003C03C0783C00", "00000F0F00783C0000003C03C0783C00","00000F0F00783C0000003C03C0783C00", "00180F0F00783C0000003C03C0783C00","001C1F0F007C7C0000003C03E0783C00", "000FFE0F003FFC0000003C01F0783C00","0007FC0F001FBC0000003C00F0783C00", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", }, { // 3rd "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "001FFF03C07800000000FC03C0780000","001FFF03C07800000003FC03C0780000", "001E0003C07800000007C003C0780000","001E0003C0780000000F0003C0780000", "001E000FF07BF000000F000FF07BF000","001E000FF07FF800001E000FF07FF800", "001E0003C07C7800001E0003C07C7800","001FFC03C0783C00001EFC03C0783C00", "001FFE03C0783C00001FFE03C0783C00","00001F03C0783C00001F1F03C0783C00", "00000F03C0783C00001E0F03C0783C00","00000F03C0783C00001E0F03C0783C00", "00000F03C0783C00001E0F03C0783C00","00000F03C0783C00001E0F03C0783C00", "00000F03C0783C00001E0F03C0783C00","00000F03C0783C00001E0F03C0783C00", "00180F03C0783C00001E0F03C0783C00","001C1F03E0783C00001F1F03E0783C00", "000FFE01F0783C00000FFE01F0783C00","0007FC00F0783C000007FC00F0783C00", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", }, { // 4th "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "001FFF03C07800000007FC03C0780000","001FFF03C0780000000FFE03C0780000", "00000F03C0780000001F1F03C0780000","00000F03C0780000001E0F03C0780000", "00000F0FF07BF000001E0F0FF07BF000","00001F0FF07FF800001E0F0FF07FF800", "00001E03C07C7800001E0F03C07C7800","00003E03C0783C00001E0F03C0783C00", "00003C03C0783C00000F1E03C0783C00","00003C03C0783C000007FC03C0783C00", "00007803C0783C000007FC03C0783C00","00007803C0783C00000F1E03C0783C00", "00007803C0783C00001E0F03C0783C00","0000F003C0783C00001E0F03C0783C00", "0000F003C0783C00001E0F03C0783C00","0000F003C0783C00001E0F03C0783C00", "0000F003C0783C00001E0F03C0783C00","0000F003E0783C00000F1E03E0783C00", "0000F001F0783C00000FFE01F0783C00","0000F000F0783C000007FC00F0783C00", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", "00000000000000000000000000000000","00000000000000000000000000000000", }, }; //-------------------------------------------------------------------// // GDI-render `text` centered into a monochrome plasma bitmap and emit // it as `rows` lines of `width/4` hex chars, MSB-first (the exact // [BitMap] format). width/height are the plasma glyph box (128x32 for // the large name, 64x16 for the small). A top-down 1bpp DIB packs bit // 7 of byte 0 as the leftmost pixel -- the same bit order as the egg // hex -- so the readback is a direct nibble-to-hex conversion. //-------------------------------------------------------------------// void RenderNameBitmap(const char *text, int width, int height, FILE *out) { HDC screen = GetDC(NULL); HDC dc = CreateCompatibleDC(screen); ReleaseDC(NULL, screen); // Top-down monochrome DIB: negative height, biBitCount 1. struct { BITMAPINFOHEADER h; RGBQUAD c[2]; } bi; memset(&bi, 0, sizeof(bi)); bi.h.biSize = sizeof(BITMAPINFOHEADER); bi.h.biWidth = width; bi.h.biHeight = -height; // top-down bi.h.biPlanes = 1; bi.h.biBitCount = 1; bi.h.biCompression = BI_RGB; bi.c[0].rgbRed = bi.c[0].rgbGreen = bi.c[0].rgbBlue = 0; // index 0 = off bi.c[1].rgbRed = bi.c[1].rgbGreen = bi.c[1].rgbBlue = 255; // index 1 = lit void *bits = 0; HBITMAP dib = CreateDIBSection(dc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, 0); HGDIOBJ oldBmp = SelectObject(dc, dib); RECT rect = { 0, 0, width, height }; SetBkColor(dc, RGB(0, 0, 0)); SetTextColor(dc, RGB(255, 255, 255)); ExtTextOutA(dc, 0, 0, ETO_OPAQUE, &rect, "", 0, NULL); // clear to off // A small readable font that fits the glyph box height. HFONT font = CreateFontA( height >= 32 ? 24 : 12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, NONANTIALIASED_QUALITY, FF_DONTCARE | DEFAULT_PITCH, "Arial"); HGDIOBJ oldFont = SelectObject(dc, font); SetBkMode(dc, TRANSPARENT); SIZE sz; GetTextExtentPoint32A(dc, text, (int)strlen(text), &sz); int tx = (width - sz.cx) / 2; if (tx < 0) tx = 0; int ty = (height - sz.cy) / 2; if (ty < 0) ty = 0; TextOutA(dc, tx, ty, text, (int)strlen(text)); GdiFlush(); // Emit: each DIB row is width bits, padded to a 4-byte (32-bit) // boundary. The egg wants width/4 hex chars per row (no padding), // so read the first width bits and repack. const int strideBytes = ((width + 31) / 32) * 4; const unsigned char *base = (const unsigned char *)bits; for (int y = 0; y < height; ++y) { const unsigned char *row = base + (size_t)y * strideBytes; fprintf(out, "bitmap="); for (int nib = 0; nib < width / 4; ++nib) { int value = 0; for (int b = 0; b < 4; ++b) { int px = nib * 4 + b; int byte = px >> 3; int bit = 7 - (px & 7); // MSB-first if (row[byte] & (1 << bit)) value |= (1 << (3 - b)); } fputc("0123456789ABCDEF"[value], out); } fputc('\n', out); } SelectObject(dc, oldFont); DeleteObject(font); SelectObject(dc, oldBmp); DeleteObject(dib); DeleteDC(dc); } void WriteOrdinalBlock(FILE *out, int index /*1..4*/) { fprintf(out, "[Ordinal::BitMap::%d]\n", index); for (int r = 0; r < 32; ++r) fprintf(out, "bitmap=%s\n", kOrdinal[index - 1][r]); fprintf(out, "x=128\ny=32\nwidth=8\n"); } } //---------------------------------------------------------------------------// // Defaults: a solo race on grass, one local pilot in a Black Hawk. //---------------------------------------------------------------------------// void BTFeMission_Default(BTFeMission *mission) { memset(mission, 0, sizeof(*mission)); strcpy(mission->map, "grass"); strcpy(mission->scenario, "freeforall"); strcpy(mission->timeOfDay, "day"); strcpy(mission->weather, "clear"); mission->temperature = 27; mission->lengthSeconds = 600; mission->pilotCount = 1; BTFePilot *p = &mission->pilots[0]; strcpy(p->address, "200.0.0.96"); // solo local (matches DEV.EGG) strcpy(p->name, "Pilot"); strcpy(p->vehicle, "bhk1"); strcpy(p->color, "White"); strcpy(p->badge, "VGL"); strcpy(p->patch, "Yellow"); strcpy(p->dropzone, "one"); p->bitmapindex = 1; p->advancedDamage = 1; p->loadzones = 1; } //---------------------------------------------------------------------------// // Write the egg. //---------------------------------------------------------------------------// int BTFeMission_WriteEgg(const BTFeMission *mission, const char *path) { FILE *out = 0; if (fopen_s(&out, path, "wb") != 0 || out == 0) return 0; // [mission] fprintf(out, "[mission]\n"); fprintf(out, "adventure=BattleTech\n"); fprintf(out, "map=%s\n", mission->map); fprintf(out, "scenario=%s\n", mission->scenario); fprintf(out, "time=%s\n", mission->timeOfDay); fprintf(out, "weather=%s\n", mission->weather); fprintf(out, "temperature=%d\n", mission->temperature); fprintf(out, "length=%d\n", mission->lengthSeconds); // [ordinals] -- the four rank-place plasma graphics. fprintf(out, "[ordinals]\n"); for (int i = 1; i <= 4; ++i) fprintf(out, "bitmap=Ordinal::BitMap::%d\n", i); for (int i = 1; i <= 4; ++i) WriteOrdinalBlock(out, i); // [pilots] -- the ordered mesh roster. fprintf(out, "[pilots]\n"); for (int i = 0; i < mission->pilotCount; ++i) fprintf(out, "pilot=%s\n", mission->pilots[i].address); // Per-pilot loadout sections. for (int i = 0; i < mission->pilotCount; ++i) { const BTFePilot *p = &mission->pilots[i]; fprintf(out, "[%s]\n", p->address); fprintf(out, "hostType=0\n"); fprintf(out, "advancedDamage=%d\n", p->advancedDamage); fprintf(out, "loadzones=%d\n", p->loadzones); fprintf(out, "name=%s\n", p->name); fprintf(out, "bitmapindex=%d\n", p->bitmapindex); fprintf(out, "experience=expert\n"); fprintf(out, "badge=%s\n", p->badge); fprintf(out, "patch=%s\n", p->patch); fprintf(out, "role=Role::Default\n"); fprintf(out, "dropzone=%s\n", p->dropzone); fprintf(out, "vehicle=%s\n", p->vehicle); fprintf(out, "vehicleValue=1000\n"); fprintf(out, "color=%s\n", p->color); } // [largebitmap] / [smallbitmap] -- the plasma name readouts, one per // pilot, GDI-rendered from the pilot name. fprintf(out, "[largebitmap]\n"); for (int i = 0; i < mission->pilotCount; ++i) fprintf(out, "bitmap=BitMap::Large::%s\n", mission->pilots[i].name); for (int i = 0; i < mission->pilotCount; ++i) { fprintf(out, "[BitMap::Large::%s]\n", mission->pilots[i].name); RenderNameBitmap(mission->pilots[i].name, 128, 32, out); fprintf(out, "x=128\ny=32\nwidth=8\n"); } fprintf(out, "[smallbitmap]\n"); for (int i = 0; i < mission->pilotCount; ++i) fprintf(out, "bitmap=BitMap::Small::%s\n", mission->pilots[i].name); for (int i = 0; i < mission->pilotCount; ++i) { fprintf(out, "[BitMap::Small::%s]\n", mission->pilots[i].name); RenderNameBitmap(mission->pilots[i].name, 64, 16, out); fprintf(out, "x=64\ny=16\nwidth=4\n"); } // Role models (referenced by each pilot's role= field). fprintf(out, "[Role::Default]\n"); fprintf(out, "model=dfltrole\n"); fprintf(out, "[Role::NoReturn]\n"); fprintf(out, "model=noretun\n"); fclose(out); return 1; } //---------------------------------------------------------------------------// // Front-end mode entry. Builds the default solo egg into frontend.egg and // points the engine at it via the standard -egg path. The interactive menu // (catalog selection on-screen) and the marshal are layered on top of this. //---------------------------------------------------------------------------// int BTFrontEnd_Run() { BTFeMission mission; BTFeMission_Default(&mission); const char *egg_path = "frontend.egg"; if (!BTFeMission_WriteEgg(&mission, egg_path)) return 0; L4Application::SetEggNotationFileName(egg_path); return 1; }