Files
RP412/RP_L4/RPL4.CPP
T
CydandClaude Fable 5 570eb3aceb Single-binary race loop: menu -> race -> menu in one process
WinMain now wraps the engine block in a loop: when a front-end-launched
mission ends under the local console, the setup screen comes back in the
same process instead of exiting (the arcade relaunch-per-mission model).
Replaces the CreateProcess self-respawn - required for Steam, where the
lobby and sockets must survive across races.

Second-cycle re-init crash fixed: d3d_OBJECT kept a static texture cache
keyed by filename, so race 2 got IDirect3DTexture9 pointers created on
race 1 destroyed device and died at first draw (DrawMesh AV). The cache
is now flushed in ~DPLRenderer before the device is released, and
ParticleEngine::Initialize drops particles left over from the previous
mission. Verified: three consecutive 30s races in one PID, each stopped
on time by the console with final scores collected.

Also: L4CONSOLELEN env override for test-length races, and the console
exposes MissionCompleted() for the loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:48:56 -05:00

357 lines
11 KiB
C++

////===========================================================================//
//// File: rp4lbe4.cc //
//// Project: MUNGA Brick: Red Planet LBE Application //
//// Contents: //
////---------------------------------------------------------------------------//
//// Date Who Modification //
//// -------- --- ---------------------------------------------------------- //
//// //
////---------------------------------------------------------------------------//
//// Copyright (C) 1994, Virtual World Entertainment, Inc. //
//// All Rights reserved worldwide //
//// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
////===========================================================================//
//
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0500
#define WINVER 0x0500
#include <tchar.h>
#include "rpl4.h"
#pragma hdrstop
#include "rpl4pb.h"
#include "rpl4fe.h"
#include "rpl4console.h"
#include "..\munga_l4\l4splr.h"
#include "rpl4ver.h"
#include "..\munga\resver.h"
#include "..\munga\resource.h"
// added for game status drawing support
#include "..\munga\player.h"
#include "..\munga\mission.h"
#include "..\munga_l4\l4ctrl.h"
// end add
#include <DbgHelp.h>
#include <strsafe.h>
#include <direct.h>
#include <shellapi.h>
#define SPOOL_SIZE 0x600000
//#define SCREEN_WIDTH 1024
//#define SCREEN_HEIGHT 768
Byte version[3] = {MAJOR_DATA_VERSION, RELEASE_VERSION, MINOR_DATA_VERSION};
char* names[] = {"InitializingState",
"WaitingForEgg",
"LoadingMission",
"WaitingForLaunch",
"LaunchingMission",
"RunningMission",
"EndingMission",
"StoppingMission",
"SuspendingMission",
"ResumingMission",
"AbortingMission",
"CreatingMission",
"ApplicationStateCount"};
//##########################################################################
//################## RP4L4Application Globals ########################
//##########################################################################
extern const char* const ProgName;
const char* const ProgName = "rpl4";
Application *rpl4App;
HWND hWnd;
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
DestroyWindow(hWnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
char filename[50];
strcpy_s(filename, 50, "rpl4.log");
std::ofstream logfile;
logfile.open(filename);
std::cout.rdbuf(logfile.rdbuf());
// load up our environment variables
//controls
if(getenv("L4CONTROLS") == NULL)
putenv("L4CONTROLS=KEYBOARD");
if(getenv("DPLARG") == NULL)
putenv("DPLARG=1");
if(getenv("L4DPLCFG") == NULL)
putenv("L4DPLCFG=RPDPL.INI");
if(getenv("MULTISAMPLE") == NULL)
putenv("MULTISAMPLE=0");
if(getenv("TARGETFPS") == NULL)
putenv("TARGETFPS=60");
if(getenv("MAXPARTICLES") == NULL)
putenv("MAXPARTICLES=8192");
FILE *file;
char line[1024];
if (fopen_s(&file, "environ.ini", "r") == 0)
{
while (!feof(file))
{
if (fgets(line, sizeof(line), file))
{
for (int i = strlen(line); i >= 0; i--)
if (line[i] == '\n' || line[i] == '\r')
line[i] = 0;
putenv(line);
}
}
fclose(file);
}
DEBUG_STREAM << "Red Planet v4.10" << std::endl << std::flush;
DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush;
// GetProcessAffinityMask(GetCurrentProcess(),
DWORD_PTR res = SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)1);
if (res == 0)
{
DWORD err = GetLastError();
DEBUG_STREAM << "Could not set thread affinity. Err Code = " << err << std::endl << std::flush;
}
//
//----------------------------------------------------------------------
// Parse command line for an egg notation file name
//----------------------------------------------------------------------
//
int argc;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
Logical run_application = RPL4Application::ParseCommandLine(argc, argv);
if (!run_application)
{
return 1;
}
else
{
Start_Registering();
}
//
//-------------------------------------------------------------------------
// Create our window. This is only temporary because this will move after
// the graphics are moved over.
//-------------------------------------------------------------------------
//
WNDCLASS wc;
if (!hPrevInstance)
{
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"MainWndClass";
if (!RegisterClass(&wc))
return FALSE;
}
DWORD wsStyle = WS_OVERLAPPED | WS_SYSMENU;
if (L4Application::GetFullscreen())
wsStyle = WS_POPUP;
hWnd = CreateWindowEx(0, L"MainWndClass", L"RPL4", WS_OVERLAPPEDWINDOW, 0, 0, L4Application::GetScreenWidth(), L4Application::GetScreenHeight(), (HWND)NULL, (HMENU)NULL, hInstance, (LPVOID)NULL);
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nShowCmd);
#if !_DEBUG
// Arcade pods have no mouse - but desktop/windowed play needs the
// cursor for the on-screen cockpit buttons, so hide it only when
// running fullscreen.
if (L4Application::GetFullscreen())
{
ShowCursor(FALSE);
}
#endif
//
//-------------------------------------------------------------------------
// Front-end mode: started without an egg, a network, or mission review.
// The race loop below keeps everything in ONE process - setup screen,
// mission, teardown, and back to the setup screen - with the local
// console marshaling each race. (-egg / -net / -mr run a single pass,
// exactly as before.)
//-------------------------------------------------------------------------
//
Logical front_end_mode =
L4Application::GetMissionReviewMode() == 0 &&
L4Application::GetNetworkCommonFlatAddress() == 0 &&
!L4Application::GetEggNotationFileName(); // no egg on the command line
for (;;)
{
if (front_end_mode)
{
if (!IsWindow(hWnd))
{
break; // the cockpit window is gone (closed mid-race)
}
// drain any WM_QUIT left over from the previous mission's
// teardown so it cannot instantly close the setup screen
MSG leftover;
while (PeekMessage(&leftover, NULL, WM_QUIT, WM_QUIT, PM_REMOVE))
{
}
static char frontend_egg[MAX_PATH];
if (!RPL4FrontEnd_Run(hInstance, hWnd, frontend_egg, sizeof(frontend_egg)))
{
break; // player closed the menu without launching
}
L4Application::SetEggNotationFileName(frontend_egg);
Exit_Code = 0;
// Front-end games are marshaled by the in-process console: it
// ends the race when the selected time expires. (Hand-fed -egg
// runs stay unmarshaled - the developer shortcut.)
RPL4LocalConsole_Install(RPL4FrontEnd_LastMissionSeconds());
}
//
//-------------------------------------------------------------------------
// Open the resource file. The brace will help correct the scoping problem
// of destruction the resource file
//-------------------------------------------------------------------------
//
{
StreamableResourceFile resources("rpl4.res", version);
Check(&resources);
//
//----------------------------------------------
// Create and initialize the application manager
//----------------------------------------------
//
ApplicationManager* app_manager;
Application *new_app;
int review_mode = RPL4Application::GetMissionReviewMode();
if (review_mode > 0)
{
MissionReviewApplicationManager *spool_mgr = new MissionReviewApplicationManager(hInstance, hWnd, atoi(getenv("TARGETFPS")), (review_mode == 2) ? 1 : 2, SPOOL_SIZE);
app_manager = spool_mgr;
Register_Object(app_manager);
//
//-------------------------------------------------------------------
// Create and initialize the playback application if there is a spool
// file
//-------------------------------------------------------------------
//
#if 1
CString spoolFileName = L4Application::GetSpoolFileName();
if (!spoolFileName)
spoolFileName = "last.spl";
SpoolFile *spool = spool_mgr->GetStoredSpoolFile(spoolFileName);
if (spool)
{
new_app = new RPL4PlaybackApplication(hInstance, hWnd, &resources, spool);
} else
{
new_app = new RPL4IdleApplication(hInstance, hWnd, &resources);
}
Register_Object(new_app);
app_manager->StartApplication(new_app);
#else
new_app = new RPL4PlaybackApplication(hInstance, hWnd, &resources, NULL);
Register_Object(new_app);
app_manager->StartApplication(new_app);
#endif
//
//------------------------------------
// Initialize the spooling application
//------------------------------------
//
if (review_mode == 1)
{
new_app = new L4SpoolingApplication(hInstance, hWnd, &resources, RPL4);
Register_Object(new_app);
app_manager->StartApplication(new_app);
}
}
else
{
app_manager = new ApplicationManager(hInstance, hWnd, atoi(getenv("TARGETFPS")));
Register_Object(app_manager);
//
//--------------------------------------
// Create and initialize the application
//--------------------------------------
//
Application *new_app = new RPL4Application(hInstance, hWnd, &resources);
rpl4App = new_app;
Register_Object(new_app);
SetCursorPos(1680 / 2, 1050 / 2);
app_manager->StartApplication(new_app);
}
//
//--------------------------
// Run missions till we done
//--------------------------
//
app_manager->RunMissions();
rpl4App = NULL;
Unregister_Object(app_manager);
delete app_manager;
}
//
//-------------------------------------------------------------------------
// Single-binary race loop: if the local console ended this mission,
// go back to the setup screen for the next one; anything else (user
// quit, -egg / -net / -mr single pass) falls out.
//-------------------------------------------------------------------------
//
if (!front_end_mode || !RPL4LocalConsole_MissionCompleted())
{
break;
}
}
#if !_DEBUG
// symmetric with the fullscreen-only hide at startup
if (L4Application::GetFullscreen())
{
ShowCursor(TRUE);
}
#endif
Stop_Registering();
return Exit_Code;
}