Alt+W had been a no-op since the 2007 DPL->Direct3D port, which stubbed DPLToggleWireframe along with every other dpl_ call. D3D9 has no global wireframe property, so the toggle now records the intent in gWireframe and ExecuteImplementation applies D3DRS_FILLMODE once per frame - which also means it re-asserts itself after a device Reset. The sky dome and the 2D pass are held solid: a wireframed dome buries the geometry you turned the key on to look at, and the gunsight would otherwise come out as bare diagonals. Proving it needed a working copy, and that turned up a separate problem. steam_api.dll was a hard import, so a machine without it died at load time with 0xC0000135 - before a window, before a log line. It is now delay-loaded, and because delay loading only moves that failure to the first call, every Steam path is gated on SteamNetTransport_ClientLibraryPresent(): Install and the two lobby entries, with everything else downstream of one of them. Absent DLL boots and races; absent DLL with RP412STEAM=1 logs the reason and stays on TCP; DLL present brings the transport up exactly as before. The documentation now says which debug keys are real. Five of the seven are still 2007 stubs and always have been, so they are named as inert in environ.ini's template and at the dispatch site - that beats letting the next person debug a dead key, which is how this started. BUILD.md gains a debug-key table, the delay-load contract for anyone adding a Steam call site, and the environ.ini BOM trap that silently reverts L4CONTROLS to KEYBOARD and then fail-fasts for want of a pod mapper. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1141 lines
32 KiB
C++
1141 lines
32 KiB
C++
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "..\munga\camship.h"
|
|
#include "..\munga\appmsg.h"
|
|
#include "l4app.h"
|
|
#include "l4ctrl.h"
|
|
#include "l4icom.h"
|
|
#include "l4video.h"
|
|
#include "l4audrnd.h"
|
|
#include "l4grend.h"
|
|
#include "l4net.h"
|
|
#include "l4mppr.h"
|
|
#include "..\munga\player.h"
|
|
#include "..\munga\mission.h"
|
|
//
|
|
// Minimal stand-in for ATL's <atlconv.h> USES_CONVERSION / W2A macros.
|
|
// VC++ Express and the Windows SDK do not ship ATL, and the only ATL feature
|
|
// this file used was wide-to-ANSI conversion of command-line tokens below.
|
|
// L4WideToAnsi mirrors ATL's W2A semantics: each use produces an independent
|
|
// buffer that stays valid until the end of the enclosing full-expression.
|
|
//
|
|
namespace
|
|
{
|
|
class L4WideToAnsi
|
|
{
|
|
public:
|
|
explicit L4WideToAnsi(const wchar_t* wide) : mBuffer(0)
|
|
{
|
|
int needed = (wide != 0)
|
|
? ::WideCharToMultiByte(CP_ACP, 0, wide, -1, 0, 0, 0, 0)
|
|
: 0;
|
|
if (needed <= 0)
|
|
{
|
|
needed = 1;
|
|
}
|
|
mBuffer = new char[needed];
|
|
if (wide == 0 ||
|
|
::WideCharToMultiByte(CP_ACP, 0, wide, -1, mBuffer, needed, 0, 0) <= 0)
|
|
{
|
|
mBuffer[0] = '\0';
|
|
}
|
|
}
|
|
~L4WideToAnsi() { delete [] mBuffer; }
|
|
operator char*() { return mBuffer; }
|
|
private:
|
|
char* mBuffer;
|
|
L4WideToAnsi(const L4WideToAnsi&); // non-copyable
|
|
L4WideToAnsi& operator=(const L4WideToAnsi&);
|
|
};
|
|
}
|
|
|
|
#define USES_CONVERSION ((void)0)
|
|
#define W2A(w) (static_cast<char*>(L4WideToAnsi(w)))
|
|
|
|
L4Application*&
|
|
l4_application = (L4Application*&)application;
|
|
|
|
const Receiver::HandlerEntry
|
|
L4Application::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(L4Application, RunMission),
|
|
MESSAGE_ENTRY(L4Application, StopMission),
|
|
MESSAGE_ENTRY(L4Application, LightsOut),
|
|
MESSAGE_ENTRY(L4Application, KeyCommand)
|
|
};
|
|
|
|
Receiver::MessageHandlerSet& L4Application::GetMessageHandlers()
|
|
{
|
|
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(L4Application::MessageHandlerEntries), L4Application::MessageHandlerEntries, Application::GetMessageHandlers());
|
|
return messageHandlers;
|
|
}
|
|
|
|
//#############################################################################
|
|
// Virtual Data support
|
|
//
|
|
Derivation* L4Application::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(Application::GetClassDerivations(), "L4Application");
|
|
return &classDerivations;
|
|
}
|
|
|
|
L4Application::SharedData
|
|
L4Application::DefaultData(
|
|
L4Application::GetClassDerivations(),
|
|
L4Application::GetMessageHandlers()
|
|
);
|
|
|
|
//##########################################################################
|
|
//################## L4Application Static Data #######################
|
|
//##########################################################################
|
|
|
|
HINSTANCE L4Application::mhInstance = NULL;
|
|
unsigned int L4Application::mScreenWidth = 800;
|
|
unsigned int L4Application::mScreenHeight = 600;
|
|
bool L4Application::mFullscreen = true;
|
|
bool L4Application::mFitDisplay = false;
|
|
bool L4Application::mResExplicit = false;
|
|
Logical L4Application::seeSolids = False;
|
|
CString L4Application::eggNotationFileName;
|
|
CString L4Application::spoolFileName;
|
|
unsigned long L4Application::networkCommonFlatAddress;
|
|
Logical L4Application::missionReviewMode = False;
|
|
CString L4Application::rioPlaybackFileName;
|
|
CString L4Application::rioRecordingFileName;
|
|
|
|
//
|
|
//#############################################################################
|
|
// ParseCommandLine
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4Application::ParseToken(
|
|
int *arguement,
|
|
int argc,
|
|
LPWSTR argv[]
|
|
)
|
|
{
|
|
USES_CONVERSION;
|
|
Check_Pointer(argv);
|
|
|
|
if (!stricmp(W2A(argv[*arguement]), "-egg"))
|
|
{
|
|
if ((*arguement+1) < argc && W2A(argv[*arguement+1])[0] != '-')
|
|
{
|
|
eggNotationFileName = W2A(argv[++(*arguement)]);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "\nEgg file not specified after -egg\n" << std::flush << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
|
|
else if (!stricmp(W2A(argv[*arguement]), "-spool"))
|
|
{
|
|
if ((*arguement+1) < argc && W2A(argv[*arguement+1])[0] != '-')
|
|
{
|
|
spoolFileName = W2A(argv[++(*arguement)]);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "\nSpool file not specified after -spool\n" << std::flush << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
|
|
else if (!stricmp(W2A(argv[*arguement]), "-net"))
|
|
{
|
|
if ((*arguement+1) < argc && W2A(argv[*arguement+1])[0] != '-')
|
|
{
|
|
networkCommonFlatAddress = atol(W2A(argv[++(*arguement)]));
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "\nAddress not specified after -net\n" << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
|
|
else if (!stricmp(W2A(argv[*arguement]), "-r"))
|
|
{
|
|
if ((*arguement+1) < argc && W2A(argv[*arguement+1])[0] != '-')
|
|
{
|
|
rioRecordingFileName = W2A(argv[++(*arguement)]);
|
|
CString playback = GetRIOPlaybackFileName();
|
|
if (playback && strlen(playback))
|
|
{
|
|
DEBUG_STREAM << "\nPlayback already specified!\n" << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "\nRecording file not specified after -r\n" << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
|
|
else if (!stricmp(W2A(argv[*arguement]), "-p"))
|
|
{
|
|
if ((*arguement+1) < argc && W2A(argv[*arguement+1])[0] != '-')
|
|
{
|
|
rioPlaybackFileName = W2A(argv[++(*arguement)]);
|
|
CString recording = GetRIORecordingFileName();
|
|
if (recording && strlen(recording))
|
|
{
|
|
DEBUG_STREAM << "\nRecording already specified!\n" << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "\nPlayback file not specified after -egg\n" << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
|
|
else if (!stricmp(W2A(argv[*arguement]), "-solids"))
|
|
{
|
|
seeSolids = True;
|
|
}
|
|
|
|
else if (!stricmp(W2A(argv[*arguement]), "-res"))
|
|
{
|
|
if ((*arguement+2) < argc && W2A(argv[*arguement+1])[0] != '-' && W2A(argv[*arguement+2])[0] != '-')
|
|
{
|
|
mScreenWidth = atoi(W2A(argv[++(*arguement)]));
|
|
mScreenHeight = atoi(W2A(argv[++(*arguement)]));
|
|
mResExplicit = true;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "\nScreen width and height not specified after -res\n" << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
else if (!stricmp(W2A(argv[*arguement]), "-windowed"))
|
|
{
|
|
mFullscreen = false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// -fit: borderless window over the whole monitor, and a render size
|
|
// picked to match. Spelled out as -windowed-fullscreen too, since
|
|
// that is what the rest of the world calls it.
|
|
//-------------------------------------------------------------------
|
|
else if (
|
|
!stricmp(W2A(argv[*arguement]), "-fit") ||
|
|
!stricmp(W2A(argv[*arguement]), "-windowed-fullscreen")
|
|
)
|
|
{
|
|
mFullscreen = false;
|
|
mFitDisplay = true;
|
|
}
|
|
|
|
else if (
|
|
!stricmp(W2A(argv[*arguement]), "-h") || !stricmp(W2A(argv[*arguement]), "-help")
|
|
)
|
|
{
|
|
DEBUG_STREAM << "\n" << argv[0] <<
|
|
" -egg <filename> -net <memory_address> -solids -h -help\n"
|
|
" -windowed windowed, title bar and all\n"
|
|
" -fit borderless over the whole monitor,\n"
|
|
" render size chosen to match\n"
|
|
" (long form: -windowed-fullscreen)\n"
|
|
" -res <width> <height> explicit render size; overrides the\n"
|
|
" size -fit would have picked\n";
|
|
return False;
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ChooseFitResolution
|
|
//#############################################################################
|
|
//
|
|
// The cockpit presents the 3D into a viewscreen that fills its 1920x1080
|
|
// canvas, and that canvas is fitted to the window at one uniform scale.
|
|
// So the render size that lands 1:1 on screen is the canvas at the same
|
|
// scale the cockpit will choose - work it out with identical arithmetic
|
|
// here and the stretch becomes a copy.
|
|
//
|
|
void
|
|
L4Application::ChooseFitResolution()
|
|
{
|
|
int monitor_w = GetSystemMetrics(SM_CXSCREEN);
|
|
int monitor_h = GetSystemMetrics(SM_CYSCREEN);
|
|
if (monitor_w <= 0 || monitor_h <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int canvas_w = 1920;
|
|
const int canvas_h = 1080;
|
|
|
|
int fit_w = (monitor_w * 100) / canvas_w;
|
|
int fit_h = (monitor_h * 100) / canvas_h;
|
|
int scale = (fit_w < fit_h) ? fit_w : fit_h;
|
|
if (scale < 25) scale = 25;
|
|
|
|
unsigned int width = (canvas_w * scale) / 100;
|
|
unsigned int height = (canvas_h * scale) / 100;
|
|
|
|
//-------------------------------------------------------------------
|
|
// A 1995 engine on a 5K panel would be asked for a back buffer well
|
|
// past anything it was built for; cap and let D3D scale the last bit.
|
|
//-------------------------------------------------------------------
|
|
if (width > 3840)
|
|
{
|
|
width = 3840;
|
|
height = 2160;
|
|
}
|
|
|
|
mScreenWidth = width;
|
|
mScreenHeight = height;
|
|
|
|
DEBUG_STREAM << "L4Application: -fit chose -res " << width << " "
|
|
<< height << " (" << scale << "% canvas) for the "
|
|
<< monitor_w << "x" << monitor_h << " monitor\n" << std::flush;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// FitWindowToMonitor
|
|
//#############################################################################
|
|
//
|
|
// -fit's borderless full-monitor placement, applied to the shell window.
|
|
//
|
|
// This has to happen BEFORE the first race, not during it. SVGA16 does the
|
|
// same thing when it assembles the cockpit, but that is not until a mission
|
|
// starts - and the D3D device is created just ahead of it, against whatever
|
|
// the window is at that moment. So the first race got a device sized to a
|
|
// still-bordered window and every race after it got one sized to the
|
|
// borderless monitor: two different render targets, two different frame
|
|
// costs, from one lobby and one set of settings.
|
|
//
|
|
// A racing sim cannot have that. The window reaches its final shape while
|
|
// the front end is still up, so every mission of a session - the first one
|
|
// included - is set up against exactly the same client area.
|
|
//
|
|
// SVGA16 still applies it when it builds the cockpit. That call becomes a
|
|
// no-op rather than a change, which is the point.
|
|
//
|
|
void
|
|
L4Application::FitWindowToMonitor(HWND window)
|
|
{
|
|
if (window == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RECT monitor_rect;
|
|
monitor_rect.left = 0;
|
|
monitor_rect.top = 0;
|
|
monitor_rect.right = GetSystemMetrics(SM_CXSCREEN);
|
|
monitor_rect.bottom = GetSystemMetrics(SM_CYSCREEN);
|
|
|
|
MONITORINFO monitor;
|
|
memset(&monitor, 0, sizeof(monitor));
|
|
monitor.cbSize = sizeof(monitor);
|
|
HMONITOR handle = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
|
|
if (GetMonitorInfoA(handle, &monitor))
|
|
{
|
|
monitor_rect = monitor.rcMonitor;
|
|
}
|
|
|
|
//
|
|
// Same style surgery SVGA16 performs, so the two agree exactly.
|
|
//
|
|
LONG_PTR style = GetWindowLongPtrA(window, GWL_STYLE);
|
|
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_SYSMENU |
|
|
WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_BORDER | WS_DLGFRAME);
|
|
style |= WS_POPUP | WS_CLIPCHILDREN;
|
|
SetWindowLongPtrA(window, GWL_STYLE, style);
|
|
|
|
SetWindowPos(window, NULL,
|
|
monitor_rect.left, monitor_rect.top,
|
|
monitor_rect.right - monitor_rect.left,
|
|
monitor_rect.bottom - monitor_rect.top,
|
|
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
|
|
|
|
DEBUG_STREAM << "L4Application: -fit placed the window borderless at "
|
|
<< (monitor_rect.right - monitor_rect.left) << "x"
|
|
<< (monitor_rect.bottom - monitor_rect.top)
|
|
<< " before the first mission\n" << std::flush;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ParseCommandLine
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4Application::ParseCommandLine(
|
|
int argc,
|
|
LPWSTR argv[],
|
|
TokenParser parser
|
|
)
|
|
{
|
|
USES_CONVERSION;
|
|
Check_Pointer(argv);
|
|
|
|
//
|
|
// Process options
|
|
//
|
|
seeSolids = False;
|
|
eggNotationFileName = "";
|
|
networkCommonFlatAddress = NULL;
|
|
missionReviewMode = 0;
|
|
suppressGauges = FALSE;
|
|
|
|
if (argc > 1)
|
|
{
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
if (!stricmp(W2A(argv[i]), "-lc"))
|
|
{
|
|
suppressGauges = TRUE;
|
|
} else if (!stricmp(W2A(argv[i]), "-mr"))
|
|
{
|
|
suppressGauges = TRUE;
|
|
missionReviewMode = 1;
|
|
} else if (!(*parser)(&i, argc, argv))
|
|
{
|
|
return False;
|
|
}
|
|
}
|
|
}
|
|
|
|
// after the whole line, so -res wins from either side of -fit
|
|
if (mFitDisplay && !mResExplicit)
|
|
{
|
|
ChooseFitResolution();
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// TestInstance
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
L4Application::TestInstance() const
|
|
{
|
|
if (!IsDerivedFrom(*GetClassDerivations()))
|
|
{
|
|
return False;
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// L4Application
|
|
//#############################################################################
|
|
//
|
|
L4Application::L4Application(
|
|
HINSTANCE hInstance,
|
|
HWND hWnd,
|
|
ResourceFile *resource_file,
|
|
ApplicationID application_ID,
|
|
ClassID class_ID,
|
|
SharedData &shared_data
|
|
):
|
|
Application(resource_file, application_ID, class_ID, shared_data),
|
|
mIsDead(false)
|
|
{
|
|
mhInstance = hInstance; // this is a static member, the app instance should never change during execution
|
|
//ghWnd = hWnd;
|
|
divisionParameters = getenv("DPLARG");
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Initialize
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4Application::Initialize()
|
|
{
|
|
Check(this);
|
|
|
|
InitializeTillConsole();
|
|
|
|
//
|
|
// Create the console host and socket
|
|
//
|
|
L4NetworkManager *net_mgr = GetNetworkManager();
|
|
net_mgr->CreateConsoleHost();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Initialize
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4Application::InitializeTillConsole()
|
|
{
|
|
Check(this);
|
|
|
|
Application::Initialize();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeModeManager
|
|
//#############################################################################
|
|
//
|
|
ModeManager*
|
|
L4Application::MakeModeManager()
|
|
{
|
|
return new ModeManager((ModeMask)ModeManager::ModeAlwaysActive); // Normally called at top level!!
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeNetworkManager
|
|
//#############################################################################
|
|
//
|
|
NetworkManager*
|
|
L4Application::MakeNetworkManager()
|
|
{
|
|
return new L4NetworkManager;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeControlsManager
|
|
//#############################################################################
|
|
//
|
|
ControlsManager*
|
|
L4Application::MakeControlsManager()
|
|
{
|
|
LBE4ControlsManager *manager = new LBE4ControlsManager();
|
|
Check(manager);
|
|
manager->keyboardGroup[LBE4ControlsManager::KeyboardPC].Add(ModeManager::ModeAlwaysActive, this, KeyCommandMessageID, this);
|
|
return manager;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeIntercomManager
|
|
//#############################################################################
|
|
//
|
|
IcomManager*
|
|
L4Application::MakeIntercomManager()
|
|
{
|
|
L4IcomManager
|
|
*manager = new L4IcomManager();
|
|
Check(manager);
|
|
|
|
return manager;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeVideoRenderer
|
|
//#############################################################################
|
|
//
|
|
VideoRenderer*
|
|
L4Application::MakeVideoRenderer()
|
|
{
|
|
if (divisionParameters)
|
|
{
|
|
return
|
|
new DPLRenderer(
|
|
ghWnd,
|
|
mScreenWidth,
|
|
mScreenHeight,
|
|
mFullscreen,
|
|
VisualInterestType,
|
|
DefaultInterestDepth
|
|
);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeAudioRenderer
|
|
//#############################################################################
|
|
//
|
|
AudioRenderer*
|
|
L4Application::MakeAudioRenderer()
|
|
{
|
|
/* char *blaster1, *blaster2;
|
|
|
|
blaster1 = getenv(FRONT_CARD_ENV_VAR);
|
|
blaster2 = getenv(REAR_CARD_ENV_VAR);
|
|
|
|
if (blaster1 != NULL && blaster2 != NULL)
|
|
{
|
|
return new L4AudioRenderer(DefaultRendererRate, False);
|
|
}
|
|
return NULL;*/
|
|
|
|
return new L4AudioRenderer(DefaultRendererRate, False);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeGaugeRenderer
|
|
//#############################################################################
|
|
//
|
|
GaugeRenderer*
|
|
L4Application::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index)
|
|
{
|
|
char *mode_string = getenv("L4GAUGE");
|
|
if (mode_string != NULL)
|
|
{
|
|
return new L4GaugeRenderer(!mFullscreen, secondaryIndex, aux1Index, aux2Index);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Terminate
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4Application::Terminate()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Turn off illumination
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
PilotIllumination(False);
|
|
|
|
//
|
|
// Call inherited method
|
|
//
|
|
Application::Terminate();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ~L4Application
|
|
//#############################################################################
|
|
//
|
|
L4Application::~L4Application()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeAndLinkViewpointEntity
|
|
//#############################################################################
|
|
//
|
|
Entity*
|
|
L4Application::MakeAndLinkViewpointEntity(Entity::MakeMessage* message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
Application::MakeAndLinkViewpointEntity(message);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Turn on lights for pilot
|
|
// (turned off by RunMissionMessageHandler)
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
PilotIllumination(True);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Return viewpoint entity
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
return GetViewpointEntity();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeViewpointEntity
|
|
//#############################################################################
|
|
//
|
|
Entity*
|
|
L4Application::MakeViewpointEntity(Entity::MakeMessage *message)
|
|
{
|
|
Entity *viewing_entity;
|
|
viewing_entity = NULL;
|
|
switch(message->classToCreate)
|
|
{
|
|
case CameraShipClassID:
|
|
viewing_entity = CameraShip::Make((CameraShip::MakeMessage*)message);
|
|
Check(viewing_entity);
|
|
CameraShip *viewing_camera = Cast_Object(CameraShip*, viewing_entity);
|
|
Check(viewing_camera);
|
|
|
|
//
|
|
// Create controls mapping object
|
|
//
|
|
CameraControlsMapper
|
|
*camera_mapper;
|
|
|
|
Verify(GetControlsManager() != NULL);
|
|
CameraControlsMapper::SubsystemResource control_subsystem_resource;
|
|
Str_Copy(
|
|
control_subsystem_resource.subsystemName,
|
|
"ControlsMapper",
|
|
sizeof(control_subsystem_resource.subsystemName)
|
|
);
|
|
control_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
control_subsystem_resource.subsystemModelSize =
|
|
sizeof(control_subsystem_resource);
|
|
|
|
LBE4ControlsManager* controls = GetControlsManager();
|
|
Check(controls);
|
|
switch (controls->primaryControlType)
|
|
{
|
|
case LBE4ControlsManager::PrimaryThrustMaster:
|
|
Tell("L4Application::MakeViewpointEntity, using ThrustMaster\n");
|
|
camera_mapper =
|
|
new CameraThrustmasterMapper(
|
|
viewing_camera,
|
|
CameraShip::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
Register_Object(camera_mapper);
|
|
viewing_camera->SetMappingSubsystem(camera_mapper);
|
|
break;
|
|
|
|
case LBE4ControlsManager::PrimaryRIO:
|
|
Tell("L4Application::MakeViewpointEntity, using RIO\n");
|
|
camera_mapper =
|
|
new CameraRIOMapper(
|
|
viewing_camera,
|
|
CameraShip::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
Register_Object(camera_mapper);
|
|
viewing_camera->SetMappingSubsystem(camera_mapper);
|
|
break;
|
|
default:
|
|
Fail("L4Application::MakeViewpointEntity, *** NO MAPPER! ***\n");
|
|
break;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------
|
|
// Create gauges for this entity
|
|
//-------------------------------------------
|
|
//
|
|
if (GetGaugeRenderer() != NULL)
|
|
{
|
|
L4GaugeRenderer *gauge_renderer = GetGaugeRenderer();
|
|
Check(gauge_renderer);
|
|
|
|
gauge_renderer->ConfigureForModel(
|
|
"Init",
|
|
viewing_entity
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
return viewing_entity;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RunMissionMessageHandler
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4Application::RunMissionMessageHandler(RunMissionMessage *message)
|
|
{
|
|
Check(this);
|
|
Verify(message->messageID == RunMissionMessageID);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If the application is already running then ignore this message
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
switch (GetApplicationState())
|
|
{
|
|
case RunningMission:
|
|
break;
|
|
|
|
//
|
|
//-------------------------------------
|
|
// Start the playback if it is required
|
|
//-------------------------------------
|
|
//
|
|
case LaunchingMission:
|
|
{
|
|
LBE4ControlsManager *controls = GetControlsManager();
|
|
Check(controls);
|
|
if (controls->IsRecording())
|
|
{
|
|
controls->StartRecording();
|
|
}
|
|
else if (controls->IsPlayingBack())
|
|
{
|
|
controls->StartPlayback();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WaitingForLaunch:
|
|
PilotIllumination(False);
|
|
break;
|
|
|
|
default:
|
|
Fail("Application::RunMissionMessageHandler - Not ready to run!\n");
|
|
break;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Call previous handler
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Application::RunMissionMessageHandler(message);
|
|
}
|
|
//
|
|
//#############################################################################
|
|
// StopMissionMessageHandler
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4Application::StopMissionMessageHandler(StopMissionMessage *message)
|
|
{
|
|
Check(this);
|
|
Verify(message->messageID == StopMissionMessageID);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Call inherited handler
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Application::StopMissionMessageHandler(message);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Turn on illumination
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
PilotIllumination(True);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Post a delayed message to ourselves to turn off the illumination
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Receiver::Message
|
|
lights_out_message(LightsOutMessageID, sizeof(Receiver::Message));
|
|
|
|
Time
|
|
event_time;
|
|
|
|
event_time = Now();
|
|
event_time += 30.0f;
|
|
|
|
Post(
|
|
LowEventPriority, // priority
|
|
this, // target
|
|
&lights_out_message, // message
|
|
event_time // when
|
|
);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// KeyCommandMessageHandler
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4Application::KeyCommandMessageHandler(
|
|
ReceiverDataMessageOf<ControlsKey> *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
// The debug keys are for developers: RP412DEVKEYS=1 arms them.
|
|
// Players get exactly one chord - Alt+Q, the deliberate abort.
|
|
//
|
|
// Only Alt+W and Alt+E actually do anything. The rest call DPLRenderer
|
|
// methods whose bodies were commented out with the rest of the DPL
|
|
// calls in the 2007 Direct3D port and have been empty ever since; each
|
|
// is marked INERT below. Reviving one means writing it against D3D9,
|
|
// not un-commenting anything - the dpl_* types it used are empty
|
|
// placeholder classes now (DPLSTUB.h).
|
|
//
|
|
static int dev_keys = -1;
|
|
if (dev_keys < 0)
|
|
{
|
|
const char *dev_value = getenv("RP412DEVKEYS");
|
|
dev_keys = (dev_value != NULL && atoi(dev_value) != 0) ? 1 : 0;
|
|
}
|
|
|
|
switch (message->dataContents)
|
|
{
|
|
//----------------------------------------------------------------
|
|
// Abort the mission, deliberately. The engine's legacy abort char
|
|
// is '&' (0x26) - but WM_KEYUP feeds VIRTUAL-KEY codes into this
|
|
// channel and VK_UP is also 0x26, so the plain code is swallowed
|
|
// below and the abort answers only to this chord.
|
|
//----------------------------------------------------------------
|
|
case PCK_ALT_Q:
|
|
message->dataContents = '&';
|
|
Application::KeyCommandMessageHandler(message);
|
|
break;
|
|
|
|
//----------------------------------------------------------------
|
|
// Swallowed collisions: 0x26 is VK_UP (the hat look-up key!) as
|
|
// well as a typed ampersand; 'E' (0x45 = the right-pedal key) was
|
|
// the event-queue dump. Neither may reach the engine handler.
|
|
//----------------------------------------------------------------
|
|
case '&':
|
|
case 'E':
|
|
break;
|
|
|
|
//------------------------------------
|
|
// Report current event queue (moved
|
|
// off the pedal key)
|
|
//------------------------------------
|
|
case PCK_ALT_E:
|
|
{
|
|
if (!dev_keys) break;
|
|
Check(application);
|
|
application->DumpEventQueue();
|
|
break;
|
|
}
|
|
|
|
//--------------------------------------------
|
|
// FrameDump from Division card to Targa file
|
|
// INERT: DPLFrameDump and dump_frame_buffer are both stubs.
|
|
//--------------------------------------------
|
|
case PCK_ALT_F:
|
|
{
|
|
if (!dev_keys) break;
|
|
DPLRenderer *dpl_renderer = l4_application->GetVideoRenderer();
|
|
if (dpl_renderer)
|
|
{
|
|
Check(dpl_renderer);
|
|
dpl_renderer->DPLFrameDump(True); // True indicates antialiased
|
|
}
|
|
break;
|
|
}
|
|
|
|
//------------------------------------
|
|
// Report current free memory in card
|
|
// INERT: the body below is commented out, so this key is silent.
|
|
//------------------------------------
|
|
case PCK_ALT_K:
|
|
{
|
|
if (!dev_keys) break;
|
|
//STUBBED: HEAP RB 1/20/07
|
|
/*DPLRenderer *dpl_renderer = l4_application->GetVideoRenderer();
|
|
if (dpl_renderer)
|
|
{
|
|
Check(dpl_renderer);
|
|
dpl_renderer->DPLReportFreeMemory(DEBUG_STREAM) << std::flush;
|
|
}
|
|
DEBUG_STREAM << UserHeap::MainStorage.GetTotalHeapLeft() << '('
|
|
<< UserHeap::MainStorage.GetBiggestHeapLeft()
|
|
<< " contiguous) bytes of MUNGA core left\n"
|
|
<< UserHeap::MainStorage.GetHeapUsed() << " bytes of heap used\n";
|
|
#if defined(USE_MEMORY_ANALYSIS)
|
|
DEBUG_STREAM << UserHeap::MainStorage.GetLowestTotalHeapLeft()
|
|
<< " bytes minimum available so far\n"
|
|
<< UserHeap::MainStorage.GetMostHeapUsed()
|
|
<< " bytes maximum used so far\n";
|
|
#endif
|
|
MemoryBlockBase::UsageReport();*/
|
|
break;
|
|
}
|
|
|
|
//---------------------------------------
|
|
// Report performance statistics (Alt-?)
|
|
// INERT: DPLReportPerfStats is a stub - it read DPL's own counters.
|
|
//---------------------------------------
|
|
case PCK_ALT_SLASH:
|
|
{
|
|
if (!dev_keys) break;
|
|
DPLRenderer *dpl_renderer = l4_application->GetVideoRenderer();
|
|
if (dpl_renderer)
|
|
{
|
|
Check(dpl_renderer);
|
|
dpl_renderer->DPLReportPerfStats(DEBUG_STREAM);
|
|
}
|
|
break;
|
|
}
|
|
//--------------------------------------------------------------
|
|
// Toggle Wireframe display. Live: reimplemented on D3D9 as a
|
|
// per-frame D3DRS_FILLMODE (see gWireframe in L4VIDEO.cpp).
|
|
//--------------------------------------------------------------
|
|
case PCK_ALT_W:
|
|
{
|
|
if (!dev_keys) break;
|
|
DPLRenderer *dpl_renderer = l4_application->GetVideoRenderer();
|
|
if (dpl_renderer)
|
|
{
|
|
Check(dpl_renderer);
|
|
dpl_renderer->DPLToggleWireframe();
|
|
}
|
|
break;
|
|
}
|
|
//--------------------------------------------------------------
|
|
// Toggle "Predator-vision" - a global false-colour/thermal mode
|
|
// the DPL renderer implemented internally, reached by passing an
|
|
// out-of-band explosion effect type (-1 on, -2 off) with a NULL
|
|
// DCS. INERT: DPLTogglePVision is a stub, and what it looked
|
|
// like is not recorded anywhere in this tree.
|
|
//--------------------------------------------------------------
|
|
case PCK_ALT_V:
|
|
{
|
|
if (!dev_keys) break;
|
|
DPLRenderer *dpl_renderer = l4_application->GetVideoRenderer();
|
|
if (dpl_renderer)
|
|
{
|
|
Check(dpl_renderer);
|
|
dpl_renderer->DPLTogglePVision();
|
|
}
|
|
break;
|
|
}
|
|
//--------------------------------------------------------------
|
|
// Toggle transparency dither pattern between random and static
|
|
//--------------------------------------------------------------
|
|
case PCK_ALT_R:
|
|
//-----------------------------
|
|
// Report position of eyepoint
|
|
//-----------------------------
|
|
case PCK_ALT_P:
|
|
DEBUG_STREAM << "Function net yet enabled.\n" << std::flush;
|
|
break;
|
|
//------------------------------
|
|
// Pass event on to Application
|
|
//------------------------------
|
|
default:
|
|
Application::KeyCommandMessageHandler(message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// LightsOutMessageHandler
|
|
//#############################################################################
|
|
//
|
|
#if DEBUG_LEVEL == 0
|
|
void
|
|
L4Application::LightsOutMessageHandler(Receiver::Message */*message*/)
|
|
{
|
|
#else
|
|
void
|
|
L4Application::LightsOutMessageHandler(Receiver::Message *message)
|
|
{
|
|
Check(this);
|
|
Verify(message->messageID == LightsOutMessageID);
|
|
#endif
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Turn off illumination
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
PilotIllumination(False);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// TeslaCoil
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4Application::TeslaCoil(Logical light_on)
|
|
{
|
|
Check(controlsManager);
|
|
|
|
if (light_on)
|
|
{
|
|
((LBE4ControlsManager *) controlsManager)->SetLamp(
|
|
LBE4ControlsManager::LampTesla1,
|
|
RIO::solid + RIO::state1Bright + RIO::state2Bright
|
|
);
|
|
((LBE4ControlsManager *) controlsManager)->SetLamp(
|
|
LBE4ControlsManager::LampTesla2,
|
|
RIO::solid + RIO::state1Bright + RIO::state2Bright
|
|
);
|
|
((LBE4ControlsManager *) controlsManager)->SetLamp(
|
|
LBE4ControlsManager::LampTesla3,
|
|
RIO::solid + RIO::state1Bright + RIO::state2Bright
|
|
);
|
|
}
|
|
else
|
|
{
|
|
((LBE4ControlsManager *) controlsManager)->SetLamp(LBE4ControlsManager::LampTesla1, RIO::solid + RIO::state1Off + RIO::state2Off);
|
|
((LBE4ControlsManager *) controlsManager)->SetLamp(LBE4ControlsManager::LampTesla2, RIO::solid + RIO::state1Off + RIO::state2Off);
|
|
((LBE4ControlsManager *) controlsManager)->SetLamp(LBE4ControlsManager::LampTesla3, RIO::solid + RIO::state1Off + RIO::state2Off);
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// PilotIllumination
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4Application::PilotIllumination(Logical light_on)
|
|
{
|
|
Check(controlsManager);
|
|
|
|
TeslaCoil(light_on);
|
|
|
|
if (light_on)
|
|
{
|
|
//--------------------------------------------
|
|
// Turn on floor light
|
|
//--------------------------------------------
|
|
((LBE4ControlsManager *) controlsManager)->SetLamp(
|
|
LBE4ControlsManager::LampFloor,
|
|
RIO::solid + RIO::state1Bright + RIO::state2Bright
|
|
);
|
|
//--------------------------------------------
|
|
// Ramp all aux screens to white
|
|
//--------------------------------------------
|
|
L4GaugeRenderer *gauge_renderer = GetGaugeRenderer();
|
|
if (gauge_renderer != NULL)
|
|
{
|
|
Check(gauge_renderer);
|
|
gauge_renderer->FadeToWhite(1.5);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--------------------------------------------
|
|
// Turn off floor light
|
|
//--------------------------------------------
|
|
((LBE4ControlsManager *) controlsManager)->SetLamp(
|
|
LBE4ControlsManager::LampFloor,
|
|
RIO::solid + RIO::state1Off + RIO::state2Off
|
|
);
|
|
//--------------------------------------------
|
|
// Ramp all aux screens to normal
|
|
//--------------------------------------------
|
|
L4GaugeRenderer *gauge_renderer = GetGaugeRenderer();
|
|
if (gauge_renderer != NULL)
|
|
{
|
|
Check(gauge_renderer);
|
|
gauge_renderer->FadeToNormal(1.5);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef TEST_CLASS
|
|
# include "l4app.tcp"
|
|
#endif
|