BTGame pane + shell de-hardcode for multi-game pods (BT port phase 3)
BTGame mirrors RPGame - same GameStateData/state-machine/NetworkScan engine driving each pod's MungaGame (take ownership, stream egg on WaitingForEgg, Run/Abort/Stop, countdown) - with the BT differences: ApplicationID.BTL4, a pilots grid carrying Mech/Camo/Patch/Badge/Experience, an Advanced Damage checkbox in place of RP's Score Compression, and the Mech* in-match messages recorded via BTMissionRecorder into BTMissionResults (.btm, gzip + BinaryFormatter like .rpm; scores are 1000-based like RP). Free For All and No Return share the pane; the mode selects the pilots' role. BTDefaults persists per-mode operator defaults to BTDefaults.btd (no dialog yet). RPGame's dead decompile members (sStopToIdleStates, IsAllPodsAppState) were not carried over. TeslaConsoleForm no longer assumes RP: the "AppID != 0 -> Pod Not Running RP" check becomes a GameTag map (RPL4 -> RP, BTL4 -> BT, otherwise Unknown Pod Application) feeding the per-game status strings, and the Games menu gains BattleTech: Free For All / No Return. Deferred to phase 4: mech/arena art, BT defaults dialog, BT score-sheet print document, Apps.xml BT411 product, live-pod verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TeslaConsole.BattleTech;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A mech took damage (<c>MechDamagedMessage</c>). Carries the Red Planet
|
||||||
|
/// damage fields plus BT's damage-matrix extras: which zone was hit, whether it
|
||||||
|
/// was destroyed, and the weapon that did it.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal class BTDamagedEvent : BTMissionEvent
|
||||||
|
{
|
||||||
|
public readonly BTPlayer Damager;
|
||||||
|
|
||||||
|
public readonly int DamageLoss;
|
||||||
|
|
||||||
|
public readonly int PointsTransfered;
|
||||||
|
|
||||||
|
public readonly int DamageZoneIndex;
|
||||||
|
|
||||||
|
public readonly bool DamageZoneDestroyed;
|
||||||
|
|
||||||
|
public readonly int WeaponIndex;
|
||||||
|
|
||||||
|
public override IEnumerable<BTPlayer> InvolvedPilots => new BTPlayer[2] { ReportingPilot, Damager };
|
||||||
|
|
||||||
|
public BTDamagedEvent(BTPlayer reportingPilot, TimeSpan missionTime, BTPlayer damager, int damageLoss, int pointsTransfered, int damageZoneIndex, bool damageZoneDestroyed, int weaponIndex)
|
||||||
|
: base(reportingPilot, missionTime)
|
||||||
|
{
|
||||||
|
Damager = damager;
|
||||||
|
DamageLoss = damageLoss;
|
||||||
|
PointsTransfered = pointsTransfered;
|
||||||
|
DamageZoneIndex = damageZoneIndex;
|
||||||
|
DamageZoneDestroyed = damageZoneDestroyed;
|
||||||
|
WeaponIndex = weaponIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BTDamagedEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TeslaConsole.BattleTech;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A mech died without a killer to credit — overheating, terrain, or
|
||||||
|
/// self-destruction (<c>MechDeathWithoutHonorMessage</c>).
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal class BTDeathWithoutHonorEvent : BTMissionEvent
|
||||||
|
{
|
||||||
|
public override IEnumerable<BTPlayer> InvolvedPilots => new BTPlayer[1] { ReportingPilot };
|
||||||
|
|
||||||
|
public BTDeathWithoutHonorEvent(BTPlayer reportingPilot, TimeSpan missionTime)
|
||||||
|
: base(reportingPilot, missionTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private BTDeathWithoutHonorEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace TeslaConsole.BattleTech;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Operator defaults for the two BattleTech game modes, persisted to
|
||||||
|
/// BTDefaults.btd in the common app-data directory (mirrors
|
||||||
|
/// <c>RPDefaults</c>/.rpd). Both modes share one defaults shape — they differ
|
||||||
|
/// only by role, which comes from the mode, not from a default. There is no
|
||||||
|
/// defaults dialog yet (RPDefaultsDialog's counterpart is a later phase); the
|
||||||
|
/// file is import/export round-trippable in the meantime.
|
||||||
|
/// </summary>
|
||||||
|
internal static class BTDefaults
|
||||||
|
{
|
||||||
|
public class BattleDefaults
|
||||||
|
{
|
||||||
|
private readonly string mNodeName;
|
||||||
|
|
||||||
|
private bool mAdvancedDamage = true;
|
||||||
|
|
||||||
|
private TimeSpan mMissionLength = new TimeSpan(0, 10, 0);
|
||||||
|
|
||||||
|
private string mMapKey;
|
||||||
|
|
||||||
|
private string mVehicleKey;
|
||||||
|
|
||||||
|
private string mTimeOfDayKey;
|
||||||
|
|
||||||
|
private string mWeatherKey;
|
||||||
|
|
||||||
|
private string mExperienceKey;
|
||||||
|
|
||||||
|
private string mCamoKey;
|
||||||
|
|
||||||
|
private string mPatchKey;
|
||||||
|
|
||||||
|
private string mEmblemKey;
|
||||||
|
|
||||||
|
private int mLaunchDelay = 15;
|
||||||
|
|
||||||
|
public bool AdvancedDamage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mAdvancedDamage;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mAdvancedDamage = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan MissionLength
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mMissionLength;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mMissionLength = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MapKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mMapKey;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mMapKey = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string VehicleKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mVehicleKey;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mVehicleKey = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TimeOfDayKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mTimeOfDayKey;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mTimeOfDayKey = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string WeatherKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mWeatherKey;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mWeatherKey = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ExperienceKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mExperienceKey;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mExperienceKey = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CamoKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mCamoKey;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mCamoKey = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PatchKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mPatchKey;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mPatchKey = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EmblemKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mEmblemKey;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mEmblemKey = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int LaunchDelay
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return mLaunchDelay;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mLaunchDelay = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal BattleDefaults(string nodeName)
|
||||||
|
{
|
||||||
|
mNodeName = nodeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(XmlNode parrentNode)
|
||||||
|
{
|
||||||
|
XmlNode xmlNode = parrentNode.AppendChild(parrentNode.OwnerDocument.CreateElement(mNodeName));
|
||||||
|
xmlNode.AppendChild(xmlNode.OwnerDocument.CreateElement("AdvancedDamage")).InnerText = mAdvancedDamage.ToString();
|
||||||
|
xmlNode.AppendChild(xmlNode.OwnerDocument.CreateElement("MissionLength")).InnerText = mMissionLength.ToString();
|
||||||
|
SaveKey(xmlNode, "MapKey", mMapKey);
|
||||||
|
SaveKey(xmlNode, "VehicleKey", mVehicleKey);
|
||||||
|
SaveKey(xmlNode, "TimeOfDayKey", mTimeOfDayKey);
|
||||||
|
SaveKey(xmlNode, "WeatherKey", mWeatherKey);
|
||||||
|
SaveKey(xmlNode, "ExperienceKey", mExperienceKey);
|
||||||
|
SaveKey(xmlNode, "CamoKey", mCamoKey);
|
||||||
|
SaveKey(xmlNode, "PatchKey", mPatchKey);
|
||||||
|
SaveKey(xmlNode, "EmblemKey", mEmblemKey);
|
||||||
|
xmlNode.AppendChild(xmlNode.OwnerDocument.CreateElement("LaunchDelay")).InnerText = mLaunchDelay.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SaveKey(XmlNode parentNode, string elementName, string value)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
parentNode.AppendChild(parentNode.OwnerDocument.CreateElement(elementName)).InnerText = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Parse(XmlNode scenarioNode)
|
||||||
|
{
|
||||||
|
foreach (XmlNode childNode in scenarioNode.ChildNodes)
|
||||||
|
{
|
||||||
|
switch (childNode.Name)
|
||||||
|
{
|
||||||
|
case "AdvancedDamage":
|
||||||
|
{
|
||||||
|
if (bool.TryParse(childNode.InnerText, out var result2))
|
||||||
|
{
|
||||||
|
mAdvancedDamage = result2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "MissionLength":
|
||||||
|
{
|
||||||
|
if (TimeSpan.TryParse(childNode.InnerText, out var result3))
|
||||||
|
{
|
||||||
|
mMissionLength = result3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "MapKey":
|
||||||
|
ParseKey(childNode, ref mMapKey);
|
||||||
|
break;
|
||||||
|
case "VehicleKey":
|
||||||
|
ParseKey(childNode, ref mVehicleKey);
|
||||||
|
break;
|
||||||
|
case "TimeOfDayKey":
|
||||||
|
ParseKey(childNode, ref mTimeOfDayKey);
|
||||||
|
break;
|
||||||
|
case "WeatherKey":
|
||||||
|
ParseKey(childNode, ref mWeatherKey);
|
||||||
|
break;
|
||||||
|
case "ExperienceKey":
|
||||||
|
ParseKey(childNode, ref mExperienceKey);
|
||||||
|
break;
|
||||||
|
case "CamoKey":
|
||||||
|
ParseKey(childNode, ref mCamoKey);
|
||||||
|
break;
|
||||||
|
case "PatchKey":
|
||||||
|
ParseKey(childNode, ref mPatchKey);
|
||||||
|
break;
|
||||||
|
case "EmblemKey":
|
||||||
|
ParseKey(childNode, ref mEmblemKey);
|
||||||
|
break;
|
||||||
|
case "LaunchDelay":
|
||||||
|
{
|
||||||
|
if (int.TryParse(childNode.InnerText, out var result))
|
||||||
|
{
|
||||||
|
mLaunchDelay = result;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseKey(XmlNode node, ref string field)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(node.InnerText))
|
||||||
|
{
|
||||||
|
field = node.InnerText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public const string FreeForAllNode = "FreeForAllDefaults";
|
||||||
|
|
||||||
|
public const string NoReturnNode = "NoReturnDefaults";
|
||||||
|
|
||||||
|
private static readonly string sDefaultsFilePath;
|
||||||
|
|
||||||
|
private static readonly BattleDefaults sFreeForAll;
|
||||||
|
|
||||||
|
private static readonly BattleDefaults sNoReturn;
|
||||||
|
|
||||||
|
public static BattleDefaults FreeForAll => sFreeForAll;
|
||||||
|
|
||||||
|
public static BattleDefaults NoReturn => sNoReturn;
|
||||||
|
|
||||||
|
static BTDefaults()
|
||||||
|
{
|
||||||
|
sDefaultsFilePath = Path.Combine(Program.GetCommonAppDataDirectory(), "BTDefaults.btd");
|
||||||
|
sFreeForAll = new BattleDefaults("FreeForAllDefaults");
|
||||||
|
sNoReturn = new BattleDefaults("NoReturnDefaults");
|
||||||
|
if (File.Exists(sDefaultsFilePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Import(sDefaultsFilePath);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
MessageBox.Show("The BattleTech defaults file appears to be corrupt. Please delete it or export a new one to replace it.", "Error Loading BattleTech Defaults!", MessageBoxButtons.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Import(string fileName)
|
||||||
|
{
|
||||||
|
XmlDocument xmlDocument = new XmlDocument();
|
||||||
|
xmlDocument.Load(fileName);
|
||||||
|
foreach (XmlNode childNode in xmlDocument.DocumentElement.ChildNodes)
|
||||||
|
{
|
||||||
|
switch (childNode.Name)
|
||||||
|
{
|
||||||
|
case "FreeForAllDefaults":
|
||||||
|
sFreeForAll.Parse(childNode);
|
||||||
|
break;
|
||||||
|
case "NoReturnDefaults":
|
||||||
|
sNoReturn.Parse(childNode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Export(sDefaultsFilePath);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
MessageBox.Show("The BattleTech defaults file could not be saved. These defaults will only be remembered until the application is closed.", "Error Saving BattleTech Defaults!", MessageBoxButtons.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Export(string filePath)
|
||||||
|
{
|
||||||
|
XmlDocument xmlDocument = new XmlDocument();
|
||||||
|
xmlDocument.AppendChild(xmlDocument.CreateElement("BTDefaults"));
|
||||||
|
sFreeForAll.Save(xmlDocument.DocumentElement);
|
||||||
|
sNoReturn.Save(xmlDocument.DocumentElement);
|
||||||
|
string directoryName = Path.GetDirectoryName(filePath);
|
||||||
|
if (!Directory.Exists(directoryName))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directoryName);
|
||||||
|
}
|
||||||
|
xmlDocument.Save(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TeslaConsole.BattleTech;
|
||||||
|
|
||||||
|
/// <summary>A mech was destroyed (<c>MechKilledMessage</c>).</summary>
|
||||||
|
[Serializable]
|
||||||
|
internal class BTKilledEvent : BTMissionEvent
|
||||||
|
{
|
||||||
|
public readonly BTPlayer Killer;
|
||||||
|
|
||||||
|
public override IEnumerable<BTPlayer> InvolvedPilots => new BTPlayer[2] { ReportingPilot, Killer };
|
||||||
|
|
||||||
|
public BTKilledEvent(BTPlayer reportingPilot, TimeSpan missionTime, BTPlayer killer)
|
||||||
|
: base(reportingPilot, missionTime)
|
||||||
|
{
|
||||||
|
Killer = killer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BTKilledEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TeslaConsole.BattleTech;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for a recorded in-match BattleTech event. Mirrors the Red Planet
|
||||||
|
/// <c>MissionEvent</c>, typed over <see cref="BTPlayer" />.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal abstract class BTMissionEvent
|
||||||
|
{
|
||||||
|
public readonly TimeSpan GameTime;
|
||||||
|
|
||||||
|
public readonly BTPlayer ReportingPilot;
|
||||||
|
|
||||||
|
public abstract IEnumerable<BTPlayer> InvolvedPilots { get; }
|
||||||
|
|
||||||
|
public BTMissionEvent(BTPlayer reportingPilot, TimeSpan missionTime)
|
||||||
|
{
|
||||||
|
ReportingPilot = reportingPilot;
|
||||||
|
GameTime = missionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BTMissionEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace TeslaConsole.BattleTech;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Records the in-match Munga event messages of one BattleTech mission into a
|
||||||
|
/// <see cref="BTMissionResults" />. Mirrors <c>RPMissionRecorder</c>; the event
|
||||||
|
/// set follows the BT messages in Munga Net.dll (Mech* / EndMission).
|
||||||
|
/// </summary>
|
||||||
|
internal class BTMissionRecorder
|
||||||
|
{
|
||||||
|
private BTMissionResults mMissionResults;
|
||||||
|
|
||||||
|
private bool mStartSet;
|
||||||
|
|
||||||
|
public BTMissionRecorder(BTMission mission)
|
||||||
|
{
|
||||||
|
mMissionResults = new BTMissionResults(mission);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMissionStart()
|
||||||
|
{
|
||||||
|
if (mStartSet)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
mMissionResults.mMissionStart = DateTime.Now;
|
||||||
|
mStartSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidatePlayer(BTPlayer player, string param)
|
||||||
|
{
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(param);
|
||||||
|
}
|
||||||
|
foreach (BTPlayer player2 in mMissionResults.Mission.Players)
|
||||||
|
{
|
||||||
|
if (player2 == player)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ArgumentException(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan GetMissionTime()
|
||||||
|
{
|
||||||
|
if (!mStartSet)
|
||||||
|
{
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
return DateTime.Now - mMissionResults.mMissionStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayerKilled(BTPlayer reportingPlayer, BTPlayer killer)
|
||||||
|
{
|
||||||
|
ValidatePlayer(reportingPlayer, "reportingPlayer");
|
||||||
|
ValidatePlayer(killer, "killer");
|
||||||
|
TimeSpan missionTime = GetMissionTime();
|
||||||
|
mMissionResults.mEvents.Add(new BTKilledEvent(reportingPlayer, missionTime, killer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayerDeathWithoutHonor(BTPlayer reportingPlayer)
|
||||||
|
{
|
||||||
|
ValidatePlayer(reportingPlayer, "reportingPlayer");
|
||||||
|
TimeSpan missionTime = GetMissionTime();
|
||||||
|
mMissionResults.mEvents.Add(new BTDeathWithoutHonorEvent(reportingPlayer, missionTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayerScoreUpdate(BTPlayer reportingPlayer, int score)
|
||||||
|
{
|
||||||
|
ValidatePlayer(reportingPlayer, "reportingPlayer");
|
||||||
|
TimeSpan missionTime = GetMissionTime();
|
||||||
|
mMissionResults.mEvents.Add(new BTScoreUpdateEvent(reportingPlayer, missionTime, score));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayerDamaged(BTPlayer reportingPlayer, BTPlayer damager, int damageLoss, int pointsTransfered, int damageZoneIndex, bool damageZoneDestroyed, int weaponIndex)
|
||||||
|
{
|
||||||
|
ValidatePlayer(reportingPlayer, "reportingPlayer");
|
||||||
|
ValidatePlayer(damager, "damager");
|
||||||
|
TimeSpan missionTime = GetMissionTime();
|
||||||
|
mMissionResults.mEvents.Add(new BTDamagedEvent(reportingPlayer, missionTime, damager, damageLoss, pointsTransfered, damageZoneIndex, damageZoneDestroyed, weaponIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayerFinalScore(BTPlayer reportingPlayer, int finalScore)
|
||||||
|
{
|
||||||
|
ValidatePlayer(reportingPlayer, "reportingPlayer");
|
||||||
|
mMissionResults.mFinalScores.Add(reportingPlayer, finalScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BTMissionResults EndMission(TimeSpan missionLength)
|
||||||
|
{
|
||||||
|
mMissionResults.mActuallMissionTime = missionLength;
|
||||||
|
BTMissionResults result = mMissionResults;
|
||||||
|
mMissionResults = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
|
|
||||||
|
namespace TeslaConsole.BattleTech;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The recorded outcome of one BattleTech mission, saved as a gzip'd
|
||||||
|
/// BinaryFormatter blob (.btm) exactly like the RP .rpm mechanism. Mirrors
|
||||||
|
/// <c>RPMissionResults</c> minus the football branches; the score-sheet print
|
||||||
|
/// document (RPPrintDocument's counterpart) is deferred until a real BT match
|
||||||
|
/// has been recorded to design it against.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal class BTMissionResults
|
||||||
|
{
|
||||||
|
private BTMission mMission;
|
||||||
|
|
||||||
|
internal DateTime mMissionStart;
|
||||||
|
|
||||||
|
internal TimeSpan mActuallMissionTime;
|
||||||
|
|
||||||
|
internal readonly List<BTMissionEvent> mEvents = new List<BTMissionEvent>();
|
||||||
|
|
||||||
|
internal readonly Dictionary<BTPlayer, int> mFinalScores = new Dictionary<BTPlayer, int>();
|
||||||
|
|
||||||
|
public BTMission Mission => mMission;
|
||||||
|
|
||||||
|
public DateTime MissionStart => mMissionStart;
|
||||||
|
|
||||||
|
public TimeSpan ActuallMissionTime => mActuallMissionTime;
|
||||||
|
|
||||||
|
public IEnumerable<BTMissionEvent> Events => mEvents;
|
||||||
|
|
||||||
|
internal BTMissionResults(BTMission mission)
|
||||||
|
{
|
||||||
|
mMission = mission;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetBTMissionsDirectory()
|
||||||
|
{
|
||||||
|
string text = Path.Combine(Program.GetCommonAppDataDirectory(), "BT Missions");
|
||||||
|
if (!Directory.Exists(text))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(text);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BTMissionResults Load(string file)
|
||||||
|
{
|
||||||
|
using GZipStream gZipStream = new GZipStream(File.OpenRead(file), CompressionMode.Decompress);
|
||||||
|
BinaryFormatter binaryFormatter = new BinaryFormatter();
|
||||||
|
BTMissionResults result = (BTMissionResults)binaryFormatter.Deserialize(gZipStream);
|
||||||
|
gZipStream.Close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
string path = Path.Combine(GetBTMissionsDirectory(), mMissionStart.ToString("yyyy-MM-dd HH-mm-ss.ffff") + ".btm");
|
||||||
|
using GZipStream gZipStream = new GZipStream(File.OpenWrite(path), CompressionMode.Compress);
|
||||||
|
BinaryFormatter binaryFormatter = new BinaryFormatter();
|
||||||
|
binaryFormatter.Serialize(gZipStream, this);
|
||||||
|
gZipStream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetScore(BTPlayer player)
|
||||||
|
{
|
||||||
|
return GetScore(player, TimeSpan.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetScore(BTPlayer player, TimeSpan gameTime)
|
||||||
|
{
|
||||||
|
if (gameTime == TimeSpan.MaxValue && mFinalScores.ContainsKey(player))
|
||||||
|
{
|
||||||
|
return mFinalScores[player];
|
||||||
|
}
|
||||||
|
int result = 1000;
|
||||||
|
TimeSpan timeSpan = TimeSpan.MinValue;
|
||||||
|
foreach (BTMissionEvent mEvent in mEvents)
|
||||||
|
{
|
||||||
|
if (mEvent is BTScoreUpdateEvent && mEvent.ReportingPilot == player && mEvent.GameTime > timeSpan && mEvent.GameTime <= gameTime)
|
||||||
|
{
|
||||||
|
result = ((BTScoreUpdateEvent)mEvent).Score;
|
||||||
|
timeSpan = mEvent.GameTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetFinalPlace(BTPlayer player)
|
||||||
|
{
|
||||||
|
int num = 1;
|
||||||
|
int score = GetScore(player, TimeSpan.MaxValue);
|
||||||
|
foreach (BTPlayer player2 in mMission.Players)
|
||||||
|
{
|
||||||
|
if (player2 != player && GetScore(player2, TimeSpan.MaxValue) > score)
|
||||||
|
{
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
||||||
namespace TeslaConsole.BattleTech;
|
namespace TeslaConsole.BattleTech;
|
||||||
@@ -6,8 +7,11 @@ namespace TeslaConsole.BattleTech;
|
|||||||
/// A BattleTech pilot role. Each participant references one role
|
/// A BattleTech pilot role. Each participant references one role
|
||||||
/// (<c>role=Role::<Key></c>); the egg then carries a matching
|
/// (<c>role=Role::<Key></c>); the egg then carries a matching
|
||||||
/// <c>[Role::<Key>] model=<Model></c> block. Free For All uses the
|
/// <c>[Role::<Key>] model=<Model></c> block. Free For All uses the
|
||||||
/// Default role, No Return uses the NoReturn role.
|
/// Default role, No Return uses the NoReturn role. Serializable because it is
|
||||||
|
/// reachable from a saved <see cref="BTMissionResults" /> via the mission's
|
||||||
|
/// players (mirrors the .rpm save mechanism).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
public class BTRole
|
public class BTRole
|
||||||
{
|
{
|
||||||
private readonly string mKey;
|
private readonly string mKey;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TeslaConsole.BattleTech;
|
||||||
|
|
||||||
|
/// <summary>A pilot's running score changed (<c>MechScoreUpdateMessage</c>).</summary>
|
||||||
|
[Serializable]
|
||||||
|
internal class BTScoreUpdateEvent : BTMissionEvent
|
||||||
|
{
|
||||||
|
public readonly int Score;
|
||||||
|
|
||||||
|
public override IEnumerable<BTPlayer> InvolvedPilots => new BTPlayer[1] { ReportingPilot };
|
||||||
|
|
||||||
|
public BTScoreUpdateEvent(BTPlayer reportingPilot, TimeSpan missionTime, int score)
|
||||||
|
: base(reportingPilot, missionTime)
|
||||||
|
{
|
||||||
|
Score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BTScoreUpdateEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ using System.Drawing.Drawing2D;
|
|||||||
using System.Drawing.Printing;
|
using System.Drawing.Printing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Munga.Net;
|
using Munga.Net;
|
||||||
|
using TeslaConsole.BattleTech;
|
||||||
using TeslaConsole.RedPlanet;
|
using TeslaConsole.RedPlanet;
|
||||||
using WeifenLuo.WinFormsUI.Docking;
|
using WeifenLuo.WinFormsUI.Docking;
|
||||||
|
|
||||||
@@ -38,6 +39,10 @@ public class TeslaConsoleForm : Form
|
|||||||
|
|
||||||
private ToolStripMenuItem mRpMartianFootball;
|
private ToolStripMenuItem mRpMartianFootball;
|
||||||
|
|
||||||
|
private ToolStripMenuItem mBtFreeForAll;
|
||||||
|
|
||||||
|
private ToolStripMenuItem mBtNoReturn;
|
||||||
|
|
||||||
private ToolStripMenuItem mPlasmaFontTool;
|
private ToolStripMenuItem mPlasmaFontTool;
|
||||||
|
|
||||||
private ToolStripMenuItem mRpMissionPrintPreview;
|
private ToolStripMenuItem mRpMissionPrintPreview;
|
||||||
@@ -136,15 +141,16 @@ public class TeslaConsoleForm : Form
|
|||||||
pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Waiting For State Update...");
|
pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Waiting For State Update...");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (pod.MungaGame.AppID.Value != 0)
|
string gameTag = GameTag(pod.MungaGame.AppID.Value);
|
||||||
|
if (gameTag == null)
|
||||||
{
|
{
|
||||||
pod.MungaGame.SetStatus(ImageCache.PodBad16, "Pod Not Running RP");
|
pod.MungaGame.SetStatus(ImageCache.PodBad16, "Unknown Pod Application");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
switch (pod.MungaGame.AppState.Value)
|
switch (pod.MungaGame.AppState.Value)
|
||||||
{
|
{
|
||||||
case ApplicationState.InitializingState:
|
case ApplicationState.InitializingState:
|
||||||
pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "RP Initalizing");
|
pod.MungaGame.SetStatus(ImageCache.PodQuestion16, gameTag + " Initalizing");
|
||||||
break;
|
break;
|
||||||
case ApplicationState.WaitingForEgg:
|
case ApplicationState.WaitingForEgg:
|
||||||
pod.MungaGame.SetStatus(ImageCache.PodOnline16, "Waiting For A Game");
|
pod.MungaGame.SetStatus(ImageCache.PodOnline16, "Waiting For A Game");
|
||||||
@@ -202,6 +208,26 @@ public class TeslaConsoleForm : Form
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short display tag for a pod's reported application, or null when the
|
||||||
|
/// console has no game module for it. RPL4 is Red Planet, BTL4 is
|
||||||
|
/// BattleTech; NDL4 (the never-released air-combat game) has no console
|
||||||
|
/// support. The "Initalizing" spelling in the status strings is preserved
|
||||||
|
/// from the original console.
|
||||||
|
/// </summary>
|
||||||
|
private static string GameTag(ApplicationID appID)
|
||||||
|
{
|
||||||
|
switch (appID)
|
||||||
|
{
|
||||||
|
case ApplicationID.RPL4:
|
||||||
|
return "RP";
|
||||||
|
case ApplicationID.BTL4:
|
||||||
|
return "BT";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void redPlanetDeathRaceToolStripMenuItem_Click(object sender, EventArgs e)
|
private void redPlanetDeathRaceToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
CreateNewRPGame(footballMode: false);
|
CreateNewRPGame(footballMode: false);
|
||||||
@@ -219,6 +245,23 @@ public class TeslaConsoleForm : Form
|
|||||||
rPGame.Show(mMainDockPanel, DockState.Document);
|
rPGame.Show(mMainDockPanel, DockState.Document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void battleTechFreeForAllToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
CreateNewBTGame(noReturnMode: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void battleTechNoReturnToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
CreateNewBTGame(noReturnMode: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateNewBTGame(bool noReturnMode)
|
||||||
|
{
|
||||||
|
BTGame bTGame = new BTGame(TeslaConsole.Site.Active, noReturnMode);
|
||||||
|
bTGame.MdiParent = this;
|
||||||
|
bTGame.Show(mMainDockPanel, DockState.Document);
|
||||||
|
}
|
||||||
|
|
||||||
private void plasmaEditorToolStripMenuItem_Click(object sender, EventArgs e)
|
private void plasmaEditorToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
new PlasmaEditor().Show();
|
new PlasmaEditor().Show();
|
||||||
@@ -409,6 +452,8 @@ public class TeslaConsoleForm : Form
|
|||||||
this.mGamesMenu = new System.Windows.Forms.ToolStripMenuItem();
|
this.mGamesMenu = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.mRpDeathRace = new System.Windows.Forms.ToolStripMenuItem();
|
this.mRpDeathRace = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.mRpMartianFootball = new System.Windows.Forms.ToolStripMenuItem();
|
this.mRpMartianFootball = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.mBtFreeForAll = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.mBtNoReturn = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.aboutToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
|
this.aboutToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.mExceptionsMenu = new System.Windows.Forms.ToolStripMenuItem();
|
this.mExceptionsMenu = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
@@ -540,7 +585,7 @@ public class TeslaConsoleForm : Form
|
|||||||
this.enableCustomBitmapsToolStripMenuItem.Size = new System.Drawing.Size(220, 22);
|
this.enableCustomBitmapsToolStripMenuItem.Size = new System.Drawing.Size(220, 22);
|
||||||
this.enableCustomBitmapsToolStripMenuItem.Text = "Enable Custom Bitmaps";
|
this.enableCustomBitmapsToolStripMenuItem.Text = "Enable Custom Bitmaps";
|
||||||
this.enableCustomBitmapsToolStripMenuItem.Click += new System.EventHandler(enableCustomBitmapsToolStripMenuItem_Click);
|
this.enableCustomBitmapsToolStripMenuItem.Click += new System.EventHandler(enableCustomBitmapsToolStripMenuItem_Click);
|
||||||
this.mGamesMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[2] { this.mRpDeathRace, this.mRpMartianFootball });
|
this.mGamesMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[4] { this.mRpDeathRace, this.mRpMartianFootball, this.mBtFreeForAll, this.mBtNoReturn });
|
||||||
this.mGamesMenu.Name = "mGamesMenu";
|
this.mGamesMenu.Name = "mGamesMenu";
|
||||||
this.mGamesMenu.Size = new System.Drawing.Size(55, 20);
|
this.mGamesMenu.Size = new System.Drawing.Size(55, 20);
|
||||||
this.mGamesMenu.Text = "Games";
|
this.mGamesMenu.Text = "Games";
|
||||||
@@ -552,6 +597,14 @@ public class TeslaConsoleForm : Form
|
|||||||
this.mRpMartianFootball.Size = new System.Drawing.Size(223, 22);
|
this.mRpMartianFootball.Size = new System.Drawing.Size(223, 22);
|
||||||
this.mRpMartianFootball.Text = "Red Planet: Martian Football";
|
this.mRpMartianFootball.Text = "Red Planet: Martian Football";
|
||||||
this.mRpMartianFootball.Click += new System.EventHandler(redPlanetMartianFootballToolStripMenuItem_Click);
|
this.mRpMartianFootball.Click += new System.EventHandler(redPlanetMartianFootballToolStripMenuItem_Click);
|
||||||
|
this.mBtFreeForAll.Name = "mBtFreeForAll";
|
||||||
|
this.mBtFreeForAll.Size = new System.Drawing.Size(223, 22);
|
||||||
|
this.mBtFreeForAll.Text = "BattleTech: Free For All";
|
||||||
|
this.mBtFreeForAll.Click += new System.EventHandler(battleTechFreeForAllToolStripMenuItem_Click);
|
||||||
|
this.mBtNoReturn.Name = "mBtNoReturn";
|
||||||
|
this.mBtNoReturn.Size = new System.Drawing.Size(223, 22);
|
||||||
|
this.mBtNoReturn.Text = "BattleTech: No Return";
|
||||||
|
this.mBtNoReturn.Click += new System.EventHandler(battleTechNoReturnToolStripMenuItem_Click);
|
||||||
this.aboutToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[1] { this.aboutToolStripMenuItem1 });
|
this.aboutToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[1] { this.aboutToolStripMenuItem1 });
|
||||||
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
|
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
|
||||||
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(52, 20);
|
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(52, 20);
|
||||||
|
|||||||
Reference in New Issue
Block a user