The DOSBox-X preservation pods run the original DOS builds of BattleTech 4.10 / Red Planet 4.10 inside an emulator whose bridged NIC is enumerated 100 above the host pod's last octet. A new checkbox on each game page (RP Death Race / Martian Football, BT Free For All / No Return) shifts every siteconfig address the page uses by +100 so the same pages control either version of the games: - Munga game connections (port 1501) target host+100; toggling the box drops and reconnects any requested pods at the new address. - Mission egg player and camera entries carry the shifted address. - The checkbox locks while a mission is loaded/running, like the other mission properties. Launcher / site-management traffic is untouched -- those services still run on the pod host itself. Pages share a pod's MungaGame, so the most recent page to toggle or enable wins if two pages fight over one pod. Verified end-to-end against vPOD: netstat shows the game connection move 127.0.0.1 -> 127.0.0.101 -> 127.0.0.1 as the box is toggled, and the egg received by vPOD carries pilot=127.0.0.101. All 103 diff tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
398 lines
6.5 KiB
C#
398 lines
6.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
using Munga.Net;
|
|
|
|
namespace TeslaConsole;
|
|
|
|
public class MungaGame
|
|
{
|
|
private readonly object mInternalLock = new object();
|
|
|
|
private readonly Pod mPod;
|
|
|
|
private Image mImage;
|
|
|
|
private string mStatusMessage;
|
|
|
|
private MungaSocket mSocket;
|
|
|
|
private DateTime mNextStateQuery = DateTime.MinValue;
|
|
|
|
private ApplicationState? mAppState;
|
|
|
|
private ApplicationID? mAppID;
|
|
|
|
private DateTime mEggDelayTimer = DateTime.MinValue;
|
|
|
|
private DateTime mEggSent = DateTime.MinValue;
|
|
|
|
private bool mEggAck;
|
|
|
|
private MungaConnectionState mState;
|
|
|
|
private bool mDosBoxAddressShift;
|
|
|
|
private readonly List<object> mRequestors = new List<object>();
|
|
|
|
private Form mOwner;
|
|
|
|
public Image Image => mImage;
|
|
|
|
public string StatusMessage => mStatusMessage;
|
|
|
|
public Pod Pod => mPod;
|
|
|
|
public ApplicationState? AppState => mAppState;
|
|
|
|
public ApplicationID? AppID => mAppID;
|
|
|
|
public DateTime EggDelayTimer
|
|
{
|
|
get
|
|
{
|
|
return mEggDelayTimer;
|
|
}
|
|
set
|
|
{
|
|
mEggDelayTimer = value;
|
|
}
|
|
}
|
|
|
|
public DateTime EggSent
|
|
{
|
|
get
|
|
{
|
|
return mEggSent;
|
|
}
|
|
set
|
|
{
|
|
mEggSent = value;
|
|
}
|
|
}
|
|
|
|
public bool EggAcknowledged => mEggAck;
|
|
|
|
public MungaConnectionState State
|
|
{
|
|
get
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mState == MungaConnectionState.Connected)
|
|
{
|
|
if (mSocket != null && mSocket.Connected)
|
|
{
|
|
return MungaConnectionState.Connected;
|
|
}
|
|
Disconnect();
|
|
}
|
|
return mState;
|
|
}
|
|
}
|
|
private set
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
mState = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool Requested
|
|
{
|
|
get
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
return mRequestors.Count > 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When set, the game connection targets the pod's DOSBox-X guest
|
|
/// (last octet +100) instead of the pod itself. Changing the value while
|
|
/// connected drops the connection; it reconnects at the new address as
|
|
/// long as the game is still requested.
|
|
/// </summary>
|
|
internal bool DosBoxAddressShift
|
|
{
|
|
get
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
return mDosBoxAddressShift;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mDosBoxAddressShift != value)
|
|
{
|
|
mDosBoxAddressShift = value;
|
|
if (mState != MungaConnectionState.Disconnected)
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsOwned
|
|
{
|
|
get
|
|
{
|
|
if (mOwner != null && mOwner.IsDisposed)
|
|
{
|
|
mOwner = null;
|
|
}
|
|
return mOwner != null;
|
|
}
|
|
}
|
|
|
|
public event EventHandler OnPodStatusUpdated;
|
|
|
|
public void QueryStateIfNeeded()
|
|
{
|
|
DateTime now = DateTime.Now;
|
|
if (mNextStateQuery < now)
|
|
{
|
|
Send(0, 1, new StateQueryMessage(1));
|
|
mNextStateQuery = now.AddSeconds(1.0);
|
|
}
|
|
}
|
|
|
|
public bool IsPodAppState(ApplicationID app, ApplicationState state)
|
|
{
|
|
return IsPodAppState(app, new ApplicationState[1] { state });
|
|
}
|
|
|
|
public bool IsPodAppState(ApplicationID app, IEnumerable<ApplicationState> states)
|
|
{
|
|
if (IsPodOperational(app))
|
|
{
|
|
foreach (ApplicationState state in states)
|
|
{
|
|
if (mAppState.Value == state)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool IsPodOperational(ApplicationID app)
|
|
{
|
|
if (State == MungaConnectionState.Connected && mAppID.HasValue && mAppID.Value == app)
|
|
{
|
|
return mAppState.HasValue;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void MakeRequested(object requestor)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (!mRequestors.Contains(requestor))
|
|
{
|
|
mRequestors.Add(requestor);
|
|
if (mRequestors.Count == 1)
|
|
{
|
|
Connect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ReleaseRequest(object requestor)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mRequestors.Contains(requestor))
|
|
{
|
|
mRequestors.Remove(requestor);
|
|
if (mRequestors.Count == 0)
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsOwner(Form owner)
|
|
{
|
|
if (mOwner != null && mOwner.IsDisposed)
|
|
{
|
|
mOwner = null;
|
|
}
|
|
return mOwner == owner;
|
|
}
|
|
|
|
public void TakeOwnership(Form owner)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mOwner == null)
|
|
{
|
|
mOwner = owner;
|
|
ForceStatusUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ReleaseOwnership(Form owner)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mOwner == owner)
|
|
{
|
|
mOwner = null;
|
|
ForceStatusUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public MungaGame(Pod pod)
|
|
{
|
|
mPod = pod;
|
|
}
|
|
|
|
public void SetStatus(Image img, string statusMessage)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mImage != img || mStatusMessage != statusMessage)
|
|
{
|
|
mImage = img;
|
|
mStatusMessage = statusMessage;
|
|
ForceStatusUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ForceStatusUpdate()
|
|
{
|
|
if (this.OnPodStatusUpdated != null)
|
|
{
|
|
this.OnPodStatusUpdated(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
private void Connect()
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (State != 0)
|
|
{
|
|
return;
|
|
}
|
|
State = MungaConnectionState.Connecting;
|
|
ShutdownSocket();
|
|
mSocket = new MungaSocket();
|
|
mSocket.BeginConnect(mDosBoxAddressShift ? DosBox.ShiftAddress(mPod.IPAddress) : mPod.IPAddress, 1501, delegate(IAsyncResult ar)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
try
|
|
{
|
|
mSocket.EndConnect(ar);
|
|
State = MungaConnectionState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
}, null);
|
|
}
|
|
}
|
|
|
|
private void Disconnect()
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
State = MungaConnectionState.Disconnected;
|
|
mAppState = null;
|
|
mAppID = null;
|
|
mNextStateQuery = DateTime.MinValue;
|
|
ShutdownSocket();
|
|
if (Requested)
|
|
{
|
|
Connect();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ShutdownSocket()
|
|
{
|
|
if (mSocket != null)
|
|
{
|
|
try
|
|
{
|
|
mSocket.Shutdown();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
mSocket = null;
|
|
}
|
|
}
|
|
|
|
public MetaMessage Receive()
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
try
|
|
{
|
|
MetaMessage metaMessage = mSocket.Receive();
|
|
if (metaMessage != null)
|
|
{
|
|
if (metaMessage.Message is StateResponseMessage)
|
|
{
|
|
StateResponseMessage stateResponseMessage = (StateResponseMessage)metaMessage.Message;
|
|
mAppID = stateResponseMessage.Application;
|
|
if (stateResponseMessage.ApplicationState == ApplicationState.WaitingForEgg && stateResponseMessage.ApplicationState != mAppState)
|
|
{
|
|
mEggDelayTimer = DateTime.Now.AddSeconds(5.0);
|
|
mEggSent = DateTime.MinValue;
|
|
mEggAck = false;
|
|
}
|
|
mAppState = stateResponseMessage.ApplicationState;
|
|
}
|
|
else if (metaMessage.Message is AcknowledgeEggFileMessage)
|
|
{
|
|
mEggAck = true;
|
|
}
|
|
}
|
|
return metaMessage;
|
|
}
|
|
catch
|
|
{
|
|
Disconnect();
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Send(int gameID, int fromHost, MungaMessage message)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
try
|
|
{
|
|
mSocket.Send(gameID, fromHost, message);
|
|
}
|
|
catch
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|