FE: the RP412 menu + lobby-room pattern; flat single-exe dist (per Cyd)
Menu (btl4fe, modeled on RP412 RP_L4/RPL4FE.cpp): full catalogs as always-visible clickable LISTS in three columns with friendly names -- 8 maps, the canonical 8 certified mechs, colors, times, weather, lengths, modes -- bright/dim green with a selection chevron, double-buffered paint, real EDIT controls for pilot name + LAN peers (green-on-black via WM_CTLCOLOREDIT), framed LAUNCH / HOST STEAM / JOIN STEAM buttons, and the loadout persisted across the per-mission relaunch (fe_last.ini, gitignored implicitly by *.ini? no -- plain file beside the eggs). ENTER/ESC kept. Lobby room (RP412 RPL4LOBBY look): pilot roster with loadouts (mech, color), framed L A U N C H M I S S I O N (owner) + LEAVE LOBBY buttons, clickable. Dist: FLAT layout (the RP412 convention) -- ONE btl4.exe entry point at the folder root beside the content tree (the shape TeslaConsole manages), DLLs + launchers at root, content flattened from git-tracked files. Verified: the staged flat tree boots the Mission Console zero-arg from its own root; the click-driven menu launches a full marshaled mission (row click -> length=60 in the egg, LAUNCH click -> ladder -> 41+ ticks, fe_last.ini written). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+35
-51
@@ -1,11 +1,11 @@
|
||||
[mission]
|
||||
adventure=BattleTech
|
||||
map=cavern
|
||||
map=grass
|
||||
scenario=freeforall
|
||||
time=night
|
||||
time=day
|
||||
weather=clear
|
||||
temperature=27
|
||||
length=600
|
||||
length=60
|
||||
[ordinals]
|
||||
bitmap=Ordinal::BitMap::1
|
||||
bitmap=Ordinal::BitMap::2
|
||||
@@ -157,64 +157,49 @@ y=32
|
||||
width=8
|
||||
[pilots]
|
||||
pilot=127.0.0.1:1502
|
||||
pilot=127.0.0.1:1602
|
||||
[127.0.0.1:1502]
|
||||
hostType=0
|
||||
advancedDamage=1
|
||||
loadzones=1
|
||||
name=Aeolus
|
||||
name=Dev
|
||||
bitmapindex=1
|
||||
experience=expert
|
||||
badge=VGL
|
||||
patch=Yellow
|
||||
role=Role::Default
|
||||
dropzone=one
|
||||
vehicle=bhk1
|
||||
vehicle=avatar
|
||||
vehicleValue=1000
|
||||
color=White
|
||||
[127.0.0.1:1602]
|
||||
hostType=0
|
||||
advancedDamage=1
|
||||
loadzones=1
|
||||
name=Boreas
|
||||
bitmapindex=2
|
||||
experience=expert
|
||||
badge=VGL
|
||||
patch=Yellow
|
||||
role=Role::Default
|
||||
dropzone=one
|
||||
vehicle=ava1
|
||||
vehicleValue=1000
|
||||
color=Red
|
||||
[largebitmap]
|
||||
bitmap=BitMap::Large::Aeolus
|
||||
[BitMap::Large::Aeolus]
|
||||
bitmap=BitMap::Large::Dev
|
||||
[BitMap::Large::Dev]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=000003FE0000000001E0000000000000
|
||||
bitmap=000007FF0000000001E0000000000000
|
||||
bitmap=00000F8F8000000001E0000000000000
|
||||
bitmap=00000F078000000001E0000000000000
|
||||
bitmap=00000F0781FF00FF81E0F0781FC00000
|
||||
bitmap=00000F0783FF81FFC1E0F0783FE00000
|
||||
bitmap=00000F0787C7C3E3E1E0F0783C700000
|
||||
bitmap=00000F078783C3C1E1E0F07878300000
|
||||
bitmap=00000F078783C3C1E1E0F07878000000
|
||||
bitmap=00000FFF8783C3C1E1E0F0787C000000
|
||||
bitmap=00000FFF8783C3C1E1E0F0787F000000
|
||||
bitmap=00000F0787FFC3C1E1E0F0783FC00000
|
||||
bitmap=00000F0787FFC3C1E1E0F0781FE00000
|
||||
bitmap=00000F07878003C1E1E0F07807F00000
|
||||
bitmap=00000F07878003C1E1E0F07801F00000
|
||||
bitmap=00000F07878003C1E1E0F07800F00000
|
||||
bitmap=00000F078780C3C1E1E0F07860F00000
|
||||
bitmap=00000F0787C1C3E3E1E078F871E00000
|
||||
bitmap=00000F0783FF81FFC1E07FF83FE00000
|
||||
bitmap=00000F0781FF00FF81E03F781FC00000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000FFC000000000000000000
|
||||
bitmap=00000000000FFE000000000000000000
|
||||
bitmap=00000000000FFF000000000000000000
|
||||
bitmap=00000000000E07800000000000000000
|
||||
bitmap=00000000000E0381F070700000000000
|
||||
bitmap=00000000000E01C3F870700000000000
|
||||
bitmap=00000000000E01C7FC70700000000000
|
||||
bitmap=00000000000E01CF1C38E00000000000
|
||||
bitmap=00000000000E01CE0E38E00000000000
|
||||
bitmap=00000000000E01CFFE38E00000000000
|
||||
bitmap=00000000000E01CFFE1DC00000000000
|
||||
bitmap=00000000000E01CE001DC00000000000
|
||||
bitmap=00000000000E038E000D800000000000
|
||||
bitmap=00000000000E07870E0F800000000000
|
||||
bitmap=00000000000FFF07FC0F800000000000
|
||||
bitmap=00000000000FFE03FC07000000000000
|
||||
bitmap=00000000000FFC01F007000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
@@ -225,15 +210,15 @@ x=128
|
||||
y=32
|
||||
width=8
|
||||
[smallbitmap]
|
||||
bitmap=BitMap::Small::Aeolus
|
||||
[BitMap::Small::Aeolus]
|
||||
bitmap=BitMap::Small::Dev
|
||||
[BitMap::Small::Dev]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=0000000000000000000007C000000000
|
||||
bitmap=000006600000000000000633D8C00000
|
||||
bitmap=0000063678C00000000006366D800000
|
||||
bitmap=00000637ED800000000006360D800000
|
||||
bitmap=0000066667000000000007C3C7000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=0000000000000000003E00000C000000
|
||||
bitmap=006300000C00000000631F0F8CC63C00
|
||||
bitmap=00633198CCC66200007F3198CCC66000
|
||||
bitmap=00633F98CCC6780000633018CCC61E00
|
||||
bitmap=00633018CCC6060000633098CCC64600
|
||||
bitmap=00631F0F8C7E3C000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=64
|
||||
y=16
|
||||
@@ -242,4 +227,3 @@ width=4
|
||||
model=dfltrole
|
||||
[Role::NoReturn]
|
||||
model=noretun
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
MAP=0
|
||||
TIME=0
|
||||
WEATHER=0
|
||||
MECH=0
|
||||
LENGTH=0
|
||||
MODE=0
|
||||
COLOR=0
|
||||
NET PORT=0
|
||||
NAME=Dev
|
||||
PEERS=
|
||||
+546
-271
@@ -19,23 +19,6 @@
|
||||
// accepts anything; these are the menu's cycle sets.
|
||||
//###########################################################################
|
||||
|
||||
static const char *kMaps[] =
|
||||
{ "grass", "cavern", "rav", "polar3", "polar4", "arena1", "arena2", "dbase" };
|
||||
static const char *kTimes[] = { "day", "night", "morning", "dusk" };
|
||||
static const char *kWeathers[] = { "clear", "fog", "rain", "snow" };
|
||||
static const int kLengths[] = { 300, 600, 1200, 60 };
|
||||
static const char *kVehicles[] = { "bhk1", "madcat", "ava1" };
|
||||
static const char *kColors[] = { "White", "Black", "Crimson" };
|
||||
static const char *kModes[] =
|
||||
{ "SOLO MISSION (networked)", "RAW SOLO (-egg, endless)",
|
||||
"HOST LAN", "JOIN LAN",
|
||||
#ifdef BT_STEAM
|
||||
"HOST STEAM LOBBY", "JOIN STEAM LOBBY",
|
||||
#endif
|
||||
};
|
||||
static const int kPorts[] = { 1501, 1601, 1701, 1801 };
|
||||
|
||||
#define COUNT(a) ((int)(sizeof(a)/sizeof((a)[0])))
|
||||
|
||||
//###########################################################################
|
||||
// The 4 static rank-place ordinal bitmaps -- byte-identical to the console
|
||||
@@ -363,184 +346,435 @@ int
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// The menu -- a green-on-black GDI catalog, own message loop (runs before
|
||||
// any engine init). Up/Down select, Left/Right (or click) cycle, type
|
||||
// into the text fields, ENTER launches, ESC quits.
|
||||
// The menu -- the RP412 front-end pattern (RP_L4/RPL4FE.cpp, per Cyd):
|
||||
// full catalogs as always-visible clickable LISTS in three columns with
|
||||
// friendly display names -- bright green = selected, dim green = the rest --
|
||||
// a real EDIT control for the pilot name (and one for LAN peers), framed
|
||||
// LAUNCH / HOST STEAM / JOIN STEAM buttons, double-buffered paint, and the
|
||||
// player's loadout persisted across runs (fe_last.ini beside the eggs; our
|
||||
// per-mission process relaunch would otherwise forget it).
|
||||
// ENTER = launch, ESC = quit.
|
||||
//###########################################################################
|
||||
|
||||
enum MenuField
|
||||
struct CatalogEntry
|
||||
{
|
||||
FieldMap = 0,
|
||||
FieldTime,
|
||||
FieldWeather,
|
||||
FieldLength,
|
||||
FieldVehicle,
|
||||
FieldColor,
|
||||
FieldName,
|
||||
FieldMode,
|
||||
FieldPort,
|
||||
FieldPeers,
|
||||
FieldLaunch,
|
||||
FieldCount
|
||||
const char *key;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
static const CatalogEntry kMaps[] =
|
||||
{
|
||||
{ "grass", "Grasslands" },
|
||||
{ "cavern", "The Cavern" },
|
||||
{ "rav", "The Ravine" },
|
||||
{ "polar3", "Polar Station 3" },
|
||||
{ "polar4", "Polar Station 4" },
|
||||
{ "arena1", "Arena One" },
|
||||
{ "arena2", "Arena Two" },
|
||||
{ "dbase", "Desert Base" },
|
||||
};
|
||||
|
||||
// The canonical 8 (ALL 18 ModelList names certified 2026-07-18; the menu
|
||||
// offers the full mechs -- variants remain reachable via hand-built eggs).
|
||||
static const CatalogEntry kVehicles[] =
|
||||
{
|
||||
{ "avatar", "Avatar" },
|
||||
{ "blkhawk", "Black Hawk" },
|
||||
{ "loki", "Loki" },
|
||||
{ "madcat", "Mad Cat" },
|
||||
{ "owens", "Owens" },
|
||||
{ "sunder", "Sunder" },
|
||||
{ "thor", "Thor" },
|
||||
{ "vulture", "Vulture" },
|
||||
};
|
||||
|
||||
static const CatalogEntry kColors[] =
|
||||
{
|
||||
{ "White", "White" }, { "Black", "Black" }, { "Crimson", "Crimson" },
|
||||
};
|
||||
|
||||
static const CatalogEntry kTimes[] =
|
||||
{
|
||||
{ "day", "Day" }, { "night", "Night" },
|
||||
{ "morning", "Morning" }, { "dusk", "Dusk" },
|
||||
};
|
||||
|
||||
static const CatalogEntry kWeathers[] =
|
||||
{
|
||||
{ "clear", "Clear" }, { "fog", "Light Fog" }, { "soup", "Heavy Fog" },
|
||||
};
|
||||
|
||||
struct LengthEntry
|
||||
{
|
||||
int seconds;
|
||||
const char *name;
|
||||
};
|
||||
static const LengthEntry kLengths[] =
|
||||
{
|
||||
{ 60, "1:00" }, { 300, "5:00" }, { 600, "10:00" }, { 1200, "20:00" },
|
||||
};
|
||||
|
||||
static const CatalogEntry kModes[] =
|
||||
{
|
||||
{ "solo", "SOLO MISSION" },
|
||||
{ "raw", "RAW SOLO (endless)" },
|
||||
{ "hostlan", "HOST LAN" },
|
||||
{ "joinlan", "JOIN LAN" },
|
||||
};
|
||||
|
||||
static const int kPorts[] = { 1501, 1601, 1701, 1801 };
|
||||
|
||||
#define COUNT(a) ((int)(sizeof(a)/sizeof((a)[0])))
|
||||
|
||||
enum MenuGroup
|
||||
{
|
||||
GroupMap = 0,
|
||||
GroupTime,
|
||||
GroupWeather,
|
||||
GroupVehicle,
|
||||
GroupLength,
|
||||
GroupMode,
|
||||
GroupColor,
|
||||
GroupPort,
|
||||
GroupLaunch,
|
||||
GroupSteamHost,
|
||||
GroupSteamJoin,
|
||||
GroupCount
|
||||
};
|
||||
|
||||
struct MenuItem
|
||||
{
|
||||
int group;
|
||||
int index;
|
||||
RECT rect;
|
||||
};
|
||||
|
||||
struct MenuState
|
||||
{
|
||||
int mapIndex, timeIndex, weatherIndex, lengthIndex;
|
||||
int vehicleIndex, colorIndex, modeIndex, portIndex;
|
||||
char pilotName[32];
|
||||
char peers[128];
|
||||
int selected;
|
||||
int selection[GroupCount];
|
||||
MenuItem items[96];
|
||||
int itemCount;
|
||||
HWND window;
|
||||
HWND nameEdit;
|
||||
HWND peersEdit;
|
||||
HFONT textFont;
|
||||
HFONT titleFont;
|
||||
HBRUSH editBrush;
|
||||
int result; // 0 pending, 1 launch, -1 quit
|
||||
int steamAction; // 0 none, 1 host lobby, 2 join lobby
|
||||
};
|
||||
|
||||
static MenuState menu;
|
||||
|
||||
static void
|
||||
CycleField(int field, int direction)
|
||||
static const COLORREF kGreenBright = RGB(64, 255, 64);
|
||||
static const COLORREF kGreenDim = RGB(24, 140, 24);
|
||||
|
||||
static int
|
||||
GroupSize(int group)
|
||||
{
|
||||
switch (field)
|
||||
switch (group)
|
||||
{
|
||||
case FieldMap:
|
||||
menu.mapIndex = (menu.mapIndex + direction + COUNT(kMaps)) % COUNT(kMaps);
|
||||
break;
|
||||
case FieldTime:
|
||||
menu.timeIndex = (menu.timeIndex + direction + COUNT(kTimes)) % COUNT(kTimes);
|
||||
break;
|
||||
case FieldWeather:
|
||||
menu.weatherIndex = (menu.weatherIndex + direction + COUNT(kWeathers)) % COUNT(kWeathers);
|
||||
break;
|
||||
case FieldLength:
|
||||
menu.lengthIndex = (menu.lengthIndex + direction + COUNT(kLengths)) % COUNT(kLengths);
|
||||
break;
|
||||
case FieldVehicle:
|
||||
menu.vehicleIndex = (menu.vehicleIndex + direction + COUNT(kVehicles)) % COUNT(kVehicles);
|
||||
break;
|
||||
case FieldColor:
|
||||
menu.colorIndex = (menu.colorIndex + direction + COUNT(kColors)) % COUNT(kColors);
|
||||
break;
|
||||
case FieldMode:
|
||||
menu.modeIndex = (menu.modeIndex + direction + COUNT(kModes)) % COUNT(kModes);
|
||||
break;
|
||||
case FieldPort:
|
||||
menu.portIndex = (menu.portIndex + direction + COUNT(kPorts)) % COUNT(kPorts);
|
||||
break;
|
||||
case GroupMap: return COUNT(kMaps);
|
||||
case GroupTime: return COUNT(kTimes);
|
||||
case GroupWeather: return COUNT(kWeathers);
|
||||
case GroupVehicle: return COUNT(kVehicles);
|
||||
case GroupLength: return COUNT(kLengths);
|
||||
case GroupMode: return COUNT(kModes);
|
||||
case GroupColor: return COUNT(kColors);
|
||||
case GroupPort: return COUNT(kPorts);
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
FieldLine(int field, char *out /*128*/)
|
||||
GroupRowText(int group, int index, char *out /*64*/)
|
||||
{
|
||||
switch (field)
|
||||
switch (group)
|
||||
{
|
||||
case FieldMap: sprintf(out, "MAP < %s >", kMaps[menu.mapIndex]); break;
|
||||
case FieldTime: sprintf(out, "TIME < %s >", kTimes[menu.timeIndex]); break;
|
||||
case FieldWeather: sprintf(out, "WEATHER < %s >", kWeathers[menu.weatherIndex]); break;
|
||||
case FieldLength: sprintf(out, "LENGTH < %d s >", kLengths[menu.lengthIndex]); break;
|
||||
case FieldVehicle: sprintf(out, "MECH < %s >", kVehicles[menu.vehicleIndex]); break;
|
||||
case FieldColor: sprintf(out, "COLOR < %s >", kColors[menu.colorIndex]); break;
|
||||
case FieldName: sprintf(out, "PILOT [ %s_ ]", menu.pilotName); break;
|
||||
case FieldMode: sprintf(out, "MODE < %s >", kModes[menu.modeIndex]); break;
|
||||
case FieldPort: sprintf(out, "NET PORT < %d >", kPorts[menu.portIndex]); break;
|
||||
case FieldPeers: sprintf(out, "PEERS [ %s_ ] (host: ip:port,...)", menu.peers); break;
|
||||
case FieldLaunch: sprintf(out, " >>> LAUNCH <<<"); break;
|
||||
default: out[0] = 0; break;
|
||||
case GroupMap: strcpy(out, kMaps[index].name); break;
|
||||
case GroupTime: strcpy(out, kTimes[index].name); break;
|
||||
case GroupWeather: strcpy(out, kWeathers[index].name); break;
|
||||
case GroupVehicle: strcpy(out, kVehicles[index].name); break;
|
||||
case GroupLength: strcpy(out, kLengths[index].name); break;
|
||||
case GroupMode: strcpy(out, kModes[index].name); break;
|
||||
case GroupColor: strcpy(out, kColors[index].name); break;
|
||||
case GroupPort: sprintf(out, "%d", kPorts[index]); break;
|
||||
default: out[0] = 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
GroupHeader(int group)
|
||||
{
|
||||
switch (group)
|
||||
{
|
||||
case GroupMap: return "MAP";
|
||||
case GroupTime: return "TIME";
|
||||
case GroupWeather: return "WEATHER";
|
||||
case GroupVehicle: return "MECH";
|
||||
case GroupLength: return "LENGTH";
|
||||
case GroupMode: return "MODE";
|
||||
case GroupColor: return "COLOR";
|
||||
case GroupPort: return "NET PORT";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Layout: three catalog columns + the button column.
|
||||
// col1: MAP, TIME, WEATHER col2: MECH, LENGTH
|
||||
// col3: MODE, COLOR, NET PORT col4: name/peers edits + buttons
|
||||
//
|
||||
enum
|
||||
{
|
||||
MenuClientW = 960,
|
||||
MenuClientH = 560,
|
||||
MenuRowH = 22,
|
||||
MenuColW = 190,
|
||||
MenuCol1X = 28,
|
||||
MenuCol2X = 254,
|
||||
MenuCol3X = 480,
|
||||
MenuCol4X = 706,
|
||||
MenuTopY = 64
|
||||
};
|
||||
|
||||
static void
|
||||
AddGroup(int group, int x, int *y)
|
||||
{
|
||||
*y += MenuRowH; // the header row
|
||||
int count = GroupSize(group);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
MenuItem *item = &menu.items[menu.itemCount++];
|
||||
item->group = group;
|
||||
item->index = i;
|
||||
item->rect.left = x;
|
||||
item->rect.top = *y;
|
||||
item->rect.right = x + MenuColW;
|
||||
item->rect.bottom = *y + MenuRowH;
|
||||
*y += MenuRowH;
|
||||
}
|
||||
*y += MenuRowH / 2; // gap below the group
|
||||
}
|
||||
|
||||
static void
|
||||
AddButton(int group, int x, int y, int width, int height)
|
||||
{
|
||||
MenuItem *item = &menu.items[menu.itemCount++];
|
||||
item->group = group;
|
||||
item->index = 0;
|
||||
item->rect.left = x;
|
||||
item->rect.top = y;
|
||||
item->rect.right = x + width;
|
||||
item->rect.bottom = y + height;
|
||||
}
|
||||
|
||||
static void
|
||||
LayoutMenu()
|
||||
{
|
||||
menu.itemCount = 0;
|
||||
int y;
|
||||
y = MenuTopY; AddGroup(GroupMap, MenuCol1X, &y);
|
||||
AddGroup(GroupTime, MenuCol1X, &y);
|
||||
AddGroup(GroupWeather, MenuCol1X, &y);
|
||||
y = MenuTopY; AddGroup(GroupVehicle, MenuCol2X, &y);
|
||||
AddGroup(GroupLength, MenuCol2X, &y);
|
||||
y = MenuTopY; AddGroup(GroupMode, MenuCol3X, &y);
|
||||
AddGroup(GroupColor, MenuCol3X, &y);
|
||||
AddGroup(GroupPort, MenuCol3X, &y);
|
||||
AddButton(GroupLaunch, MenuCol4X, MenuClientH - 190, 220, 52);
|
||||
#ifdef BT_STEAM
|
||||
AddButton(GroupSteamHost, MenuCol4X, MenuClientH - 120, 220, 34);
|
||||
AddButton(GroupSteamJoin, MenuCol4X, MenuClientH - 76, 220, 34);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// fe_last.ini -- the persisted loadout (our process-relaunch model would
|
||||
// otherwise forget it between missions; RP412 persists in-process).
|
||||
//
|
||||
static void
|
||||
LoadPersisted(char *pilot_name, int name_capacity,
|
||||
char *peers, int peers_capacity)
|
||||
{
|
||||
FILE *f = fopen("fe_last.ini", "rt");
|
||||
if (f == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
char line[160];
|
||||
while (fgets(line, sizeof(line), f))
|
||||
{
|
||||
char *newline = strchr(line, '\n');
|
||||
if (newline) *newline = 0;
|
||||
char *equals = strchr(line, '=');
|
||||
if (equals == NULL) continue;
|
||||
*equals = 0;
|
||||
const char *value = equals + 1;
|
||||
for (int g = 0; g < GroupCount; ++g)
|
||||
{
|
||||
if (_stricmp(line, GroupHeader(g)) == 0)
|
||||
{
|
||||
int v = atoi(value);
|
||||
if (v >= 0 && v < GroupSize(g)) menu.selection[g] = v;
|
||||
}
|
||||
}
|
||||
if (_stricmp(line, "NAME") == 0 && value[0])
|
||||
{
|
||||
strncpy(pilot_name, value, name_capacity - 1);
|
||||
pilot_name[name_capacity - 1] = 0;
|
||||
}
|
||||
if (_stricmp(line, "PEERS") == 0)
|
||||
{
|
||||
strncpy(peers, value, peers_capacity - 1);
|
||||
peers[peers_capacity - 1] = 0;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static void
|
||||
SavePersisted(const char *pilot_name, const char *peers)
|
||||
{
|
||||
FILE *f = fopen("fe_last.ini", "wt");
|
||||
if (f == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int g = 0; g <= GroupPort; ++g)
|
||||
{
|
||||
fprintf(f, "%s=%d\n", GroupHeader(g), menu.selection[g]);
|
||||
}
|
||||
fprintf(f, "NAME=%s\nPEERS=%s\n", pilot_name, peers);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static void
|
||||
DrawMenuText(HDC dc, const char *text, RECT *rect, COLORREF color,
|
||||
UINT format)
|
||||
{
|
||||
WCHAR wide[128];
|
||||
int n = 0;
|
||||
for (const char *s = text; *s && n < 127; ++s) wide[n++] = (WCHAR)*s;
|
||||
wide[n] = 0;
|
||||
SetTextColor(dc, color);
|
||||
DrawTextW(dc, wide, -1, rect, format);
|
||||
}
|
||||
|
||||
static void
|
||||
PaintMenu(HWND window)
|
||||
{
|
||||
PAINTSTRUCT paint;
|
||||
HDC dc = BeginPaint(window, &paint);
|
||||
|
||||
HDC screen = BeginPaint(window, &paint);
|
||||
RECT client;
|
||||
GetClientRect(window, &client);
|
||||
HBRUSH background = CreateSolidBrush(RGB(4, 12, 4));
|
||||
|
||||
// double-buffered (the RP412 pattern)
|
||||
HDC dc = CreateCompatibleDC(screen);
|
||||
HBITMAP backing = CreateCompatibleBitmap(screen, client.right, client.bottom);
|
||||
HBITMAP old_backing = (HBITMAP)SelectObject(dc, backing);
|
||||
|
||||
HBRUSH background = CreateSolidBrush(RGB(0, 0, 0));
|
||||
FillRect(dc, &client, background);
|
||||
DeleteObject(background);
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
|
||||
HFONT font = CreateFontW(-16, 0, 0, 0, FW_NORMAL, 0, 0, 0,
|
||||
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, FIXED_PITCH, L"Consolas");
|
||||
HFONT old_font = (HFONT)SelectObject(dc, font);
|
||||
HFONT old_font = (HFONT)SelectObject(dc, menu.titleFont);
|
||||
RECT title_rect = { MenuCol1X, 14, client.right - 24, 54 };
|
||||
DrawMenuText(dc, "BATTLETECH -- MISSION CONSOLE", &title_rect,
|
||||
kGreenBright, DT_LEFT | DT_TOP | DT_SINGLELINE);
|
||||
|
||||
SetTextColor(dc, RGB(90, 255, 90));
|
||||
RECT title_rect = { 24, 16, client.right - 24, 44 };
|
||||
DrawTextW(dc, L"BATTLETECH -- MISSION CONSOLE", -1, &title_rect,
|
||||
DT_LEFT | DT_TOP | DT_SINGLELINE);
|
||||
SelectObject(dc, menu.textFont);
|
||||
|
||||
for (int field = 0; field < FieldCount; ++field)
|
||||
//
|
||||
// Group headers (above each group's first row).
|
||||
//
|
||||
for (int i = 0; i < menu.itemCount; ++i)
|
||||
{
|
||||
char line[160];
|
||||
FieldLine(field, line);
|
||||
WCHAR wide[160];
|
||||
int n = 0;
|
||||
for (const char *s = line; *s && n < 159; ++s)
|
||||
const MenuItem &item = menu.items[i];
|
||||
if (item.index != 0 || item.group > GroupPort)
|
||||
{
|
||||
wide[n++] = (WCHAR)*s;
|
||||
continue;
|
||||
}
|
||||
wide[n] = 0;
|
||||
RECT header = item.rect;
|
||||
header.top -= MenuRowH;
|
||||
header.bottom = item.rect.top;
|
||||
DrawMenuText(dc, GroupHeader(item.group), &header, kGreenDim,
|
||||
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
|
||||
int selected = (field == menu.selected);
|
||||
SetTextColor(dc, selected ? RGB(200, 255, 120) : RGB(70, 200, 70));
|
||||
RECT row = { 40, 64 + field * 28, client.right - 24, 92 + field * 28 };
|
||||
//
|
||||
// Catalog rows: bright = selected, dim = not.
|
||||
//
|
||||
for (int i = 0; i < menu.itemCount; ++i)
|
||||
{
|
||||
const MenuItem &item = menu.items[i];
|
||||
if (item.group > GroupPort)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
char text[64];
|
||||
GroupRowText(item.group, item.index, text);
|
||||
int selected = (menu.selection[item.group] == item.index);
|
||||
RECT row = item.rect;
|
||||
row.left += 14;
|
||||
if (selected)
|
||||
{
|
||||
RECT marker = { 24, row.top, 40, row.bottom };
|
||||
DrawTextW(dc, L">", -1, &marker, DT_LEFT | DT_TOP | DT_SINGLELINE);
|
||||
RECT marker = item.rect;
|
||||
marker.right = marker.left + 14;
|
||||
DrawMenuText(dc, ">", &marker, kGreenBright,
|
||||
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
DrawTextW(dc, wide, -1, &row, DT_LEFT | DT_TOP | DT_SINGLELINE);
|
||||
DrawMenuText(dc, text, &row,
|
||||
selected ? kGreenBright : kGreenDim,
|
||||
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
|
||||
SetTextColor(dc, RGB(40, 140, 40));
|
||||
RECT help_rect = { 24, client.bottom - 52, client.right - 24, client.bottom - 8 };
|
||||
DrawTextW(dc,
|
||||
L"UP/DOWN select LEFT/RIGHT or click = change type into [ ] fields\n"
|
||||
L"ENTER = launch ESC = quit",
|
||||
-1, &help_rect, DT_LEFT | DT_TOP);
|
||||
//
|
||||
// Edit labels (the controls themselves are child windows).
|
||||
//
|
||||
RECT label = { MenuCol4X, MenuTopY, MenuCol4X + 220, MenuTopY + MenuRowH };
|
||||
DrawMenuText(dc, "PILOT NAME", &label, kGreenDim,
|
||||
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
label.top = MenuTopY + 3 * MenuRowH;
|
||||
label.bottom = label.top + MenuRowH;
|
||||
DrawMenuText(dc, "LAN PEERS (host: ip:port,...)", &label, kGreenDim,
|
||||
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
||||
|
||||
//
|
||||
// Buttons (framed, RP412 style).
|
||||
//
|
||||
HBRUSH frame = CreateSolidBrush(kGreenBright);
|
||||
for (int i = 0; i < menu.itemCount; ++i)
|
||||
{
|
||||
const MenuItem &item = menu.items[i];
|
||||
const char *text =
|
||||
(item.group == GroupLaunch) ? "L A U N C H" :
|
||||
(item.group == GroupSteamHost) ? "HOST STEAM LOBBY" :
|
||||
(item.group == GroupSteamJoin) ? "JOIN STEAM LOBBY" : NULL;
|
||||
if (text == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
RECT box = item.rect;
|
||||
FrameRect(dc, &box, frame);
|
||||
DrawMenuText(dc, text, &box, kGreenBright,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
DeleteObject(frame);
|
||||
|
||||
RECT help = { MenuCol1X, client.bottom - 30, client.right - 24, client.bottom - 6 };
|
||||
DrawMenuText(dc, "click to select ENTER = launch ESC = quit",
|
||||
&help, kGreenDim, DT_LEFT | DT_TOP | DT_SINGLELINE);
|
||||
|
||||
SelectObject(dc, old_font);
|
||||
DeleteObject(font);
|
||||
BitBlt(screen, 0, 0, client.right, client.bottom, dc, 0, 0, SRCCOPY);
|
||||
SelectObject(dc, old_backing);
|
||||
DeleteObject(backing);
|
||||
DeleteDC(dc);
|
||||
EndPaint(window, &paint);
|
||||
}
|
||||
|
||||
static void
|
||||
TypeIntoField(int field, WPARAM ch)
|
||||
{
|
||||
char *buffer = (field == FieldName) ? menu.pilotName
|
||||
: (field == FieldPeers) ? menu.peers : NULL;
|
||||
int capacity = (field == FieldName) ? (int)sizeof(menu.pilotName)
|
||||
: (int)sizeof(menu.peers);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int length = (int)strlen(buffer);
|
||||
if (ch == VK_BACK)
|
||||
{
|
||||
if (length > 0)
|
||||
{
|
||||
buffer[length - 1] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ch >= 32 && ch < 127 && length < capacity - 1)
|
||||
{
|
||||
//
|
||||
// Pilot names become NotationFile page names -- keep them clean.
|
||||
//
|
||||
if (field == FieldName &&
|
||||
!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= '0' && ch <= '9')))
|
||||
{
|
||||
return;
|
||||
}
|
||||
buffer[length] = (char)ch;
|
||||
buffer[length + 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK
|
||||
MenuWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
|
||||
{
|
||||
@@ -550,59 +784,59 @@ static LRESULT CALLBACK
|
||||
PaintMenu(window);
|
||||
return 0;
|
||||
|
||||
case WM_KEYDOWN:
|
||||
switch (wparam)
|
||||
{
|
||||
case VK_UP:
|
||||
menu.selected = (menu.selected + FieldCount - 1) % FieldCount;
|
||||
break;
|
||||
case VK_DOWN:
|
||||
menu.selected = (menu.selected + 1) % FieldCount;
|
||||
break;
|
||||
case VK_LEFT:
|
||||
CycleField(menu.selected, -1);
|
||||
break;
|
||||
case VK_RIGHT:
|
||||
CycleField(menu.selected, +1);
|
||||
break;
|
||||
case VK_RETURN:
|
||||
menu.result = 1;
|
||||
break;
|
||||
case VK_ESCAPE:
|
||||
menu.result = -1;
|
||||
break;
|
||||
case VK_BACK:
|
||||
TypeIntoField(menu.selected, VK_BACK);
|
||||
break;
|
||||
}
|
||||
InvalidateRect(window, NULL, FALSE);
|
||||
return 0;
|
||||
|
||||
case WM_CHAR:
|
||||
if (wparam >= 32)
|
||||
{
|
||||
TypeIntoField(menu.selected, wparam);
|
||||
InvalidateRect(window, NULL, FALSE);
|
||||
}
|
||||
return 0;
|
||||
case WM_CTLCOLOREDIT:
|
||||
//
|
||||
// The edits render green-on-black like everything else.
|
||||
//
|
||||
SetTextColor((HDC)wparam, kGreenBright);
|
||||
SetBkColor((HDC)wparam, RGB(6, 20, 6));
|
||||
return (LRESULT)menu.editBrush;
|
||||
|
||||
case WM_LBUTTONDOWN:
|
||||
{
|
||||
int x = (int)(short)LOWORD(lparam);
|
||||
int y = (int)(short)HIWORD(lparam);
|
||||
int field = (y - 64) / 28;
|
||||
if (field >= 0 && field < FieldCount)
|
||||
for (int i = 0; i < menu.itemCount; ++i)
|
||||
{
|
||||
menu.selected = field;
|
||||
if (field == FieldLaunch)
|
||||
const MenuItem &item = menu.items[i];
|
||||
if (x < item.rect.left || x >= item.rect.right ||
|
||||
y < item.rect.top || y >= item.rect.bottom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.group == GroupLaunch)
|
||||
{
|
||||
menu.result = 1;
|
||||
}
|
||||
else if (item.group == GroupSteamHost)
|
||||
{
|
||||
menu.steamAction = 1;
|
||||
menu.result = 1;
|
||||
}
|
||||
else if (item.group == GroupSteamJoin)
|
||||
{
|
||||
menu.steamAction = 2;
|
||||
menu.result = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
CycleField(field, +1);
|
||||
menu.selection[item.group] = item.index;
|
||||
InvalidateRect(window, NULL, FALSE);
|
||||
}
|
||||
InvalidateRect(window, NULL, FALSE);
|
||||
break;
|
||||
}
|
||||
SetFocus(window); // clicks outside the edits take focus back
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_KEYDOWN:
|
||||
if (wparam == VK_RETURN)
|
||||
{
|
||||
menu.result = 1;
|
||||
}
|
||||
else if (wparam == VK_ESCAPE)
|
||||
{
|
||||
menu.result = -1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -618,8 +852,12 @@ int
|
||||
{
|
||||
memset(spec, 0, sizeof(*spec));
|
||||
memset(&menu, 0, sizeof(menu));
|
||||
strcpy(menu.pilotName, "Dev");
|
||||
menu.lengthIndex = 1; // 600 s, the console default
|
||||
menu.selection[GroupLength] = 2; // 10:00, the console default
|
||||
|
||||
char pilot_name[32] = "Dev";
|
||||
char peers[128] = "";
|
||||
LoadPersisted(pilot_name, sizeof(pilot_name), peers, sizeof(peers));
|
||||
LayoutMenu();
|
||||
|
||||
WNDCLASSW window_class;
|
||||
memset(&window_class, 0, sizeof(window_class));
|
||||
@@ -629,53 +867,152 @@ int
|
||||
window_class.lpszClassName = L"BTFrontEndWnd";
|
||||
RegisterClassW(&window_class);
|
||||
|
||||
HWND window = CreateWindowW(
|
||||
RECT frame_rect = { 0, 0, MenuClientW, MenuClientH };
|
||||
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
||||
AdjustWindowRect(&frame_rect, style, FALSE);
|
||||
menu.window = CreateWindowW(
|
||||
L"BTFrontEndWnd", L"BattleTech - Mission Console",
|
||||
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
|
||||
120, 120, 700, 480,
|
||||
style, 100, 80,
|
||||
frame_rect.right - frame_rect.left, frame_rect.bottom - frame_rect.top,
|
||||
NULL, NULL, GetModuleHandleW(NULL), NULL);
|
||||
if (window == NULL)
|
||||
if (menu.window == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ShowWindow(window, SW_SHOW);
|
||||
SetForegroundWindow(window);
|
||||
|
||||
menu.textFont = CreateFontW(-16, 0, 0, 0, FW_NORMAL, 0, 0, 0,
|
||||
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, FIXED_PITCH, L"Consolas");
|
||||
menu.titleFont = CreateFontW(-24, 0, 0, 0, FW_BOLD, 0, 0, 0,
|
||||
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, FIXED_PITCH, L"Consolas");
|
||||
menu.editBrush = CreateSolidBrush(RGB(6, 20, 6));
|
||||
|
||||
//
|
||||
// The two edit controls (name; LAN peers for host mode).
|
||||
//
|
||||
menu.nameEdit = CreateWindowW(L"EDIT", L"",
|
||||
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
|
||||
MenuCol4X, MenuTopY + MenuRowH, 220, MenuRowH + 4,
|
||||
menu.window, NULL, GetModuleHandleW(NULL), NULL);
|
||||
menu.peersEdit = CreateWindowW(L"EDIT", L"",
|
||||
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
|
||||
MenuCol4X, MenuTopY + 4 * MenuRowH, 220, MenuRowH + 4,
|
||||
menu.window, NULL, GetModuleHandleW(NULL), NULL);
|
||||
SendMessageW(menu.nameEdit, WM_SETFONT, (WPARAM)menu.textFont, TRUE);
|
||||
SendMessageW(menu.peersEdit, WM_SETFONT, (WPARAM)menu.textFont, TRUE);
|
||||
SetWindowTextA(menu.nameEdit, pilot_name);
|
||||
SetWindowTextA(menu.peersEdit, peers);
|
||||
|
||||
ShowWindow(menu.window, SW_SHOW);
|
||||
SetForegroundWindow(menu.window);
|
||||
|
||||
MSG message;
|
||||
while (menu.result == 0 && GetMessageW(&message, NULL, 0, 0))
|
||||
{
|
||||
//
|
||||
// ENTER/ESC work with an edit focused too.
|
||||
//
|
||||
if (message.message == WM_KEYDOWN &&
|
||||
(message.wParam == VK_RETURN || message.wParam == VK_ESCAPE))
|
||||
{
|
||||
menu.result = (message.wParam == VK_RETURN) ? 1 : -1;
|
||||
continue;
|
||||
}
|
||||
TranslateMessage(&message);
|
||||
DispatchMessageW(&message);
|
||||
}
|
||||
DestroyWindow(window);
|
||||
|
||||
GetWindowTextA(menu.nameEdit, pilot_name, sizeof(pilot_name));
|
||||
GetWindowTextA(menu.peersEdit, peers, sizeof(peers));
|
||||
|
||||
DestroyWindow(menu.window);
|
||||
DeleteObject(menu.textFont);
|
||||
DeleteObject(menu.titleFont);
|
||||
DeleteObject(menu.editBrush);
|
||||
|
||||
if (menu.result != 1)
|
||||
{
|
||||
return 1; // quit
|
||||
}
|
||||
SavePersisted(pilot_name[0] ? pilot_name : "Dev", peers);
|
||||
|
||||
//
|
||||
// Resolve the launch spec + write the egg.
|
||||
//
|
||||
int console_port = kPorts[menu.portIndex];
|
||||
int console_port = kPorts[menu.selection[GroupPort]];
|
||||
BTFeMission mission;
|
||||
memset(&mission, 0, sizeof(mission));
|
||||
strcpy(mission.map, kMaps[menu.mapIndex]);
|
||||
strcpy(mission.time, kTimes[menu.timeIndex]);
|
||||
strcpy(mission.weather, kWeathers[menu.weatherIndex]);
|
||||
strcpy(mission.map, kMaps[menu.selection[GroupMap]].key);
|
||||
strcpy(mission.time, kTimes[menu.selection[GroupTime]].key);
|
||||
strcpy(mission.weather, kWeathers[menu.selection[GroupWeather]].key);
|
||||
mission.temperature = 27;
|
||||
mission.lengthSeconds = kLengths[menu.lengthIndex];
|
||||
mission.lengthSeconds = kLengths[menu.selection[GroupLength]].seconds;
|
||||
mission.pilotCount = 1;
|
||||
BTFePilot &self = mission.pilots[0];
|
||||
strcpy(self.name, menu.pilotName[0] ? menu.pilotName : "Dev");
|
||||
strcpy(self.vehicle, kVehicles[menu.vehicleIndex]);
|
||||
strcpy(self.color, kColors[menu.colorIndex]);
|
||||
strcpy(self.name, pilot_name[0] ? pilot_name : "Dev");
|
||||
strcpy(self.vehicle, kVehicles[menu.selection[GroupVehicle]].key);
|
||||
strcpy(self.color, kColors[menu.selection[GroupColor]].key);
|
||||
|
||||
spec->consolePort = console_port;
|
||||
spec->missionSeconds = mission.lengthSeconds;
|
||||
strcpy(spec->eggPath, "frontend.egg");
|
||||
|
||||
switch (menu.modeIndex)
|
||||
int mode = menu.selection[GroupMode];
|
||||
#ifdef BT_STEAM
|
||||
if (menu.steamAction == 1)
|
||||
{
|
||||
BTLobbyRoster roster;
|
||||
if (BTLobby_HostAndRoom(self.name, self.vehicle, self.color,
|
||||
&roster, spec->steamMyToken, sizeof(spec->steamMyToken),
|
||||
spec->steamMap, sizeof(spec->steamMap)) != 0 ||
|
||||
roster.memberCount == 0)
|
||||
{
|
||||
return 1; // cancelled / Steam unavailable
|
||||
}
|
||||
spec->mode = BTFeLaunchHostSteam;
|
||||
mission.pilotCount = 0;
|
||||
sprintf(spec->podList, "127.0.0.1:%d", console_port);
|
||||
for (int m = 0; m < roster.memberCount && m < 8; ++m)
|
||||
{
|
||||
const BTLobbyMember &member = roster.members[m];
|
||||
BTFePilot &pilot = mission.pilots[mission.pilotCount++];
|
||||
strncpy(pilot.name, member.name, sizeof(pilot.name) - 1);
|
||||
strncpy(pilot.vehicle,
|
||||
member.vehicle[0] ? member.vehicle : kVehicles[0].key,
|
||||
sizeof(pilot.vehicle) - 1);
|
||||
strncpy(pilot.color,
|
||||
member.color[0] ? member.color : kColors[0].key,
|
||||
sizeof(pilot.color) - 1);
|
||||
sprintf(pilot.address, "%s:%d",
|
||||
member.fakeAddress, member.gamePort);
|
||||
if (!member.isSelf)
|
||||
{
|
||||
char entry[64];
|
||||
sprintf(entry, ",%s:%d",
|
||||
member.fakeAddress, member.consolePort);
|
||||
strcat(spec->podList, entry);
|
||||
}
|
||||
}
|
||||
if (BTFeMission_WriteEgg(&mission, spec->eggPath) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (menu.steamAction == 2)
|
||||
{
|
||||
if (BTLobby_JoinAndWait(self.name, self.vehicle, self.color,
|
||||
spec->steamMyToken, sizeof(spec->steamMyToken),
|
||||
spec->steamMap, sizeof(spec->steamMap)) != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
spec->mode = BTFeLaunchJoinSteam;
|
||||
spec->eggPath[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case 0: // SOLO MISSION: networked 1-pod, marshal feeds self
|
||||
spec->mode = BTFeLaunchSolo;
|
||||
@@ -693,12 +1030,8 @@ int
|
||||
spec->mode = BTFeLaunchHostLan;
|
||||
sprintf(self.address, "127.0.0.1:%d", console_port + 1);
|
||||
sprintf(spec->podList, "127.0.0.1:%d", console_port);
|
||||
//
|
||||
// Each peer "ip:consolePort" joins the roster with its GAME
|
||||
// port (console+1) and the marshal list with its console port.
|
||||
//
|
||||
char peers_copy[128];
|
||||
strcpy(peers_copy, menu.peers);
|
||||
strcpy(peers_copy, peers);
|
||||
char *cursor = strtok(peers_copy, ", ");
|
||||
while (cursor != NULL && mission.pilotCount < 8)
|
||||
{
|
||||
@@ -713,9 +1046,9 @@ int
|
||||
BTFePilot &peer = mission.pilots[mission.pilotCount];
|
||||
sprintf(peer.name, "Pilot%d", mission.pilotCount + 1);
|
||||
strcpy(peer.vehicle, kVehicles[
|
||||
(menu.vehicleIndex + mission.pilotCount) % COUNT(kVehicles)]);
|
||||
(menu.selection[GroupVehicle] + mission.pilotCount) % COUNT(kVehicles)].key);
|
||||
strcpy(peer.color, kColors[
|
||||
(menu.colorIndex + mission.pilotCount) % COUNT(kColors)]);
|
||||
(menu.selection[GroupColor] + mission.pilotCount) % COUNT(kColors)].key);
|
||||
sprintf(peer.address, "%s:%d", host, port + 1);
|
||||
++mission.pilotCount;
|
||||
char entry[64];
|
||||
@@ -727,68 +1060,10 @@ int
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // JOIN LAN: plain -net pod; the host's marshal feeds us
|
||||
default: // JOIN LAN: plain -net pod; the host's marshal feeds us
|
||||
spec->mode = BTFeLaunchJoinLan;
|
||||
spec->eggPath[0] = 0; // no egg written; host provides
|
||||
return 0;
|
||||
|
||||
#ifdef BT_STEAM
|
||||
case 4: // HOST STEAM LOBBY: the room fills the roster (self first)
|
||||
{
|
||||
BTLobbyRoster roster;
|
||||
if (BTLobby_HostAndRoom(self.name, self.vehicle, self.color,
|
||||
&roster, spec->steamMyToken, sizeof(spec->steamMyToken),
|
||||
spec->steamMap, sizeof(spec->steamMap)) != 0 ||
|
||||
roster.memberCount == 0)
|
||||
{
|
||||
return 1; // cancelled / Steam unavailable
|
||||
}
|
||||
spec->mode = BTFeLaunchHostSteam;
|
||||
//
|
||||
// Roster -> egg pilots (FAKE game addresses; self is pilot 1)
|
||||
// + the marshal pod list (self over TCP loopback, members at
|
||||
// their FAKE console addresses over the Steam wire).
|
||||
//
|
||||
mission.pilotCount = 0;
|
||||
sprintf(spec->podList, "127.0.0.1:%d", console_port);
|
||||
for (int m = 0; m < roster.memberCount && m < 8; ++m)
|
||||
{
|
||||
const BTLobbyMember &member = roster.members[m];
|
||||
BTFePilot &pilot = mission.pilots[mission.pilotCount++];
|
||||
strncpy(pilot.name, member.name, sizeof(pilot.name) - 1);
|
||||
strncpy(pilot.vehicle,
|
||||
member.vehicle[0] ? member.vehicle : kVehicles[0],
|
||||
sizeof(pilot.vehicle) - 1);
|
||||
strncpy(pilot.color,
|
||||
member.color[0] ? member.color : kColors[0],
|
||||
sizeof(pilot.color) - 1);
|
||||
sprintf(pilot.address, "%s:%d",
|
||||
member.fakeAddress, member.gamePort);
|
||||
if (!member.isSelf)
|
||||
{
|
||||
char entry[64];
|
||||
sprintf(entry, ",%s:%d",
|
||||
member.fakeAddress, member.consolePort);
|
||||
strcat(spec->podList, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // JOIN STEAM LOBBY: wait for GO, then a Steam-wire pod
|
||||
if (BTLobby_JoinAndWait(self.name, self.vehicle, self.color,
|
||||
spec->steamMyToken, sizeof(spec->steamMyToken),
|
||||
spec->steamMap, sizeof(spec->steamMap)) != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
spec->mode = BTFeLaunchJoinSteam;
|
||||
spec->eggPath[0] = 0;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (BTFeMission_WriteEgg(&mission, spec->eggPath) != 0)
|
||||
|
||||
@@ -129,6 +129,8 @@ static int
|
||||
|
||||
static int roomIsHost = 0;
|
||||
static int roomResult = 0; // 0 pending, 1 go, -1 cancel
|
||||
static RECT roomLaunchRect = { 140, 250, 420, 296 };
|
||||
static RECT roomLeaveRect = { 140, 306, 300, 336 };
|
||||
|
||||
static void
|
||||
PaintRoom(HWND window)
|
||||
@@ -147,14 +149,17 @@ static void
|
||||
HFONT old_font = (HFONT)SelectObject(dc, font);
|
||||
SetTextColor(dc, RGB(90, 255, 90));
|
||||
|
||||
//
|
||||
// The RP412 room look (RP_L4/RPL4LOBBY.cpp): pilot roster with
|
||||
// loadouts, a framed LAUNCH button for the owner, LEAVE for all.
|
||||
//
|
||||
WCHAR line[160];
|
||||
int y = 16;
|
||||
wsprintfW(line, L"STEAM LOBBY -- %s",
|
||||
roomIsHost ? L"HOSTING (ENTER = launch, ESC = cancel)"
|
||||
: L"WAITING FOR THE HOST (ESC = leave)");
|
||||
RECT row = { 24, y, client.right - 24, y + 24 };
|
||||
roomIsHost ? L"YOU ARE HOSTING" : L"WAITING FOR THE HOST");
|
||||
RECT row = { 24, y, client.right - 24, y + 26 };
|
||||
DrawTextW(dc, line, -1, &row, DT_LEFT | DT_TOP | DT_SINGLELINE);
|
||||
y += 40;
|
||||
y += 44;
|
||||
|
||||
ISteamMatchmaking *matchmaking = SteamMatchmaking();
|
||||
int count = matchmaking->GetNumLobbyMembers(currentLobby);
|
||||
@@ -167,18 +172,35 @@ static void
|
||||
int n = 0;
|
||||
for (const char *s = member.name; *s && n < 60; ++s) name[n++] = (WCHAR)*s;
|
||||
name[n] = 0;
|
||||
wsprintfW(line, L" %d. %s%s", i + 1, name,
|
||||
member.isSelf ? L" (you)" : L"");
|
||||
wsprintfW(line, L" %d. %-16s %hs, %hs%s", i + 1, name,
|
||||
member.vehicle[0] ? member.vehicle : "mech",
|
||||
member.color[0] ? member.color : "-",
|
||||
member.isSelf ? L" (you)" : L"");
|
||||
}
|
||||
else
|
||||
{
|
||||
wsprintfW(line, L" %d. (joining...)", i + 1);
|
||||
wsprintfW(line, L" %d. (joining...)", i + 1);
|
||||
}
|
||||
RECT member_row = { 24, y, client.right - 24, y + 24 };
|
||||
DrawTextW(dc, line, -1, &member_row, DT_LEFT | DT_TOP | DT_SINGLELINE);
|
||||
y += 26;
|
||||
}
|
||||
|
||||
//
|
||||
// Framed buttons (hit rects shared with the WndProc).
|
||||
//
|
||||
HBRUSH frame = CreateSolidBrush(RGB(90, 255, 90));
|
||||
if (roomIsHost)
|
||||
{
|
||||
FrameRect(dc, &roomLaunchRect, frame);
|
||||
DrawTextW(dc, L"L A U N C H M I S S I O N", -1, &roomLaunchRect,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
FrameRect(dc, &roomLeaveRect, frame);
|
||||
DrawTextW(dc, L"LEAVE LOBBY", -1, &roomLeaveRect,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
DeleteObject(frame);
|
||||
|
||||
SelectObject(dc, old_font);
|
||||
DeleteObject(font);
|
||||
EndPaint(window, &paint);
|
||||
@@ -217,6 +239,23 @@ static LRESULT CALLBACK
|
||||
roomResult = -1;
|
||||
}
|
||||
return 0;
|
||||
case WM_LBUTTONDOWN:
|
||||
{
|
||||
int x = (int)(short)LOWORD(lparam);
|
||||
int y = (int)(short)HIWORD(lparam);
|
||||
if (roomIsHost &&
|
||||
x >= roomLaunchRect.left && x < roomLaunchRect.right &&
|
||||
y >= roomLaunchRect.top && y < roomLaunchRect.bottom)
|
||||
{
|
||||
roomResult = 1;
|
||||
}
|
||||
else if (x >= roomLeaveRect.left && x < roomLeaveRect.right &&
|
||||
y >= roomLeaveRect.top && y < roomLeaveRect.bottom)
|
||||
{
|
||||
roomResult = -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
case WM_CLOSE:
|
||||
roomResult = -1;
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user