Files
CydandClaude Fable 5 6a77fd0991 Console: DOSBox IPs (+100) checkbox on all four game pages
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>
2026-07-10 14:59:58 -05:00

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();
}
}
}
}