Closes the gap left by the football commit. Members publish their picks as lobby member data (tm/ps) alongside their loadout, and the owner publishes the scenario (sc) so members know when the picks matter. The lobby room turns into a team sheet: each member row shows their team and position, and MY TEAM / MY POSITION buttons cycle the local pick and republish it live, so everyone watches the sides fill out before the host launches. The egg builder honors explicit picks and only fills gaps: - pilots who chose a team get it; the rest are spread across the host team and one other so the sides stay balanced - a team with no volunteer runner promotes its first UNPICKED member, never overriding someone who chose crusher or blocker - if two pilots on a team both claim runner, the first keeps it and the second lines up - colors stay derived: runner in the team runner color, everyone else in the team color Verified: a three-pilot egg puts the host on crusher in team color with an unpicked teammate promoted to runner in runner color, the other side gets its own runner, and the teams/pilots blocks group correctly; the lobby room cycles picks and repaints the roster; both scenarios still pass their single-player regressions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1952 lines
53 KiB
C++
1952 lines
53 KiB
C++
#include "..\munga_l4\mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "rpl4fe.h"
|
|
#include "rpl4console.h"
|
|
#include "rpl4lobby.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string>
|
|
|
|
//########################################################################
|
|
// The Death Race catalog (from TeslaConsole's RedPlanet/RPConfig.xml;
|
|
// 'headmf' is excluded for the race scenario there).
|
|
//########################################################################
|
|
|
|
namespace
|
|
{
|
|
struct CatalogEntry
|
|
{
|
|
const char *key;
|
|
const char *name;
|
|
};
|
|
|
|
const CatalogEntry kMaps[] =
|
|
{
|
|
{ "wise", "Wiseguy's Wake" },
|
|
{ "lyzlane", "Lyz's Lane" },
|
|
{ "yip", "Yip's Yahoorama" },
|
|
{ "blade", "Blade's Edge" },
|
|
{ "otto", "Berserker's Bahnzai" },
|
|
{ "frstrm", "Firestorm's Fury" },
|
|
{ "burnt", "Burnt Cookies" },
|
|
{ "brewers", "Brewer's Bane" },
|
|
{ "pain", "Paingod's Passage" },
|
|
{ "headoff", "Freezemoon's Freeway" },
|
|
};
|
|
|
|
// Football excludes pain/headoff and adds the football build of
|
|
// Freezemoon's Freeway (RPConfig.xml per-scenario invalid lists).
|
|
const CatalogEntry kFootballMaps[] =
|
|
{
|
|
{ "wise", "Wiseguy's Wake" },
|
|
{ "lyzlane", "Lyz's Lane" },
|
|
{ "yip", "Yip's Yahoorama" },
|
|
{ "blade", "Blade's Edge" },
|
|
{ "otto", "Berserker's Bahnzai" },
|
|
{ "frstrm", "Firestorm's Fury" },
|
|
{ "burnt", "Burnt Cookies" },
|
|
{ "brewers", "Brewer's Bane" },
|
|
{ "headmf", "Freezemoon's Freeway" },
|
|
};
|
|
|
|
const CatalogEntry kScenarios[] =
|
|
{
|
|
{ "race", "Death Race" },
|
|
{ "football", "Football" },
|
|
};
|
|
|
|
// Two-color teams: crushers/blockers wear the team color, the
|
|
// runner wears the runner color (that is how you spot the ball).
|
|
struct TeamEntry
|
|
{
|
|
const char *key;
|
|
const char *name;
|
|
const char *teamColor;
|
|
const char *runnerColor;
|
|
};
|
|
|
|
const TeamEntry kTeams[] =
|
|
{
|
|
{ "Red/Pink", "Red / Pink", "Red", "Pink" },
|
|
{ "Blue/Aqua", "Blue / Aqua", "Blue", "Aqua" },
|
|
{ "Green/Yellow", "Green / Yellow", "Green", "Yellow" },
|
|
{ "Black/Purple", "Black / Purple", "Black", "Purple" },
|
|
};
|
|
|
|
const CatalogEntry kPositions[] =
|
|
{
|
|
{ "runner", "Runner" },
|
|
{ "crusher", "Crusher" },
|
|
{ "blocker", "Blocker" },
|
|
};
|
|
|
|
const CatalogEntry kVehicles[] =
|
|
{
|
|
{ "quark", "Quark" },
|
|
{ "lepton", "Lepton" },
|
|
{ "proton", "Proton" },
|
|
{ "bug", "Bug" },
|
|
{ "speck", "Speck" },
|
|
{ "blkspk", "Black Speck" },
|
|
{ "chigger", "Chigger" },
|
|
{ "flea", "Flea" },
|
|
{ "roach", "Roach" },
|
|
{ "skeeter", "Skeeter" },
|
|
{ "wasp", "Wasp" },
|
|
{ "barge", "Barge" },
|
|
{ "broccoli", "Screaming Broccoli" },
|
|
{ "burro", "Burro" },
|
|
{ "mule", "Mule" },
|
|
{ "bttlbrg", "Battle Barge" },
|
|
{ "gator", "Gator" },
|
|
{ "grunt", "Grunt" },
|
|
{ "vole", "Vole" },
|
|
{ "bull", "Bull" },
|
|
{ "tarntula", "Tarantula" },
|
|
{ "mantis", "Mantis" },
|
|
{ "ripper", "Ripper" },
|
|
{ "roadblk", "Road Block" },
|
|
{ "spitter", "Spitter" },
|
|
{ "puck", "Armadillo" },
|
|
{ "dragon", "Dragon" },
|
|
};
|
|
|
|
const CatalogEntry kColors[] =
|
|
{
|
|
{ "Red", "Red" }, { "Blue", "Blue" }, { "Green", "Green" },
|
|
{ "White", "White" }, { "Pink", "Pink" }, { "Aqua", "Aqua" },
|
|
{ "Yellow", "Yellow" }, { "Purple", "Purple" }, { "Black", "Black" },
|
|
};
|
|
|
|
const CatalogEntry kBadges[] =
|
|
{
|
|
{ "None", "None" }, { "Celtic", "Celtic" }, { "Desert", "Desert" },
|
|
{ "Flames", "Flames" }, { "Giraffe", "Giraffe" }, { "Hawaii", "Hawaii" },
|
|
{ "Korean", "Korean Camo" }, { "Lightning", "Lightning" },
|
|
{ "Razzle Dazzle", "Razzle Dazzle" }, { "Rising Sun", "Rising Sun" },
|
|
{ "Tiger Stripe", "Tiger Stripes" },
|
|
};
|
|
|
|
const CatalogEntry kTimes[] =
|
|
{
|
|
{ "day", "Day" }, { "night", "Night" },
|
|
};
|
|
|
|
const CatalogEntry kWeather[] =
|
|
{
|
|
{ "clear", "Clear" }, { "fog", "Light Fog" }, { "soup", "Heavy Fog" },
|
|
};
|
|
|
|
struct LengthEntry
|
|
{
|
|
int seconds; // 0 = endless (length line omitted)
|
|
const char *name;
|
|
};
|
|
|
|
const LengthEntry kLengths[] =
|
|
{
|
|
{ 0, "Endless" },
|
|
{ 180, "3:00" },
|
|
{ 300, "5:00" },
|
|
{ 480, "8:00" },
|
|
{ 600, "10:00" },
|
|
};
|
|
|
|
// Standalone pilot address, as used by TEST.EGG: single-user mode
|
|
// adopts the first pilot entry without resolving it.
|
|
const char kLocalPilotAddress[] = "200.0.0.255";
|
|
|
|
#define FE_COUNT(t) ((int)(sizeof(t)/sizeof((t)[0])))
|
|
|
|
//---------------------------------------------------------------
|
|
// Menu model
|
|
//---------------------------------------------------------------
|
|
enum FEGroup
|
|
{
|
|
GroupMap = 0,
|
|
GroupVehicle,
|
|
GroupColor,
|
|
GroupBadge,
|
|
GroupTime,
|
|
GroupWeather,
|
|
GroupLength,
|
|
GroupScenario,
|
|
GroupTeam, // football only
|
|
GroupPosition, // football only
|
|
GroupLaunch,
|
|
GroupSteamHost, // buttons, not selections (Steam builds only)
|
|
GroupSteamJoin,
|
|
GroupCount
|
|
};
|
|
|
|
Logical IsFootball(const int *selection)
|
|
{
|
|
return selection[GroupScenario] == 1;
|
|
}
|
|
|
|
const CatalogEntry *ActiveMaps(const int *selection, int *count)
|
|
{
|
|
if (IsFootball(selection))
|
|
{
|
|
*count = (int)(sizeof(kFootballMaps) / sizeof(kFootballMaps[0]));
|
|
return kFootballMaps;
|
|
}
|
|
*count = (int)(sizeof(kMaps) / sizeof(kMaps[0]));
|
|
return kMaps;
|
|
}
|
|
|
|
struct FEItem
|
|
{
|
|
int group;
|
|
int index;
|
|
RECT rect;
|
|
};
|
|
|
|
struct FEState
|
|
{
|
|
int selection[GroupCount];
|
|
char pilotName[24];
|
|
|
|
FEItem items[128];
|
|
int itemCount;
|
|
|
|
HWND menuWindow;
|
|
HWND nameEdit;
|
|
HFONT textFont;
|
|
HFONT titleFont;
|
|
HBRUSH editBrush;
|
|
Logical launched;
|
|
Logical closed;
|
|
int steamAction; // 1 = host lobby, 2 = join lobby
|
|
};
|
|
|
|
FEState *gFE = NULL;
|
|
int gLastMissionSeconds = 0;
|
|
|
|
// carried across races so cycling back to the menu keeps the
|
|
// player's loadout (and names the results screen's score rows)
|
|
int gPersistSelection[GroupCount];
|
|
Logical gHavePersist = False;
|
|
char gLastPilotName[24] = "Pilot";
|
|
|
|
// [pilots]-order names of the last launched race (owner first),
|
|
// comma separated - the network console labels results with them
|
|
char gLastPilotNamesCsv[256] = "";
|
|
|
|
//---------------------------------------------------------------
|
|
// Multiplayer host mode (lobby stand-in): RP412HOSTPODS lists the
|
|
// member pods' console channels ("ip[:port]" comma separated) and
|
|
// the egg gains one pilot per member. RP412HOSTPORT is the
|
|
// owner's console port (default 1501; game port is +1) and
|
|
// RP412HOSTADDR the owner's mesh address (default 127.0.0.1 -
|
|
// the Steam lobby supplies the FakeIP instead).
|
|
//---------------------------------------------------------------
|
|
enum { maxExtraPilots = 8 };
|
|
struct ExtraPilot
|
|
{
|
|
char address[64]; // mesh (game port) address for [pilots]
|
|
char name[32];
|
|
char vehicle[24];
|
|
char color[16];
|
|
char badge[24];
|
|
char team[32]; // football pick, "" = assign for them
|
|
char position[16];
|
|
};
|
|
|
|
// lobby-fed override (real personas + loadouts); empty = env parsing
|
|
FEHostedPilot gHostedPilots[maxExtraPilots];
|
|
int gHostedPilotCount = 0;
|
|
char gHostedOwnerAddress[64] = "";
|
|
|
|
int CollectExtraPilots(const FEState *fe, ExtraPilot *extras, char *owner_address)
|
|
{
|
|
if (gHostedPilotCount > 0)
|
|
{
|
|
sprintf(owner_address, "%s:1502", gHostedOwnerAddress);
|
|
for (int i = 0; i < gHostedPilotCount; ++i)
|
|
{
|
|
strcpy(extras[i].address, gHostedPilots[i].address);
|
|
strcpy(extras[i].name,
|
|
gHostedPilots[i].name[0] ? gHostedPilots[i].name : "Pilot");
|
|
strcpy(extras[i].vehicle,
|
|
gHostedPilots[i].vehicle[0] ? gHostedPilots[i].vehicle : "speck");
|
|
strcpy(extras[i].color,
|
|
gHostedPilots[i].color[0] ? gHostedPilots[i].color : "Red");
|
|
strcpy(extras[i].badge,
|
|
gHostedPilots[i].badge[0] ? gHostedPilots[i].badge : "None");
|
|
strcpy(extras[i].team, gHostedPilots[i].team);
|
|
strcpy(extras[i].position, gHostedPilots[i].position);
|
|
}
|
|
return gHostedPilotCount;
|
|
}
|
|
|
|
const char *host_pods = getenv("RP412HOSTPODS");
|
|
if (host_pods == NULL || host_pods[0] == '\0')
|
|
{
|
|
return -1; // single player: standalone address
|
|
}
|
|
|
|
int host_port = 1501;
|
|
const char *port_override = getenv("RP412HOSTPORT");
|
|
if (port_override != NULL && atoi(port_override) > 0)
|
|
{
|
|
host_port = atoi(port_override);
|
|
}
|
|
const char *host_address = getenv("RP412HOSTADDR");
|
|
if (host_address == NULL || host_address[0] == '\0')
|
|
{
|
|
host_address = "127.0.0.1";
|
|
}
|
|
sprintf(owner_address, "%s:%d", host_address, host_port + 1);
|
|
|
|
int extra_count = 0;
|
|
const char *cursor = host_pods;
|
|
while (*cursor != '\0' && extra_count < maxExtraPilots)
|
|
{
|
|
char entry[64];
|
|
int length = 0;
|
|
while (cursor[length] != '\0' && cursor[length] != ',' &&
|
|
length < (int) sizeof(entry) - 1)
|
|
{
|
|
entry[length] = cursor[length];
|
|
++length;
|
|
}
|
|
entry[length] = '\0';
|
|
cursor += length;
|
|
if (*cursor == ',')
|
|
{
|
|
++cursor;
|
|
}
|
|
|
|
int console_port = 1501;
|
|
char *colon = strrchr(entry, ':');
|
|
if (colon != NULL)
|
|
{
|
|
*colon = '\0';
|
|
console_port = atoi(colon + 1);
|
|
}
|
|
|
|
ExtraPilot *pilot = &extras[extra_count];
|
|
memset(pilot, 0, sizeof(*pilot));
|
|
sprintf(pilot->address, "%s:%d", entry, console_port + 1);
|
|
sprintf(pilot->name, "PILOT %d", extra_count + 2);
|
|
strcpy(pilot->vehicle, kVehicles[
|
|
(fe->selection[GroupVehicle] + extra_count + 1) % FE_COUNT(kVehicles)].key);
|
|
strcpy(pilot->color, kColors[
|
|
(fe->selection[GroupColor] + extra_count + 1) % FE_COUNT(kColors)].key);
|
|
strcpy(pilot->badge, "None");
|
|
++extra_count;
|
|
}
|
|
return extra_count;
|
|
}
|
|
|
|
const COLORREF kGreenBright = RGB(64, 255, 64);
|
|
const COLORREF kGreenDim = RGB(24, 140, 24);
|
|
const COLORREF kBlack = RGB(0, 0, 0);
|
|
|
|
//---------------------------------------------------------------
|
|
// Layout: three columns of lists + the launch button
|
|
//---------------------------------------------------------------
|
|
void AddGroupItems(
|
|
FEState *fe, int group, int count,
|
|
int x, int *y, int row_h, int width)
|
|
{
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
FEItem *item = &fe->items[fe->itemCount++];
|
|
item->group = group;
|
|
item->index = i;
|
|
item->rect.left = x;
|
|
item->rect.top = *y;
|
|
item->rect.right = x + width;
|
|
item->rect.bottom = *y + row_h;
|
|
*y += row_h;
|
|
}
|
|
*y += row_h; // gap below the group
|
|
}
|
|
|
|
void LayoutMenu(FEState *fe, int client_w, int client_h)
|
|
{
|
|
fe->itemCount = 0;
|
|
|
|
int row_h = client_h / 36;
|
|
if (row_h < 18) row_h = 18;
|
|
if (row_h > 30) row_h = 30;
|
|
|
|
int col_w = client_w / 5;
|
|
int col1 = client_w / 14;
|
|
int col2 = col1 + col_w + client_w / 28;
|
|
int col3 = col2 + col_w + client_w / 28;
|
|
int top = client_h / 7;
|
|
|
|
int y = top;
|
|
AddGroupItems(fe, GroupScenario, FE_COUNT(kScenarios), col1, &y, row_h, col_w);
|
|
int map_count;
|
|
ActiveMaps(fe->selection, &map_count);
|
|
AddGroupItems(fe, GroupMap, map_count, col1, &y, row_h, col_w);
|
|
AddGroupItems(fe, GroupTime, FE_COUNT(kTimes), col1, &y, row_h, col_w);
|
|
AddGroupItems(fe, GroupWeather, FE_COUNT(kWeather), col1, &y, row_h, col_w);
|
|
AddGroupItems(fe, GroupLength, FE_COUNT(kLengths), col1, &y, row_h, col_w);
|
|
|
|
y = top;
|
|
AddGroupItems(fe, GroupVehicle, FE_COUNT(kVehicles), col2, &y, row_h, col_w);
|
|
|
|
y = top + 2 * row_h; // leave room for the name edit + header
|
|
if (IsFootball(fe->selection))
|
|
{
|
|
AddGroupItems(fe, GroupTeam, FE_COUNT(kTeams), col3, &y, row_h, col_w);
|
|
AddGroupItems(fe, GroupPosition, FE_COUNT(kPositions), col3, &y, row_h, col_w);
|
|
}
|
|
else
|
|
{
|
|
AddGroupItems(fe, GroupColor, FE_COUNT(kColors), col3, &y, row_h, col_w);
|
|
AddGroupItems(fe, GroupBadge, FE_COUNT(kBadges), col3, &y, row_h, col_w);
|
|
}
|
|
|
|
// launch button
|
|
FEItem *launch = &fe->items[fe->itemCount++];
|
|
launch->group = GroupLaunch;
|
|
launch->index = 0;
|
|
launch->rect.left = col3;
|
|
launch->rect.top = client_h - 3 * row_h;
|
|
launch->rect.right = col3 + col_w;
|
|
launch->rect.bottom = client_h - row_h;
|
|
|
|
// Steam lobby buttons (only when the Steam wire is live)
|
|
if (RPL4Lobby_Available())
|
|
{
|
|
FEItem *host = &fe->items[fe->itemCount++];
|
|
host->group = GroupSteamHost;
|
|
host->index = 0;
|
|
host->rect.left = col3;
|
|
host->rect.top = client_h - 6 * row_h;
|
|
host->rect.right = col3 + col_w;
|
|
host->rect.bottom = client_h - 5 * row_h;
|
|
|
|
FEItem *join = &fe->items[fe->itemCount++];
|
|
join->group = GroupSteamJoin;
|
|
join->index = 0;
|
|
join->rect.left = col3;
|
|
join->rect.top = client_h - (9 * row_h) / 2;
|
|
join->rect.right = col3 + col_w;
|
|
join->rect.bottom = client_h - (7 * row_h) / 2;
|
|
}
|
|
|
|
// pilot name edit sits at the top of column 3
|
|
if (fe->nameEdit != NULL)
|
|
{
|
|
MoveWindow(fe->nameEdit,
|
|
col3, top - row_h / 4, col_w, row_h, TRUE);
|
|
}
|
|
}
|
|
|
|
const char *GroupTitle(int group)
|
|
{
|
|
switch (group)
|
|
{
|
|
case GroupScenario: return "SCENARIO";
|
|
case GroupMap: return "TRACK";
|
|
case GroupVehicle: return "VEHICLE";
|
|
case GroupColor: return "COLOR";
|
|
case GroupBadge: return "BADGE";
|
|
case GroupTeam: return "TEAM";
|
|
case GroupPosition: return "POSITION";
|
|
case GroupTime: return "TIME OF DAY";
|
|
case GroupWeather: return "WEATHER";
|
|
case GroupLength: return "GAME LENGTH";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// the map rows need the active scenario's list
|
|
const int *gItemNameSelection = NULL;
|
|
|
|
const char *ItemName(int group, int index)
|
|
{
|
|
switch (group)
|
|
{
|
|
case GroupScenario: return kScenarios[index].name;
|
|
case GroupMap:
|
|
if (gItemNameSelection != NULL && IsFootball(gItemNameSelection))
|
|
{
|
|
return kFootballMaps[index].name;
|
|
}
|
|
return kMaps[index].name;
|
|
case GroupVehicle: return kVehicles[index].name;
|
|
case GroupColor: return kColors[index].name;
|
|
case GroupBadge: return kBadges[index].name;
|
|
case GroupTeam: return kTeams[index].name;
|
|
case GroupPosition: return kPositions[index].name;
|
|
case GroupTime: return kTimes[index].name;
|
|
case GroupWeather: return kWeather[index].name;
|
|
case GroupLength: return kLengths[index].name;
|
|
case GroupLaunch: return "L A U N C H G A M E";
|
|
case GroupSteamHost: return "HOST STEAM GAME";
|
|
case GroupSteamJoin: return "JOIN STEAM GAME";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Painting (double buffered, pod green on black)
|
|
//---------------------------------------------------------------
|
|
void PaintMenu(FEState *fe)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(fe->menuWindow, &ps);
|
|
|
|
RECT client;
|
|
GetClientRect(fe->menuWindow, &client);
|
|
|
|
HDC mem = CreateCompatibleDC(hdc);
|
|
HBITMAP surface = CreateCompatibleBitmap(hdc, client.right, client.bottom);
|
|
HBITMAP old_surface = (HBITMAP) SelectObject(mem, surface);
|
|
|
|
FillRect(mem, &client, (HBRUSH) GetStockObject(BLACK_BRUSH));
|
|
SetBkMode(mem, TRANSPARENT);
|
|
|
|
// title
|
|
gItemNameSelection = fe->selection;
|
|
SelectObject(mem, fe->titleFont);
|
|
SetTextColor(mem, kGreenBright);
|
|
RECT title = client;
|
|
title.top = client.bottom / 28;
|
|
title.bottom = title.top + client.bottom / 10;
|
|
DrawTextA(mem,
|
|
IsFootball(fe->selection)
|
|
? "RED PLANET - MARTIAN FOOTBALL"
|
|
: "RED PLANET - MARTIAN DEATH RACE",
|
|
-1, &title, DT_CENTER | DT_TOP | DT_SINGLELINE);
|
|
|
|
SelectObject(mem, fe->textFont);
|
|
|
|
// group headers (drawn above each group's first item)
|
|
int previous_group = -1;
|
|
for (int i = 0; i < fe->itemCount; ++i)
|
|
{
|
|
const FEItem *item = &fe->items[i];
|
|
if (item->group != previous_group && item->group < GroupLaunch)
|
|
{
|
|
previous_group = item->group;
|
|
RECT header = item->rect;
|
|
header.top -= (item->rect.bottom - item->rect.top);
|
|
header.bottom = item->rect.top;
|
|
SetTextColor(mem, kGreenBright);
|
|
DrawTextA(mem, GroupTitle(item->group), -1, &header,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
|
|
Logical selected =
|
|
(item->group >= GroupLaunch) ||
|
|
(fe->selection[item->group] == item->index);
|
|
|
|
RECT row = item->rect;
|
|
if (item->group >= GroupLaunch)
|
|
{
|
|
HBRUSH launch_brush = CreateSolidBrush(kGreenBright);
|
|
FrameRect(mem, &row, launch_brush);
|
|
DeleteObject(launch_brush);
|
|
SetTextColor(mem, kGreenBright);
|
|
DrawTextA(mem, ItemName(item->group, item->index), -1, &row,
|
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
continue;
|
|
}
|
|
|
|
if (selected)
|
|
{
|
|
HBRUSH sel_brush = CreateSolidBrush(kGreenDim);
|
|
RECT fill = row;
|
|
FillRect(mem, &fill, sel_brush);
|
|
DeleteObject(sel_brush);
|
|
SetTextColor(mem, kGreenBright);
|
|
}
|
|
else
|
|
{
|
|
SetTextColor(mem, kGreenDim);
|
|
}
|
|
RECT text = row;
|
|
text.left += 6;
|
|
DrawTextA(mem, ItemName(item->group, item->index), -1, &text,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
|
|
// pilot-name header
|
|
if (fe->nameEdit != NULL)
|
|
{
|
|
RECT edit_rect;
|
|
GetWindowRect(fe->nameEdit, &edit_rect);
|
|
POINT corner = { edit_rect.left, edit_rect.top };
|
|
ScreenToClient(fe->menuWindow, &corner);
|
|
RECT header;
|
|
header.left = corner.x;
|
|
header.right = corner.x + 300;
|
|
header.bottom = corner.y;
|
|
header.top = corner.y - 26;
|
|
SetTextColor(mem, kGreenBright);
|
|
DrawTextA(mem, "PILOT NAME", -1, &header,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
|
|
BitBlt(hdc, 0, 0, client.right, client.bottom, mem, 0, 0, SRCCOPY);
|
|
|
|
SelectObject(mem, old_surface);
|
|
DeleteObject(surface);
|
|
DeleteDC(mem);
|
|
EndPaint(fe->menuWindow, &ps);
|
|
}
|
|
|
|
void MenuClick(FEState *fe, int x, int y)
|
|
{
|
|
for (int i = 0; i < fe->itemCount; ++i)
|
|
{
|
|
const FEItem *item = &fe->items[i];
|
|
if (x >= item->rect.left && x < item->rect.right &&
|
|
y >= item->rect.top && y < item->rect.bottom)
|
|
{
|
|
if (item->group == GroupLaunch)
|
|
{
|
|
fe->launched = True;
|
|
// wake the modal loop (a click delivered via
|
|
// SendMessage never passes through the queue)
|
|
PostMessageA(fe->menuWindow, WM_NULL, 0, 0);
|
|
}
|
|
else if (item->group == GroupSteamHost || item->group == GroupSteamJoin)
|
|
{
|
|
fe->steamAction = (item->group == GroupSteamHost) ? 1 : 2;
|
|
PostMessageA(fe->menuWindow, WM_NULL, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
fe->selection[item->group] = item->index;
|
|
if (item->group == GroupScenario)
|
|
{
|
|
// the scenario swaps the map list and the
|
|
// color/badge vs team/position columns
|
|
int map_count;
|
|
ActiveMaps(fe->selection, &map_count);
|
|
if (fe->selection[GroupMap] >= map_count)
|
|
{
|
|
fe->selection[GroupMap] = 0;
|
|
}
|
|
RECT client;
|
|
GetClientRect(fe->menuWindow, &client);
|
|
LayoutMenu(fe, client.right, client.bottom);
|
|
}
|
|
InvalidateRect(fe->menuWindow, NULL, FALSE);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
LRESULT CALLBACK
|
|
FrontEndWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
FEState *fe = gFE;
|
|
|
|
switch (message)
|
|
{
|
|
case WM_PAINT:
|
|
if (fe != NULL)
|
|
{
|
|
PaintMenu(fe);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
if (fe != NULL)
|
|
{
|
|
MenuClick(fe, (int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam));
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_CTLCOLOREDIT:
|
|
if (fe != NULL)
|
|
{
|
|
SetTextColor((HDC) wParam, kGreenBright);
|
|
SetBkColor((HDC) wParam, kBlack);
|
|
return (LRESULT) fe->editBrush;
|
|
}
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
}
|
|
return DefWindowProcA(hwnd, message, wParam, lParam);
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Plasma name bitmaps: white text auto-fit into WxH, thresholded
|
|
// to 1bpp hex rows - the C++ twin of the console's PlasmaBitmaps.
|
|
//---------------------------------------------------------------
|
|
void AppendNameBitmap(std::string &egg, int width, int height, const char *name)
|
|
{
|
|
BITMAPINFO info;
|
|
memset(&info, 0, sizeof(info));
|
|
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
info.bmiHeader.biWidth = width;
|
|
info.bmiHeader.biHeight = -height; // top-down
|
|
info.bmiHeader.biPlanes = 1;
|
|
info.bmiHeader.biBitCount = 32;
|
|
info.bmiHeader.biCompression = BI_RGB;
|
|
|
|
void *bits = NULL;
|
|
HDC dc = CreateCompatibleDC(NULL);
|
|
HBITMAP bitmap = CreateDIBSection(dc, &info, DIB_RGB_COLORS, &bits, NULL, 0);
|
|
HBITMAP old_bitmap = (HBITMAP) SelectObject(dc, bitmap);
|
|
|
|
memset(bits, 0, width * height * 4);
|
|
SetBkMode(dc, TRANSPARENT);
|
|
SetTextColor(dc, RGB(255, 255, 255));
|
|
|
|
// shrink the font until the name fits (console: 24pt downward)
|
|
int point_size = 24;
|
|
for (;;)
|
|
{
|
|
HFONT font = CreateFontA(
|
|
-MulDiv(point_size, GetDeviceCaps(dc, LOGPIXELSY), 72),
|
|
0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
|
|
ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
|
NONANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
|
|
"Microsoft Sans Serif");
|
|
HFONT old_font = (HFONT) SelectObject(dc, font);
|
|
|
|
SIZE extent;
|
|
GetTextExtentPoint32A(dc, name, (int) strlen(name), &extent);
|
|
if ((extent.cx <= width && extent.cy <= height) || point_size <= 1)
|
|
{
|
|
RECT rect = { 0, 0, width, height };
|
|
DrawTextA(dc, name, -1, &rect,
|
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
|
|
SelectObject(dc, old_font);
|
|
DeleteObject(font);
|
|
break;
|
|
}
|
|
SelectObject(dc, old_font);
|
|
DeleteObject(font);
|
|
point_size = (point_size <= 2) ? (point_size / 2) : (point_size - 2);
|
|
}
|
|
|
|
GdiFlush();
|
|
const unsigned long *pixels = (const unsigned long *) bits;
|
|
char hex[8];
|
|
for (int row = 0; row < height; ++row)
|
|
{
|
|
egg += "bitmap=";
|
|
for (int byte_index = 0; byte_index < width / 8; ++byte_index)
|
|
{
|
|
unsigned char packed = 0;
|
|
for (int bit = 0; bit < 8; ++bit)
|
|
{
|
|
unsigned long pixel = pixels[row * width + byte_index * 8 + bit];
|
|
if ((pixel & 0xFF) >= 128) // white text on black
|
|
{
|
|
packed |= (unsigned char)(128 >> bit);
|
|
}
|
|
}
|
|
sprintf(hex, "%02X", packed);
|
|
egg += hex;
|
|
}
|
|
egg += "\n";
|
|
}
|
|
sprintf(hex, "%d", width);
|
|
egg += "x="; egg += hex; egg += "\n";
|
|
sprintf(hex, "%d", height);
|
|
egg += "y="; egg += hex; egg += "\n";
|
|
|
|
SelectObject(dc, old_bitmap);
|
|
DeleteObject(bitmap);
|
|
DeleteDC(dc);
|
|
}
|
|
|
|
// The 1st-4th place plasma graphics, verbatim from the console
|
|
// (RPMission.AppendOrdinals) / TEST.EGG.
|
|
extern const char *kOrdinalsBlock;
|
|
|
|
//---------------------------------------------------------------
|
|
// Egg assembly (console RPMission.ToEggString / RPFootballMission).
|
|
// Single player uses the standalone address; a hosted network game
|
|
// lists the owner first plus one pilot per member pod. Football
|
|
// adds team=/position= per pilot and the [teams] blocks; the
|
|
// owner's team/position picks seed a deterministic assignment
|
|
// across two teams (each team's first member without a runner
|
|
// becomes one; the rest alternate crusher/blocker; the runner
|
|
// wears the team's runner color, everyone else the team color).
|
|
//---------------------------------------------------------------
|
|
void BuildEggText(const FEState *fe, std::string &egg)
|
|
{
|
|
char line[256];
|
|
Logical football = IsFootball(fe->selection);
|
|
|
|
ExtraPilot extras[maxExtraPilots];
|
|
char owner_address[64];
|
|
strcpy(owner_address, kLocalPilotAddress);
|
|
int extra_count = CollectExtraPilots(fe, extras, owner_address);
|
|
if (extra_count < 0)
|
|
{
|
|
extra_count = 0; // single player
|
|
}
|
|
|
|
//
|
|
// One flat pilot table: the owner then the extras
|
|
//
|
|
struct PilotEntry
|
|
{
|
|
const char *address;
|
|
const char *name;
|
|
const char *vehicle;
|
|
const char *color;
|
|
const char *badge;
|
|
int team; // kTeams index, -1 outside football
|
|
const char *position;
|
|
Logical chosePosition; // picked it themselves
|
|
};
|
|
PilotEntry pilots[maxExtraPilots + 1];
|
|
int pilot_count = 0;
|
|
|
|
PilotEntry *owner = &pilots[pilot_count++];
|
|
owner->address = owner_address;
|
|
owner->name = fe->pilotName;
|
|
owner->vehicle = kVehicles[fe->selection[GroupVehicle]].key;
|
|
owner->color = kColors[fe->selection[GroupColor]].key;
|
|
owner->badge = kBadges[fe->selection[GroupBadge]].key;
|
|
owner->team = -1;
|
|
owner->position = NULL;
|
|
owner->chosePosition = False;
|
|
for (int p = 0; p < extra_count; ++p)
|
|
{
|
|
PilotEntry *pilot = &pilots[pilot_count++];
|
|
pilot->address = extras[p].address;
|
|
pilot->name = extras[p].name;
|
|
pilot->vehicle = extras[p].vehicle;
|
|
pilot->color = extras[p].color;
|
|
pilot->badge = extras[p].badge;
|
|
pilot->team = -1;
|
|
pilot->position = NULL;
|
|
pilot->chosePosition = False;
|
|
}
|
|
|
|
//
|
|
// Football: every pilot's own team/position pick is honored;
|
|
// unset picks get filled in, then each team is normalized to
|
|
// exactly one runner (the console's rule).
|
|
//
|
|
if (football)
|
|
{
|
|
//
|
|
// 1. Teams: the host's pick, then each member's, then
|
|
// alternate whoever did not choose across the two
|
|
// most-used teams so the sides stay balanced.
|
|
//
|
|
owner->team = fe->selection[GroupTeam];
|
|
for (int p = 1; p < pilot_count; ++p)
|
|
{
|
|
for (int t = 0; t < FE_COUNT(kTeams); ++t)
|
|
{
|
|
if (strcmp(extras[p - 1].team, kTeams[t].key) == 0)
|
|
{
|
|
pilots[p].team = t;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int fallback_team = (owner->team + 1) % FE_COUNT(kTeams);
|
|
int unassigned = 0;
|
|
for (int p = 1; p < pilot_count; ++p)
|
|
{
|
|
if (pilots[p].team < 0)
|
|
{
|
|
pilots[p].team = (unassigned++ % 2) ? owner->team : fallback_team;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 2. Positions: honor each pick, default the rest to the
|
|
// line, then give every team exactly one runner.
|
|
//
|
|
for (int p = 1; p < pilot_count; ++p)
|
|
{
|
|
const char *pick = extras[p - 1].position;
|
|
for (int i = 0; i < FE_COUNT(kPositions); ++i)
|
|
{
|
|
if (strcmp(pick, kPositions[i].key) == 0)
|
|
{
|
|
pilots[p].position = kPositions[i].key;
|
|
pilots[p].chosePosition = True;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
owner->position = kPositions[fe->selection[GroupPosition]].key;
|
|
owner->chosePosition = True;
|
|
|
|
for (int t = 0; t < FE_COUNT(kTeams); ++t)
|
|
{
|
|
int members = 0, runner = -1, spare = -1, line = 0;
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
if (pilots[p].team != t)
|
|
{
|
|
continue;
|
|
}
|
|
++members;
|
|
if (spare < 0 && !pilots[p].chosePosition)
|
|
{
|
|
spare = p; // nobody's plans to disturb
|
|
}
|
|
if (pilots[p].position != NULL &&
|
|
strcmp(pilots[p].position, "runner") == 0)
|
|
{
|
|
if (runner < 0)
|
|
{
|
|
runner = p; // first runner keeps the ball
|
|
}
|
|
else
|
|
{
|
|
// two pilots claimed it - the later one lines up
|
|
pilots[p].position = NULL;
|
|
pilots[p].chosePosition = False;
|
|
}
|
|
}
|
|
}
|
|
if (members == 0)
|
|
{
|
|
continue;
|
|
}
|
|
if (runner < 0 && spare >= 0)
|
|
{
|
|
// no volunteer: the first pilot without a pick of
|
|
// their own runs (explicit picks are never overridden)
|
|
pilots[spare].position = "runner";
|
|
runner = spare;
|
|
}
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
if (pilots[p].team == t && p != runner &&
|
|
pilots[p].position == NULL)
|
|
{
|
|
pilots[p].position = (line++ % 2) ? "crusher" : "blocker";
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 3. Colors follow team and position (runner wears the
|
|
// team's runner color - that is how you spot the ball)
|
|
//
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
const TeamEntry *team = &kTeams[pilots[p].team];
|
|
pilots[p].color =
|
|
(strcmp(pilots[p].position, "runner") == 0)
|
|
? team->runnerColor : team->teamColor;
|
|
pilots[p].badge = "None";
|
|
}
|
|
}
|
|
|
|
//
|
|
// [mission]
|
|
//
|
|
int map_count;
|
|
const CatalogEntry *maps = ActiveMaps(fe->selection, &map_count);
|
|
|
|
egg += "[mission]\n";
|
|
egg += "adventure=Red Planet\n";
|
|
sprintf(line, "map=%s\n", maps[fe->selection[GroupMap]].key);
|
|
egg += line;
|
|
sprintf(line, "scenario=%s\n", kScenarios[fe->selection[GroupScenario]].key);
|
|
egg += line;
|
|
sprintf(line, "time=%s\n", kTimes[fe->selection[GroupTime]].key);
|
|
egg += line;
|
|
sprintf(line, "weather=%s\n", kWeather[fe->selection[GroupWeather]].key);
|
|
egg += line;
|
|
egg += "temperature=0\n";
|
|
egg += "compression=0\n";
|
|
int seconds = kLengths[fe->selection[GroupLength]].seconds;
|
|
if (seconds > 0)
|
|
{
|
|
sprintf(line, "length=%d\n", seconds);
|
|
egg += line;
|
|
}
|
|
|
|
//
|
|
// [pilots] + per-pilot sections
|
|
//
|
|
egg += "[pilots]\n";
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
sprintf(line, "pilot=%s\n", pilots[p].address);
|
|
egg += line;
|
|
}
|
|
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
sprintf(line, "[%s]\n", pilots[p].address);
|
|
egg += line;
|
|
egg += "hostType=0\n";
|
|
egg += "dropzone=one\n";
|
|
sprintf(line, "name=%s\n", pilots[p].name);
|
|
egg += line;
|
|
sprintf(line, "bitmapindex=%d\n", p + 1);
|
|
egg += line;
|
|
egg += "loadzones=1\n";
|
|
sprintf(line, "vehicle=%s\n", pilots[p].vehicle);
|
|
egg += line;
|
|
sprintf(line, "color=%s\n", pilots[p].color);
|
|
egg += line;
|
|
sprintf(line, "badge=%s\n", pilots[p].badge);
|
|
egg += line;
|
|
if (football)
|
|
{
|
|
sprintf(line, "team=team::%s\n", kTeams[pilots[p].team].key);
|
|
egg += line;
|
|
sprintf(line, "position=%s\n", pilots[p].position);
|
|
egg += line;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Football team blocks (console RPFootballMission layout)
|
|
//
|
|
if (football)
|
|
{
|
|
std::string teams_list = "[teams]\n";
|
|
std::string team_sections, pilots_sections;
|
|
std::string teambitmap_list = "[teambitmap]\n";
|
|
std::string teambitmap_sections;
|
|
int bitmap_index = 1;
|
|
for (int side = 0; side < FE_COUNT(kTeams); ++side)
|
|
{
|
|
Logical team_present = False;
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
if (pilots[p].team == side)
|
|
{
|
|
team_present = True;
|
|
}
|
|
}
|
|
if (!team_present)
|
|
{
|
|
continue;
|
|
}
|
|
const char *team_key = kTeams[side].key;
|
|
sprintf(line, "team=team::%s\n", team_key);
|
|
teams_list += line;
|
|
sprintf(line, "[team::%s]\nbitmapindex=%d\npilots=pilots::%s\n",
|
|
team_key, bitmap_index++, team_key);
|
|
team_sections += line;
|
|
sprintf(line, "[pilots::%s]\n", team_key);
|
|
pilots_sections += line;
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
if (pilots[p].team == side)
|
|
{
|
|
sprintf(line, "pilot=%s\n", pilots[p].address);
|
|
pilots_sections += line;
|
|
}
|
|
}
|
|
sprintf(line, "bitmap=BitMap::Small::team::%s\n", team_key);
|
|
teambitmap_list += line;
|
|
sprintf(line, "[BitMap::Small::team::%s]\n", team_key);
|
|
teambitmap_sections += line;
|
|
AppendNameBitmap(teambitmap_sections, 64, 16, team_key);
|
|
teambitmap_sections += "width=4\n";
|
|
}
|
|
egg += teams_list;
|
|
egg += team_sections;
|
|
egg += pilots_sections;
|
|
egg += teambitmap_list;
|
|
egg += teambitmap_sections;
|
|
}
|
|
|
|
//
|
|
// Name bitmaps + ordinals
|
|
//
|
|
egg += "[largebitmap]\n";
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
sprintf(line, "bitmap=BitMap::Large::%s\n", pilots[p].name);
|
|
egg += line;
|
|
}
|
|
egg += "[smallbitmap]\n";
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
sprintf(line, "bitmap=BitMap::Small::%s\n", pilots[p].name);
|
|
egg += line;
|
|
}
|
|
for (int p = 0; p < pilot_count; ++p)
|
|
{
|
|
sprintf(line, "[BitMap::Large::%s]\n", pilots[p].name);
|
|
egg += line;
|
|
AppendNameBitmap(egg, 128, 32, pilots[p].name);
|
|
egg += "width=8\n";
|
|
sprintf(line, "[BitMap::Small::%s]\n", pilots[p].name);
|
|
egg += line;
|
|
AppendNameBitmap(egg, 64, 16, pilots[p].name);
|
|
egg += "width=4\n";
|
|
}
|
|
|
|
egg += kOrdinalsBlock;
|
|
|
|
// [pilots]-order names for the network console's results
|
|
strcpy(gLastPilotNamesCsv, fe->pilotName);
|
|
for (int p = 1; p < pilot_count; ++p)
|
|
{
|
|
if (strlen(gLastPilotNamesCsv) + strlen(pilots[p].name) + 2 <
|
|
sizeof(gLastPilotNamesCsv))
|
|
{
|
|
strcat(gLastPilotNamesCsv, ",");
|
|
strcat(gLastPilotNamesCsv, pilots[p].name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//########################################################################
|
|
//############################ RPL4FrontEnd_Run ##########################
|
|
//########################################################################
|
|
|
|
static int gLastLaunchMode = FELaunchSingle;
|
|
|
|
//---------------------------------------------------------------
|
|
// Build frontend.egg from the persisted loadout (the menu persists
|
|
// on every exit; lobby launches can happen without a live menu)
|
|
//---------------------------------------------------------------
|
|
static Logical
|
|
BuildEggFromPersisted(char *egg_path_out, int egg_path_size)
|
|
{
|
|
FEState fe;
|
|
memset(&fe, 0, sizeof(fe));
|
|
strcpy(fe.pilotName, gLastPilotName);
|
|
if (gHavePersist)
|
|
{
|
|
memcpy(fe.selection, gPersistSelection, sizeof(fe.selection));
|
|
}
|
|
else
|
|
{
|
|
fe.selection[GroupLength] = 2; // 5:00
|
|
}
|
|
gLastMissionSeconds = kLengths[fe.selection[GroupLength]].seconds;
|
|
|
|
std::string egg;
|
|
egg.reserve(16384);
|
|
BuildEggText(&fe, egg);
|
|
|
|
strncpy(egg_path_out, "frontend.egg", egg_path_size - 1);
|
|
egg_path_out[egg_path_size - 1] = '\0';
|
|
|
|
FILE *file = fopen(egg_path_out, "wb");
|
|
if (file == NULL)
|
|
{
|
|
DEBUG_STREAM << "FrontEnd: could not write " << egg_path_out
|
|
<< "\n" << std::flush;
|
|
return False;
|
|
}
|
|
fwrite(egg.data(), 1, egg.size(), file);
|
|
fclose(file);
|
|
DEBUG_STREAM << "FrontEnd: wrote " << egg_path_out << " ("
|
|
<< (int) egg.size() << " bytes)\n" << std::flush;
|
|
return True;
|
|
}
|
|
|
|
Logical
|
|
RPL4FrontEnd_Run(
|
|
HINSTANCE instance,
|
|
HWND main_window,
|
|
char *egg_path_out,
|
|
int egg_path_size
|
|
)
|
|
{
|
|
gLastLaunchMode = FELaunchSingle;
|
|
|
|
//---------------------------------------------------------------
|
|
// Coming back from a race while still in a lobby: straight to
|
|
// the room (the lobby outlives races - single binary payoff)
|
|
//---------------------------------------------------------------
|
|
if (RPL4Lobby_InRoom())
|
|
{
|
|
int outcome = RPL4Lobby_Room(instance, main_window);
|
|
if (outcome == LobbyRoomClosed)
|
|
{
|
|
return False;
|
|
}
|
|
if (outcome == LobbyLaunchMember)
|
|
{
|
|
gLastLaunchMode = FELaunchMember;
|
|
egg_path_out[0] = '\0';
|
|
return True;
|
|
}
|
|
if (outcome == LobbyLaunchHost)
|
|
{
|
|
gLastLaunchMode = FELaunchHost;
|
|
return BuildEggFromPersisted(egg_path_out, egg_path_size);
|
|
}
|
|
// LobbyRoomLeft: fall through to the setup menu
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
FEState fe;
|
|
memset(&fe, 0, sizeof(fe));
|
|
strcpy(fe.pilotName, gLastPilotName);
|
|
if (gHavePersist)
|
|
{
|
|
memcpy(fe.selection, gPersistSelection, sizeof(fe.selection));
|
|
}
|
|
else
|
|
{
|
|
fe.selection[GroupLength] = 2; // 5:00
|
|
}
|
|
gFE = &fe;
|
|
|
|
//---------------------------------------------------------------
|
|
// Window class + fonts
|
|
//---------------------------------------------------------------
|
|
static Logical class_registered = False;
|
|
if (!class_registered)
|
|
{
|
|
class_registered = True;
|
|
WNDCLASSA window_class;
|
|
memset(&window_class, 0, sizeof(window_class));
|
|
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
|
window_class.lpfnWndProc = FrontEndWndProc;
|
|
window_class.hInstance = instance;
|
|
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
|
|
window_class.lpszClassName = "RPFrontEnd";
|
|
RegisterClassA(&window_class);
|
|
}
|
|
|
|
RECT client;
|
|
GetClientRect(main_window, &client);
|
|
|
|
fe.menuWindow = CreateWindowExA(
|
|
0, "RPFrontEnd", "",
|
|
WS_CHILD | WS_VISIBLE,
|
|
0, 0, client.right, client.bottom,
|
|
main_window, NULL, instance, NULL);
|
|
if (fe.menuWindow == NULL)
|
|
{
|
|
gFE = NULL;
|
|
return False;
|
|
}
|
|
|
|
int text_h = client.bottom / 40;
|
|
if (text_h < 16) text_h = 16;
|
|
if (text_h > 24) text_h = 24;
|
|
fe.textFont = CreateFontA(-text_h, 0, 0, 0, FW_NORMAL,
|
|
FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_MODERN,
|
|
"Consolas");
|
|
fe.titleFont = CreateFontA(-(text_h * 2), 0, 0, 0, FW_BOLD,
|
|
FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_MODERN,
|
|
"Consolas");
|
|
fe.editBrush = CreateSolidBrush(kBlack);
|
|
|
|
fe.nameEdit = CreateWindowExA(
|
|
0, "EDIT", fe.pilotName,
|
|
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
|
|
0, 0, 10, 10,
|
|
fe.menuWindow, NULL, instance, NULL);
|
|
SendMessageA(fe.nameEdit, WM_SETFONT, (WPARAM) fe.textFont, TRUE);
|
|
SendMessageA(fe.nameEdit, EM_SETLIMITTEXT, sizeof(fe.pilotName) - 2, 0);
|
|
|
|
LayoutMenu(&fe, client.right, client.bottom);
|
|
InvalidateRect(fe.menuWindow, NULL, TRUE);
|
|
|
|
//---------------------------------------------------------------
|
|
// Modal loop until launch, lobby button, or close
|
|
//---------------------------------------------------------------
|
|
MSG msg;
|
|
while (!fe.launched && !fe.closed && fe.steamAction == 0)
|
|
{
|
|
BOOL result = GetMessageA(&msg, NULL, 0, 0);
|
|
if (result <= 0)
|
|
{
|
|
fe.closed = True;
|
|
break;
|
|
}
|
|
if (msg.message == WM_QUIT ||
|
|
(msg.message == WM_SYSCOMMAND && msg.wParam == SC_CLOSE))
|
|
{
|
|
fe.closed = True;
|
|
}
|
|
// closing the main window ends the front end too
|
|
if (msg.message == WM_CLOSE && msg.hwnd == main_window)
|
|
{
|
|
fe.closed = True;
|
|
}
|
|
TranslateMessage(&msg);
|
|
DispatchMessageA(&msg);
|
|
if (!IsWindow(main_window))
|
|
{
|
|
fe.closed = True;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Harvest the loadout whichever way we leave the menu - the
|
|
// lobby publishes it as member data, launches build from it
|
|
//---------------------------------------------------------------
|
|
if (!fe.closed)
|
|
{
|
|
GetWindowTextA(fe.nameEdit, fe.pilotName, sizeof(fe.pilotName) - 1);
|
|
if (fe.pilotName[0] == '\0')
|
|
{
|
|
strcpy(fe.pilotName, "Pilot");
|
|
}
|
|
strcpy(gLastPilotName, fe.pilotName);
|
|
memcpy(gPersistSelection, fe.selection, sizeof(gPersistSelection));
|
|
gHavePersist = True;
|
|
}
|
|
|
|
Logical launched = fe.launched;
|
|
Logical closed = fe.closed;
|
|
int steam_action = fe.steamAction;
|
|
|
|
DestroyWindow(fe.menuWindow);
|
|
DeleteObject(fe.textFont);
|
|
DeleteObject(fe.titleFont);
|
|
DeleteObject(fe.editBrush);
|
|
gFE = NULL;
|
|
|
|
if (closed)
|
|
{
|
|
return False;
|
|
}
|
|
if (launched)
|
|
{
|
|
// plain launch: single player, or a LAN-hosted race when
|
|
// RP412HOSTPODS is set in the environment
|
|
return BuildEggFromPersisted(egg_path_out, egg_path_size);
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Steam lobby (host or join), then back here if they leave
|
|
//---------------------------------------------------------------
|
|
int outcome = (steam_action == 1)
|
|
? RPL4Lobby_Host(instance, main_window)
|
|
: RPL4Lobby_Join(instance, main_window);
|
|
if (outcome == LobbyRoomClosed)
|
|
{
|
|
return False;
|
|
}
|
|
if (outcome == LobbyLaunchMember)
|
|
{
|
|
gLastLaunchMode = FELaunchMember;
|
|
egg_path_out[0] = '\0';
|
|
return True;
|
|
}
|
|
if (outcome == LobbyLaunchHost)
|
|
{
|
|
gLastLaunchMode = FELaunchHost;
|
|
return BuildEggFromPersisted(egg_path_out, egg_path_size);
|
|
}
|
|
// LobbyRoomLeft: show the menu again
|
|
}
|
|
}
|
|
|
|
int
|
|
RPL4FrontEnd_LastMissionSeconds()
|
|
{
|
|
return gLastMissionSeconds;
|
|
}
|
|
|
|
const char *
|
|
RPL4FrontEnd_LastPilotNames()
|
|
{
|
|
return gLastPilotNamesCsv;
|
|
}
|
|
|
|
int
|
|
RPL4FrontEnd_LastLaunchMode()
|
|
{
|
|
return gLastLaunchMode;
|
|
}
|
|
|
|
void
|
|
RPL4FrontEnd_GetLoadout(char *vehicle, char *color, char *badge)
|
|
{
|
|
int v = gHavePersist ? gPersistSelection[GroupVehicle] : 0;
|
|
int c = gHavePersist ? gPersistSelection[GroupColor] : 0;
|
|
int b = gHavePersist ? gPersistSelection[GroupBadge] : 0;
|
|
strcpy(vehicle, kVehicles[v].key);
|
|
strcpy(color, kColors[c].key);
|
|
strcpy(badge, kBadges[b].key);
|
|
}
|
|
|
|
//########################################################################
|
|
// Football team / position picks (the lobby publishes and cycles these)
|
|
//########################################################################
|
|
|
|
int
|
|
RPL4FrontEnd_TeamCount()
|
|
{
|
|
return FE_COUNT(kTeams);
|
|
}
|
|
|
|
const char *
|
|
RPL4FrontEnd_TeamName(int index)
|
|
{
|
|
if (index < 0 || index >= FE_COUNT(kTeams))
|
|
{
|
|
return "";
|
|
}
|
|
return kTeams[index].name;
|
|
}
|
|
|
|
const char *
|
|
RPL4FrontEnd_TeamKey(int index)
|
|
{
|
|
if (index < 0 || index >= FE_COUNT(kTeams))
|
|
{
|
|
return "";
|
|
}
|
|
return kTeams[index].key;
|
|
}
|
|
|
|
int
|
|
RPL4FrontEnd_PositionCount()
|
|
{
|
|
return FE_COUNT(kPositions);
|
|
}
|
|
|
|
const char *
|
|
RPL4FrontEnd_PositionName(int index)
|
|
{
|
|
if (index < 0 || index >= FE_COUNT(kPositions))
|
|
{
|
|
return "";
|
|
}
|
|
return kPositions[index].name;
|
|
}
|
|
|
|
const char *
|
|
RPL4FrontEnd_PositionKey(int index)
|
|
{
|
|
if (index < 0 || index >= FE_COUNT(kPositions))
|
|
{
|
|
return "";
|
|
}
|
|
return kPositions[index].key;
|
|
}
|
|
|
|
int
|
|
RPL4FrontEnd_GetTeamIndex()
|
|
{
|
|
return gHavePersist ? gPersistSelection[GroupTeam] : 0;
|
|
}
|
|
|
|
void
|
|
RPL4FrontEnd_SetTeamIndex(int index)
|
|
{
|
|
if (index < 0 || index >= FE_COUNT(kTeams))
|
|
{
|
|
return;
|
|
}
|
|
gPersistSelection[GroupTeam] = index;
|
|
gHavePersist = True;
|
|
}
|
|
|
|
int
|
|
RPL4FrontEnd_GetPositionIndex()
|
|
{
|
|
return gHavePersist ? gPersistSelection[GroupPosition] : 0;
|
|
}
|
|
|
|
void
|
|
RPL4FrontEnd_SetPositionIndex(int index)
|
|
{
|
|
if (index < 0 || index >= FE_COUNT(kPositions))
|
|
{
|
|
return;
|
|
}
|
|
gPersistSelection[GroupPosition] = index;
|
|
gHavePersist = True;
|
|
}
|
|
|
|
Logical
|
|
RPL4FrontEnd_IsFootballSelected()
|
|
{
|
|
return gHavePersist && IsFootball(gPersistSelection);
|
|
}
|
|
|
|
void
|
|
RPL4FrontEnd_SetHostedPilots(
|
|
const char *owner_address,
|
|
const FEHostedPilot *pilots,
|
|
int count
|
|
)
|
|
{
|
|
if (count > maxExtraPilots)
|
|
{
|
|
count = maxExtraPilots;
|
|
}
|
|
gHostedPilotCount = (pilots != NULL) ? count : 0;
|
|
for (int i = 0; i < gHostedPilotCount; ++i)
|
|
{
|
|
gHostedPilots[i] = pilots[i];
|
|
}
|
|
strncpy(gHostedOwnerAddress,
|
|
(owner_address != NULL) ? owner_address : "",
|
|
sizeof(gHostedOwnerAddress) - 1);
|
|
gHostedOwnerAddress[sizeof(gHostedOwnerAddress) - 1] = '\0';
|
|
}
|
|
|
|
//########################################################################
|
|
//######################## RPL4FrontEnd_ShowResults ######################
|
|
//########################################################################
|
|
|
|
namespace
|
|
{
|
|
struct ResultRow
|
|
{
|
|
char name[32];
|
|
int score;
|
|
};
|
|
|
|
struct ResultsState
|
|
{
|
|
HWND window;
|
|
HFONT textFont;
|
|
HFONT titleFont;
|
|
ResultRow rows[16];
|
|
int rowCount;
|
|
RECT continueRect;
|
|
Logical dismissed;
|
|
Logical closed;
|
|
};
|
|
|
|
ResultsState *gRS = NULL;
|
|
|
|
const char *PlaceName(int place)
|
|
{
|
|
static const char *kPlaces[] = { "1ST", "2ND", "3RD", "4TH" };
|
|
static char other[8];
|
|
if (place < 4)
|
|
{
|
|
return kPlaces[place];
|
|
}
|
|
sprintf(other, "%dTH", place + 1);
|
|
return other;
|
|
}
|
|
|
|
void PaintResults(ResultsState *rs)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(rs->window, &ps);
|
|
|
|
RECT client;
|
|
GetClientRect(rs->window, &client);
|
|
|
|
HDC mem = CreateCompatibleDC(hdc);
|
|
HBITMAP surface = CreateCompatibleBitmap(hdc, client.right, client.bottom);
|
|
HBITMAP old_surface = (HBITMAP) SelectObject(mem, surface);
|
|
|
|
FillRect(mem, &client, (HBRUSH) GetStockObject(BLACK_BRUSH));
|
|
SetBkMode(mem, TRANSPARENT);
|
|
|
|
SelectObject(mem, rs->titleFont);
|
|
SetTextColor(mem, kGreenBright);
|
|
RECT title = client;
|
|
title.top = client.bottom / 28;
|
|
title.bottom = title.top + client.bottom / 10;
|
|
DrawTextA(mem, "RACE RESULTS", -1, &title,
|
|
DT_CENTER | DT_TOP | DT_SINGLELINE);
|
|
|
|
SelectObject(mem, rs->textFont);
|
|
int row_h = client.bottom / 16;
|
|
int top = client.bottom / 4;
|
|
int left = client.right / 3;
|
|
int right = 2 * client.right / 3;
|
|
char text[48];
|
|
for (int i = 0; i < rs->rowCount; ++i)
|
|
{
|
|
RECT row;
|
|
row.left = left;
|
|
row.right = right;
|
|
row.top = top + i * row_h;
|
|
row.bottom = row.top + row_h;
|
|
|
|
SetTextColor(mem, (i == 0) ? kGreenBright : kGreenDim);
|
|
DrawTextA(mem, PlaceName(i), -1, &row,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
|
|
RECT name = row;
|
|
name.left += (right - left) / 5;
|
|
DrawTextA(mem, rs->rows[i].name, -1, &name,
|
|
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
|
|
sprintf(text, "%d", rs->rows[i].score);
|
|
DrawTextA(mem, text, -1, &row,
|
|
DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
|
|
}
|
|
|
|
HBRUSH frame_brush = CreateSolidBrush(kGreenBright);
|
|
FrameRect(mem, &rs->continueRect, frame_brush);
|
|
DeleteObject(frame_brush);
|
|
SetTextColor(mem, kGreenBright);
|
|
DrawTextA(mem, "C O N T I N U E", -1, &rs->continueRect,
|
|
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
|
|
BitBlt(hdc, 0, 0, client.right, client.bottom, mem, 0, 0, SRCCOPY);
|
|
|
|
SelectObject(mem, old_surface);
|
|
DeleteObject(surface);
|
|
DeleteDC(mem);
|
|
EndPaint(rs->window, &ps);
|
|
}
|
|
|
|
LRESULT CALLBACK
|
|
ResultsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
ResultsState *rs = gRS;
|
|
|
|
switch (message)
|
|
{
|
|
case WM_PAINT:
|
|
if (rs != NULL)
|
|
{
|
|
PaintResults(rs);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
if (rs != NULL)
|
|
{
|
|
POINT point = { (int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam) };
|
|
if (PtInRect(&rs->continueRect, point))
|
|
{
|
|
rs->dismissed = True;
|
|
PostMessageA(rs->window, WM_NULL, 0, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
}
|
|
return DefWindowProcA(hwnd, message, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
Logical
|
|
RPL4FrontEnd_ShowResults(
|
|
HINSTANCE instance,
|
|
HWND main_window
|
|
)
|
|
{
|
|
int result_count = RPL4LocalConsole_ResultCount();
|
|
if (result_count <= 0)
|
|
{
|
|
return True; // nothing to show (first boot)
|
|
}
|
|
if (!IsWindow(main_window))
|
|
{
|
|
return False;
|
|
}
|
|
|
|
ResultsState rs;
|
|
memset(&rs, 0, sizeof(rs));
|
|
|
|
//---------------------------------------------------------------
|
|
// Intake, then sort descending by score. Single-player results
|
|
// carry the pilot's own name; with more pods the roster will map
|
|
// host IDs to Steam personas (until then: pod number).
|
|
//---------------------------------------------------------------
|
|
for (int i = 0; i < result_count && rs.rowCount < 16; ++i)
|
|
{
|
|
int host_ID, score;
|
|
if (!RPL4LocalConsole_GetResult(i, &host_ID, &score))
|
|
{
|
|
continue;
|
|
}
|
|
ResultRow *row = &rs.rows[rs.rowCount++];
|
|
const char *pilot_name = RPL4LocalConsole_GetResultName(host_ID);
|
|
if (pilot_name != NULL)
|
|
{
|
|
sprintf(row->name, "%.30s", pilot_name);
|
|
}
|
|
else if (result_count == 1)
|
|
{
|
|
sprintf(row->name, "%s", gLastPilotName);
|
|
}
|
|
else
|
|
{
|
|
sprintf(row->name, "POD %d", host_ID);
|
|
}
|
|
row->score = score;
|
|
}
|
|
for (int i = 1; i < rs.rowCount; ++i)
|
|
{
|
|
ResultRow key = rs.rows[i];
|
|
int j = i - 1;
|
|
while (j >= 0 && rs.rows[j].score < key.score)
|
|
{
|
|
rs.rows[j + 1] = rs.rows[j];
|
|
--j;
|
|
}
|
|
rs.rows[j + 1] = key;
|
|
}
|
|
|
|
static Logical class_registered = False;
|
|
if (!class_registered)
|
|
{
|
|
class_registered = True;
|
|
WNDCLASSA window_class;
|
|
memset(&window_class, 0, sizeof(window_class));
|
|
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
|
window_class.lpfnWndProc = ResultsWndProc;
|
|
window_class.hInstance = instance;
|
|
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
|
|
window_class.lpszClassName = "RPResults";
|
|
RegisterClassA(&window_class);
|
|
}
|
|
|
|
RECT client;
|
|
GetClientRect(main_window, &client);
|
|
|
|
rs.window = CreateWindowExA(
|
|
0, "RPResults", "",
|
|
WS_CHILD | WS_VISIBLE,
|
|
0, 0, client.right, client.bottom,
|
|
main_window, NULL, instance, NULL);
|
|
if (rs.window == NULL)
|
|
{
|
|
return False;
|
|
}
|
|
|
|
int text_h = client.bottom / 40;
|
|
if (text_h < 16) text_h = 16;
|
|
if (text_h > 24) text_h = 24;
|
|
rs.textFont = CreateFontA(-(text_h * 3 / 2), 0, 0, 0, FW_NORMAL,
|
|
FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_MODERN,
|
|
"Consolas");
|
|
rs.titleFont = CreateFontA(-(text_h * 2), 0, 0, 0, FW_BOLD,
|
|
FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_MODERN,
|
|
"Consolas");
|
|
|
|
int row_h = client.bottom / 36;
|
|
if (row_h < 18) row_h = 18;
|
|
if (row_h > 30) row_h = 30;
|
|
int col_w = client.right / 5;
|
|
rs.continueRect.left = (client.right - col_w) / 2;
|
|
rs.continueRect.right = rs.continueRect.left + col_w;
|
|
rs.continueRect.top = client.bottom - 3 * row_h;
|
|
rs.continueRect.bottom = client.bottom - row_h;
|
|
|
|
gRS = &rs;
|
|
SetFocus(rs.window);
|
|
InvalidateRect(rs.window, NULL, TRUE);
|
|
|
|
//---------------------------------------------------------------
|
|
// Modal until CONTINUE (or any key) - same shape as the menu loop
|
|
//---------------------------------------------------------------
|
|
MSG msg;
|
|
while (!rs.dismissed && !rs.closed)
|
|
{
|
|
BOOL result = GetMessageA(&msg, NULL, 0, 0);
|
|
if (result <= 0)
|
|
{
|
|
rs.closed = True;
|
|
break;
|
|
}
|
|
if (msg.message == WM_QUIT ||
|
|
(msg.message == WM_SYSCOMMAND && msg.wParam == SC_CLOSE))
|
|
{
|
|
rs.closed = True;
|
|
}
|
|
if (msg.message == WM_CLOSE && msg.hwnd == main_window)
|
|
{
|
|
rs.closed = True;
|
|
}
|
|
if (msg.message == WM_KEYDOWN &&
|
|
(msg.wParam == VK_RETURN || msg.wParam == VK_SPACE ||
|
|
msg.wParam == VK_ESCAPE))
|
|
{
|
|
rs.dismissed = True;
|
|
}
|
|
TranslateMessage(&msg);
|
|
DispatchMessageA(&msg);
|
|
if (!IsWindow(main_window))
|
|
{
|
|
rs.closed = True;
|
|
}
|
|
}
|
|
|
|
DestroyWindow(rs.window);
|
|
DeleteObject(rs.textFont);
|
|
DeleteObject(rs.titleFont);
|
|
gRS = NULL;
|
|
|
|
return !rs.closed;
|
|
}
|
|
|
|
//########################################################################
|
|
// Ordinal plasma graphics (verbatim from the console / TEST.EGG)
|
|
//########################################################################
|
|
|
|
namespace
|
|
{
|
|
const char *kOrdinalsBlock =
|
|
"[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";
|
|
}
|