Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfd5fa163e | ||
|
|
a52fec80a9 | ||
|
|
40b00ddde1 | ||
|
|
16ce4dfbea | ||
|
|
2a23ec0923 | ||
|
|
1efd5137d6 | ||
|
|
b97dcce3a2 | ||
|
|
f31c8401c7 | ||
|
|
e1a3ef7cf1 | ||
|
|
858fb7fb42 | ||
|
|
28df53aa31 | ||
|
|
461dcfb6b9 | ||
|
|
9389ec2003 |
@@ -40,6 +40,11 @@ ipch/
|
||||
*.log
|
||||
rpl4.log
|
||||
|
||||
# Where the game window and the exploded view's panes were last dragged
|
||||
# to (RP412MFDLAYOUT). Per-machine by nature - it is screen coordinates
|
||||
# on somebody's desk.
|
||||
mfd_layout.cfg
|
||||
|
||||
# Build-output static libs that land in lib/ (the two committed dependency
|
||||
# libs, OpenAL32.lib and libsndfile-1.lib, stay tracked).
|
||||
/lib/Munga_L4.lib
|
||||
|
||||
+78
-1
@@ -13,6 +13,7 @@ L4TEXOP::WrapType d3d_OBJECT::mLastWrapV = L4TEXOP::WrapType::REPEAT;
|
||||
bool d3d_OBJECT::mLastTexturingState = true;
|
||||
long d3d_OBJECT::mNextID = 1;
|
||||
stdext::hash_map<std::string, L4TEXOP> d3d_OBJECT::mTextureCache;
|
||||
LPDIRECT3DTEXTURE9 d3d_OBJECT::mNamePlateMarkers[d3d_OBJECT::kNamePlateCount + 1] = { NULL };
|
||||
|
||||
void chgext(char *filePath, const char *newExtension)
|
||||
{
|
||||
@@ -156,7 +157,36 @@ d3d_OBJECT *d3d_OBJECT::LoadObject(LPDIRECT3DDEVICE9 device, char *fileName)
|
||||
{
|
||||
sprintf(textureFilename, "VIDEO\\%s", materials[i].pTextureFilename);
|
||||
}
|
||||
object->mDrawOps[i].texture = LoadTexture(device, textureFilename);
|
||||
//
|
||||
// playerN is not a file - it is a pilot's callsign, drawn into
|
||||
// a texture at run time, so loading it always fails. Give the
|
||||
// plate its marker texture instead: it keeps the eight sign
|
||||
// faces as eight separate draw ops through consolidation, and
|
||||
// the Winners Circle swaps the real callsign in later.
|
||||
//
|
||||
const char *base = strrchr(textureFilename, '\\');
|
||||
base = (base != NULL) ? base + 1 : textureFilename;
|
||||
int plate = 0;
|
||||
if (_strnicmp(base, "player", 6) == 0 &&
|
||||
base[6] >= '1' && base[6] <= '8')
|
||||
{
|
||||
plate = base[6] - '0';
|
||||
}
|
||||
|
||||
if (plate != 0)
|
||||
{
|
||||
memset(&object->mDrawOps[i].texture, 0, sizeof(L4TEXOP));
|
||||
object->mDrawOps[i].texture.texture =
|
||||
NamePlateMarker(device, plate);
|
||||
if (object->mDrawOps[i].texture.texture != NULL)
|
||||
{
|
||||
object->mDrawOps[i].texture.texture->AddRef();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
object->mDrawOps[i].texture = LoadTexture(device, textureFilename);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextDetailOp < numDetailOps && detailOps[nextDetailOp] == i)
|
||||
@@ -286,6 +316,53 @@ void d3d_OBJECT::FlushTextureCache()
|
||||
(*iter).second.texture->Release();
|
||||
}
|
||||
mTextureCache.clear();
|
||||
|
||||
for (int plate = 0; plate <= kNamePlateCount; ++plate)
|
||||
{
|
||||
if (mNamePlateMarkers[plate] != NULL)
|
||||
{
|
||||
mNamePlateMarkers[plate]->Release();
|
||||
mNamePlateMarkers[plate] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// A distinct 1x1 texture per plate, so the eight sign faces stay eight
|
||||
// separate draw ops through mesh consolidation instead of merging into one.
|
||||
// Its content never shows - the callsign replaces it before the podium.
|
||||
//
|
||||
LPDIRECT3DTEXTURE9 d3d_OBJECT::NamePlateMarker(LPDIRECT3DDEVICE9 device, int plate)
|
||||
{
|
||||
if (plate < 1 || plate > kNamePlateCount || device == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (mNamePlateMarkers[plate] == NULL)
|
||||
{
|
||||
device->CreateTexture(1, 1, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED,
|
||||
&mNamePlateMarkers[plate], NULL);
|
||||
}
|
||||
return mNamePlateMarkers[plate];
|
||||
}
|
||||
|
||||
//
|
||||
// Which plate a texture is the marker for, or 0 if it is not one.
|
||||
//
|
||||
int d3d_OBJECT::NamePlateFor(LPDIRECT3DTEXTURE9 texture)
|
||||
{
|
||||
if (texture == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
for (int plate = 1; plate <= kNamePlateCount; ++plate)
|
||||
{
|
||||
if (mNamePlateMarkers[plate] == texture)
|
||||
{
|
||||
return plate;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
d3d_OBJECT::d3d_OBJECT(LPDIRECT3DDEVICE9 device, int vertCount)
|
||||
|
||||
@@ -111,6 +111,27 @@ public:
|
||||
// race loop tears the renderer down between missions).
|
||||
static void FlushTextureCache();
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Pilot name plates.
|
||||
//
|
||||
// The Winners Circle signs ask for textures called player1..player8,
|
||||
// which are not files - the renderer draws each pilot's callsign into
|
||||
// a texture at run time. Loading them fails and the plates come out
|
||||
// blank, so the draw ops that wanted them are noted here as the
|
||||
// geometry loads, and bound to the real textures once the finishing
|
||||
// order is known.
|
||||
//
|
||||
// Cleared with the texture cache: the entries point at objects that
|
||||
// belong to the mission being torn down.
|
||||
//------------------------------------------------------------------
|
||||
// Each plate is given a distinct marker texture as it loads. Without
|
||||
// one they are eight identical untextured draw ops, and consolidation
|
||||
// merges every static mesh by material - all eight plates would
|
||||
// collapse into a single shared op that can only ever show one name.
|
||||
enum { kNamePlateCount = 8 };
|
||||
static LPDIRECT3DTEXTURE9 NamePlateMarker(LPDIRECT3DDEVICE9 device, int plate);
|
||||
static int NamePlateFor(LPDIRECT3DTEXTURE9 texture);
|
||||
|
||||
private:
|
||||
static d3d_OBJECT* LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName);
|
||||
|
||||
@@ -150,6 +171,9 @@ private:
|
||||
|
||||
static long mNextID;
|
||||
static stdext::hash_map< std::string , L4TEXOP > mTextureCache;
|
||||
|
||||
// index 1..8; [0] unused so the index is the plate number
|
||||
static LPDIRECT3DTEXTURE9 mNamePlateMarkers[kNamePlateCount + 1];
|
||||
};
|
||||
|
||||
extern int gNumBatches;
|
||||
|
||||
@@ -10,6 +10,74 @@ namespace
|
||||
|
||||
const int buttonGap = 4;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Sticky placement for the exploded view. See L4MFDVIEW.h; ported
|
||||
// from BT411's BT_GLASS_LAYOUT.
|
||||
//---------------------------------------------------------------
|
||||
enum { LayoutOff = 0, LayoutLoad, LayoutSave };
|
||||
|
||||
const char layoutFileName[] = "mfd_layout.cfg";
|
||||
|
||||
// every window taking part, so Save can walk them
|
||||
enum { maxLayoutWindows = 12 };
|
||||
struct LayoutWindow
|
||||
{
|
||||
void *window;
|
||||
const char *title;
|
||||
Logical withSize;
|
||||
Logical bare; // frame stripped, per the file
|
||||
};
|
||||
LayoutWindow layoutWindows[maxLayoutWindows];
|
||||
int layoutWindowCount = 0;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Take a window's frame off: no title bar, no border, nothing to
|
||||
// drag it by. WS_SYSMENU stays - it draws nothing once the caption
|
||||
// is gone, but without one DefWindowProc will not honour Alt+F4,
|
||||
// and a bare window with no way to close it is a trap.
|
||||
//---------------------------------------------------------------
|
||||
void StripWindowFrame(HWND window)
|
||||
{
|
||||
LONG_PTR style = GetWindowLongPtrA(window, GWL_STYLE);
|
||||
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX |
|
||||
WS_MAXIMIZEBOX | WS_BORDER | WS_DLGFRAME);
|
||||
style |= WS_POPUP;
|
||||
SetWindowLongPtrA(window, GWL_STYLE, style);
|
||||
}
|
||||
|
||||
int LayoutMode()
|
||||
{
|
||||
static int mode = -1;
|
||||
if (mode < 0)
|
||||
{
|
||||
const char *setting = getenv("RP412MFDLAYOUT");
|
||||
if (setting == NULL || setting[0] == '\0')
|
||||
{
|
||||
mode = LayoutOff;
|
||||
}
|
||||
else if (!stricmp(setting, "off") || !stricmp(setting, "0"))
|
||||
{
|
||||
mode = LayoutOff;
|
||||
}
|
||||
else if (!stricmp(setting, "load") || !stricmp(setting, "restore"))
|
||||
{
|
||||
mode = LayoutLoad;
|
||||
}
|
||||
else // save / adjust / on / 1
|
||||
{
|
||||
mode = LayoutSave;
|
||||
}
|
||||
|
||||
if (mode != LayoutOff)
|
||||
{
|
||||
DEBUG_STREAM << "MFDLayout: RP412MFDLAYOUT="
|
||||
<< ((mode == LayoutSave) ? "save" : "load")
|
||||
<< " (" << layoutFileName << ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Button banks run UNDER the glass: a button reaches this far in
|
||||
// from the display edge, but only the indicator strip clears that
|
||||
@@ -108,6 +176,15 @@ namespace
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_EXITSIZEMOVE:
|
||||
//
|
||||
// A drag just finished. Write the whole arrangement now rather
|
||||
// than trusting teardown, so a hard kill still leaves the
|
||||
// layout that was on screen. No-op unless the mode is save.
|
||||
//
|
||||
RPWindowLayout_Save();
|
||||
return 0;
|
||||
|
||||
case WM_CLOSE:
|
||||
// Part of the cockpit; just hide it.
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
@@ -148,6 +225,8 @@ MFDSplitView::MFDSplitView(
|
||||
sourceHeight = source_height;
|
||||
buttonCount = 0;
|
||||
pressedIndex = -1;
|
||||
paneTitle = title;
|
||||
ownWindow = (parent == NULL) ? True : False;
|
||||
|
||||
pixels = new unsigned long[source_width * source_height];
|
||||
memset(pixels, 0, source_width * source_height * sizeof(unsigned long));
|
||||
@@ -230,6 +309,14 @@ MFDSplitView::MFDSplitView(
|
||||
{
|
||||
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
|
||||
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
||||
|
||||
// only the exploded view's own windows can be dragged, so only
|
||||
// those have a placement worth remembering. Position only - see
|
||||
// RPWindowLayout_Register.
|
||||
if (ownWindow)
|
||||
{
|
||||
RPWindowLayout_Register(window, paneTitle, False);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -241,6 +328,9 @@ MFDSplitView::~MFDSplitView()
|
||||
{
|
||||
Check_Pointer(this);
|
||||
|
||||
// out of the layout registry before the window goes
|
||||
RPWindowLayout_Forget(window);
|
||||
|
||||
if (window != NULL)
|
||||
{
|
||||
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0);
|
||||
@@ -259,6 +349,272 @@ Logical
|
||||
return pixels != NULL;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Join the saved layout. Registering twice just refreshes the entry, so
|
||||
// a window re-registering after being rebuilt is harmless.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
void
|
||||
RPWindowLayout_Register(void *window, const char *title, Logical with_size)
|
||||
{
|
||||
if (window == NULL || title == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < layoutWindowCount; ++i)
|
||||
{
|
||||
if (layoutWindows[i].window == window)
|
||||
{
|
||||
layoutWindows[i].title = title;
|
||||
layoutWindows[i].withSize = with_size;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (layoutWindowCount >= maxLayoutWindows)
|
||||
{
|
||||
return;
|
||||
}
|
||||
layoutWindows[layoutWindowCount].window = window;
|
||||
layoutWindows[layoutWindowCount].title = title;
|
||||
layoutWindows[layoutWindowCount].withSize = with_size;
|
||||
layoutWindows[layoutWindowCount].bare = False;
|
||||
++layoutWindowCount;
|
||||
}
|
||||
|
||||
void
|
||||
RPWindowLayout_Forget(void *window)
|
||||
{
|
||||
for (int i = 0; i < layoutWindowCount; ++i)
|
||||
{
|
||||
if (layoutWindows[i].window == window)
|
||||
{
|
||||
layoutWindows[i] = layoutWindows[--layoutWindowCount];
|
||||
layoutWindows[layoutWindowCount].window = NULL;
|
||||
layoutWindows[layoutWindowCount].title = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Sticky placement: restore the saved placement over the computed one.
|
||||
//
|
||||
// Called once everything has been built and placed, so a window the file
|
||||
// does not mention simply keeps where it was put. Size comes back only
|
||||
// for windows registered with it - see the header.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
void
|
||||
RPWindowLayout_Load()
|
||||
{
|
||||
if (LayoutMode() == LayoutOff || layoutWindowCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = fopen(layoutFileName, "rt");
|
||||
if (file == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "MFDLayout: no " << layoutFileName
|
||||
<< " yet (using the computed arrangement)\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
int restored = 0;
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), file) != NULL)
|
||||
{
|
||||
char *cursor = line;
|
||||
while (*cursor == ' ' || *cursor == '\t')
|
||||
{
|
||||
++cursor;
|
||||
}
|
||||
if (*cursor == '#' || *cursor == '\r' || *cursor == '\n' || *cursor == '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// titles carry no '=', so the first one splits the line
|
||||
char *equals = strchr(cursor, '=');
|
||||
if (equals == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
*equals = '\0';
|
||||
char *end = equals;
|
||||
while (end > cursor && (end[-1] == ' ' || end[-1] == '\t'))
|
||||
{
|
||||
--end;
|
||||
}
|
||||
*end = '\0';
|
||||
|
||||
int x = 0, y = 0, w = 0, h = 0;
|
||||
if (sscanf(equals + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Optional trailing keyword, "x,y,w,h,noframe". Searched for
|
||||
// rather than parsed in place so a hand-edited line survives a
|
||||
// stray space or a missing comma.
|
||||
//
|
||||
Logical bare = (strstr(equals + 1, "noframe") != NULL) ? True : False;
|
||||
|
||||
for (int i = 0; i < layoutWindowCount; ++i)
|
||||
{
|
||||
LayoutWindow &entry = layoutWindows[i];
|
||||
if (entry.title == NULL || strcmp(entry.title, cursor) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
HWND target = (HWND) entry.window;
|
||||
if (target == NULL || !IsWindow(target))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// a saved size of nothing is a corrupt line, not an instruction
|
||||
Logical size_it = (entry.withSize && w > 0 && h > 0) ? True : False;
|
||||
|
||||
//
|
||||
// Saved coordinates belong to whatever monitors were plugged
|
||||
// in that day. If the placement lands on none of today's, drop
|
||||
// it and keep the computed one - restoring the game window
|
||||
// somewhere invisible would leave nothing to drag back.
|
||||
//
|
||||
RECT landing;
|
||||
landing.left = x;
|
||||
landing.top = y;
|
||||
if (size_it)
|
||||
{
|
||||
landing.right = x + w;
|
||||
landing.bottom = y + h;
|
||||
}
|
||||
else
|
||||
{
|
||||
RECT current;
|
||||
if (!GetWindowRect(target, ¤t))
|
||||
{
|
||||
break;
|
||||
}
|
||||
landing.right = x + (current.right - current.left);
|
||||
landing.bottom = y + (current.bottom - current.top);
|
||||
}
|
||||
if (MonitorFromRect(&landing, MONITOR_DEFAULTTONULL) == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "MFDLayout: " << cursor << " was saved at "
|
||||
<< x << "," << y << " - off every monitor now, ignoring\n"
|
||||
<< std::flush;
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Frame off, if the line asked. A bare window's rect IS its
|
||||
// client rect, so whatever client area it has now is the size
|
||||
// to hand back - which also makes the round trip stable: once
|
||||
// bare, what Save records is already the client.
|
||||
//
|
||||
entry.bare = bare;
|
||||
if (bare)
|
||||
{
|
||||
if (!size_it)
|
||||
{
|
||||
RECT client;
|
||||
if (GetClientRect(target, &client))
|
||||
{
|
||||
w = client.right;
|
||||
h = client.bottom;
|
||||
size_it = True;
|
||||
}
|
||||
}
|
||||
StripWindowFrame(target);
|
||||
}
|
||||
|
||||
SetWindowPos(target, NULL, x, y, w, h,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE |
|
||||
(size_it ? 0 : SWP_NOSIZE) | (bare ? SWP_FRAMECHANGED : 0));
|
||||
++restored;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
DEBUG_STREAM << "MFDLayout: restored " << restored << " window placement(s) from "
|
||||
<< layoutFileName << "\n" << std::flush;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Write where every registered window currently is. The whole file each
|
||||
// time - it is a handful of lines, and a rewrite leaves nothing
|
||||
// half-written for a hard kill to catch.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
void
|
||||
RPWindowLayout_Save()
|
||||
{
|
||||
if (LayoutMode() != LayoutSave || layoutWindowCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = fopen(layoutFileName, "wt");
|
||||
if (file == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "MFDLayout: could not write " << layoutFileName
|
||||
<< "\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
fputs("# RP412 window placement. RP412MFDLAYOUT=save writes this on each\n"
|
||||
"# finished drag and on exit; =load restores it.\n"
|
||||
"# <title>=<x>,<y>,<w>,<h>. The game window gets its size back too;\n"
|
||||
"# for the display panes the size is reference only, since theirs\n"
|
||||
"# follows their content.\n"
|
||||
"# Append ,noframe to take that window's title bar and border off.\n"
|
||||
"# Put it where you want it first - a bare window has nothing to\n"
|
||||
"# drag by, so its line stops changing once the frame is gone.\n", file);
|
||||
|
||||
int wrote = 0;
|
||||
for (int i = 0; i < layoutWindowCount; ++i)
|
||||
{
|
||||
LayoutWindow &entry = layoutWindows[i];
|
||||
if (entry.title == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
HWND entry_window = (HWND) entry.window;
|
||||
if (entry_window == NULL || !IsWindow(entry_window))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//
|
||||
// rcNormalPosition rather than GetWindowRect: a minimised window
|
||||
// reports a nonsense rect and a maximised one reports the screen,
|
||||
// and neither is what to come back to. This is the restored
|
||||
// placement whatever state the window is in, so quitting from
|
||||
// maximised still records where the window will reappear.
|
||||
//
|
||||
WINDOWPLACEMENT placement;
|
||||
memset(&placement, 0, sizeof(placement));
|
||||
placement.length = sizeof(placement);
|
||||
if (!GetWindowPlacement(entry_window, &placement))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
RECT bounds = placement.rcNormalPosition;
|
||||
// the flag is the player's instruction, not something measured off
|
||||
// the window, so a rewrite has to carry it back out
|
||||
fprintf(file, "%s=%ld,%ld,%ld,%ld%s\n", entry.title,
|
||||
(long) bounds.left, (long) bounds.top,
|
||||
(long) (bounds.right - bounds.left),
|
||||
(long) (bounds.bottom - bounds.top),
|
||||
entry.bare ? ",noframe" : "");
|
||||
++wrote;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
DEBUG_STREAM << "MFDLayout: saved " << wrote << " window placement(s) to "
|
||||
<< layoutFileName << "\n" << std::flush;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Compute the display rectangle and the button rectangles, growing the
|
||||
// client area to make room for the strips.
|
||||
@@ -490,6 +846,20 @@ void
|
||||
// one message per frame, and queued WM_PAINTs (lowest priority) starve
|
||||
// behind it - panes would freeze on their first frame.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Off screen, and stay off: the pane keeps its pixels and geometry, it
|
||||
// simply stops being shown. Repaint() on a hidden window is harmless.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
void
|
||||
MFDSplitView::Hide()
|
||||
{
|
||||
Check_Pointer(this);
|
||||
if (window != NULL)
|
||||
{
|
||||
ShowWindow((HWND) window, SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MFDSplitView::SetPosition(int x, int y)
|
||||
{
|
||||
|
||||
@@ -71,6 +71,20 @@ public:
|
||||
void
|
||||
Resize(int display_width, int display_height);
|
||||
|
||||
// Take the pane off screen. Used for the Winners Circle, which wants
|
||||
// the whole viewscreen: the panes overlap it like the pod's bezels,
|
||||
// so hiding them uncovers the full canvas underneath.
|
||||
void
|
||||
Hide();
|
||||
|
||||
// caption and HWND, for the sticky-placement file
|
||||
const char *
|
||||
Title() const
|
||||
{ return paneTitle; }
|
||||
void *
|
||||
Window()
|
||||
{ return window; }
|
||||
|
||||
~MFDSplitView();
|
||||
|
||||
Logical
|
||||
@@ -141,4 +155,78 @@ protected:
|
||||
int
|
||||
buttonAnchorA,
|
||||
buttonAnchorB;
|
||||
|
||||
// the window caption, and the key this pane is saved under
|
||||
const char
|
||||
*paneTitle;
|
||||
// True for a pane of its own on the desktop (the exploded view); the
|
||||
// composited cockpit's panes are chrome-less children and cannot be
|
||||
// dragged, so there is nothing to remember for them.
|
||||
Logical
|
||||
ownWindow;
|
||||
};
|
||||
|
||||
//########################################################################
|
||||
// Sticky window placement.
|
||||
//
|
||||
// The game window and, in the exploded view, each display pane are all
|
||||
// draggable, but their placement is recomputed on every launch - so
|
||||
// putting a window somewhere useful never survived the menu-race-menu
|
||||
// loop. RP412MFDLAYOUT remembers it, in mfd_layout.cfg beside
|
||||
// bindings.txt:
|
||||
//
|
||||
// off / 0 / unset computed placement only, no file (default)
|
||||
// load / restore restore saved placement at startup, never write
|
||||
// save / adjust restore, then rewrite on each finished drag and
|
||||
// on teardown - the round trip
|
||||
//
|
||||
// One "<title>=x,y,w,h" line per window, plus an optional ",noframe"
|
||||
// that takes that window's title bar and border off - for a cockpit that
|
||||
// fills a monitor edge to edge without -fit's all-or-nothing, or an
|
||||
// exploded pane photographed without chrome. A bare window has nothing
|
||||
// to drag by, so place it first and add the flag after; Save carries the
|
||||
// flag back out, since it is an instruction rather than something
|
||||
// measured off the window. Windows are always built framed, so deleting
|
||||
// the flag is all it takes to get the frame back.
|
||||
//
|
||||
// Whether the size comes back depends on the window, which is why
|
||||
// Register takes it as a flag:
|
||||
//
|
||||
// display panes position only. A pane's size follows its content and
|
||||
// its button banks, so an old size from a different
|
||||
// build must not distort it.
|
||||
// the game window position and size. Nothing derives that size - -res
|
||||
// only decides how sharp the scene is, and the cockpit
|
||||
// fits itself to whatever client area it is given - so
|
||||
// a window sized to suit a monitor should come back
|
||||
// that way, and half-restoring it would be the strange
|
||||
// behaviour. RPL4.CPP registers it before the console
|
||||
// screen, so the saved placement is there from the
|
||||
// first frame rather than arriving when a race starts.
|
||||
//
|
||||
// A placement that lands on none of the monitors currently plugged in is
|
||||
// ignored rather than applied - restoring the game window off screen
|
||||
// would leave nothing to drag back.
|
||||
//
|
||||
// Lives here because the display panes were the first to need it; the
|
||||
// game window joined later rather than grow a second copy of the same
|
||||
// file format.
|
||||
//
|
||||
// Ported from BT411's BT_GLASS_LAYOUT.
|
||||
//########################################################################
|
||||
|
||||
// Take part in the saved layout. title is the key in the file and must
|
||||
// outlive the window (the callers pass string literals).
|
||||
void
|
||||
RPWindowLayout_Register(void *window, const char *title, Logical with_size);
|
||||
void
|
||||
RPWindowLayout_Forget(void *window);
|
||||
|
||||
// Apply the saved placement over the computed one. Call once everything
|
||||
// has been built and placed.
|
||||
void
|
||||
RPWindowLayout_Load();
|
||||
|
||||
// Write every registered window's placement. No-op unless mode is save.
|
||||
void
|
||||
RPWindowLayout_Save();
|
||||
|
||||
@@ -3839,6 +3839,11 @@ static LRESULT CALLBACK
|
||||
cockpit->LayoutCockpit(LOWORD(lParam), HIWORD(lParam));
|
||||
}
|
||||
}
|
||||
//
|
||||
// WM_EXITSIZEMOVE is not handled here: RPL4.CPP's own WndProc takes
|
||||
// it, and this chains straight through to that, so the shell saves on
|
||||
// every finished drag whether or not a cockpit has been built.
|
||||
//
|
||||
return CallWindowProcA(gCockpitBaseProc, hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
@@ -4161,6 +4166,29 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Clear the glass off the screen for the Winners Circle.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
void
|
||||
SVGA16::HideSecondaryDisplays()
|
||||
{
|
||||
Check_Pointer(this);
|
||||
|
||||
if (!splitViews)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int view = 0; view < SplitViewCount; ++view)
|
||||
{
|
||||
if (splitView[view] != NULL)
|
||||
{
|
||||
splitView[view]->Hide();
|
||||
}
|
||||
}
|
||||
DEBUG_STREAM << "SVGA16: secondary displays hidden for the podium\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Fit the 1920x1080 cockpit canvas into the given client area: one
|
||||
// uniform scale (so the canvas never stretches - a wider desktop just
|
||||
@@ -4434,6 +4462,15 @@ SVGA16::SVGA16(
|
||||
splitView[SplitMFDLowerRight]->SetPosition(
|
||||
work.left + right_x, work.top + bottom_y);
|
||||
splitView[SplitMap]->SetPosition(work.left + map_x, work.top + map_y);
|
||||
|
||||
//
|
||||
// ...and then let RP412MFDLAYOUT put everything back where it was
|
||||
// dragged to last time. Windows the file does not mention keep the
|
||||
// arrangement just computed above. The game window is registered
|
||||
// already - RPL4.CPP does that before the console screen ever
|
||||
// shows - so this picks it up alongside the panes.
|
||||
//
|
||||
RPWindowLayout_Load();
|
||||
}
|
||||
else if (splitViews)
|
||||
{
|
||||
@@ -4645,6 +4682,23 @@ SVGA16::SVGA16(
|
||||
// catch maximise / restore / drag-resize and re-fit
|
||||
gCockpitBaseProc = (WNDPROC) SetWindowLongPtrA(
|
||||
cockpit, GWLP_WNDPROC, (LONG_PTR) CockpitShellProc);
|
||||
|
||||
//
|
||||
// Sticky placement for the shell. Position AND size: nothing
|
||||
// derives that size here - -res only decides how sharp the
|
||||
// scene is - so a window sized to suit a monitor should come
|
||||
// back that way. RPL4.CPP registered it before the console
|
||||
// screen; this reload undoes the sizing just done above, and
|
||||
// runs after the subclass on purpose so its WM_SIZE puts
|
||||
// LayoutCockpit over the restored client area.
|
||||
//
|
||||
// -fit sits it borderless over the whole monitor, so there is
|
||||
// no placement of the player's to remember.
|
||||
//
|
||||
if (!fit_display)
|
||||
{
|
||||
RPWindowLayout_Load();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4840,6 +4894,14 @@ SVGA16::~SVGA16()
|
||||
//---------------------------------------------------------
|
||||
//SVGASetSplitterClock(False);
|
||||
|
||||
//
|
||||
// Backstop for the sticky placement: every finished drag has already
|
||||
// been written, but a window moved and then closed straight away would
|
||||
// otherwise be missed. Must run before the panes go - it reads their
|
||||
// live window rects, and the game window's.
|
||||
//
|
||||
RPWindowLayout_Save();
|
||||
|
||||
for (int view = 0; view < SplitViewCount; ++view)
|
||||
{
|
||||
delete splitView[view];
|
||||
|
||||
@@ -349,6 +349,18 @@ public:
|
||||
GetCockpit()
|
||||
{ return activeCockpit; }
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Take the six secondary displays off screen and leave the whole
|
||||
// canvas to the viewscreen. The Winners Circle wants the podium
|
||||
// uncluttered, and the panes are only bezels over the 3D - hiding
|
||||
// them uncovers what is already being drawn behind.
|
||||
//
|
||||
// There is no matching show: the mission is over by the time this
|
||||
// is called, and the next race builds a fresh cockpit.
|
||||
//------------------------------------------------------------------
|
||||
void
|
||||
HideSecondaryDisplays();
|
||||
|
||||
protected:
|
||||
Logical
|
||||
splitViews;
|
||||
|
||||
+437
-22
@@ -1797,6 +1797,19 @@ DPLRenderer::DPLRenderer(
|
||||
backgroundGreen = 0.0f;
|
||||
backgroundBlue = 0.0f;
|
||||
viewAngle = 30.0f;
|
||||
// the Winners Circle camera is off until the podium asks for it. No
|
||||
// restore path is needed: a fresh DPLRenderer is built per mission, so
|
||||
// the next race re-reads viewangle from RPDPL.INI.
|
||||
mPresentationCamera = False;
|
||||
mPresentationFog = False;
|
||||
mPresentationAspect = 0.0f;
|
||||
mPresentationFadeTime = 0.0f;
|
||||
mPresentationFogRed = 0.0f;
|
||||
mPresentationFogGreen = 0.0f;
|
||||
mPresentationFogBlue = 0.0f;
|
||||
mPresentationFogNear = 0.0f;
|
||||
mPresentationFogFar = 0.0f;
|
||||
D3DXMatrixIdentity(&mPresentationView);
|
||||
dplMainView = NULL;
|
||||
dplDeathZone = NULL;
|
||||
dplMainZone = NULL;
|
||||
@@ -2343,10 +2356,53 @@ void
|
||||
}
|
||||
break;
|
||||
case winnersCircleFogStyle:
|
||||
//
|
||||
// The presentation shot, restored from the DPL body below. The
|
||||
// stand sits far off the track in open ground, so the track's own
|
||||
// fog leaves it in the dark - this is the lighter blue-violet the
|
||||
// original used to lift it, with the fog pushed back to 100/1050.
|
||||
//
|
||||
// HACK!! This really shouldn't reset the clip planes, but since
|
||||
// it only happens at the end of the review, it should be safe for now.
|
||||
//
|
||||
fogRed = 0.32f;
|
||||
fogGreen = 0.30f;
|
||||
fogBlue = 0.65f;
|
||||
fogNear = 100.0f;
|
||||
fogFar = 1050.0f;
|
||||
|
||||
// the per-frame FOGSTART/FOGEND come from these
|
||||
currentFogNear = fogNear;
|
||||
currentFogFar = fogFar;
|
||||
|
||||
clipNear = 0.25f;
|
||||
clipFar = 1100.0f;
|
||||
|
||||
// tells the end-of-mission fade to stand down
|
||||
mPresentationFog = True;
|
||||
|
||||
//
|
||||
// Crop to the shape the stand was composed for. 4:3 unless
|
||||
// RP412PODIUMASPECT says otherwise; 0 turns it off and lets
|
||||
// the podium run full width.
|
||||
//
|
||||
mPresentationAspect = 4.0f / 3.0f;
|
||||
{
|
||||
const char *aspect = getenv("RP412PODIUMASPECT");
|
||||
if (aspect != NULL)
|
||||
{
|
||||
mPresentationAspect = (float) atof(aspect);
|
||||
}
|
||||
}
|
||||
|
||||
// unconditional: fogUpdating is off while a vehicle drives its own
|
||||
// headlight fog, and the podium overrides all of that
|
||||
if (mDevice != NULL)
|
||||
{
|
||||
mDevice->SetRenderState(D3DRS_FOGCOLOR,
|
||||
D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen),
|
||||
(int)(255 * fogBlue)));
|
||||
}
|
||||
// dpl_SetViewClipPlanes ( dplMainView, 0.25f, 1100.0f );
|
||||
// dpl_SetViewFog(
|
||||
// dplMainView,
|
||||
@@ -5608,6 +5664,24 @@ void
|
||||
intersect_mask = INTERSECT_ALL;
|
||||
}
|
||||
|
||||
//
|
||||
// RP412RENDERDIAG=1: name what actually gets built, so a missing
|
||||
// Winners Circle can be told from one that is simply out of shot.
|
||||
//
|
||||
{
|
||||
static const char *diag = getenv("RP412RENDERDIAG");
|
||||
if (diag != NULL && atoi(diag) != 0)
|
||||
{
|
||||
DEBUG_STREAM << "RenderDiag: building class"
|
||||
<< entity->GetClassID() << " res "
|
||||
<< entity->GetResourceID() << " at "
|
||||
<< entity->localOrigin.linearPosition.x << ","
|
||||
<< entity->localOrigin.linearPosition.y << ","
|
||||
<< entity->localOrigin.linearPosition.z
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
Logical first_object = True;
|
||||
|
||||
video_iterator.First();
|
||||
@@ -5997,12 +6071,110 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
if (mCamShipHUD)
|
||||
mCamShipHUD->Execute();
|
||||
|
||||
//
|
||||
// The eye renderable has just written D3DTS_VIEW from the viewpoint
|
||||
// entity. For the Winners Circle the shot comes from off the stand
|
||||
// instead, so stomp it here - before the read-back below that feeds
|
||||
// every draw call.
|
||||
//
|
||||
if (mPresentationCamera)
|
||||
{
|
||||
mDevice->SetTransform(D3DTS_VIEW, &mPresentationView);
|
||||
}
|
||||
|
||||
//
|
||||
// RP412RENDERDIAG=1: report what the frame is actually made of once the
|
||||
// mission is ending, which is the only way to tell an empty scene from a
|
||||
// misaimed camera.
|
||||
//
|
||||
{
|
||||
static const char *diag = getenv("RP412RENDERDIAG");
|
||||
static int reported = 0;
|
||||
if (diag != NULL && atoi(diag) != 0 &&
|
||||
currentAppState == Application::EndingMission && reported < 4)
|
||||
{
|
||||
++reported;
|
||||
int dynamic_count = 0;
|
||||
SChainIteratorOf<HierarchicalDrawComponent*> diag_iter(&mRenderables);
|
||||
while (diag_iter.ReadAndNext() != NULL)
|
||||
{
|
||||
++dynamic_count;
|
||||
}
|
||||
DEBUG_STREAM << "RenderDiag: ending frame " << reported
|
||||
<< " dead=" << (l4_application->IsDead() ? 1 : 0)
|
||||
<< " statics=" << (int) mConsolidatedStaticObjects.size()
|
||||
<< " renderables=" << dynamic_count
|
||||
<< " camera=" << (mPresentationCamera ? 1 : 0)
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
gNumBatches = 0;
|
||||
static Time lastFrameTime = mTargetRenderTime;
|
||||
Scalar dT = mTargetRenderTime - lastFrameTime;
|
||||
lastFrameTime = mTargetRenderTime;
|
||||
currentFrameTime = Now();
|
||||
|
||||
//
|
||||
// Fade the presentation up out of black, if one is running. Same lever
|
||||
// the end-of-mission fade pulls: scale the fog colour and both fog
|
||||
// distances, here from nothing up to what the podium asked for.
|
||||
//
|
||||
if (mPresentationFadeTime > 0.0f)
|
||||
{
|
||||
Scalar remaining = mPresentationFadeEnd - Now();
|
||||
Scalar fade = 1.0f - (remaining / mPresentationFadeTime);
|
||||
if (fade >= 1.0f)
|
||||
{
|
||||
fade = 1.0f;
|
||||
mPresentationFadeTime = 0.0f; // done
|
||||
}
|
||||
if (fade < 0.0f)
|
||||
{
|
||||
fade = 0.0f;
|
||||
}
|
||||
|
||||
fogRed = mPresentationFogRed * fade;
|
||||
fogGreen = mPresentationFogGreen * fade;
|
||||
fogBlue = mPresentationFogBlue * fade;
|
||||
currentFogNear = mPresentationFogNear * fade;
|
||||
currentFogFar = mPresentationFogFar * fade;
|
||||
|
||||
mDevice->SetRenderState(D3DRS_FOGCOLOR,
|
||||
D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen),
|
||||
(int)(255 * fogBlue)));
|
||||
}
|
||||
|
||||
//
|
||||
// Pillarbox: black the whole target first, then narrow the viewport so
|
||||
// everything after this - clear, scene, reticle - lands inside the crop
|
||||
// and the surround stays black. Restored after Present.
|
||||
//
|
||||
if (mPresentationAspect > 0.0f)
|
||||
{
|
||||
D3DVIEWPORT9 full;
|
||||
full.X = 0;
|
||||
full.Y = 0;
|
||||
full.Width = mPresentParams.BackBufferWidth;
|
||||
full.Height = mPresentParams.BackBufferHeight;
|
||||
full.MinZ = 0.0f;
|
||||
full.MaxZ = 1.0f;
|
||||
mDevice->SetViewport(&full);
|
||||
mDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
|
||||
|
||||
DWORD cropped = (DWORD)
|
||||
(mPresentParams.BackBufferHeight * mPresentationAspect);
|
||||
if (cropped > mPresentParams.BackBufferWidth)
|
||||
{
|
||||
cropped = mPresentParams.BackBufferWidth;
|
||||
}
|
||||
|
||||
D3DVIEWPORT9 crop = full;
|
||||
crop.Width = cropped;
|
||||
crop.X = (mPresentParams.BackBufferWidth - cropped) / 2;
|
||||
mDevice->SetViewport(&crop);
|
||||
}
|
||||
|
||||
DWORD currentFog;
|
||||
mDevice->GetRenderState(D3DRS_FOGCOLOR, ¤tFog);
|
||||
hr = mDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, currentFog, 1.0f, 0);
|
||||
@@ -6152,15 +6324,70 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
mDevice->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
|
||||
mDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
|
||||
if (mReticle && !l4_application->IsDead())
|
||||
//
|
||||
// No gunsight on the podium - the race is over and nothing is being
|
||||
// aimed at. It is drawn in the 2D pass, so it survives everything else
|
||||
// the presentation turns off.
|
||||
//
|
||||
if (mReticle && !l4_application->IsDead() && !InPresentation())
|
||||
mReticle->Render(0, &viewTransform);
|
||||
|
||||
if (mCamShipHUD)
|
||||
if (mCamShipHUD && !InPresentation())
|
||||
mCamShipHUD->Render(0, &viewTransform);
|
||||
|
||||
hr = mDevice->EndScene();
|
||||
|
||||
hr = mDevice->Present(NULL, NULL, gMainPresentWindow, NULL);
|
||||
|
||||
// hand the whole target back
|
||||
if (mPresentationAspect > 0.0f)
|
||||
{
|
||||
D3DVIEWPORT9 full;
|
||||
full.X = 0;
|
||||
full.Y = 0;
|
||||
full.Width = mPresentParams.BackBufferWidth;
|
||||
full.Height = mPresentParams.BackBufferHeight;
|
||||
full.MinZ = 0.0f;
|
||||
full.MaxZ = 1.0f;
|
||||
mDevice->SetViewport(&full);
|
||||
}
|
||||
|
||||
//
|
||||
// RP412RENDERDIAG=1: did the ending frame actually reach the window, and
|
||||
// what colour did it clear to? A black clear with a blue-violet fog set
|
||||
// means something is overwriting the fog behind us.
|
||||
//
|
||||
{
|
||||
static const char *diag = getenv("RP412RENDERDIAG");
|
||||
static int presented = 0;
|
||||
if (diag != NULL && atoi(diag) != 0 &&
|
||||
application->GetApplicationState() == Application::EndingMission &&
|
||||
presented < 4)
|
||||
{
|
||||
++presented;
|
||||
DWORD fog_now = 0;
|
||||
mDevice->GetRenderState(D3DRS_FOGCOLOR, &fog_now);
|
||||
HWND present_window = (HWND) gMainPresentWindow;
|
||||
RECT present_rect;
|
||||
present_rect.left = present_rect.top = 0;
|
||||
present_rect.right = present_rect.bottom = 0;
|
||||
if (present_window != NULL)
|
||||
{
|
||||
GetClientRect(present_window, &present_rect);
|
||||
}
|
||||
DEBUG_STREAM << "RenderDiag: present " << presented
|
||||
<< " hr=0x" << std::hex << (unsigned int) hr << std::dec
|
||||
<< " fogcolor=0x" << std::hex << (unsigned int) fog_now << std::dec
|
||||
<< " window=" << (void*) present_window
|
||||
<< " visible=" << (present_window != NULL &&
|
||||
IsWindowVisible(present_window) ? 1 : 0)
|
||||
<< " rect=" << present_rect.right << "x" << present_rect.bottom
|
||||
<< " backbuf=" << mPresentParams.BackBufferWidth << "x"
|
||||
<< mPresentParams.BackBufferHeight
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
if (hr == D3DERR_DEVICELOST)
|
||||
{
|
||||
int bbCount = mPresentParams.BackBufferCount;
|
||||
@@ -6736,27 +6963,167 @@ void
|
||||
}
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
//
|
||||
// Un-stubbed for the Winners Circle, which asks for 45 degrees to get all
|
||||
// eight racers in frame. The DPL body below it built a dpl_View; the D3D9
|
||||
// path builds the same two matrices DPLReadINIPage does and pushes them,
|
||||
// because mProjectionMatrix only reaches the device on the LaunchingMission
|
||||
// edge and on the sky-pass restore - rebuilding alone would not show until
|
||||
// the next frame.
|
||||
//
|
||||
// Pass degrees, as the INI does. RPDPL.INI ships viewangle=40, which is what
|
||||
// to hand back afterwards - not the 30 in the constructor, which the INI
|
||||
// overwrites at load.
|
||||
//
|
||||
void DPLRenderer::SetViewAngle(Degree new_angle)
|
||||
{
|
||||
//STUBBED: DPL RB 1/14/07
|
||||
//Check(this);
|
||||
////
|
||||
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//// Convert From Degree To Radian
|
||||
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
////
|
||||
//Radian view_angle;
|
||||
//view_angle = new_angle;
|
||||
//viewAngle = view_angle;
|
||||
//viewRatio = tan(viewAngle/2.0f);
|
||||
////
|
||||
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//// Calc Aspect Ratio and Set View Projection
|
||||
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
////
|
||||
//aspectRatio = (float) y_size / (float) x_size;
|
||||
//dpl_SetViewProjection ( dplMainView, -1.0f, -aspectRatio, 1.0f, aspectRatio, 1.0f/viewRatio);
|
||||
//dpl_FlushView(dplMainView);
|
||||
Check(this);
|
||||
|
||||
viewAngle = (float) new_angle.angle;
|
||||
|
||||
Radian view_angle;
|
||||
view_angle = new_angle;
|
||||
// tan(half angle): the culling helper GetViewRatio() reads this, and
|
||||
// nothing has written it since the DPL body was stubbed out
|
||||
viewRatio = (float) tan(view_angle / 2.0f);
|
||||
aspectRatio = (float) y_size / (float) x_size;
|
||||
|
||||
// pillarboxed shots render into a narrower viewport, so the projection
|
||||
// has to use that shape or the scene comes out squashed rather than
|
||||
// cropped
|
||||
float projection_aspect = (mPresentationAspect > 0.0f)
|
||||
? mPresentationAspect
|
||||
: ((float) x_size / (float) y_size);
|
||||
|
||||
D3DXMatrixIdentity(&mProjectionMatrix);
|
||||
D3DXMatrixPerspectiveFovLH(
|
||||
&mProjectionMatrix,
|
||||
viewAngle * (PI / 180.0f),
|
||||
projection_aspect,
|
||||
clipNear,
|
||||
clipFar);
|
||||
mProjectionMatrix(0, 0) *= -1; // handedness flip - the view is RH
|
||||
|
||||
mDecalProjectionMatrix = mProjectionMatrix;
|
||||
mDecalProjectionMatrix._33 -= mDecalEpsilon;
|
||||
|
||||
if (mDevice != NULL)
|
||||
{
|
||||
mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// The presentation camera. DPLEyeRenderable writes D3DTS_VIEW from the
|
||||
// viewpoint entity every frame; ExecuteImplementation reads it straight back
|
||||
// out of the device and hands it to every draw call. Overriding it between
|
||||
// those two points is enough to move the shot without touching the eye
|
||||
// renderable or building a CameraShip.
|
||||
//
|
||||
void
|
||||
DPLRenderer::SetPresentationCamera(const Point3D &eye, const Point3D &look_at)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
D3DXVECTOR3 from((float) eye.x, (float) eye.y, (float) eye.z);
|
||||
D3DXVECTOR3 at((float) look_at.x, (float) look_at.y, (float) look_at.z);
|
||||
|
||||
//
|
||||
// LookAt*LH*, to match the LH projection. The two differ by exactly the
|
||||
// sign of the view direction, and RH here points the camera the opposite
|
||||
// way: ask to look down at the stand and you get sky behind you.
|
||||
//
|
||||
// The engine's own eye renderable does use RH, and is right to - its
|
||||
// forward and up come out of the entity matrix already in that
|
||||
// convention. A camera aimed with plain world coordinates does not, so
|
||||
// it wants the handedness the projection was built with.
|
||||
//
|
||||
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
|
||||
|
||||
D3DXMatrixLookAtLH(&mPresentationView, &from, &at, &up);
|
||||
mPresentationCamera = True;
|
||||
}
|
||||
|
||||
void
|
||||
DPLRenderer::ClearPresentationCamera()
|
||||
{
|
||||
Check(this);
|
||||
mPresentationCamera = False;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Give the pilot a vehicle to look at. Their own is built insideEntity - a
|
||||
// cockpit and no hull - so on the podium they would be the one empty spot.
|
||||
// outsideEntity is what builds the exterior, and the renderables for it are
|
||||
// added to what is already there.
|
||||
//
|
||||
void
|
||||
DPLRenderer::ShowViewpointFromOutside()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
Entity *viewpoint = GetLinkedEntity();
|
||||
if (viewpoint == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "WinnersCircle: no viewpoint entity to turn around\n"
|
||||
<< std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Add the exterior alongside what is already there rather than tearing
|
||||
// the entity down and rebuilding it. The teardown path is safe for
|
||||
// scenery going out of range, but not for the viewpoint entity: it is
|
||||
// never uninteresting, the eye renderable goes with it, and doing it
|
||||
// mid-mission stops the scene rendering altogether.
|
||||
//
|
||||
// This is what NotifyOfNewInterestingEntity does, minus the teardown
|
||||
// and with the view type forced.
|
||||
//
|
||||
Check(application);
|
||||
ResourceFile *resource_file = application->GetResourceFile();
|
||||
Check(resource_file);
|
||||
ResourceDescription *video_resource = resource_file->SearchList(
|
||||
viewpoint->GetResourceID(),
|
||||
ResourceDescription::VideoModelResourceType);
|
||||
|
||||
if (video_resource == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "WinnersCircle: own vehicle has no exterior model\n"
|
||||
<< std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
video_resource->Lock();
|
||||
MakeEntityRenderables(viewpoint, video_resource, outsideEntity);
|
||||
video_resource->Unlock();
|
||||
|
||||
DEBUG_STREAM << "WinnersCircle: own vehicle given an exterior\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Come up out of the black. SetFogStyle(winnersCircleFogStyle) has already
|
||||
// put the target fog in place, so remember it and ramp toward it - the same
|
||||
// multiply the end-of-mission fade uses, run the other way.
|
||||
//
|
||||
void
|
||||
DPLRenderer::StartPresentationFadeIn(Scalar seconds)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
mPresentationFogRed = fogRed;
|
||||
mPresentationFogGreen = fogGreen;
|
||||
mPresentationFogBlue = fogBlue;
|
||||
mPresentationFogNear = currentFogNear;
|
||||
mPresentationFogFar = currentFogFar;
|
||||
|
||||
mPresentationFadeTime = (seconds > 0.0f) ? seconds : 0.01f;
|
||||
mPresentationFadeEnd = Now();
|
||||
mPresentationFadeEnd += mPresentationFadeTime;
|
||||
}
|
||||
//
|
||||
//#############################################################################
|
||||
@@ -6800,8 +7167,56 @@ void DPLRenderer::SortAndReloadNameBitmaps()
|
||||
LoadBitSliceTexture(name_bitmap, mNameTextures[index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LoadOrdinalBitmaps();
|
||||
|
||||
BindNamePlates();
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Put the callsigns on the Winners Circle plates.
|
||||
//
|
||||
// mNameTextures is indexed by finishing place (rank + 1), which is how the
|
||||
// signs are numbered, so the plate beside each spot names whoever is standing
|
||||
// on it. The plates are static geometry and have already been merged into the
|
||||
// consolidated mesh by now, so it is the consolidated draw ops that have to be
|
||||
// re-pointed - the objects they were built from are no longer drawn.
|
||||
//
|
||||
void DPLRenderer::BindNamePlates()
|
||||
{
|
||||
int name_count = (int) (sizeof(mNameTextures) / sizeof(mNameTextures[0]));
|
||||
int bound = 0;
|
||||
int found = 0;
|
||||
|
||||
std::list<d3d_OBJECT*>::const_iterator iter;
|
||||
for (iter = mConsolidatedStaticObjects.begin();
|
||||
iter != mConsolidatedStaticObjects.end(); ++iter)
|
||||
{
|
||||
d3d_OBJECT *object = *iter;
|
||||
if (object == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int op = 0; op < object->GetDrawOpCount(); ++op)
|
||||
{
|
||||
L4DRAWOP *draw_op = object->GetDrawOp(op);
|
||||
int plate = d3d_OBJECT::NamePlateFor(draw_op->texture.texture);
|
||||
if (plate == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
++found;
|
||||
if (plate < name_count && mNameTextures[plate] != NULL)
|
||||
{
|
||||
draw_op->texture.texture = mNameTextures[plate];
|
||||
++bound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_STREAM << "NamePlates: " << bound << " of " << found
|
||||
<< " plates given a callsign\n" << std::flush;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -310,10 +310,57 @@ public:
|
||||
|
||||
void SetViewAngle(Degree new_angle);
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Winners Circle presentation camera.
|
||||
//
|
||||
// The eye normally rides the viewpoint entity - your own vehicle -
|
||||
// and DPLEyeRenderable writes D3DTS_VIEW from it each frame. For the
|
||||
// podium the shot has to come from off the stand looking back, with
|
||||
// every racer including you in frame, so this overrides that view
|
||||
// for as long as it is set. Culling still runs from the viewpoint
|
||||
// entity, which is standing on the podium, so the stand and everyone
|
||||
// on it stay resident.
|
||||
//
|
||||
// Eye and target are world space. ClearPresentationCamera() hands
|
||||
// the view back to the entity.
|
||||
//------------------------------------------------------------------
|
||||
void SetPresentationCamera(const Point3D &eye, const Point3D &look_at);
|
||||
void ClearPresentationCamera();
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// True once the Winners Circle has taken the screen. The end-of-
|
||||
// mission fade-to-black checks this and stands down: it multiplies
|
||||
// the fog to black over FADE_OUT_TIME, which would otherwise black
|
||||
// out the podium no matter what is drawn.
|
||||
//------------------------------------------------------------------
|
||||
Logical InPresentation() const { return mPresentationFog; }
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Bring the presentation up out of black over the given seconds. The
|
||||
// race's own fade-to-black has already run by this point; this is
|
||||
// its mirror image, ramping the fog colour and both fog distances
|
||||
// back up from nothing to what the Winners Circle asked for.
|
||||
//------------------------------------------------------------------
|
||||
void StartPresentationFadeIn(Scalar seconds);
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Rebuild the viewpoint entity - the pilot's own vehicle - as an
|
||||
// exterior. It is normally built insideEntity, which is a cockpit
|
||||
// and no hull, so from any camera that is not in it you see nothing
|
||||
// where your own vehicle should be. Disconnected_Eye is the engine's
|
||||
// own switch for this: "so higher level renderers can fix the eye in
|
||||
// one spot and watch the viewpoint entity drive around."
|
||||
//------------------------------------------------------------------
|
||||
void ShowViewpointFromOutside();
|
||||
|
||||
unsigned int* MakeBitSliceStorage();
|
||||
|
||||
void SortAndReloadNameBitmaps();
|
||||
|
||||
// re-point the Winners Circle sign faces at the current callsign
|
||||
// textures; the plates live in the consolidated static mesh
|
||||
void BindNamePlates();
|
||||
|
||||
void LoadNameBitmaps();
|
||||
|
||||
void LoadOrdinalBitmaps();
|
||||
@@ -438,6 +485,22 @@ private:
|
||||
D3DXMATRIX mDecalProjectionMatrix;
|
||||
float mDecalEpsilon;
|
||||
|
||||
// Winners Circle: overrides D3DTS_VIEW while set (see
|
||||
// SetPresentationCamera)
|
||||
D3DXMATRIX mPresentationView;
|
||||
Logical mPresentationCamera;
|
||||
Logical mPresentationFog;
|
||||
// >0 pillarboxes the scene to this aspect. The Winners Circle was
|
||||
// built to be looked at from a 4:3 pod monitor and its platform runs
|
||||
// out at the sides of a 16:9 canvas, so the shot is cropped to the
|
||||
// shape it was composed for and the surround left black.
|
||||
float mPresentationAspect;
|
||||
// fade-in: end time, duration, and the fog it is ramping toward
|
||||
Time mPresentationFadeEnd;
|
||||
Scalar mPresentationFadeTime;
|
||||
float mPresentationFogRed, mPresentationFogGreen, mPresentationFogBlue;
|
||||
float mPresentationFogNear, mPresentationFogFar;
|
||||
|
||||
void FindBestAdapterIndices(bool isWindowed);
|
||||
|
||||
float mCloudRed, mCloudGreen, mCloudBlue;
|
||||
|
||||
@@ -2283,6 +2283,20 @@ void
|
||||
//
|
||||
case FadeOutState:
|
||||
{
|
||||
//
|
||||
// The Winners Circle has taken the screen, so leave it alone.
|
||||
// This fade multiplies the fog colour and both fog distances
|
||||
// toward zero every frame, which blacks out the whole scene -
|
||||
// correct when the race just ends, fatal to a podium shown
|
||||
// afterwards. Stand down and stop running.
|
||||
//
|
||||
if (myRenderer->InPresentation())
|
||||
{
|
||||
myState = WaitForStartState;
|
||||
myRenderer->RemoveDynamicRenderable(this);
|
||||
break;
|
||||
}
|
||||
|
||||
percent_time_left = (myStateTimer - current_time)/FADE_OUT_TIME;
|
||||
if(percent_time_left <= 0.0f)
|
||||
{
|
||||
|
||||
@@ -50,6 +50,17 @@ position. And a lobby holds **eight** players rather than four, a full grid as
|
||||
the pod hall ran it, with the room sizing its roster to whatever space the
|
||||
window gives it.
|
||||
|
||||
[v4.12.5](https://gitea.mysticmachines.com/VWE/RP412/releases/tag/v4.12.5)
|
||||
restores the **Winners Circle**. The pod hall stood the finishers on a
|
||||
numbered award platform when the race ended, and all of it was still in this
|
||||
repo — the stand, eight ranked spots in every map, and the code to put racers
|
||||
on them — wired only into the mission-review build and so never once run by a
|
||||
pod. The race now fades out and fades back in on the platform: finishers in
|
||||
finishing order, each pilot's callsign on the plate beside their spot, the
|
||||
cockpit glass cleared away, held for a few seconds before the results screen.
|
||||
Three pieces of it had been stubbed out in the D3D9 port and are working
|
||||
again.
|
||||
|
||||
## Playing
|
||||
|
||||
Grab the release zip (or run `pack-dist.ps1` on a build). Single player:
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
#include "..\munga\hostmgr.h"
|
||||
#include "..\munga\nttmgr.h"
|
||||
|
||||
//
|
||||
// How long the mission stays up after the buzzer, for the Winners Circle.
|
||||
// This timer is the only thing holding the simulation and the renderer
|
||||
// open once the race is over - when it runs out the player dispatches the
|
||||
// StopMission that retires the application. The stock 3 seconds is the
|
||||
// fade; the rest is the podium. Kept under the +30s LightsOut post so that
|
||||
// never fires while the stand is up.
|
||||
//
|
||||
const Scalar winnersCircleHoldTime = 11.0f;
|
||||
|
||||
//#############################################################################
|
||||
//######################## RPPlayer__StatusMessage ######################
|
||||
//#############################################################################
|
||||
@@ -188,6 +198,24 @@ void
|
||||
ForceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
// Hold the mission open for the Winners Circle.
|
||||
//
|
||||
// The base handler sets a 3 second fade, and when it runs out the
|
||||
// player dispatches the StopMission that retires the application and
|
||||
// takes the renderer with it. That is the only thing keeping the sim
|
||||
// and the renderer alive after the race, so the podium gets exactly as
|
||||
// long as this timer says. Sim and render both keep running throughout
|
||||
// EndingMission - neither has a case for it that bails out.
|
||||
//
|
||||
// Only on a clean finish: an abort should still leave promptly.
|
||||
//
|
||||
if (application->GetApplicationState() == Application::EndingMission)
|
||||
{
|
||||
fadeTimeRemaining = winnersCircleHoldTime;
|
||||
}
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
|
||||
+39
-1
@@ -26,6 +26,7 @@
|
||||
#include "rpl4lobby.h"
|
||||
#include "..\munga_l4\l4steamtransport.h"
|
||||
#include "..\munga_l4\l4splr.h"
|
||||
#include "..\munga_l4\l4mfdview.h" // RPWindowLayout_*
|
||||
#include "rpl4ver.h"
|
||||
#include "..\munga\resver.h"
|
||||
#include "..\munga\resource.h"
|
||||
@@ -123,6 +124,15 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_EXITSIZEMOVE:
|
||||
//
|
||||
// A drag or resize just finished. Here rather than in the
|
||||
// cockpit's subclass so it also covers the console screen, which
|
||||
// is where the window gets moved before there is any cockpit to
|
||||
// subclass. No-op unless RP412MFDLAYOUT is save.
|
||||
//
|
||||
RPWindowLayout_Save();
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
@@ -177,7 +187,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
DEBUG_STREAM << "Red Planet 4.12.4" << std::endl << std::flush;
|
||||
DEBUG_STREAM << "Red Planet 4.12.5" << std::endl << std::flush;
|
||||
DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush;
|
||||
|
||||
#ifdef RP412_STEAM
|
||||
@@ -254,6 +264,21 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
return FALSE;
|
||||
|
||||
ShowWindow(hWnd, nShowCmd);
|
||||
|
||||
//
|
||||
// Sticky placement, from the first frame the player sees. SVGA16 also
|
||||
// registers and reloads when it builds the cockpit, but that is not
|
||||
// until a mission starts - without this the console screen would come
|
||||
// up at the default rect with its title bar still on, and only jump
|
||||
// to the saved placement once a race began. -fit takes the whole
|
||||
// monitor and has no placement of the player's to restore.
|
||||
//
|
||||
if (!L4Application::GetFitDisplay() && !L4Application::GetFullscreen())
|
||||
{
|
||||
RPWindowLayout_Register(hWnd, "RPL4", True);
|
||||
RPWindowLayout_Load();
|
||||
}
|
||||
|
||||
#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
|
||||
@@ -475,6 +500,19 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Last word on the window's placement. Every finished drag has already
|
||||
// written it and SVGA16 writes again as the cockpit comes down, but a
|
||||
// session that never started a race has neither - and quitting from
|
||||
// the console screen is exactly how somebody would leave after moving
|
||||
// the window to where they want it. No-op unless the mode is save.
|
||||
//
|
||||
if (IsWindow(hWnd))
|
||||
{
|
||||
RPWindowLayout_Save();
|
||||
}
|
||||
RPWindowLayout_Forget(hWnd);
|
||||
|
||||
#if !_DEBUG
|
||||
// symmetric with the fullscreen-only hide at startup
|
||||
if (L4Application::GetFullscreen())
|
||||
|
||||
@@ -15,12 +15,56 @@
|
||||
#include "..\rp\vtv.h"
|
||||
#include "rpl4mppr.h"
|
||||
#include "..\rp\rpplayer.h"
|
||||
#include "..\munga_l4\l4video.h"
|
||||
#include "..\munga\dropzone.h"
|
||||
#include "..\munga\director.h"
|
||||
#include "..\munga\nttmgr.h"
|
||||
#include "..\munga_l4\l4vb16.h"
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// RPL4Application
|
||||
//#############################################################################
|
||||
//
|
||||
const Receiver::HandlerEntry
|
||||
RPL4Application::MessageHandlerEntries[]=
|
||||
{
|
||||
MESSAGE_ENTRY(RPL4Application, StopMission),
|
||||
MESSAGE_ENTRY(RPL4Application, WinnersCircle)
|
||||
};
|
||||
|
||||
Receiver::MessageHandlerSet& RPL4Application::GetMessageHandlers()
|
||||
{
|
||||
static Receiver::MessageHandlerSet messageHandlers(
|
||||
ELEMENTS(RPL4Application::MessageHandlerEntries),
|
||||
RPL4Application::MessageHandlerEntries,
|
||||
L4Application::GetMessageHandlers());
|
||||
return messageHandlers;
|
||||
}
|
||||
|
||||
Derivation* RPL4Application::GetClassDerivations()
|
||||
{
|
||||
static Derivation classDerivations(
|
||||
L4Application::GetClassDerivations(), "RPL4Application");
|
||||
return &classDerivations;
|
||||
}
|
||||
|
||||
RPL4Application::SharedData
|
||||
RPL4Application::DefaultData(
|
||||
RPL4Application::GetClassDerivations(),
|
||||
RPL4Application::GetMessageHandlers()
|
||||
);
|
||||
|
||||
//
|
||||
// How long to sit on black between the race fading out and the podium
|
||||
// fading in. The race fade is FADE_OUT_TIME (half a second), so this is
|
||||
// that plus a beat, to land on black rather than on the tail of it.
|
||||
//
|
||||
const Scalar winnersCircleFadeOutTime = 0.7f;
|
||||
|
||||
// and how quickly the stand comes up out of the black afterwards
|
||||
const Scalar winnersCircleFadeInTime = 0.45f;
|
||||
|
||||
RPL4Application::RPL4Application(
|
||||
HINSTANCE hInstance,
|
||||
HWND hWnd,
|
||||
@@ -43,6 +87,333 @@ RPL4Application::~RPL4Application()
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// ShowWinnersCircle
|
||||
//#############################################################################
|
||||
//
|
||||
// The pod hall's award platform. Every map carries a "wcircle" stand at
|
||||
// (1200,0,0) with eight ranked dropzones named win1..win8 on it - rank 1 at
|
||||
// the front on the low tier, ranks 4-8 across the back on the high one. All
|
||||
// this shipped; only the mission-review build ever drove it.
|
||||
//
|
||||
// Stand the finishers on their spots in finishing order, freeze them, and
|
||||
// look back at the stand from in front of it.
|
||||
//
|
||||
void
|
||||
RPL4Application::ShowWinnersCircle()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
// RP412PODIUM=0 skips the whole thing
|
||||
const char *podium_mode = getenv("RP412PODIUM");
|
||||
if (podium_mode != NULL && atoi(podium_mode) == 0)
|
||||
{
|
||||
DEBUG_STREAM << "WinnersCircle: disabled\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
EntityManager *entity_manager = GetEntityManager();
|
||||
if (entity_manager == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
EntityGroup *dropzones = entity_manager->FindGroup("DropZones");
|
||||
if (dropzones == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "WinnersCircle: no DropZones group\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
// Walk the finishing order. Player::CalcRanking has been ranking every
|
||||
// scoring player by score each frame, in football as much as in a race
|
||||
// (CalcFootballRanking exists but is never called), so rank order is
|
||||
// meaningful in both.
|
||||
//---------------------------------------------------------------------
|
||||
//
|
||||
char winners_spot[] = "win?";
|
||||
char *place = winners_spot + 3;
|
||||
int placed = 0;
|
||||
Point3D standFront(0.0f, 0.0f, 0.0f);
|
||||
Point3D standCentre(0.0f, 0.0f, 0.0f);
|
||||
|
||||
Player *p;
|
||||
for (int rank = 0; (p = CameraDirector::FindPlayerByRank(rank)) != NULL; ++rank)
|
||||
{
|
||||
if (rank > 7)
|
||||
{
|
||||
break; // only eight spots exist on the stand
|
||||
}
|
||||
*place = (char) ('1' + rank);
|
||||
|
||||
ChainIteratorOf<Node*> iterator(dropzones->groupMembers);
|
||||
DropZone *dropzone;
|
||||
while ((dropzone = (DropZone*) iterator.ReadAndNext()) != NULL)
|
||||
{
|
||||
if (!strcmp(dropzone->GetDropZoneName(), winners_spot))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dropzone == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Entity *vehicle = p->GetPlayerVehicle();
|
||||
if (vehicle == NULL || vehicle->GetClassID() != VTVClassID)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
VTV *vtv = (VTV*) vehicle;
|
||||
vtv->Reset(dropzone->localOrigin, VTV::MissionReviewReset);
|
||||
vtv->SetPerformance(&VTV::DoNothing);
|
||||
vtv->FlushEvents();
|
||||
|
||||
Point3D spot = dropzone->localOrigin.linearPosition;
|
||||
if (placed == 0)
|
||||
{
|
||||
// remember where the front of the stand is - the shot is framed
|
||||
// off it rather than off hardcoded map coordinates
|
||||
standFront = spot;
|
||||
}
|
||||
standCentre += spot;
|
||||
DEBUG_STREAM << "WinnersCircle: " << winners_spot << " at "
|
||||
<< spot.x << "," << spot.y << "," << spot.z << "\n" << std::flush;
|
||||
++placed;
|
||||
}
|
||||
|
||||
if (placed == 0)
|
||||
{
|
||||
DEBUG_STREAM << "WinnersCircle: nobody to place\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
// Switch the world back on.
|
||||
//
|
||||
// Dying collapses the view and sets the application "dead" until the
|
||||
// pilot reincarnates - while that flag is up the renderer skips every
|
||||
// static object, which is the whole map. A pilot killed near the buzzer
|
||||
// is still waiting for a respawn that will never come, so the flag is
|
||||
// still up and the podium would play out against a black screen.
|
||||
//
|
||||
// The mission-review build never hit this: it watches from a CameraShip,
|
||||
// which cannot die, so nothing ever set the flag there.
|
||||
//
|
||||
// Standing the finishers up IS the resurrection, so clear it.
|
||||
//---------------------------------------------------------------------
|
||||
//
|
||||
SetIsDead(false);
|
||||
|
||||
DPLRenderer *dpl_renderer = GetVideoRenderer();
|
||||
if (dpl_renderer == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
// Your own vehicle is built as a cockpit with no hull, so from any
|
||||
// camera outside it there is nothing where you should be - on the stand
|
||||
// you would be the one empty spot. Turn it inside out before the shot.
|
||||
//---------------------------------------------------------------------
|
||||
//
|
||||
dpl_renderer->ShowViewpointFromOutside();
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
// The name plates are drawn per rank slot, so they have to be re-sorted
|
||||
// now that the finishing order is final - otherwise the signs read in
|
||||
// whatever order the players were created.
|
||||
//---------------------------------------------------------------------
|
||||
//
|
||||
dpl_renderer->SortAndReloadNameBitmaps();
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
// Widen to 45 degrees and pull back in front of the stand so the whole
|
||||
// line-up frames up. The stand runs from z~3 (rank 1, low) to z~39
|
||||
// (ranks 4-8, high) and x~1180..1219; at 45 degrees that needs roughly
|
||||
// 30 units of standoff. Eye is above the top tier looking slightly
|
||||
// down at the middle of the group.
|
||||
//---------------------------------------------------------------------
|
||||
//
|
||||
standCentre.x /= (Scalar) placed;
|
||||
standCentre.y /= (Scalar) placed;
|
||||
standCentre.z /= (Scalar) placed;
|
||||
|
||||
//
|
||||
// Stand off along the line from the middle of the group out through the
|
||||
// front spot, so the shot faces the stand however the map has it turned,
|
||||
// and lift the eye above the top tier to look down on the line-up.
|
||||
//
|
||||
Vector3D facing;
|
||||
facing.x = standFront.x - standCentre.x;
|
||||
facing.y = 0.0f;
|
||||
facing.z = standFront.z - standCentre.z;
|
||||
Scalar reach = (Scalar) sqrt(facing.x * facing.x + facing.z * facing.z);
|
||||
if (reach < 0.01f)
|
||||
{
|
||||
facing.x = 0.0f; facing.z = -1.0f; reach = 1.0f;
|
||||
}
|
||||
//
|
||||
// Framing is tunable while the shot is being dialled in:
|
||||
// RP412PODIUMSTANDOFF distance out in front of the stand
|
||||
// RP412PODIUMHEIGHT eye height above the group
|
||||
// RP412PODIUMAIM height of the aim point above the group
|
||||
//
|
||||
//
|
||||
// Framed off the stand itself: down low and tilted up across the tiers,
|
||||
// which is how you photograph a podium. It is a balance in both
|
||||
// directions - drop the camera further or tilt harder and the sky takes
|
||||
// the top half while the winner's spot slides off the bottom; tilt down
|
||||
// instead and it becomes a floor plan. Pillarboxing to 4:3 is what lets
|
||||
// it sit this close without the platform trailing off at the sides.
|
||||
//
|
||||
Scalar standoff = 36.0f;
|
||||
Scalar height = 12.0f;
|
||||
Scalar aim_lift = 2.0f;
|
||||
const char *tune = getenv("RP412PODIUMSTANDOFF");
|
||||
if (tune != NULL && atof(tune) != 0.0) standoff = (Scalar) atof(tune);
|
||||
tune = getenv("RP412PODIUMHEIGHT");
|
||||
if (tune != NULL && atof(tune) != 0.0) height = (Scalar) atof(tune);
|
||||
tune = getenv("RP412PODIUMAIM");
|
||||
if (tune != NULL) aim_lift = (Scalar) atof(tune);
|
||||
|
||||
Point3D eye;
|
||||
eye.x = standCentre.x + (facing.x / reach) * standoff;
|
||||
eye.y = standCentre.y + height;
|
||||
eye.z = standCentre.z + (facing.z / reach) * standoff;
|
||||
standCentre.y += aim_lift;
|
||||
|
||||
//
|
||||
// RP412PODIUMCAM=0 leaves the view in the cockpit, which is also the
|
||||
// way to tell a bad camera from a scene that is not drawing at all.
|
||||
//
|
||||
//
|
||||
// Clear the cockpit glass away. The MFDs and the radar sit over the
|
||||
// viewscreen like the pod's bezels and have nothing to say once the
|
||||
// race is over; the podium gets the whole canvas.
|
||||
//
|
||||
SVGA16 *cockpit = SVGA16::GetCockpit();
|
||||
if (cockpit != NULL)
|
||||
{
|
||||
cockpit->HideSecondaryDisplays();
|
||||
}
|
||||
|
||||
//
|
||||
// Fog first: it pulls the clip plane in to 1100, and SetViewAngle is what
|
||||
// rebuilds the projection that reads it.
|
||||
//
|
||||
dpl_renderer->SetFogStyle(DPLRenderer::winnersCircleFogStyle);
|
||||
|
||||
const char *camera_mode = getenv("RP412PODIUMCAM");
|
||||
if (camera_mode == NULL || atoi(camera_mode) != 0)
|
||||
{
|
||||
dpl_renderer->SetViewAngle(Degree(45.0f));
|
||||
dpl_renderer->SetPresentationCamera(eye, standCentre);
|
||||
}
|
||||
else
|
||||
{
|
||||
// still rebuild the projection so the new clip plane takes effect
|
||||
dpl_renderer->SetViewAngle(Degree(40.0f));
|
||||
}
|
||||
if (camera_mode != NULL && atoi(camera_mode) == 0)
|
||||
{
|
||||
DEBUG_STREAM << "WinnersCircle: presentation camera disabled\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
// Everything is in place behind the black - bring it up.
|
||||
// RP412PODIUMFADEIN sets the ramp in seconds.
|
||||
//
|
||||
Scalar fade_in = winnersCircleFadeInTime;
|
||||
const char *fade_tune = getenv("RP412PODIUMFADEIN");
|
||||
if (fade_tune != NULL && atof(fade_tune) > 0.0)
|
||||
{
|
||||
fade_in = (Scalar) atof(fade_tune);
|
||||
}
|
||||
dpl_renderer->StartPresentationFadeIn(fade_in);
|
||||
|
||||
DEBUG_STREAM << "WinnersCircle: " << placed << " placed; centre "
|
||||
<< standCentre.x << "," << standCentre.y << "," << standCentre.z
|
||||
<< " eye " << eye.x << "," << eye.y << "," << eye.z
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// StopMissionMessageHandler
|
||||
//#############################################################################
|
||||
//
|
||||
// The mission is over. Show the podium, then let the base handler run - it
|
||||
// puts the player into MissionEndingState, whose fade timer is what actually
|
||||
// keeps the sim and the renderer alive until teardown.
|
||||
//
|
||||
void
|
||||
RPL4Application::StopMissionMessageHandler(StopMissionMessage *message)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
//
|
||||
// StopMission arrives twice: once from the console at the buzzer, and
|
||||
// again from the player when the ending fade runs out - that second one
|
||||
// is what actually retires the application. Only the first is the end of
|
||||
// the race.
|
||||
//
|
||||
if (GetApplicationState() != Application::EndingMission)
|
||||
{
|
||||
//
|
||||
//-------------------------------------------------------------
|
||||
// Don't cut straight to the podium. The race gets its own
|
||||
// fade-to-black first - that fade is already running by the time
|
||||
// this returns - and the Winners Circle comes up out of the
|
||||
// black afterwards. Standing everyone up now would just fade out
|
||||
// the podium instead of the race.
|
||||
//
|
||||
// FADE_OUT_TIME is half a second; a beat more than that lands on
|
||||
// black rather than on the tail of the fade.
|
||||
//-------------------------------------------------------------
|
||||
//
|
||||
Receiver::Message podium_message(
|
||||
WinnersCircleMessageID, sizeof(Receiver::Message));
|
||||
|
||||
Time event_time;
|
||||
event_time = Now();
|
||||
event_time += winnersCircleFadeOutTime;
|
||||
|
||||
Post(LowEventPriority, this, &podium_message, event_time);
|
||||
|
||||
DEBUG_STREAM << "WinnersCircle: race over, fading out\n" << std::flush;
|
||||
}
|
||||
|
||||
L4Application::StopMissionMessageHandler(message);
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// WinnersCircleMessageHandler
|
||||
//#############################################################################
|
||||
//
|
||||
// The race has faded to black. Set the stand up behind the black and fade
|
||||
// back in to it.
|
||||
//
|
||||
void
|
||||
RPL4Application::WinnersCircleMessageHandler(Receiver::Message *)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
DEBUG_STREAM << "WinnersCircle: standing the finishers up\n" << std::flush;
|
||||
ShowWinnersCircle();
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// MakeRegistry
|
||||
|
||||
@@ -26,4 +26,38 @@ private:
|
||||
Mission* MakeMission(NotationFile *notation_file, ResourceFile *resources);
|
||||
|
||||
Entity* MakeViewpointEntity(Entity__MakeMessage *);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// The Winners Circle.
|
||||
//
|
||||
// The podium is engine content that only the mission-review build ever
|
||||
// ran: geometry, the eight ranked dropzones and the presentation code all
|
||||
// ship, but the sequence lived on RPL4PlaybackApplication behind a spool
|
||||
// file. This is the same sequence on the path the pods actually race.
|
||||
//
|
||||
public:
|
||||
enum
|
||||
{
|
||||
// posted at the buzzer, fires once the race has faded out
|
||||
WinnersCircleMessageID = L4Application::NextMessageID,
|
||||
NextMessageID
|
||||
};
|
||||
|
||||
static const HandlerEntry
|
||||
MessageHandlerEntries[];
|
||||
static MessageHandlerSet& GetMessageHandlers();
|
||||
static Derivation *GetClassDerivations();
|
||||
static SharedData DefaultData;
|
||||
|
||||
void
|
||||
StopMissionMessageHandler(StopMissionMessage *message);
|
||||
|
||||
// the podium proper, once the screen is already black
|
||||
void
|
||||
WinnersCircleMessageHandler(Receiver::Message *message);
|
||||
|
||||
private:
|
||||
// stand the finishers on their ranked spots and frame the shot
|
||||
void
|
||||
ShowWinnersCircle();
|
||||
};
|
||||
|
||||
@@ -498,6 +498,19 @@ namespace
|
||||
<< mission_seconds << "s\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
// RP412MISSIONSECONDS overrides the menu's game length. The shortest
|
||||
// the menu offers is 3:00, which is a long wait when what you are
|
||||
// testing is what happens at the buzzer.
|
||||
//
|
||||
const char *seconds_override = getenv("RP412MISSIONSECONDS");
|
||||
if (seconds_override != NULL && atoi(seconds_override) > 0)
|
||||
{
|
||||
mission_seconds = atoi(seconds_override);
|
||||
DEBUG_STREAM << "LocalConsole: length overridden to "
|
||||
<< mission_seconds << "s by RP412MISSIONSECONDS\n" << std::flush;
|
||||
}
|
||||
|
||||
gMissionSeconds = mission_seconds;
|
||||
InterlockedExchange(&gLengthMs, (LONG) mission_seconds * 1000);
|
||||
gPhase = PhaseWaiting;
|
||||
|
||||
+61
-5
@@ -177,6 +177,7 @@ namespace
|
||||
GroupLaunch,
|
||||
GroupSteamHost, // buttons, not selections (Steam builds only)
|
||||
GroupSteamJoin,
|
||||
GroupExit,
|
||||
GroupCount
|
||||
};
|
||||
|
||||
@@ -413,8 +414,9 @@ namespace
|
||||
launch->rect.right = col3 + col_w;
|
||||
launch->rect.bottom = client_h - row_h;
|
||||
|
||||
// Steam lobby buttons (only when the Steam wire is live)
|
||||
if (RPL4Lobby_Available())
|
||||
// Steam lobby buttons, offered whenever environ.ini asked for Steam.
|
||||
// Painting greys them out and says so if the wire never came up.
|
||||
if (RPL4Lobby_Configured())
|
||||
{
|
||||
FEItem *host = &fe->items[fe->itemCount++];
|
||||
host->group = GroupSteamHost;
|
||||
@@ -433,6 +435,23 @@ namespace
|
||||
join->rect.bottom = client_h - (7 * row_h) / 2;
|
||||
}
|
||||
|
||||
//
|
||||
// Exit, bottom left. Diagonally opposite LAUNCH on purpose: it is
|
||||
// the one button on this screen you cannot undo, so it does not go
|
||||
// next to the one people are aiming for. Half width for the same
|
||||
// reason - it is a way out, not a peer of LAUNCH.
|
||||
//
|
||||
// The window frame used to be the way out, and mfd_layout.cfg's
|
||||
// ,noframe takes it away, so the screen has to offer its own.
|
||||
//
|
||||
FEItem *quit = &fe->items[fe->itemCount++];
|
||||
quit->group = GroupExit;
|
||||
quit->index = 0;
|
||||
quit->rect.left = col1;
|
||||
quit->rect.top = client_h - 2 * row_h;
|
||||
quit->rect.right = col1 + col_w / 2;
|
||||
quit->rect.bottom = client_h - row_h;
|
||||
|
||||
// pilot name edit sits at the top of column 3
|
||||
if (fe->nameEdit != NULL)
|
||||
{
|
||||
@@ -484,6 +503,7 @@ namespace
|
||||
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";
|
||||
case GroupExit: return "EXIT GAME";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -544,12 +564,38 @@ namespace
|
||||
RECT row = item->rect;
|
||||
if (item->group >= GroupLaunch)
|
||||
{
|
||||
HBRUSH launch_brush = CreateSolidBrush(kGreenBright);
|
||||
//
|
||||
// The lobby buttons are offered whenever environ.ini asked
|
||||
// for Steam, but they only work once the client is actually
|
||||
// there. Dim them and say why rather than leaving them out -
|
||||
// two buttons quietly missing looks like a broken build.
|
||||
//
|
||||
Logical steam_button =
|
||||
(item->group == GroupSteamHost || item->group == GroupSteamJoin);
|
||||
Logical dead = steam_button && !RPL4Lobby_Available();
|
||||
|
||||
COLORREF ink = dead ? kGreenDim : kGreenBright;
|
||||
HBRUSH launch_brush = CreateSolidBrush(ink);
|
||||
FrameRect(mem, &row, launch_brush);
|
||||
DeleteObject(launch_brush);
|
||||
SetTextColor(mem, kGreenBright);
|
||||
SetTextColor(mem, ink);
|
||||
DrawTextA(mem, ItemName(item->group, item->index), -1, &row,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
|
||||
//
|
||||
// The reason, on its own line above the pair. It does not
|
||||
// fit inside a button - they are about sixteen characters
|
||||
// wide and this is more than twice that.
|
||||
//
|
||||
if (dead && item->group == GroupSteamHost)
|
||||
{
|
||||
RECT notice = row;
|
||||
notice.bottom = row.top;
|
||||
notice.top = row.top - (row.bottom - row.top);
|
||||
SetTextColor(mem, kGreenDim);
|
||||
DrawTextA(mem, "STEAM NOT RUNNING", -1, ¬ice,
|
||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -613,7 +659,17 @@ namespace
|
||||
}
|
||||
else if (item->group == GroupSteamHost || item->group == GroupSteamJoin)
|
||||
{
|
||||
fe->steamAction = (item->group == GroupSteamHost) ? 1 : 2;
|
||||
// dead until the Steam client is there; the button says so
|
||||
if (RPL4Lobby_Available())
|
||||
{
|
||||
fe->steamAction = (item->group == GroupSteamHost) ? 1 : 2;
|
||||
PostMessageA(fe->menuWindow, WM_NULL, 0, 0);
|
||||
}
|
||||
}
|
||||
else if (item->group == GroupExit)
|
||||
{
|
||||
// the same door closing the window goes through
|
||||
fe->closed = True;
|
||||
PostMessageA(fe->menuWindow, WM_NULL, 0, 0);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
//########################################################################
|
||||
|
||||
Logical RPL4Lobby_Available() { return False; }
|
||||
Logical RPL4Lobby_Configured() { return False; }
|
||||
Logical RPL4Lobby_InRoom() { return False; }
|
||||
int RPL4Lobby_Host(HINSTANCE, HWND) { return LobbyRoomLeft; }
|
||||
int RPL4Lobby_Join(HINSTANCE, HWND) { return LobbyRoomLeft; }
|
||||
@@ -919,6 +920,18 @@ Logical
|
||||
return SteamNetTransport_GetFakeAddressString()[0] != '\0';
|
||||
}
|
||||
|
||||
//
|
||||
// The environ.ini switch, read the same way RPL4.CPP reads it to decide
|
||||
// whether to install the transport at all. Says nothing about whether the
|
||||
// Steam client was actually there.
|
||||
//
|
||||
Logical
|
||||
RPL4Lobby_Configured()
|
||||
{
|
||||
const char *steam_switch = getenv("RP412STEAM");
|
||||
return (steam_switch != NULL && atoi(steam_switch) != 0) ? True : False;
|
||||
}
|
||||
|
||||
Logical
|
||||
RPL4Lobby_InRoom()
|
||||
{
|
||||
|
||||
@@ -33,6 +33,15 @@ enum RPL4LobbyOutcome
|
||||
Logical
|
||||
RPL4Lobby_Available();
|
||||
|
||||
// True when this build has Steam and environ.ini asked for it, whether
|
||||
// or not it actually came up. The menu offers the lobby buttons on this
|
||||
// and greys them out on Available() - a player who turned Steam on and
|
||||
// then launched without the client running should be told so, not left
|
||||
// looking at a menu that quietly has two fewer buttons than the last
|
||||
// time they saw it.
|
||||
Logical
|
||||
RPL4Lobby_Configured();
|
||||
|
||||
// True while we sit in a lobby (races return to the room).
|
||||
Logical
|
||||
RPL4Lobby_InRoom();
|
||||
|
||||
+52
-2
@@ -166,6 +166,26 @@ L4PLASMA=SCREEN
|
||||
# 480x640 - decoded exactly as the pod's VDB split them, no downscale).
|
||||
L4MFDSPLIT=1
|
||||
|
||||
# The game window - and in the exploded view (L4MFDSPLIT=2) each display
|
||||
# window - is placed fresh every launch, so moving one somewhere useful
|
||||
# never survived the menu-race-menu loop. This remembers where you put
|
||||
# them, in mfd_layout.cfg beside this file:
|
||||
# off / 0 / unset computed placement only, no file (default)
|
||||
# load put the windows back where they were saved
|
||||
# save the same, and re-save on every finished drag
|
||||
# The game window gets its size back too, so you can size the cockpit to
|
||||
# suit your monitor once and keep it. The display windows get position
|
||||
# only: their size follows their content and their button banks, so an
|
||||
# old one is never restored over them. Arrange everything once with
|
||||
# save, then leave it on load.
|
||||
#
|
||||
# Each line in mfd_layout.cfg reads <title>=<x>,<y>,<w>,<h>, and you can
|
||||
# append ,noframe to take that window's title bar and border off - a
|
||||
# cockpit that fills the monitor edge to edge without -fit taking the
|
||||
# whole screen. Put the window where you want it first: a bare window
|
||||
# has nothing to drag by. Delete the flag to get the frame back.
|
||||
#RP412MFDLAYOUT=off
|
||||
|
||||
# Size of the six secondary displays in the glass cockpit, as a
|
||||
# percentage of their pod size. The pod bolted them down at one size;
|
||||
# on a big panel there is room to trade viewscreen for instrument, so
|
||||
@@ -216,6 +236,36 @@ L4RADARSCALE=100
|
||||
# both of them, and it grows from the middle in both directions).
|
||||
L4RADARPOS=CENTER
|
||||
|
||||
# The Winners Circle: at the end of a race the finishers are stood on
|
||||
# the award platform in finishing order, with each pilot's callsign on
|
||||
# the plate beside their spot, and held there for a few seconds before
|
||||
# the results screen. 1 = show it, 0 = straight to the results.
|
||||
RP412PODIUM=1
|
||||
|
||||
# The shot is framed for you, but these move the camera if you want it
|
||||
# somewhere else. Distances are in game units, measured from the middle
|
||||
# of the group of finishers.
|
||||
# STANDOFF how far out in front of the stand the camera sits
|
||||
# HEIGHT how far above the group
|
||||
# AIM height of the point it looks at, relative to the group -
|
||||
# negative tilts down, positive tilts up
|
||||
# ASPECT the stand was composed for a 4:3 pod monitor, so the shot
|
||||
# is cropped to that shape with black either side. 0 runs it
|
||||
# full width instead.
|
||||
# FADEIN seconds to come up out of the black after the race fades
|
||||
# CAM 0 watches from your own cockpit rather than off the stand
|
||||
#RP412PODIUMSTANDOFF=36
|
||||
#RP412PODIUMHEIGHT=12
|
||||
#RP412PODIUMAIM=2
|
||||
#RP412PODIUMASPECT=1.333
|
||||
#RP412PODIUMFADEIN=0.45
|
||||
#RP412PODIUMCAM=1
|
||||
|
||||
# Override the game length the menu picked, in seconds. The shortest the
|
||||
# menu offers is 3:00, which is a long wait when what you are testing is
|
||||
# what happens at the buzzer. Unset = use the menu's choice.
|
||||
#RP412MISSIONSECONDS=20
|
||||
|
||||
# Simulation/render frame rate, integer frames/second. The desktop
|
||||
# default is 60; the arcade pods shipped at 25.
|
||||
TARGETFPS=60
|
||||
@@ -327,7 +377,7 @@ start rpl4opt.exe -fit
|
||||
"@
|
||||
|
||||
Set-Content -Path "$dist\README.txt" -Encoding ascii -Value @"
|
||||
Red Planet 4.12.4
|
||||
Red Planet 4.12.5
|
||||
=================
|
||||
|
||||
Run start-fullscreen.bat for borderless over the whole monitor, or
|
||||
@@ -392,7 +442,7 @@ $size = (Get-ChildItem $dist -Recurse | Measure-Object Length -Sum).Sum
|
||||
Write-Host ("dist ready: {0:N1} MB" -f ($size / 1MB))
|
||||
|
||||
if ($Zip) {
|
||||
$zipPath = Join-Path $root 'RedPlanet-4.12.4.zip'
|
||||
$zipPath = Join-Path $root 'RedPlanet-4.12.5.zip'
|
||||
Write-Host "zipping to $zipPath..."
|
||||
|
||||
# Everything lives under a single RP412\ folder inside the zip, so
|
||||
|
||||
Reference in New Issue
Block a user