//===========================================================================// // 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; } //===========================================================================// // THE MINICONSOLE MENU -- the in-game race setup that replaces the operator // console. A GDI-painted green-on-black terminal (RP412 RPL4FE pattern): the // catalogs render as clickable columns, a pilot-name edit box, and a LAUNCH // button; clicking LAUNCH fills a BTFeMission from the selections and writes // the egg. Runs in its own top-level window before the mission (front-end // mode), so no D3D is up yet. //===========================================================================// namespace { struct MenuEntry { const char *key; const char *name; }; const MenuEntry kMenuMaps[] = { { "grass", "Grasslands" }, { "cavern", "Cavern" }, { "rav", "Ravine" }, { "polar3", "Polar III" }, { "polar4", "Polar IV" }, { "arena1", "Arena I" }, { "arena2", "Arena II" }, { "dbase", "Database" }, }; const MenuEntry kMenuMechs[] = { { "bhk1", "Black Hawk" }, { "avatar", "Avatar" }, { "loki", "Loki" }, { "madcat", "Mad Cat" }, { "owens", "Owens" }, { "sunder", "Sunder" }, { "thor", "Thor" }, { "vulture","Vulture" }, }; const MenuEntry kMenuColors[] = { { "White","White" }, { "Red","Red" }, { "Blue","Blue" }, { "Green","Green" }, { "Yellow","Yellow" },{ "Orange","Orange" },{ "Purple","Purple" },{ "Cyan","Cyan" }, }; const MenuEntry kMenuTimes[] = { { "day","Day" }, { "night","Night" } }; const MenuEntry kMenuWeather[] = { { "clear","Clear" }, { "fog","Light Fog" }, { "soup","Heavy Fog" } }; struct LenEntry { int seconds; const char *name; }; const LenEntry kMenuLengths[] = { { 180,"3:00" }, { 300,"5:00" }, { 600,"10:00" }, { 900,"15:00" }, { 0,"Endless" } }; #define MENU_COUNT(t) ((int)(sizeof(t)/sizeof((t)[0]))) enum MenuGroup { GMap=0, GMech, GColor, GTime, GWeather, GLength, GLaunch, GCount }; const char *GroupTitle(int g) { switch (g) { case GMap: return "MAP"; case GMech: return "MECH"; case GColor: return "COLOR"; case GTime: return "TIME"; case GWeather: return "WEATHER"; case GLength: return "LENGTH"; } return ""; } int GroupSize(int g) { switch (g) { case GMap: return MENU_COUNT(kMenuMaps); case GMech: return MENU_COUNT(kMenuMechs); case GColor: return MENU_COUNT(kMenuColors); case GTime: return MENU_COUNT(kMenuTimes); case GWeather: return MENU_COUNT(kMenuWeather); case GLength: return MENU_COUNT(kMenuLengths); } return 0; } const char *ItemName(int g, int i) { switch (g) { case GMap: return kMenuMaps[i].name; case GMech: return kMenuMechs[i].name; case GColor: return kMenuColors[i].name; case GTime: return kMenuTimes[i].name; case GWeather: return kMenuWeather[i].name; case GLength: return kMenuLengths[i].name; case GLaunch: return "L A U N C H"; } return ""; } struct MenuItem { int group, index; RECT rect; }; struct MenuState { int selection[GCount]; MenuItem items[128]; int itemCount; HWND window, nameEdit; HFONT textFont, titleFont; HBRUSH editBrush; int launched, closed; }; MenuState *gMenu = NULL; const COLORREF kGreenBright = RGB(64, 255, 64); const COLORREF kGreenDim = RGB(24, 140, 24); const COLORREF kBlack = RGB(0, 0, 0); void AddGroupItems(MenuState *m, int group, int x, int *y, int row_h, int col_w) { int n = GroupSize(group); for (int i = 0; i < n; ++i) { MenuItem *it = &m->items[m->itemCount++]; it->group = group; it->index = i; it->rect.left = x; it->rect.top = *y; it->rect.right = x + col_w; it->rect.bottom = *y + row_h; *y += row_h; } *y += row_h; // gap + header room for the next group } void LayoutMenu(MenuState *m, int cw, int ch) { m->itemCount = 0; int row_h = ch / 26; if (row_h < 20) row_h = 20; if (row_h > 34) row_h = 34; int col_w = cw / 5; int col1 = cw / 12; int col2 = col1 + col_w + cw / 24; int col3 = col2 + col_w + cw / 24; int top = ch / 7; int y = top; AddGroupItems(m, GMap, col1, &y, row_h, col_w); AddGroupItems(m, GTime, col1, &y, row_h, col_w); AddGroupItems(m, GWeather, col1, &y, row_h, col_w); AddGroupItems(m, GLength, col1, &y, row_h, col_w); y = top; AddGroupItems(m, GMech, col2, &y, row_h, col_w); y = top + 2 * row_h; AddGroupItems(m, GColor, col3, &y, row_h, col_w); MenuItem *launch = &m->items[m->itemCount++]; launch->group = GLaunch; launch->index = 0; launch->rect.left = col3; launch->rect.top = ch - 3 * row_h; launch->rect.right = col3 + col_w; launch->rect.bottom = ch - row_h; if (m->nameEdit != NULL) MoveWindow(m->nameEdit, col3, top - row_h / 4, col_w, row_h, TRUE); } void PaintMenu(MenuState *m) { PAINTSTRUCT ps; HDC hdc = BeginPaint(m->window, &ps); RECT client; GetClientRect(m->window, &client); HDC mem = CreateCompatibleDC(hdc); HBITMAP surf = CreateCompatibleBitmap(hdc, client.right, client.bottom); HBITMAP oldSurf = (HBITMAP) SelectObject(mem, surf); FillRect(mem, &client, (HBRUSH) GetStockObject(BLACK_BRUSH)); SetBkMode(mem, TRANSPARENT); SelectObject(mem, m->titleFont); SetTextColor(mem, kGreenBright); RECT title = client; title.top = client.bottom / 26; title.bottom = title.top + client.bottom / 9; DrawTextA(mem, "B A T T L E T E C H - MISSION SETUP", -1, &title, DT_CENTER | DT_TOP | DT_SINGLELINE); SelectObject(mem, m->textFont); int prevGroup = -1; for (int i = 0; i < m->itemCount; ++i) { const MenuItem *it = &m->items[i]; if (it->group != prevGroup && it->group < GLaunch) { prevGroup = it->group; RECT h = it->rect; h.top -= (it->rect.bottom - it->rect.top); h.bottom = it->rect.top; SetTextColor(mem, kGreenBright); DrawTextA(mem, GroupTitle(it->group), -1, &h, DT_LEFT | DT_VCENTER | DT_SINGLELINE); } RECT row = it->rect; if (it->group == GLaunch) { HBRUSH b = CreateSolidBrush(kGreenBright); FrameRect(mem, &row, b); DeleteObject(b); SetTextColor(mem, kGreenBright); DrawTextA(mem, ItemName(it->group, it->index), -1, &row, DT_CENTER | DT_VCENTER | DT_SINGLELINE); continue; } int selected = (m->selection[it->group] == it->index); if (selected) { HBRUSH b = CreateSolidBrush(kGreenDim); FillRect(mem, &row, b); DeleteObject(b); SetTextColor(mem, kGreenBright); } else SetTextColor(mem, kGreenDim); RECT t = row; t.left += 8; DrawTextA(mem, ItemName(it->group, it->index), -1, &t, DT_LEFT | DT_VCENTER | DT_SINGLELINE); } // pilot-name header if (m->nameEdit != NULL) { RECT er; GetWindowRect(m->nameEdit, &er); POINT c = { er.left, er.top }; ScreenToClient(m->window, &c); RECT h; h.left = c.x; h.right = c.x + 300; h.bottom = c.y; h.top = c.y - 26; SetTextColor(mem, kGreenBright); DrawTextA(mem, "PILOT NAME", -1, &h, DT_LEFT | DT_VCENTER | DT_SINGLELINE); } BitBlt(hdc, 0, 0, client.right, client.bottom, mem, 0, 0, SRCCOPY); SelectObject(mem, oldSurf); DeleteObject(surf); DeleteDC(mem); EndPaint(m->window, &ps); } void MenuClick(MenuState *m, int x, int y) { for (int i = 0; i < m->itemCount; ++i) { const MenuItem *it = &m->items[i]; if (x >= it->rect.left && x < it->rect.right && y >= it->rect.top && y < it->rect.bottom) { if (it->group == GLaunch) { m->launched = 1; PostMessageA(m->window, WM_NULL, 0, 0); } else { m->selection[it->group] = it->index; InvalidateRect(m->window, NULL, FALSE); } return; } } } LRESULT CALLBACK MenuWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { MenuState *m = gMenu; switch (msg) { case WM_PAINT: if (m) { PaintMenu(m); return 0; } break; case WM_LBUTTONDOWN: if (m) { MenuClick(m, (int)(short)LOWORD(lp), (int)(short)HIWORD(lp)); return 0; } break; case WM_CTLCOLOREDIT: if (m) { SetTextColor((HDC)wp, kGreenBright); SetBkColor((HDC)wp, kBlack); return (LRESULT)m->editBrush; } break; case WM_CLOSE: if (m) m->closed = 1; return 0; case WM_DESTROY: return 0; } return DefWindowProcA(hwnd, msg, wp, lp); } } // namespace //---------------------------------------------------------------------------// // Run the menu; on LAUNCH build the egg + point the engine at it. Returns 1 // if a mission was launched, 0 if the player closed the menu (quit). //---------------------------------------------------------------------------// int BTFrontEnd_Run() { HINSTANCE instance = GetModuleHandleA(NULL); MenuState m; memset(&m, 0, sizeof(m)); m.selection[GLength] = 1; // default 5:00 gMenu = &m; static int classReg = 0; if (!classReg) { classReg = 1; WNDCLASSA wc; memset(&wc, 0, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = MenuWndProc; wc.hInstance = instance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); wc.lpszClassName = "BTFrontEnd"; RegisterClassA(&wc); } int W = 1000, H = 720; int sx = (GetSystemMetrics(SM_CXSCREEN) - W) / 2; int sy = (GetSystemMetrics(SM_CYSCREEN) - H) / 2; m.window = CreateWindowExA(0, "BTFrontEnd", "BattleTech", WS_OVERLAPPEDWINDOW | WS_VISIBLE, sx < 0 ? 0 : sx, sy < 0 ? 0 : sy, W, H, NULL, NULL, instance, NULL); if (m.window == NULL) { gMenu = NULL; return 0; } RECT client; GetClientRect(m.window, &client); int text_h = client.bottom / 32; if (text_h < 16) text_h = 16; if (text_h > 26) text_h = 26; m.textFont = CreateFontA(-text_h, 0,0,0, FW_NORMAL, 0,0,0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_MODERN, "Consolas"); m.titleFont = CreateFontA(-(text_h * 2), 0,0,0, FW_BOLD, 0,0,0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_MODERN, "Consolas"); m.editBrush = CreateSolidBrush(kBlack); m.nameEdit = CreateWindowExA(0, "EDIT", "Pilot", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 0,0,10,10, m.window, NULL, instance, NULL); SendMessageA(m.nameEdit, WM_SETFONT, (WPARAM) m.textFont, TRUE); SendMessageA(m.nameEdit, EM_SETLIMITTEXT, 22, 0); LayoutMenu(&m, client.right, client.bottom); InvalidateRect(m.window, NULL, TRUE); SetForegroundWindow(m.window); MSG msg; while (!m.launched && !m.closed) { BOOL r = GetMessageA(&msg, NULL, 0, 0); if (r <= 0) { m.closed = 1; break; } if (msg.message == WM_QUIT) m.closed = 1; TranslateMessage(&msg); DispatchMessageA(&msg); } // harvest the loadout char pilotName[24] = "Pilot"; if (!m.closed) { GetWindowTextA(m.nameEdit, pilotName, sizeof(pilotName) - 1); if (pilotName[0] == '\0') strcpy(pilotName, "Pilot"); } int launched = m.launched, closed = m.closed; int selMap = m.selection[GMap], selMech = m.selection[GMech], selColor = m.selection[GColor]; int selTime = m.selection[GTime], selWx = m.selection[GWeather], selLen = m.selection[GLength]; DestroyWindow(m.window); DeleteObject(m.textFont); DeleteObject(m.titleFont); DeleteObject(m.editBrush); gMenu = NULL; if (closed || !launched) return 0; // Build the mission from the selections. BTFeMission mission; BTFeMission_Default(&mission); strcpy(mission.map, kMenuMaps[selMap].key); strcpy(mission.timeOfDay, kMenuTimes[selTime].key); strcpy(mission.weather, kMenuWeather[selWx].key); mission.lengthSeconds = kMenuLengths[selLen].seconds; strcpy(mission.pilots[0].name, pilotName); strcpy(mission.pilots[0].vehicle, kMenuMechs[selMech].key); strcpy(mission.pilots[0].color, kMenuColors[selColor].key); const char *egg_path = "frontend.egg"; if (!BTFeMission_WriteEgg(&mission, egg_path)) return 0; L4Application::SetEggNotationFileName(egg_path); return 1; }