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"