From 3194d3f9730eabf8a0a8db2d00fe315bf37a9398 Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 24 Jul 2026 19:42:33 -0500 Subject: [PATCH] Martian Football: the second scenario is playable The setup menu gains a SCENARIO group. Picking Football relayouts the menu live: the track list swaps to the football-legal set (drops Paingod Passage and the race build of Freezemoon Freeway, adds the football build headmf - RPConfig per-scenario invalid lists), the COLOR and BADGE columns become TEAM and POSITION, and the title reads MARTIAN FOOTBALL. The egg builder was restructured around one flat pilot table so both scenarios share it. Football emits scenario=football, per-pilot team= and position= entries, and the [teams] / [team::X] / [pilots::X] / [teambitmap] blocks in RPFootballMission layout (team name bitmaps generated by the same GDI plasma renderer as pilot names). Colors are derived rather than chosen, as the arcade did it: the runner wears the team runner color, crushers and blockers the team color. Multi-pod games alternate pilots across two teams and give each team exactly one runner, honoring the host own position pick. Verified end to end: Football launches from the menu, the engine loads the football mission (the cockpit shows the RUNNER - GO FOR POINTS panel), the console marshals it to a scored finish, and the Death Race path still passes its regression unchanged. Known gap: lobby members cannot pick their own team or position yet - the host pick seeds a deterministic assignment. Co-Authored-By: Claude Fable 5 --- README.md | 11 +- RP_L4/RPL4FE.cpp | 374 +++++++++++++++++++++++++++------- docs/RP412-FRONTEND-DESIGN.md | 28 ++- pack-dist.ps1 | 89 +++++--- 4 files changed, 397 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 9e6d692..cdf9a46 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Red Planet 4.12 — the Steamification -**Red Planet** is VWE's pod-racing game: eight-player VTV races on Mars, -originally played from inside VWE Tesla cockpit pods. This repo is the -third life of that code: +**Red Planet** is VWE's pod-racing game: eight-player VTV contests on Mars +— **Martian Death Race** and **Martian Football** — originally played from +inside VWE Tesla cockpit pods. This repo is the third life of that code: | Generation | What it was | |------------|-------------| @@ -34,8 +34,9 @@ lobby. ## Playing Grab the release zip (or run `pack-dist.ps1` on a build). Single player: -run `start-windowed.bat` — the game boots into the race setup menu. Steam -multiplayer: see +run `start-windowed.bat` — the game boots into the setup menu, where the +SCENARIO group picks Death Race or Football (Football swaps in the +team/position columns and its own track list). Steam multiplayer: see [docs/STEAM-3-MACHINE-TEST.md](docs/STEAM-3-MACHINE-TEST.md) (until RP412 has its own AppID it runs under Spacewar, 480). diff --git a/RP_L4/RPL4FE.cpp b/RP_L4/RPL4FE.cpp index 701d5bf..2b0c4ee 100644 --- a/RP_L4/RPL4FE.cpp +++ b/RP_L4/RPL4FE.cpp @@ -35,6 +35,52 @@ namespace { "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" }, @@ -125,12 +171,31 @@ namespace 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; @@ -311,8 +376,10 @@ namespace int top = client_h / 7; int y = top; - AddGroupItems(fe, GroupMap, FE_COUNT(kMaps), col1, &y, row_h, col_w); - int y_after_maps = y; + 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); @@ -321,8 +388,16 @@ namespace AddGroupItems(fe, GroupVehicle, FE_COUNT(kVehicles), col2, &y, row_h, col_w); y = top + 2 * row_h; // leave room for the name edit + header - AddGroupItems(fe, GroupColor, FE_COUNT(kColors), col3, &y, row_h, col_w); - AddGroupItems(fe, GroupBadge, FE_COUNT(kBadges), col3, &y, row_h, col_w); + 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++]; @@ -365,31 +440,45 @@ namespace { 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 "RACE LENGTH"; + 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 GroupMap: return kMaps[index].name; + 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 R A C E"; - case GroupSteamHost: return "HOST STEAM RACE"; - case GroupSteamJoin: return "JOIN STEAM RACE"; + 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 ""; } @@ -413,13 +502,17 @@ namespace 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, "RED PLANET - MARTIAN DEATH RACE", -1, &title, - DT_CENTER | DT_TOP | DT_SINGLELINE); + 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); @@ -521,6 +614,20 @@ namespace 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; @@ -655,13 +762,19 @@ namespace extern const char *kOrdinalsBlock; //--------------------------------------------------------------- - // Egg assembly (console RPMission.ToEggString, race scenario). - // Single player uses the standalone address; a hosted network - // race lists the owner first plus one pilot per member pod. + // 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]; @@ -672,11 +785,100 @@ namespace 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; + }; + 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; + 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; + } + + // + // Football: assign teams and positions, derive colors + // + int teams_used[2] = { 0, 1 }; + if (football) + { + teams_used[0] = fe->selection[GroupTeam]; + teams_used[1] = (teams_used[0] + 1) % FE_COUNT(kTeams); + + int runner_on_team[2] = { 0, 0 }; + int line_position[2] = { 0, 0 }; // crusher/blocker rotation + + owner->team = 0; + owner->position = kPositions[fe->selection[GroupPosition]].key; + if (strcmp(owner->position, "runner") == 0) + { + runner_on_team[0] = 1; + } + + for (int p = 1; p < pilot_count; ++p) + { + int side = p % 2; // alternate: B, A, B, A... + side = (side == 1) ? 1 : 0; + pilots[p].team = side; + if (!runner_on_team[side]) + { + pilots[p].position = "runner"; + runner_on_team[side] = 1; + } + else + { + pilots[p].position = + (line_position[side]++ % 2) ? "blocker" : "crusher"; + } + } + + for (int p = 0; p < pilot_count; ++p) + { + const TeamEntry *team = &kTeams[teams_used[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", kMaps[fe->selection[GroupMap]].key); + sprintf(line, "map=%s\n", maps[fe->selection[GroupMap]].key); + egg += line; + sprintf(line, "scenario=%s\n", kScenarios[fe->selection[GroupScenario]].key); egg += line; - egg += "scenario=race\n"; sprintf(line, "time=%s\n", kTimes[fe->selection[GroupTime]].key); egg += line; sprintf(line, "weather=%s\n", kWeather[fe->selection[GroupWeather]].key); @@ -690,84 +892,118 @@ namespace egg += line; } + // + // [pilots] + per-pilot sections + // egg += "[pilots]\n"; - sprintf(line, "pilot=%s\n", owner_address); - egg += line; - for (int p = 0; p < extra_count; ++p) + for (int p = 0; p < pilot_count; ++p) { - sprintf(line, "pilot=%s\n", extras[p].address); + sprintf(line, "pilot=%s\n", pilots[p].address); egg += line; } - sprintf(line, "[%s]\n", owner_address); - egg += line; - egg += "hostType=0\n"; - egg += "dropzone=one\n"; - sprintf(line, "name=%s\n", fe->pilotName); - egg += line; - egg += "bitmapindex=1\n"; - egg += "loadzones=1\n"; - sprintf(line, "vehicle=%s\n", kVehicles[fe->selection[GroupVehicle]].key); - egg += line; - sprintf(line, "color=%s\n", kColors[fe->selection[GroupColor]].key); - egg += line; - sprintf(line, "badge=%s\n", kBadges[fe->selection[GroupBadge]].key); - egg += line; - - for (int p = 0; p < extra_count; ++p) + for (int p = 0; p < pilot_count; ++p) { - sprintf(line, "[%s]\n", extras[p].address); + sprintf(line, "[%s]\n", pilots[p].address); egg += line; egg += "hostType=0\n"; egg += "dropzone=one\n"; - sprintf(line, "name=%s\n", extras[p].name); + sprintf(line, "name=%s\n", pilots[p].name); egg += line; - sprintf(line, "bitmapindex=%d\n", p + 2); + sprintf(line, "bitmapindex=%d\n", p + 1); egg += line; egg += "loadzones=1\n"; - sprintf(line, "vehicle=%s\n", extras[p].vehicle); + sprintf(line, "vehicle=%s\n", pilots[p].vehicle); egg += line; - sprintf(line, "color=%s\n", extras[p].color); + sprintf(line, "color=%s\n", pilots[p].color); egg += line; - sprintf(line, "badge=%s\n", extras[p].badge); + sprintf(line, "badge=%s\n", pilots[p].badge); egg += line; + if (football) + { + sprintf(line, "team=team::%s\n", kTeams[teams_used[pilots[p].team]].key); + egg += line; + sprintf(line, "position=%s\n", pilots[p].position); + egg += line; + } } - egg += "[largebitmap]\n"; - sprintf(line, "bitmap=BitMap::Large::%s\n", fe->pilotName); - egg += line; - for (int p = 0; p < extra_count; ++p) + // + // Football team blocks (console RPFootballMission layout) + // + if (football) { - sprintf(line, "bitmap=BitMap::Large::%s\n", extras[p].name); + Logical team_present[2] = { False, False }; + for (int p = 0; p < pilot_count; ++p) + { + team_present[pilots[p].team] = True; + } + + 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 < 2; ++side) + { + if (!team_present[side]) + { + continue; + } + const char *team_key = kTeams[teams_used[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"; - sprintf(line, "bitmap=BitMap::Small::%s\n", fe->pilotName); - egg += line; - for (int p = 0; p < extra_count; ++p) + for (int p = 0; p < pilot_count; ++p) { - sprintf(line, "bitmap=BitMap::Small::%s\n", extras[p].name); + sprintf(line, "bitmap=BitMap::Small::%s\n", pilots[p].name); egg += line; } - - sprintf(line, "[BitMap::Large::%s]\n", fe->pilotName); - egg += line; - AppendNameBitmap(egg, 128, 32, fe->pilotName); - egg += "width=8\n"; - sprintf(line, "[BitMap::Small::%s]\n", fe->pilotName); - egg += line; - AppendNameBitmap(egg, 64, 16, fe->pilotName); - egg += "width=4\n"; - - for (int p = 0; p < extra_count; ++p) + for (int p = 0; p < pilot_count; ++p) { - sprintf(line, "[BitMap::Large::%s]\n", extras[p].name); + sprintf(line, "[BitMap::Large::%s]\n", pilots[p].name); egg += line; - AppendNameBitmap(egg, 128, 32, extras[p].name); + AppendNameBitmap(egg, 128, 32, pilots[p].name); egg += "width=8\n"; - sprintf(line, "[BitMap::Small::%s]\n", extras[p].name); + sprintf(line, "[BitMap::Small::%s]\n", pilots[p].name); egg += line; - AppendNameBitmap(egg, 64, 16, extras[p].name); + AppendNameBitmap(egg, 64, 16, pilots[p].name); egg += "width=4\n"; } @@ -775,13 +1011,13 @@ namespace // [pilots]-order names for the network console's results strcpy(gLastPilotNamesCsv, fe->pilotName); - for (int p = 0; p < extra_count; ++p) + for (int p = 1; p < pilot_count; ++p) { - if (strlen(gLastPilotNamesCsv) + strlen(extras[p].name) + 2 < + if (strlen(gLastPilotNamesCsv) + strlen(pilots[p].name) + 2 < sizeof(gLastPilotNamesCsv)) { strcat(gLastPilotNamesCsv, ","); - strcat(gLastPilotNamesCsv, extras[p].name); + strcat(gLastPilotNamesCsv, pilots[p].name); } } } diff --git a/docs/RP412-FRONTEND-DESIGN.md b/docs/RP412-FRONTEND-DESIGN.md index 8280816..b1a9246 100644 --- a/docs/RP412-FRONTEND-DESIGN.md +++ b/docs/RP412-FRONTEND-DESIGN.md @@ -155,12 +155,24 @@ launch flow, no in-game rejoin). Useful as a dev tool, not the product. **C. Hybrid:** in-engine UI, but mission/egg logic in a small shared C++ library so a dev console tool and the game share one egg builder. -### Open decisions +### Open decisions — all resolved -1. Front end location — Option A (in-engine, cockpit-rendered) confirmed? -2. v1 scenario scope — Death Race only, or Football (needs teams UI) too? -3. Results screen — post-race on the cockpit displays (replaces printout)? -4. Steam model — lobby-owner-as-console confirmed? (Implies owner migration - handling later; the egg re-issue path makes host migration plausible.) -5. Pilot identity — Steam persona as pilot name; vehicle/color/badge - persisted per player (Steam Cloud later)? +1. Front end location — Option A (in-engine) **confirmed and shipped**. +2. v1 scenario scope — **both scenarios shipped** (2026-07-24). The setup + menu has a SCENARIO group; picking Football swaps the map list to the + football-legal set (no `pain`/`headoff`, adds `headmf`), replaces the + COLOR/BADGE columns with TEAM/POSITION, and the egg builder emits + `scenario=football`, per-pilot `team=`/`position=`, and the + `[teams]` / `[team::X]` / `[pilots::X]` / `[teambitmap]` blocks in + RPFootballMission's layout. Colors are derived, not chosen: the + runner wears the team's runner color, everyone else the team color. + Multi-pod games alternate pilots between two teams and auto-assign + one runner per team (the host's own position pick is honored). + *Open:* lobby members cannot yet choose their own team/position — + the host's pick seeds a deterministic assignment. +3. Results screen — **shipped**, on the cockpit displays, with the owner + publishing the score sheet to lobby members. +4. Steam model — lobby-owner-as-console **confirmed and verified** on + three machines. (Owner migration still unhandled.) +5. Pilot identity — Steam persona as pilot name **shipped**; loadout + persists in-session (Steam Cloud later). diff --git a/pack-dist.ps1 b/pack-dist.ps1 index c88e14f..a755af9 100644 --- a/pack-dist.ps1 +++ b/pack-dist.ps1 @@ -85,27 +85,48 @@ Set-Content -Path "$dist\environ.ini" -Encoding ascii -Value @" # ---- Core (the shipped configuration) -------------------------------------- -# Control stack. PAD;KEYBOARD = the virtual RIO (XInput controller + -# keyboard, rebindable via bindings.txt). RIO;KEYBOARD = real serial -# cockpit hardware. KEYBOARD = engine keyboard only (no virtual RIO). +# Control stack: tokens separated by ; or , processed left to right. +# PAD the virtual RIO (XInput controller + keyboard, +# rebindable via bindings.txt) +# RIO real serial cockpit hardware on COM1 +# RIO:COMn same, on another port (RIO:COM3, ...) +# KEYBOARD the engine keyboard handler +# MOUSE, JOYSTICK, FLIGHTSTICKPRO, THRUSTMASTER, DIJOYSTICK +# legacy pointer/joystick drivers (untested here) +# Unset falls back to KEYBOARD alone. L4CONTROLS=PAD;KEYBOARD -# Renderer selection argument and its config file. Leave as shipped. +# Renderer bring-up argument. Only its presence is checked (the DPL +# resolution parsing it once fed is gone) and the game refuses to start +# without it - any non-empty value works. Leave as shipped. DPLARG=1 + +# DPL (renderer/scene) configuration file, searched beside the exe. +# Any notation file name; RPDPL.INI is the one that ships. L4DPLCFG=RPDPL.INI -# Gauge (MFD/instrument) canvas: WIDTHxHEIGHTxDEPTH. +# Gauge (MFD/instrument) canvas. Must name a page of GAUGE\L4GAUGE.INI: +# 640x480x8 | 640x480x16 | 800x600x16 +# Unset disables the gauge renderer (and with it all MFDs). L4GAUGE=640x480x16 -# Plasma display: SCREEN renders the pod's plasma glass in-window -# (currently parked off-layout); hardware values drive real glass. +# Plasma display. +# SCREEN render the pod's plasma glass in-window (currently +# parked off-layout) +# COM1, COM2... drive real plasma glass on that serial port +# (9600 baud, N81) +# Unset = no plasma display. L4PLASMA=SCREEN -# 1 = the single-window cockpit: all seven displays composed on a -# locked 1920x1080 canvas around the viewscreen. +# 0 = classic separate gauge windows; 1 = the single-window glass +# cockpit (all seven displays composed on a locked 1920x1080 canvas +# around the viewscreen); 2 = exploded diagnostic view (each display +# in its own native-resolution desktop window - MFDs 640x480, map +# 480x640 - decoded exactly as the pod's VDB split them, no downscale). L4MFDSPLIT=1 -# Simulation/render frame rate. +# Simulation/render frame rate, integer frames/second. The desktop +# default is 60; the arcade pods shipped at 25. TARGETFPS=60 # 1 = Steam networking (lobbies, FakeIP mesh). Needs the Steam client @@ -116,48 +137,70 @@ RP412STEAM=1 # ---- Optional --------------------------------------------------------------- # RGB keyboard lamp mirror (Windows Dynamic Lighting): keys bound to -# lamp buttons glow with the panel, flash modes and all. Default on; -# set 0 to disable. +# lamp buttons glow with the panel, flash modes and all. +# Unset or nonzero = on (the default); 0 = off. #RP412KEYLIGHT=0 -# Invert the stick on top of whatever bindings.txt produces: X, Y, XY. +# Invert the stick on top of whatever bindings.txt produces: +# X = invert X only, Y = invert Y only, XY = both (case-insensitive). #L4PADFLIP=XY -# Anti-aliasing samples (0 = off). +# Anti-aliasing sample count, passed straight to Direct3D 9: +# 0 = off, else 2..16 as the GPU supports (1 selects the driver's +# "nonmaskable" mode; unsupported counts fail device creation). #MULTISAMPLE=0 -# Particle budget. +# Particle budget, integer. Default 8192. #MAXPARTICLES=8192 -# On-screen plasma glass pixel size and position (when re-homed). +# On-screen plasma glass (L4PLASMA=SCREEN only). SCALE = integer pixel +# size 1..16, default 4 (out-of-range values are ignored). POS = window +# top-left as X,Y screen coordinates; unset = auto, parked below the +# main window. #L4PLASMASCALE=4 #L4PLASMAPOS=0,0 -# Fixed random seed (repeatable runs). +# Fixed random seed (repeatable runs): any unsigned integer. +# Unset seeds from the clock. #RANDOM=12345 # ---- LAN play without Steam ------------------------------------------------- # Host a race over plain TCP: list the member pods' console channels # (members run: rpl4opt.exe -windowed -res 1920 1080 -net 1501). -# HOSTADDR is this machine's LAN IP as members can reach it. +# RP412HOSTPODS comma-separated IP[:port] list, one entry per member +# pod; port defaults to 1501 per entry +# RP412HOSTPORT this machine's console port, integer > 0 +# (default 1501) +# RP412HOSTADDR this machine's LAN IP as members can reach it +# (default 127.0.0.1) #RP412HOSTPODS=192.168.1.20:1501,192.168.1.21:1501 #RP412HOSTPORT=1501 #RP412HOSTADDR=192.168.1.10 # ---- Developer / testing ---------------------------------------------------- -# 1 arms the debug keys: Alt+W wireframe, Alt+V predator vision, +# Nonzero arms the debug keys: Alt+W wireframe, Alt+V predator vision, # Alt+F frame dump, Alt+/ perf stats, Alt+E event-queue dump. -# (Alt+Q, the mission abort, is always live.) +# 0 or unset = off. (Alt+Q, the mission abort, is always live.) #RP412DEVKEYS=1 -# Console race-length override in seconds (short test races). +# Console race-length override, integer seconds (short test races). +# Values <= 0 are ignored. #L4CONSOLELEN=30 -# Steam transport loopback self-test at boot (logs PASS/FAIL). +# Nonzero = Steam transport loopback self-test at boot (logs PASS/FAIL). #RP412STEAMSELFTEST=1 # ---- Arcade heritage (multi-monitor pods; not used on the desktop) ---------- +# PRIMGAUGE / SECGAUGE / MFDGAUGE / MFDGAUGE2 pin a display to a monitor +# by adapter index (0, 1, 2...). SPANDISABLE: 0 = let the MFDs span one +# wide surface, nonzero = separate windows (setting MFDGAUGE2 alone also +# forces spanning off). L4EYES = "x y z xrot yrot zrot [type]" floats +# for a detached camera; a type starting with r offsets it relative to +# the pod. L4INTERCOM enables the crew intercom - only its presence +# matters (traditionally COM2). NOMODES skips the mode/lamp programming; +# presence alone triggers it, even NOMODES=0. LOGSIZE > 0 sizes the +# trace log in dev builds compiled with tracing. #PRIMGAUGE=1 #SECGAUGE=2 #MFDGAUGE=3 @@ -171,7 +214,7 @@ RP412STEAM=1 Set-Content -Path "$dist\start-windowed.bat" -Encoding ascii -Value @" @echo off -rem Red Planet 4.12 - desktop prototype. Boots into the race-setup +rem Red Planet 4.12 - desktop prototype. Boots into the game-setup rem front end; the cockpit canvas is 1920x1080 and -res matches it. rem (Add -egg TEST.EGG to skip the menu and run the canned mission.) cd /d "%~dp0"