Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd61316a40 | ||
|
|
dce8818273 | ||
|
|
a1f9c0e3c0 | ||
|
|
bfd5fa163e | ||
|
|
a52fec80a9 | ||
|
|
40b00ddde1 | ||
|
|
16ce4dfbea | ||
|
|
2a23ec0923 |
@@ -40,6 +40,11 @@ ipch/
|
|||||||
*.log
|
*.log
|
||||||
rpl4.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
|
# Build-output static libs that land in lib/ (the two committed dependency
|
||||||
# libs, OpenAL32.lib and libsndfile-1.lib, stay tracked).
|
# libs, OpenAL32.lib and libsndfile-1.lib, stay tracked).
|
||||||
/lib/Munga_L4.lib
|
/lib/Munga_L4.lib
|
||||||
|
|||||||
@@ -10,6 +10,74 @@ namespace
|
|||||||
|
|
||||||
const int buttonGap = 4;
|
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
|
// Button banks run UNDER the glass: a button reaches this far in
|
||||||
// from the display edge, but only the indicator strip clears that
|
// from the display edge, but only the indicator strip clears that
|
||||||
@@ -108,6 +176,15 @@ namespace
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case WM_CLOSE:
|
||||||
// Part of the cockpit; just hide it.
|
// Part of the cockpit; just hide it.
|
||||||
ShowWindow(hwnd, SW_HIDE);
|
ShowWindow(hwnd, SW_HIDE);
|
||||||
@@ -148,6 +225,8 @@ MFDSplitView::MFDSplitView(
|
|||||||
sourceHeight = source_height;
|
sourceHeight = source_height;
|
||||||
buttonCount = 0;
|
buttonCount = 0;
|
||||||
pressedIndex = -1;
|
pressedIndex = -1;
|
||||||
|
paneTitle = title;
|
||||||
|
ownWindow = (parent == NULL) ? True : False;
|
||||||
|
|
||||||
pixels = new unsigned long[source_width * source_height];
|
pixels = new unsigned long[source_width * source_height];
|
||||||
memset(pixels, 0, source_width * source_height * sizeof(unsigned long));
|
memset(pixels, 0, source_width * source_height * sizeof(unsigned long));
|
||||||
@@ -230,6 +309,14 @@ MFDSplitView::MFDSplitView(
|
|||||||
{
|
{
|
||||||
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
|
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
|
||||||
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -241,6 +328,9 @@ MFDSplitView::~MFDSplitView()
|
|||||||
{
|
{
|
||||||
Check_Pointer(this);
|
Check_Pointer(this);
|
||||||
|
|
||||||
|
// out of the layout registry before the window goes
|
||||||
|
RPWindowLayout_Forget(window);
|
||||||
|
|
||||||
if (window != NULL)
|
if (window != NULL)
|
||||||
{
|
{
|
||||||
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0);
|
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0);
|
||||||
@@ -259,6 +349,272 @@ Logical
|
|||||||
return pixels != NULL;
|
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
|
// Compute the display rectangle and the button rectangles, growing the
|
||||||
// client area to make room for the strips.
|
// client area to make room for the strips.
|
||||||
|
|||||||
@@ -77,6 +77,14 @@ public:
|
|||||||
void
|
void
|
||||||
Hide();
|
Hide();
|
||||||
|
|
||||||
|
// caption and HWND, for the sticky-placement file
|
||||||
|
const char *
|
||||||
|
Title() const
|
||||||
|
{ return paneTitle; }
|
||||||
|
void *
|
||||||
|
Window()
|
||||||
|
{ return window; }
|
||||||
|
|
||||||
~MFDSplitView();
|
~MFDSplitView();
|
||||||
|
|
||||||
Logical
|
Logical
|
||||||
@@ -147,4 +155,79 @@ protected:
|
|||||||
int
|
int
|
||||||
buttonAnchorA,
|
buttonAnchorA,
|
||||||
buttonAnchorB;
|
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 plasma glass registers
|
||||||
|
// the same way - its size is L4PLASMASCALE's business.
|
||||||
|
// 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();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "l4plasmascreen.h"
|
#include "l4plasmascreen.h"
|
||||||
#include "l4app.h"
|
#include "l4app.h"
|
||||||
|
#include "l4mfdview.h" // RPWindowLayout_*
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -45,6 +46,15 @@ namespace
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WM_EXITSIZEMOVE:
|
||||||
|
//
|
||||||
|
// Dragged to a new spot. Write the whole arrangement now rather
|
||||||
|
// than trusting teardown, so a hard kill still leaves what was
|
||||||
|
// on screen. No-op unless RP412MFDLAYOUT is save.
|
||||||
|
//
|
||||||
|
RPWindowLayout_Save();
|
||||||
|
return 0;
|
||||||
|
|
||||||
case WM_CLOSE:
|
case WM_CLOSE:
|
||||||
// The glass is part of the cockpit; just hide it.
|
// The glass is part of the cockpit; just hide it.
|
||||||
ShowWindow(hwnd, SW_HIDE);
|
ShowWindow(hwnd, SW_HIDE);
|
||||||
@@ -176,6 +186,7 @@ PlasmaScreen::~PlasmaScreen()
|
|||||||
}
|
}
|
||||||
if (window != NULL)
|
if (window != NULL)
|
||||||
{
|
{
|
||||||
|
RPWindowLayout_Forget(window);
|
||||||
RemovePropA((HWND) window, "PlasmaBitmapInfo");
|
RemovePropA((HWND) window, "PlasmaBitmapInfo");
|
||||||
RemovePropA((HWND) window, "PlasmaPixels");
|
RemovePropA((HWND) window, "PlasmaPixels");
|
||||||
DestroyWindow((HWND) window);
|
DestroyWindow((HWND) window);
|
||||||
@@ -275,6 +286,22 @@ void
|
|||||||
SetPropA((HWND) window, "PlasmaPixels", pixelBuffer->Data.MapPointer);
|
SetPropA((HWND) window, "PlasmaPixels", pixelBuffer->Data.MapPointer);
|
||||||
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
|
||||||
activeInstance = this;
|
activeInstance = this;
|
||||||
|
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Join the saved layout. Position only, like the display
|
||||||
|
// panes: the glass is 128x32 at L4PLASMASCALE, so its size
|
||||||
|
// is a setting rather than something to drag.
|
||||||
|
//
|
||||||
|
// Loading here rather than leaving it to SVGA16 keeps this
|
||||||
|
// independent of which is built first - the glass comes from
|
||||||
|
// the gauge renderer, the panes from the video mode, and
|
||||||
|
// whichever runs second re-applies a placement the first
|
||||||
|
// already has.
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
//
|
||||||
|
RPWindowLayout_Register(window, "Plasma Display", False);
|
||||||
|
RPWindowLayout_Load();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3839,6 +3839,11 @@ static LRESULT CALLBACK
|
|||||||
cockpit->LayoutCockpit(LOWORD(lParam), HIWORD(lParam));
|
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);
|
return CallWindowProcA(gCockpitBaseProc, hwnd, message, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4457,6 +4462,15 @@ SVGA16::SVGA16(
|
|||||||
splitView[SplitMFDLowerRight]->SetPosition(
|
splitView[SplitMFDLowerRight]->SetPosition(
|
||||||
work.left + right_x, work.top + bottom_y);
|
work.left + right_x, work.top + bottom_y);
|
||||||
splitView[SplitMap]->SetPosition(work.left + map_x, work.top + map_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)
|
else if (splitViews)
|
||||||
{
|
{
|
||||||
@@ -4668,6 +4682,23 @@ SVGA16::SVGA16(
|
|||||||
// catch maximise / restore / drag-resize and re-fit
|
// catch maximise / restore / drag-resize and re-fit
|
||||||
gCockpitBaseProc = (WNDPROC) SetWindowLongPtrA(
|
gCockpitBaseProc = (WNDPROC) SetWindowLongPtrA(
|
||||||
cockpit, GWLP_WNDPROC, (LONG_PTR) CockpitShellProc);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -4863,6 +4894,14 @@ SVGA16::~SVGA16()
|
|||||||
//---------------------------------------------------------
|
//---------------------------------------------------------
|
||||||
//SVGASetSplitterClock(False);
|
//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)
|
for (int view = 0; view < SplitViewCount; ++view)
|
||||||
{
|
{
|
||||||
delete splitView[view];
|
delete splitView[view];
|
||||||
|
|||||||
@@ -61,6 +61,20 @@ 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
|
Three pieces of it had been stubbed out in the D3D9 port and are working
|
||||||
again.
|
again.
|
||||||
|
|
||||||
|
[v4.12.6](https://gitea.mysticmachines.com/VWE/RP412/releases/tag/v4.12.6)
|
||||||
|
is about the windows. Where you put them now survives the
|
||||||
|
menu-race-menu loop: `RP412MFDLAYOUT` remembers the game window, the
|
||||||
|
exploded view's display panes and the plasma glass in `mfd_layout.cfg`
|
||||||
|
beside `bindings.txt`. Append `,noframe` to a line to take that
|
||||||
|
window's title bar and border off — a cockpit filling the monitor edge
|
||||||
|
to edge at a rect you chose, rather than `-fit` taking the whole
|
||||||
|
screen — and the setup screen carries its own **EXIT GAME** button,
|
||||||
|
since a window with no title bar needs a way out. The Steam buttons
|
||||||
|
now dim and say *STEAM NOT RUNNING* instead of disappearing. And the
|
||||||
|
Winners Circle camera is framed off the award stand itself rather than
|
||||||
|
off whoever is standing on it, so the shot is the same one for every
|
||||||
|
player at every head count.
|
||||||
|
|
||||||
## Playing
|
## Playing
|
||||||
|
|
||||||
Grab the release zip (or run `pack-dist.ps1` on a build). Single player:
|
Grab the release zip (or run `pack-dist.ps1` on a build). Single player:
|
||||||
|
|||||||
+39
-1
@@ -26,6 +26,7 @@
|
|||||||
#include "rpl4lobby.h"
|
#include "rpl4lobby.h"
|
||||||
#include "..\munga_l4\l4steamtransport.h"
|
#include "..\munga_l4\l4steamtransport.h"
|
||||||
#include "..\munga_l4\l4splr.h"
|
#include "..\munga_l4\l4splr.h"
|
||||||
|
#include "..\munga_l4\l4mfdview.h" // RPWindowLayout_*
|
||||||
#include "rpl4ver.h"
|
#include "rpl4ver.h"
|
||||||
#include "..\munga\resver.h"
|
#include "..\munga\resver.h"
|
||||||
#include "..\munga\resource.h"
|
#include "..\munga\resource.h"
|
||||||
@@ -123,6 +124,15 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
break;
|
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);
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||||
}
|
}
|
||||||
@@ -177,7 +187,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_STREAM << "Red Planet 4.12.5" << std::endl << std::flush;
|
DEBUG_STREAM << "Red Planet 4.12.6" << std::endl << std::flush;
|
||||||
DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush;
|
DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush;
|
||||||
|
|
||||||
#ifdef RP412_STEAM
|
#ifdef RP412_STEAM
|
||||||
@@ -254,6 +264,21 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
ShowWindow(hWnd, nShowCmd);
|
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
|
#if !_DEBUG
|
||||||
// Arcade pods have no mouse - but desktop/windowed play needs the
|
// Arcade pods have no mouse - but desktop/windowed play needs the
|
||||||
// cursor for the on-screen cockpit buttons, so hide it only when
|
// 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
|
#if !_DEBUG
|
||||||
// symmetric with the fullscreen-only hide at startup
|
// symmetric with the fullscreen-only hide at startup
|
||||||
if (L4Application::GetFullscreen())
|
if (L4Application::GetFullscreen())
|
||||||
|
|||||||
+95
-39
@@ -133,20 +133,27 @@ void
|
|||||||
// meaningful in both.
|
// meaningful in both.
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// The stand's own geometry first, before anybody stands on it.
|
||||||
|
//
|
||||||
|
// The eight spots are fixed furniture on every map, so reading them up
|
||||||
|
// front gives one set of numbers to both jobs below: where to stand
|
||||||
|
// the finishers, and where to put the camera. Only the first is
|
||||||
|
// allowed to care who finished.
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
enum { winnersSpotCount = 8 };
|
||||||
|
DropZone *spot[winnersSpotCount];
|
||||||
|
int spots_found = 0;
|
||||||
|
|
||||||
char winners_spot[] = "win?";
|
char winners_spot[] = "win?";
|
||||||
char *place = winners_spot + 3;
|
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 i = 0; i < winnersSpotCount; ++i)
|
||||||
for (int rank = 0; (p = CameraDirector::FindPlayerByRank(rank)) != NULL; ++rank)
|
|
||||||
{
|
{
|
||||||
if (rank > 7)
|
*place = (char) ('1' + i);
|
||||||
{
|
spot[i] = NULL;
|
||||||
break; // only eight spots exist on the stand
|
|
||||||
}
|
|
||||||
*place = (char) ('1' + rank);
|
|
||||||
|
|
||||||
ChainIteratorOf<Node*> iterator(dropzones->groupMembers);
|
ChainIteratorOf<Node*> iterator(dropzones->groupMembers);
|
||||||
DropZone *dropzone;
|
DropZone *dropzone;
|
||||||
@@ -154,10 +161,36 @@ void
|
|||||||
{
|
{
|
||||||
if (!strcmp(dropzone->GetDropZoneName(), winners_spot))
|
if (!strcmp(dropzone->GetDropZoneName(), winners_spot))
|
||||||
{
|
{
|
||||||
|
spot[i] = dropzone;
|
||||||
|
++spots_found;
|
||||||
|
Point3D at = dropzone->localOrigin.linearPosition;
|
||||||
|
DEBUG_STREAM << "WinnersCircle: " << winners_spot << " at "
|
||||||
|
<< at.x << "," << at.y << "," << at.z << "\n" << std::flush;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dropzone == NULL)
|
}
|
||||||
|
|
||||||
|
if (spot[0] == NULL)
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "WinnersCircle: no win1 spot on this map\n" << std::flush;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// Stand the finishers up, in finishing order.
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
int placed = 0;
|
||||||
|
Player *p;
|
||||||
|
for (int rank = 0; (p = CameraDirector::FindPlayerByRank(rank)) != NULL; ++rank)
|
||||||
|
{
|
||||||
|
if (rank >= winnersSpotCount)
|
||||||
|
{
|
||||||
|
break; // only eight spots exist on the stand
|
||||||
|
}
|
||||||
|
if (spot[rank] == NULL)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -168,20 +201,9 @@ void
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
VTV *vtv = (VTV*) vehicle;
|
VTV *vtv = (VTV*) vehicle;
|
||||||
vtv->Reset(dropzone->localOrigin, VTV::MissionReviewReset);
|
vtv->Reset(spot[rank]->localOrigin, VTV::MissionReviewReset);
|
||||||
vtv->SetPerformance(&VTV::DoNothing);
|
vtv->SetPerformance(&VTV::DoNothing);
|
||||||
vtv->FlushEvents();
|
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;
|
++placed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,25 +261,52 @@ void
|
|||||||
// line-up frames up. The stand runs from z~3 (rank 1, low) to z~39
|
// 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
|
// (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
|
// 30 units of standoff. Eye is above the top tier looking slightly
|
||||||
// down at the middle of the group.
|
// down at the winner's spot.
|
||||||
|
//
|
||||||
|
// Framed off the stand, NOT off who is standing on it. This used to
|
||||||
|
// average the filled spots and anchor on the first one filled, which
|
||||||
|
// meant the camera moved with the head count: eight finishers put the
|
||||||
|
// anchor in the middle of the tiers and the eye about 58 units out,
|
||||||
|
// one finisher collapsed it onto the winner's spot and brought the eye
|
||||||
|
// in to 36. Same stand, different shot per race - and per machine, if
|
||||||
|
// a remote player's vehicle was not there to be placed. The furniture
|
||||||
|
// does not move, so the camera does not either.
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
standCentre.x /= (Scalar) placed;
|
Point3D standFront = spot[0]->localOrigin.linearPosition;
|
||||||
standCentre.y /= (Scalar) placed;
|
|
||||||
standCentre.z /= (Scalar) placed;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Stand off along the line from the middle of the group out through the
|
// The stand's own axis: back rows to the winner's spot, which points
|
||||||
// front spot, so the shot faces the stand however the map has it turned,
|
// out of the front however the map has the stand turned. Taken from
|
||||||
// and lift the eye above the top tier to look down on the line-up.
|
// the geometry rather than assumed, so a map that mounts its stand at
|
||||||
|
// another angle still gets photographed from the front.
|
||||||
//
|
//
|
||||||
|
Point3D standBack(0.0f, 0.0f, 0.0f);
|
||||||
|
int back_count = 0;
|
||||||
|
for (int i = 1; i < winnersSpotCount; ++i)
|
||||||
|
{
|
||||||
|
if (spot[i] != NULL)
|
||||||
|
{
|
||||||
|
standBack += spot[i]->localOrigin.linearPosition;
|
||||||
|
++back_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (back_count > 0)
|
||||||
|
{
|
||||||
|
standBack.x /= (Scalar) back_count;
|
||||||
|
standBack.y /= (Scalar) back_count;
|
||||||
|
standBack.z /= (Scalar) back_count;
|
||||||
|
}
|
||||||
|
|
||||||
Vector3D facing;
|
Vector3D facing;
|
||||||
facing.x = standFront.x - standCentre.x;
|
facing.x = standFront.x - standBack.x;
|
||||||
facing.y = 0.0f;
|
facing.y = 0.0f;
|
||||||
facing.z = standFront.z - standCentre.z;
|
facing.z = standFront.z - standBack.z;
|
||||||
Scalar reach = (Scalar) sqrt(facing.x * facing.x + facing.z * facing.z);
|
Scalar reach = (Scalar) sqrt(facing.x * facing.x + facing.z * facing.z);
|
||||||
if (reach < 0.01f)
|
if (reach < 0.01f)
|
||||||
{
|
{
|
||||||
|
// a one-spot stand, or all eight stacked: nothing to take a
|
||||||
|
// direction from, so fall back to the way the shipped stands face
|
||||||
facing.x = 0.0f; facing.z = -1.0f; reach = 1.0f;
|
facing.x = 0.0f; facing.z = -1.0f; reach = 1.0f;
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
@@ -285,10 +334,12 @@ void
|
|||||||
if (tune != NULL) aim_lift = (Scalar) atof(tune);
|
if (tune != NULL) aim_lift = (Scalar) atof(tune);
|
||||||
|
|
||||||
Point3D eye;
|
Point3D eye;
|
||||||
eye.x = standCentre.x + (facing.x / reach) * standoff;
|
eye.x = standFront.x + (facing.x / reach) * standoff;
|
||||||
eye.y = standCentre.y + height;
|
eye.y = standFront.y + height;
|
||||||
eye.z = standCentre.z + (facing.z / reach) * standoff;
|
eye.z = standFront.z + (facing.z / reach) * standoff;
|
||||||
standCentre.y += aim_lift;
|
|
||||||
|
Point3D aim = standFront;
|
||||||
|
aim.y += aim_lift;
|
||||||
|
|
||||||
//
|
//
|
||||||
// RP412PODIUMCAM=0 leaves the view in the cockpit, which is also the
|
// RP412PODIUMCAM=0 leaves the view in the cockpit, which is also the
|
||||||
@@ -315,7 +366,7 @@ void
|
|||||||
if (camera_mode == NULL || atoi(camera_mode) != 0)
|
if (camera_mode == NULL || atoi(camera_mode) != 0)
|
||||||
{
|
{
|
||||||
dpl_renderer->SetViewAngle(Degree(45.0f));
|
dpl_renderer->SetViewAngle(Degree(45.0f));
|
||||||
dpl_renderer->SetPresentationCamera(eye, standCentre);
|
dpl_renderer->SetPresentationCamera(eye, aim);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -340,8 +391,13 @@ void
|
|||||||
}
|
}
|
||||||
dpl_renderer->StartPresentationFadeIn(fade_in);
|
dpl_renderer->StartPresentationFadeIn(fade_in);
|
||||||
|
|
||||||
DEBUG_STREAM << "WinnersCircle: " << placed << " placed; centre "
|
//
|
||||||
<< standCentre.x << "," << standCentre.y << "," << standCentre.z
|
// placed is reported for diagnosis only - the camera numbers beside it
|
||||||
|
// must not move when it does.
|
||||||
|
//
|
||||||
|
DEBUG_STREAM << "WinnersCircle: " << placed << " placed on "
|
||||||
|
<< spots_found << " spots; aim "
|
||||||
|
<< aim.x << "," << aim.y << "," << aim.z
|
||||||
<< " eye " << eye.x << "," << eye.y << "," << eye.z
|
<< " eye " << eye.x << "," << eye.y << "," << eye.z
|
||||||
<< "\n" << std::flush;
|
<< "\n" << std::flush;
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-5
@@ -177,6 +177,7 @@ namespace
|
|||||||
GroupLaunch,
|
GroupLaunch,
|
||||||
GroupSteamHost, // buttons, not selections (Steam builds only)
|
GroupSteamHost, // buttons, not selections (Steam builds only)
|
||||||
GroupSteamJoin,
|
GroupSteamJoin,
|
||||||
|
GroupExit,
|
||||||
GroupCount
|
GroupCount
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -413,8 +414,9 @@ namespace
|
|||||||
launch->rect.right = col3 + col_w;
|
launch->rect.right = col3 + col_w;
|
||||||
launch->rect.bottom = client_h - row_h;
|
launch->rect.bottom = client_h - row_h;
|
||||||
|
|
||||||
// Steam lobby buttons (only when the Steam wire is live)
|
// Steam lobby buttons, offered whenever environ.ini asked for Steam.
|
||||||
if (RPL4Lobby_Available())
|
// Painting greys them out and says so if the wire never came up.
|
||||||
|
if (RPL4Lobby_Configured())
|
||||||
{
|
{
|
||||||
FEItem *host = &fe->items[fe->itemCount++];
|
FEItem *host = &fe->items[fe->itemCount++];
|
||||||
host->group = GroupSteamHost;
|
host->group = GroupSteamHost;
|
||||||
@@ -433,6 +435,23 @@ namespace
|
|||||||
join->rect.bottom = client_h - (7 * row_h) / 2;
|
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
|
// pilot name edit sits at the top of column 3
|
||||||
if (fe->nameEdit != NULL)
|
if (fe->nameEdit != NULL)
|
||||||
{
|
{
|
||||||
@@ -484,6 +503,7 @@ namespace
|
|||||||
case GroupLaunch: return "L A U N C H G A M E";
|
case GroupLaunch: return "L A U N C H G A M E";
|
||||||
case GroupSteamHost: return "HOST STEAM GAME";
|
case GroupSteamHost: return "HOST STEAM GAME";
|
||||||
case GroupSteamJoin: return "JOIN STEAM GAME";
|
case GroupSteamJoin: return "JOIN STEAM GAME";
|
||||||
|
case GroupExit: return "EXIT GAME";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -544,12 +564,38 @@ namespace
|
|||||||
RECT row = item->rect;
|
RECT row = item->rect;
|
||||||
if (item->group >= GroupLaunch)
|
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);
|
FrameRect(mem, &row, launch_brush);
|
||||||
DeleteObject(launch_brush);
|
DeleteObject(launch_brush);
|
||||||
SetTextColor(mem, kGreenBright);
|
SetTextColor(mem, ink);
|
||||||
DrawTextA(mem, ItemName(item->group, item->index), -1, &row,
|
DrawTextA(mem, ItemName(item->group, item->index), -1, &row,
|
||||||
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,7 +659,17 @@ namespace
|
|||||||
}
|
}
|
||||||
else if (item->group == GroupSteamHost || item->group == GroupSteamJoin)
|
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);
|
PostMessageA(fe->menuWindow, WM_NULL, 0, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
//########################################################################
|
//########################################################################
|
||||||
|
|
||||||
Logical RPL4Lobby_Available() { return False; }
|
Logical RPL4Lobby_Available() { return False; }
|
||||||
|
Logical RPL4Lobby_Configured() { return False; }
|
||||||
Logical RPL4Lobby_InRoom() { return False; }
|
Logical RPL4Lobby_InRoom() { return False; }
|
||||||
int RPL4Lobby_Host(HINSTANCE, HWND) { return LobbyRoomLeft; }
|
int RPL4Lobby_Host(HINSTANCE, HWND) { return LobbyRoomLeft; }
|
||||||
int RPL4Lobby_Join(HINSTANCE, HWND) { return LobbyRoomLeft; }
|
int RPL4Lobby_Join(HINSTANCE, HWND) { return LobbyRoomLeft; }
|
||||||
@@ -919,6 +920,18 @@ Logical
|
|||||||
return SteamNetTransport_GetFakeAddressString()[0] != '\0';
|
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
|
Logical
|
||||||
RPL4Lobby_InRoom()
|
RPL4Lobby_InRoom()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ enum RPL4LobbyOutcome
|
|||||||
Logical
|
Logical
|
||||||
RPL4Lobby_Available();
|
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).
|
// True while we sit in a lobby (races return to the room).
|
||||||
Logical
|
Logical
|
||||||
RPL4Lobby_InRoom();
|
RPL4Lobby_InRoom();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<title>Red Planet 4.12.3 — Controls</title>
|
<title>Red Planet 4.12.6 — Controls</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
@@ -754,7 +754,7 @@
|
|||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
|
|
||||||
<header class="masthead">
|
<header class="masthead">
|
||||||
<p class="eyebrow">Virtual World Entertainment · Tesla pod · RP 4.12.3</p>
|
<p class="eyebrow">Virtual World Entertainment · Tesla pod · RP 4.12.6</p>
|
||||||
<h1>Red Planet<br>Controls Map</h1>
|
<h1>Red Planet<br>Controls Map</h1>
|
||||||
<p class="lede">
|
<p class="lede">
|
||||||
Every input the pod answers to, on the gamepad and the keyboard. The
|
Every input the pod answers to, on the gamepad and the keyboard. The
|
||||||
@@ -1197,7 +1197,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<span>Red Planet 4.12.3</span>
|
<span>Red Planet 4.12.6</span>
|
||||||
<span>gitea.mysticmachines.com/VWE/RP412</span>
|
<span>gitea.mysticmachines.com/VWE/RP412</span>
|
||||||
<span>docs/CONTROLS.md · CONTROLS.txt ships with the game</span>
|
<span>docs/CONTROLS.md · CONTROLS.txt ships with the game</span>
|
||||||
<span>RGB keyboards mirror the pod lamps · RP412KEYLIGHT=0 to disable</span>
|
<span>RGB keyboards mirror the pod lamps · RP412KEYLIGHT=0 to disable</span>
|
||||||
|
|||||||
+24
-2
@@ -166,6 +166,28 @@ L4PLASMA=SCREEN
|
|||||||
# 480x640 - decoded exactly as the pod's VDB split them, no downscale).
|
# 480x640 - decoded exactly as the pod's VDB split them, no downscale).
|
||||||
L4MFDSPLIT=1
|
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.
|
||||||
|
#
|
||||||
|
# The plasma display window takes part too, under "Plasma Display".
|
||||||
|
#
|
||||||
|
# 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
|
# 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;
|
# 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
|
# on a big panel there is room to trade viewscreen for instrument, so
|
||||||
@@ -357,7 +379,7 @@ start rpl4opt.exe -fit
|
|||||||
"@
|
"@
|
||||||
|
|
||||||
Set-Content -Path "$dist\README.txt" -Encoding ascii -Value @"
|
Set-Content -Path "$dist\README.txt" -Encoding ascii -Value @"
|
||||||
Red Planet 4.12.5
|
Red Planet 4.12.6
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Run start-fullscreen.bat for borderless over the whole monitor, or
|
Run start-fullscreen.bat for borderless over the whole monitor, or
|
||||||
@@ -422,7 +444,7 @@ $size = (Get-ChildItem $dist -Recurse | Measure-Object Length -Sum).Sum
|
|||||||
Write-Host ("dist ready: {0:N1} MB" -f ($size / 1MB))
|
Write-Host ("dist ready: {0:N1} MB" -f ($size / 1MB))
|
||||||
|
|
||||||
if ($Zip) {
|
if ($Zip) {
|
||||||
$zipPath = Join-Path $root 'RedPlanet-4.12.5.zip'
|
$zipPath = Join-Path $root 'RedPlanet-4.12.6.zip'
|
||||||
Write-Host "zipping to $zipPath..."
|
Write-Host "zipping to $zipPath..."
|
||||||
|
|
||||||
# Everything lives under a single RP412\ folder inside the zip, so
|
# Everything lives under a single RP412\ folder inside the zip, so
|
||||||
|
|||||||
Reference in New Issue
Block a user