Domain correction from playtest: hand-fed eggs are a developer shortcut - a mission only ends on a console command, so the clock hits 00:00 and counts up forever. Even single-player games need a console marshal. RPL4CONSOLE is that console. Like the real one it lives on its own thread: it owns the mission clock and raises the stop request at the selected length; the app-manager per-frame hook (new gPerFrameHook seam in APPMGR, called while the application global is live - the loop condition NULLs it on exit, which ate the first attempt) executes the engine-safe part, dispatching the same StopMissionMessage TeslaConsole sent. Final scores flow in through a new RP-layer sink (gConsoleScoreSink in RPCNSL): RPPlayer feeds it the same score it sends a real console at mission end. It also inherits the launcher role: the application tears down after a stop (arcade pods were relaunched per mission by TeslaLauncher), so WinMain respawns the process when the console ended the mission, landing back on the race-setup screen. L4NetworkManager grows FeedLocalEgg (the single-user egg-inject path, callable mid-session) for the future in-process loop. Verified end to end: menu -> 3:00 race -> stop dispatched exactly on time -> final score collected (host 1 = 4113) -> process respawned with the front end up. -egg runs stay unmarshaled (the dev shortcut). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
345 lines
11 KiB
C++
345 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: started without an egg, a network, or mission review ->
|
|
// run the in-game race setup and build the mission egg locally.
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
if (L4Application::GetMissionReviewMode() == 0 &&
|
|
L4Application::GetNetworkCommonFlatAddress() == 0 &&
|
|
!L4Application::GetEggNotationFileName()) // no egg on the command line
|
|
{
|
|
static char frontend_egg[MAX_PATH];
|
|
if (!RPL4FrontEnd_Run(hInstance, hWnd, frontend_egg, sizeof(frontend_egg)))
|
|
{
|
|
return 0; // player closed the menu without launching
|
|
}
|
|
L4Application::SetEggNotationFileName(frontend_egg);
|
|
|
|
// Front-end games are marshaled by the in-process console: it
|
|
// ends the race when the selected time expires and loops back
|
|
// to the setup screen. (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;
|
|
}
|
|
|
|
#if !_DEBUG
|
|
// symmetric with the fullscreen-only hide at startup
|
|
if (L4Application::GetFullscreen())
|
|
{
|
|
ShowCursor(TRUE);
|
|
}
|
|
#endif
|
|
|
|
Stop_Registering();
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// Launcher role: when the local console ended the mission, respawn the
|
|
// process for the next race (the arcade launcher restarted the pod after
|
|
// every mission) - the fresh instance boots back into the setup screen.
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
if (RPL4LocalConsole_ShouldRelaunch())
|
|
{
|
|
STARTUPINFOW startup_info;
|
|
PROCESS_INFORMATION process_info;
|
|
memset(&startup_info, 0, sizeof(startup_info));
|
|
startup_info.cb = sizeof(startup_info);
|
|
if (CreateProcessW(NULL, GetCommandLineW(), NULL, NULL, FALSE,
|
|
0, NULL, NULL, &startup_info, &process_info))
|
|
{
|
|
CloseHandle(process_info.hThread);
|
|
CloseHandle(process_info.hProcess);
|
|
}
|
|
}
|
|
|
|
return Exit_Code;
|
|
}
|