vPOD: pod power vs game split; install bar finishes; command column
- Pod Power (new group) is the whole machine: Power On boots the launcher / site-management side and auto-starts the game (a real pod's boot-time autoRestart launch); Power Off darkens everything. Start Game / Stop Game control just the emulated game exe, so "machine up, game not running" is now representable — Manage Site keeps seeing a healthy pod while the console's game connection is down. Watchdog stays game-level and no-ops if the pod is powered off during its window; console Shutdown/Restart stays a pod-level power cycle. - The install progress bar snaps to 100% when the transfer completes (the wire still reports 99 — the console retries on anything else). - Installed-apps list shows the full command line (exe + arguments). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,9 +28,15 @@ end-to-end with no cockpit and **no console changes**. See
|
||||
`WaitingForEgg`. This is the real pod's per-game cycle (`autoRestart`); the
|
||||
*Restart game after mission ends (watchdog)* checkbox (on by default) toggles
|
||||
it — unchecked, the pod just returns to `WaitingForEgg` without exiting.
|
||||
- **Power On / Power Off** — Power Off closes the TCP listener so the console
|
||||
cannot connect, mimicking a pod with no game client running; Power On reopens
|
||||
it. **Reset** returns a live pod to `WaitingForEgg`.
|
||||
- **Pod Power (Power On / Power Off)** — the whole virtual machine: Power Off
|
||||
darkens the game listener AND the launcher / site-management side; Power On
|
||||
boots the launcher (or provisioning beacons) and auto-starts the game, like a
|
||||
real pod booting with an `autoRestart` product installed.
|
||||
- **Start Game / Stop Game** — just the emulated game "exe", separate from pod
|
||||
power: Stop Game closes the Munga listener (the console's game connection
|
||||
drops) while the pod and its launcher service stay up — the state of a pod
|
||||
whose game client crashed or was killed. **Reset** returns a running game to
|
||||
`WaitingForEgg`.
|
||||
- **Reassembles and shows the egg** the console streams (the `EggFileMessage`
|
||||
chunks), one field per line, with a summary line (adventure / map / scenario /
|
||||
pilot count). The last egg is **kept** across missions/restarts (so it can be
|
||||
|
||||
+115
-40
@@ -35,6 +35,8 @@ internal sealed class VPodForm : Form
|
||||
private RadioButton mBattleTechRadio;
|
||||
private Button mPowerButton;
|
||||
private Button mPowerOffButton;
|
||||
private Button mStartGameButton;
|
||||
private Button mStopGameButton;
|
||||
private Button mResetButton;
|
||||
private CheckBox mRestartCheckbox;
|
||||
private Timer mRestartTimer;
|
||||
@@ -142,7 +144,22 @@ internal sealed class VPodForm : Form
|
||||
Text = "—"
|
||||
};
|
||||
|
||||
// game toggle
|
||||
// pod power: the whole virtual machine (launcher/site management included),
|
||||
// distinct from starting/stopping the emulated game client below.
|
||||
GroupBox powerGroup = new GroupBox
|
||||
{
|
||||
Text = "Pod Power",
|
||||
Location = new Point(400, 20),
|
||||
Size = new Size(150, 130)
|
||||
};
|
||||
mPowerButton = new Button { Text = "Power On", Location = new Point(12, 28), Size = new Size(126, 30) };
|
||||
mPowerOffButton = new Button { Text = "Power Off", Location = new Point(12, 66), Size = new Size(126, 30) };
|
||||
mPowerButton.Click += PowerOnClicked;
|
||||
mPowerOffButton.Click += PowerOffClicked;
|
||||
powerGroup.Controls.Add(mPowerButton);
|
||||
powerGroup.Controls.Add(mPowerOffButton);
|
||||
|
||||
// game toggle + game-process controls (the "exe", not the machine)
|
||||
GroupBox gameGroup = new GroupBox
|
||||
{
|
||||
Text = "Mimicking Game",
|
||||
@@ -165,16 +182,16 @@ internal sealed class VPodForm : Form
|
||||
};
|
||||
mRedPlanetRadio.CheckedChanged += GameToggleChanged;
|
||||
mBattleTechRadio.CheckedChanged += GameToggleChanged;
|
||||
mPowerButton = new Button { Text = "Power On", Location = new Point(12, 90), Size = new Size(94, 28) };
|
||||
mPowerOffButton = new Button { Text = "Power Off", Location = new Point(112, 90), Size = new Size(94, 28) };
|
||||
mStartGameButton = new Button { Text = "Start Game", Location = new Point(12, 90), Size = new Size(94, 28) };
|
||||
mStopGameButton = new Button { Text = "Stop Game", Location = new Point(112, 90), Size = new Size(94, 28) };
|
||||
mResetButton = new Button { Text = "Reset", Location = new Point(212, 90), Size = new Size(94, 28) };
|
||||
mPowerButton.Click += PowerOnClicked;
|
||||
mPowerOffButton.Click += PowerOffClicked;
|
||||
mStartGameButton.Click += StartGameClicked;
|
||||
mStopGameButton.Click += StopGameClicked;
|
||||
mResetButton.Click += (s, e) => mSimulator.Reset();
|
||||
gameGroup.Controls.Add(mRedPlanetRadio);
|
||||
gameGroup.Controls.Add(mBattleTechRadio);
|
||||
gameGroup.Controls.Add(mPowerButton);
|
||||
gameGroup.Controls.Add(mPowerOffButton);
|
||||
gameGroup.Controls.Add(mStartGameButton);
|
||||
gameGroup.Controls.Add(mStopGameButton);
|
||||
gameGroup.Controls.Add(mResetButton);
|
||||
|
||||
mRestartCheckbox = new CheckBox
|
||||
@@ -192,6 +209,7 @@ internal sealed class VPodForm : Form
|
||||
statusGroup.Controls.Add(stateCaption);
|
||||
statusGroup.Controls.Add(mStateLabel);
|
||||
statusGroup.Controls.Add(mRestartCheckbox);
|
||||
statusGroup.Controls.Add(powerGroup);
|
||||
statusGroup.Controls.Add(gameGroup);
|
||||
|
||||
// ---- egg viewer + log ----
|
||||
@@ -351,7 +369,7 @@ internal sealed class VPodForm : Form
|
||||
mAppsView.Columns.Add("Installed App", 150);
|
||||
mAppsView.Columns.Add("PID", 48);
|
||||
mAppsView.Columns.Add("Auto", 42);
|
||||
mAppsView.Columns.Add("Exe", 200);
|
||||
mAppsView.Columns.Add("Command", 320);
|
||||
|
||||
siteGroup.Controls.Add(mAppsView);
|
||||
siteGroup.Controls.Add(siteTop);
|
||||
@@ -371,16 +389,7 @@ internal sealed class VPodForm : Form
|
||||
UpdateConnectionLabel(null);
|
||||
UpdateStateLabel(mSimulator.State);
|
||||
RefreshAppsList();
|
||||
if (StartServer())
|
||||
{
|
||||
mSimulator.PowerOn();
|
||||
SetPoweredState(on: true);
|
||||
StartManagement();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPoweredState(on: false);
|
||||
}
|
||||
PowerOnClicked(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void OnFormClosing(object sender, FormClosingEventArgs e)
|
||||
@@ -389,34 +398,68 @@ internal sealed class VPodForm : Form
|
||||
StopManagement();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pod power = the whole virtual machine. Powering on boots the
|
||||
/// launcher/site-management side and auto-starts the game client (a real
|
||||
/// pod's boot-time autoRestart launch); the game can then be stopped and
|
||||
/// started on its own while the pod stays up.
|
||||
/// </summary>
|
||||
private void PowerOnClicked(object sender, EventArgs e)
|
||||
{
|
||||
mRestartTimer.Stop(); // cancel any pending watchdog restart
|
||||
mRebootTimer.Stop();
|
||||
if (!mServer.IsListening && !StartServer())
|
||||
{
|
||||
return; // couldn't bind the port; stay powered off
|
||||
}
|
||||
mSimulator.PowerOn();
|
||||
SetPoweredState(on: true);
|
||||
StartManagement();
|
||||
StartGameClicked(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Powers the pod off by closing the TCP listener, so the console can no
|
||||
/// longer connect — the same condition as a pod with no game client running.
|
||||
/// The launcher/provisioning side goes dark too (the whole machine is "off",
|
||||
/// unlike an end-mission game exit where the launcher service survives).
|
||||
/// Powers the whole pod off: the game listener AND the launcher/provisioning
|
||||
/// side go dark, like unplugging the machine. (To mimic just the game exe
|
||||
/// exiting while the pod stays up, use Stop Game.)
|
||||
/// </summary>
|
||||
private void PowerOffClicked(object sender, EventArgs e)
|
||||
{
|
||||
mRestartTimer.Stop();
|
||||
mRebootTimer.Stop();
|
||||
mServer.Stop();
|
||||
StopGame();
|
||||
StopManagement();
|
||||
SetPoweredState(on: false);
|
||||
}
|
||||
|
||||
/// <summary>Starts the emulated game client (Munga listener) on a powered-on pod.</summary>
|
||||
private void StartGameClicked(object sender, EventArgs e)
|
||||
{
|
||||
if (!mPoweredOn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
mRestartTimer.Stop(); // cancel any pending watchdog restart
|
||||
if (!mServer.IsListening && !StartServer())
|
||||
{
|
||||
SetGameState(running: false);
|
||||
return; // couldn't bind the port; pod stays up, game stays stopped
|
||||
}
|
||||
mSimulator.PowerOn();
|
||||
SetGameState(running: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops just the emulated game "exe": the Munga listener closes (the
|
||||
/// console's game connection drops) but the pod — and its launcher /
|
||||
/// site-management side — stays up, like killing the game process on a
|
||||
/// real pod.
|
||||
/// </summary>
|
||||
private void StopGameClicked(object sender, EventArgs e)
|
||||
{
|
||||
StopGame();
|
||||
}
|
||||
|
||||
private void StopGame()
|
||||
{
|
||||
mRestartTimer.Stop();
|
||||
mServer.Stop();
|
||||
SetGameState(running: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The game exited gracefully on the console's end-mission command. Simulate
|
||||
/// the process exit by closing the listener (the console's connection drops),
|
||||
@@ -433,8 +476,8 @@ internal sealed class VPodForm : Form
|
||||
UpdateConnectionLabel(null);
|
||||
mStateLabel.Text = "Restarting...";
|
||||
mStateLabel.ForeColor = Color.DarkOrange;
|
||||
mPowerButton.Enabled = true;
|
||||
mPowerOffButton.Enabled = false;
|
||||
mStartGameButton.Enabled = true;
|
||||
mStopGameButton.Enabled = false;
|
||||
mResetButton.Enabled = false;
|
||||
mRestartTimer.Stop();
|
||||
mRestartTimer.Start();
|
||||
@@ -444,15 +487,19 @@ internal sealed class VPodForm : Form
|
||||
private void OnRestartTimerTick(object sender, EventArgs e)
|
||||
{
|
||||
mRestartTimer.Stop();
|
||||
if (!mPoweredOn)
|
||||
{
|
||||
return; // pod was powered off while the watchdog was pending
|
||||
}
|
||||
if (StartServer())
|
||||
{
|
||||
OnLog("Watchdog restarted the game.");
|
||||
mSimulator.PowerOn();
|
||||
SetPoweredState(on: true);
|
||||
SetGameState(running: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPoweredState(on: false);
|
||||
SetGameState(running: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,16 +524,19 @@ internal sealed class VPodForm : Form
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reflects the on/off state in the buttons and, when off, the status labels.</summary>
|
||||
/// <summary>Reflects the pod's (machine-level) on/off state in the buttons and,
|
||||
/// when off, the status labels. Game-level state is <see cref="SetGameState" />.</summary>
|
||||
private void SetPoweredState(bool on)
|
||||
{
|
||||
mPoweredOn = on;
|
||||
mPowerButton.Enabled = !on;
|
||||
mPowerOffButton.Enabled = on;
|
||||
mResetButton.Enabled = on;
|
||||
mReprovisionButton.Enabled = on && !mOptions.NoManage;
|
||||
if (!on)
|
||||
{
|
||||
mStartGameButton.Enabled = false;
|
||||
mStopGameButton.Enabled = false;
|
||||
mResetButton.Enabled = false;
|
||||
UpdateListeningLabel(false);
|
||||
UpdateConnectionLabel(null);
|
||||
mStateLabel.Text = "Powered Off";
|
||||
@@ -499,6 +549,25 @@ internal sealed class VPodForm : Form
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reflects whether the emulated game client is running; the pod
|
||||
/// itself (launcher/site management) may still be up with the game stopped.</summary>
|
||||
private void SetGameState(bool running)
|
||||
{
|
||||
mStartGameButton.Enabled = mPoweredOn && !running;
|
||||
mStopGameButton.Enabled = mPoweredOn && running;
|
||||
mResetButton.Enabled = mPoweredOn && running;
|
||||
if (!running)
|
||||
{
|
||||
UpdateListeningLabel(false);
|
||||
UpdateConnectionLabel(null);
|
||||
if (mPoweredOn)
|
||||
{
|
||||
mStateLabel.Text = "Game Not Running";
|
||||
mStateLabel.ForeColor = Color.Gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- virtual launcher / site management lifecycle ----
|
||||
|
||||
/// <summary>Brings the launcher side up for a powered-on pod: the RPC server
|
||||
@@ -739,7 +808,7 @@ internal sealed class VPodForm : Form
|
||||
app.LaunchPair.DisplayName,
|
||||
pids.ToString(),
|
||||
app.AutoRestart ? "yes" : "",
|
||||
app.ExeFile
|
||||
string.IsNullOrEmpty(app.Arguments) ? app.ExeFile : app.ExeFile + " " + app.Arguments
|
||||
});
|
||||
mAppsView.Items.Add(item);
|
||||
}
|
||||
@@ -751,7 +820,14 @@ internal sealed class VPodForm : Form
|
||||
if (IsDisposed) return;
|
||||
BeginInvoke((Action)(() =>
|
||||
{
|
||||
mInstallProgressBar.Value = Math.Max(0, Math.Min(100, progress.PercentComplete));
|
||||
int percent = Math.Max(0, Math.Min(100, progress.PercentComplete));
|
||||
if (progress.IsCompleted && percent >= 99)
|
||||
{
|
||||
// The wire deliberately tops out at 99 (the console retries on
|
||||
// 100) — but the local bar can still show a finished install.
|
||||
percent = 100;
|
||||
}
|
||||
mInstallProgressBar.Value = percent;
|
||||
mInstallStatusLabel.Text = progress.IsCompleted
|
||||
? "Install: " + progress.Status
|
||||
: $"Installing: {progress.Status} ({progress.PercentComplete}%)";
|
||||
@@ -765,9 +841,8 @@ internal sealed class VPodForm : Form
|
||||
if (IsDisposed) return;
|
||||
BeginInvoke((Action)(() =>
|
||||
{
|
||||
mRestartTimer.Stop();
|
||||
mRebootTimer.Stop();
|
||||
mServer.Stop();
|
||||
StopGame();
|
||||
StopManagement();
|
||||
SetPoweredState(on: false);
|
||||
if (restart)
|
||||
|
||||
Reference in New Issue
Block a user