From 18d787bdd53f882f059a5b7898efe2583a46aec7 Mon Sep 17 00:00:00 2001 From: Cyd Date: Tue, 7 Jul 2026 23:04:03 -0500 Subject: [PATCH] BattleTech results printing, defaults dialog, and BT411 catalog (BT port phase 4) Completes the BattleTech console feature to parity with Red Planet: - BTPrintDocument: the per-pilot landscape score sheet (mech portrait, placing, kill/death/damage stats, randomized mission-highlights narrative, pilot-vs-pilot damage matrix, place-over-time chart), mirroring RPPrintDocument minus the football/lap/boost/score-zone concepts. Wired into BTGame (Auto Print checkbox + Print Last Mission button) and the shell's File menu (BT Mission Print Preview / Print BT Mission, loading .btm files). - BTStrings(.xml): the BT mission-highlight narrative pools (damage tiers, kill, death-without-honor, recap), loaded from BattleTech\BTStrings.xml like RPStrings. - BTDefaultsDialog: two-column (Free For All / No Return) editor for the BT operator defaults, reachable from Settings (Change/Import/Export BattleTech Defaults). Verified via UI Automation: opens with all 18 option combos populated. - Apps.xml: the BattleTech 4.11 product (btl4.exe on the shared RP411 engine, same -net/-res/-lc/-mr command line; deploys to C:\Games\BT411) with Game Client / Live Camera / Mission Review launch entries. CatalogTests updated to 4 products / 8 entries plus the three new BT entry assertions. Role model spelling corrected to "noreturn": the Mac Console.ini tag and the game's BTL4.RES role resource are dfltrole/NoReturn; the 4.10 console's eggs emitted a broken "noretun" that cannot resolve against the RES. BTGoldenEggTests and BTConfig.xml updated accordingly. Co-Authored-By: Claude Fable 5 --- Console/BattleTech/BTConfig.xml | 8 +- Console/BattleTech/BTStrings.xml | 34 + Console/RedPlanet/Apps.xml | 27 + .../BTDefaultsDialog.cs | 441 +++++++++++ Console/TeslaConsole.BattleTech/BTGame.cs | 51 +- .../BTMissionResults.cs | 43 +- .../BTPrintDocument.cs | 748 ++++++++++++++++++ Console/TeslaConsole.BattleTech/BTStrings.cs | 144 ++++ Console/TeslaConsole.csproj | 1 + Console/TeslaConsole/TeslaConsoleForm.cs | 136 +++- .../BTGoldenEggTests.cs | 9 +- .../TeslaConsole.DiffTests/CatalogTests.cs | 31 +- 12 files changed, 1658 insertions(+), 15 deletions(-) create mode 100644 Console/BattleTech/BTStrings.xml create mode 100644 Console/TeslaConsole.BattleTech/BTDefaultsDialog.cs create mode 100644 Console/TeslaConsole.BattleTech/BTPrintDocument.cs create mode 100644 Console/TeslaConsole.BattleTech/BTStrings.cs diff --git a/Console/BattleTech/BTConfig.xml b/Console/BattleTech/BTConfig.xml index 9b547f0..08f7b32 100644 --- a/Console/BattleTech/BTConfig.xml +++ b/Console/BattleTech/BTConfig.xml @@ -64,10 +64,12 @@ + egg value. The Mac ini tag is "noreturn" and the game's BTL4.RES role + resources are named dfltrole/NoReturn; the 4.10 console emitted a broken + "noretun" (a dropped character) in every egg on the production box, which + cannot resolve against the RES, so the correct spelling is used here. --> - + diff --git a/Console/BattleTech/BTStrings.xml b/Console/BattleTech/BTStrings.xml new file mode 100644 index 0000000..95e0803 --- /dev/null +++ b/Console/BattleTech/BTStrings.xml @@ -0,0 +1,34 @@ + + + + {time} - Armor plating flakes off as {damager} lightly damages {player}'s mech. + {time} - {damager} lands a glancing hit on {player}. + {player}'s armor shrugs off a light volley from {damager}. + {player} laughs over the comm as {damager} can only scratch {player}'s paint. + + {time} - Armor buckles as {damager} moderately damages {player}'s mech. + {player} is still in the fight as {damager} delivers a solid blow to {player}'s mech. + {damager} carves a respectable chunk out of {player}'s armor. + {time} - {damager} rocks {player}'s cockpit with a moderate impact. + + {time} - Molten armor sprays as {damager} heavily damages {player}'s mech. + {player} fights the controls as {damager} nearly cores {player}'s mech. + {damager} unloads a devastating alpha strike into {player}'s mech. + {time} - Warning klaxons scream in {player}'s cockpit after a heavy impact from {damager}. + + {time} - {damager} delivers a righteous kill to {player}. + {damager} stands victorious over the smoking wreckage of {player}'s {vehicle}. + {time} - {damager} cores {player}'s reactor. Glory to the victor. + The shattered remains of {player}'s {vehicle} litter the arena floor as {damager} salutes. + + {time} - {player} dies without honor as the {vehicle} destroys itself. + Shame falls upon {player}, whose {vehicle} is lost to no enemy's hand. + {time} - {player}'s {vehicle} overheats catastrophically. A death without honor. + {player} learns a hard lesson in mech management as the {vehicle} goes down on its own. + + ...and at the end of the mission it's: {winners} followed by {looser}. + diff --git a/Console/RedPlanet/Apps.xml b/Console/RedPlanet/Apps.xml index de112e5..ec28749 100644 --- a/Console/RedPlanet/Apps.xml +++ b/Console/RedPlanet/Apps.xml @@ -53,6 +53,33 @@ hostType="MissionReview" /> + + + + + + + +/// The BattleTech operator-defaults editor: one column per game mode (Free For +/// All / No Return), mirroring RPDefaultsDialog's Death Race / Football +/// layout. OK persists to (BTDefaults.btd). Both +/// modes share the single BT catalog; the rows are the BT option set (mech, +/// camo, patch, badge, experience, advanced damage) instead of RP's. +/// +public class BTDefaultsDialog : Form +{ + private IContainer components; + + private TableLayoutPanel mMainTable; + + private Label mFfaHeaderLabel; + + private Label mNoReturnHeaderLabel; + + private Label mMapLabel; + + private Label mWeatherLabel; + + private Label mTimeOfDayLabel; + + private Label mAdvancedDamageLabel; + + private Label mMissionLengthLabel; + + private Label mMechLabel; + + private Label mCamoLabel; + + private Label mPatchLabel; + + private Label mBadgeLabel; + + private Label mExperienceLabel; + + private Label mLaunchDelayLabel; + + private ComboBox mFfaMap; + + private ComboBox mNoReturnMap; + + private ComboBox mFfaWeather; + + private ComboBox mNoReturnWeather; + + private ComboBox mFfaTimeOfDay; + + private ComboBox mNoReturnTimeOfDay; + + private ComboBox mFfaAdvancedDamage; + + private ComboBox mNoReturnAdvancedDamage; + + private MaskedTextBox mFfaMissionLength; + + private MaskedTextBox mNoReturnMissionLength; + + private ComboBox mFfaVehicle; + + private ComboBox mNoReturnVehicle; + + private ComboBox mFfaCamo; + + private ComboBox mNoReturnCamo; + + private ComboBox mFfaPatch; + + private ComboBox mNoReturnPatch; + + private ComboBox mFfaBadge; + + private ComboBox mNoReturnBadge; + + private ComboBox mFfaExperience; + + private ComboBox mNoReturnExperience; + + private NumericUpDown mFfaLaunchDelay; + + private NumericUpDown mNoReturnLaunchDelay; + + private TableLayoutPanel mButtonsTable; + + private Button mCancel; + + private Button mOK; + + private static BTDefaultsDialog mSingletonDialog; + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + public static void ShowSingleton() + { + if (mSingletonDialog == null || mSingletonDialog.IsDisposed) + { + mSingletonDialog = new BTDefaultsDialog(); + } + mSingletonDialog.Show(); + mSingletonDialog.Focus(); + mSingletonDialog.WindowState = FormWindowState.Normal; + } + + private BTDefaultsDialog() + { + InitializeComponent(); + base.Icon = Resources.swirl; + BTScenario scenario = BTConfig.FreeForAll; + BuildColumn(scenario, BTDefaults.FreeForAll, mFfaMap, mFfaWeather, mFfaTimeOfDay, mFfaAdvancedDamage, mFfaMissionLength, mFfaVehicle, mFfaCamo, mFfaPatch, mFfaBadge, mFfaExperience, mFfaLaunchDelay); + BuildColumn(scenario, BTDefaults.NoReturn, mNoReturnMap, mNoReturnWeather, mNoReturnTimeOfDay, mNoReturnAdvancedDamage, mNoReturnMissionLength, mNoReturnVehicle, mNoReturnCamo, mNoReturnPatch, mNoReturnBadge, mNoReturnExperience, mNoReturnLaunchDelay); + } + + private static void BuildColumn(BTScenario scenario, BTDefaults.BattleDefaults defaults, ComboBox map, ComboBox weather, ComboBox timeOfDay, ComboBox advancedDamage, MaskedTextBox missionLength, ComboBox vehicle, ComboBox camo, ComboBox patch, ComboBox badge, ComboBox experience, NumericUpDown launchDelay) + { + BuildNamedOption(map, scenario.Maps.Keys, scenario.Maps.Values, defaults.MapKey); + BuildNamedOption(weather, scenario.Weather.Keys, scenario.Weather.Values, defaults.WeatherKey); + BuildGenericOption(timeOfDay, scenario.TimesOfDay, defaults.TimeOfDayKey); + BuildOnOffOptions(advancedDamage, defaults.AdvancedDamage); + BuildMissionLengthControl(missionLength, defaults.MissionLength); + BuildNamedOption(vehicle, scenario.Vehicles.Keys, scenario.Vehicles.Values, defaults.VehicleKey); + BuildGenericOption(camo, scenario.Colors, defaults.CamoKey); + BuildGenericOption(patch, scenario.Patches, defaults.PatchKey); + BuildGenericOption(badge, scenario.Badges, defaults.EmblemKey); + BuildGenericOption(experience, scenario.Experiences, defaults.ExperienceKey); + launchDelay.Value = defaults.LaunchDelay; + } + + private static void BuildOnOffOptions(ComboBox comboBox, bool defaultValue) + { + comboBox.DisplayMember = "Value"; + comboBox.ValueMember = "Key"; + comboBox.Items.Add(new KeyValuePair(key: false, "Off")); + comboBox.Items.Add(new KeyValuePair(key: true, "On")); + comboBox.SelectedIndex = (defaultValue ? 1 : 0); + } + + private static void BuildMissionLengthControl(MaskedTextBox maskedTextBox, TimeSpan defaultValue) + { + maskedTextBox.Mask = "00:00:00"; + maskedTextBox.ValidatingType = typeof(TimeSpan); + maskedTextBox.Text = defaultValue.ToString(); + } + + /// + /// Fills a combo from a keyed catalog of objects whose Name property is the + /// display text and whose ToString returns it (BTMap/BTVehicle/BTWeather). + /// + private static void BuildNamedOption(ComboBox comboBox, IEnumerable keys, IEnumerable values, string defaultKey) + { + int selectedIndex = 0; + int num = 0; + comboBox.DisplayMember = "Name"; + comboBox.ValueMember = "Key"; + IEnumerator keyEnumerator = keys.GetEnumerator(); + foreach (T value in values) + { + keyEnumerator.MoveNext(); + comboBox.Items.Add(value); + if (keyEnumerator.Current == defaultKey) + { + selectedIndex = num; + } + num++; + } + comboBox.SelectedIndex = selectedIndex; + } + + private static void BuildGenericOption(ComboBox comboBox, IEnumerable> options, string defaultKey) + { + int selectedIndex = 0; + int num = 0; + comboBox.DisplayMember = "Value"; + comboBox.ValueMember = "Key"; + foreach (KeyValuePair option in options) + { + comboBox.Items.Add(option); + if (option.Key == defaultKey) + { + selectedIndex = num; + } + num++; + } + comboBox.SelectedIndex = selectedIndex; + } + + private void mCancel_Click(object sender, EventArgs e) + { + Close(); + } + + private void mOK_Click(object sender, EventArgs e) + { + SaveColumn(BTDefaults.FreeForAll, mFfaMap, mFfaWeather, mFfaTimeOfDay, mFfaAdvancedDamage, mFfaMissionLength, mFfaVehicle, mFfaCamo, mFfaPatch, mFfaBadge, mFfaExperience, mFfaLaunchDelay); + SaveColumn(BTDefaults.NoReturn, mNoReturnMap, mNoReturnWeather, mNoReturnTimeOfDay, mNoReturnAdvancedDamage, mNoReturnMissionLength, mNoReturnVehicle, mNoReturnCamo, mNoReturnPatch, mNoReturnBadge, mNoReturnExperience, mNoReturnLaunchDelay); + BTDefaults.Save(); + Hide(); + } + + private static void SaveColumn(BTDefaults.BattleDefaults defaults, ComboBox map, ComboBox weather, ComboBox timeOfDay, ComboBox advancedDamage, MaskedTextBox missionLength, ComboBox vehicle, ComboBox camo, ComboBox patch, ComboBox badge, ComboBox experience, NumericUpDown launchDelay) + { + defaults.MapKey = ((BTMap)map.SelectedItem).Key; + defaults.WeatherKey = ((BTWeather)weather.SelectedItem).Key; + defaults.TimeOfDayKey = ExtractSelectedKey(timeOfDay); + defaults.AdvancedDamage = ExtractSelectedKey(advancedDamage); + defaults.MissionLength = (TimeSpan)missionLength.ValidateText(); + defaults.VehicleKey = ((BTVehicle)vehicle.SelectedItem).Key; + defaults.CamoKey = ExtractSelectedKey(camo); + defaults.PatchKey = ExtractSelectedKey(patch); + defaults.EmblemKey = ExtractSelectedKey(badge); + defaults.ExperienceKey = ExtractSelectedKey(experience); + defaults.LaunchDelay = (int)launchDelay.Value; + } + + private static T ExtractSelectedKey(ComboBox comboBox) + { + if (comboBox.SelectedItem is KeyValuePair) + { + return ((KeyValuePair)comboBox.SelectedItem).Key; + } + return (T)comboBox.SelectedItem; + } + + private void InitializeComponent() + { + this.mMainTable = new System.Windows.Forms.TableLayoutPanel(); + this.mFfaHeaderLabel = new System.Windows.Forms.Label(); + this.mNoReturnHeaderLabel = new System.Windows.Forms.Label(); + this.mMapLabel = new System.Windows.Forms.Label(); + this.mWeatherLabel = new System.Windows.Forms.Label(); + this.mTimeOfDayLabel = new System.Windows.Forms.Label(); + this.mAdvancedDamageLabel = new System.Windows.Forms.Label(); + this.mMissionLengthLabel = new System.Windows.Forms.Label(); + this.mMechLabel = new System.Windows.Forms.Label(); + this.mCamoLabel = new System.Windows.Forms.Label(); + this.mPatchLabel = new System.Windows.Forms.Label(); + this.mBadgeLabel = new System.Windows.Forms.Label(); + this.mExperienceLabel = new System.Windows.Forms.Label(); + this.mLaunchDelayLabel = new System.Windows.Forms.Label(); + this.mFfaMap = new System.Windows.Forms.ComboBox(); + this.mNoReturnMap = new System.Windows.Forms.ComboBox(); + this.mFfaWeather = new System.Windows.Forms.ComboBox(); + this.mNoReturnWeather = new System.Windows.Forms.ComboBox(); + this.mFfaTimeOfDay = new System.Windows.Forms.ComboBox(); + this.mNoReturnTimeOfDay = new System.Windows.Forms.ComboBox(); + this.mFfaAdvancedDamage = new System.Windows.Forms.ComboBox(); + this.mNoReturnAdvancedDamage = new System.Windows.Forms.ComboBox(); + this.mFfaMissionLength = new System.Windows.Forms.MaskedTextBox(); + this.mNoReturnMissionLength = new System.Windows.Forms.MaskedTextBox(); + this.mFfaVehicle = new System.Windows.Forms.ComboBox(); + this.mNoReturnVehicle = new System.Windows.Forms.ComboBox(); + this.mFfaCamo = new System.Windows.Forms.ComboBox(); + this.mNoReturnCamo = new System.Windows.Forms.ComboBox(); + this.mFfaPatch = new System.Windows.Forms.ComboBox(); + this.mNoReturnPatch = new System.Windows.Forms.ComboBox(); + this.mFfaBadge = new System.Windows.Forms.ComboBox(); + this.mNoReturnBadge = new System.Windows.Forms.ComboBox(); + this.mFfaExperience = new System.Windows.Forms.ComboBox(); + this.mNoReturnExperience = new System.Windows.Forms.ComboBox(); + this.mFfaLaunchDelay = new System.Windows.Forms.NumericUpDown(); + this.mNoReturnLaunchDelay = new System.Windows.Forms.NumericUpDown(); + this.mButtonsTable = new System.Windows.Forms.TableLayoutPanel(); + this.mCancel = new System.Windows.Forms.Button(); + this.mOK = new System.Windows.Forms.Button(); + this.mMainTable.SuspendLayout(); + this.mButtonsTable.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)this.mFfaLaunchDelay).BeginInit(); + ((System.ComponentModel.ISupportInitialize)this.mNoReturnLaunchDelay).BeginInit(); + base.SuspendLayout(); + this.mMainTable.ColumnCount = 3; + this.mMainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50f)); + this.mMainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50f)); + this.mMainTable.Controls.Add(this.mFfaHeaderLabel, 1, 0); + this.mMainTable.Controls.Add(this.mNoReturnHeaderLabel, 2, 0); + this.mMainTable.Controls.Add(this.mMapLabel, 0, 1); + this.mMainTable.Controls.Add(this.mFfaMap, 1, 1); + this.mMainTable.Controls.Add(this.mNoReturnMap, 2, 1); + this.mMainTable.Controls.Add(this.mWeatherLabel, 0, 2); + this.mMainTable.Controls.Add(this.mFfaWeather, 1, 2); + this.mMainTable.Controls.Add(this.mNoReturnWeather, 2, 2); + this.mMainTable.Controls.Add(this.mTimeOfDayLabel, 0, 3); + this.mMainTable.Controls.Add(this.mFfaTimeOfDay, 1, 3); + this.mMainTable.Controls.Add(this.mNoReturnTimeOfDay, 2, 3); + this.mMainTable.Controls.Add(this.mAdvancedDamageLabel, 0, 4); + this.mMainTable.Controls.Add(this.mFfaAdvancedDamage, 1, 4); + this.mMainTable.Controls.Add(this.mNoReturnAdvancedDamage, 2, 4); + this.mMainTable.Controls.Add(this.mMissionLengthLabel, 0, 5); + this.mMainTable.Controls.Add(this.mFfaMissionLength, 1, 5); + this.mMainTable.Controls.Add(this.mNoReturnMissionLength, 2, 5); + this.mMainTable.Controls.Add(this.mMechLabel, 0, 6); + this.mMainTable.Controls.Add(this.mFfaVehicle, 1, 6); + this.mMainTable.Controls.Add(this.mNoReturnVehicle, 2, 6); + this.mMainTable.Controls.Add(this.mCamoLabel, 0, 7); + this.mMainTable.Controls.Add(this.mFfaCamo, 1, 7); + this.mMainTable.Controls.Add(this.mNoReturnCamo, 2, 7); + this.mMainTable.Controls.Add(this.mPatchLabel, 0, 8); + this.mMainTable.Controls.Add(this.mFfaPatch, 1, 8); + this.mMainTable.Controls.Add(this.mNoReturnPatch, 2, 8); + this.mMainTable.Controls.Add(this.mBadgeLabel, 0, 9); + this.mMainTable.Controls.Add(this.mFfaBadge, 1, 9); + this.mMainTable.Controls.Add(this.mNoReturnBadge, 2, 9); + this.mMainTable.Controls.Add(this.mExperienceLabel, 0, 10); + this.mMainTable.Controls.Add(this.mFfaExperience, 1, 10); + this.mMainTable.Controls.Add(this.mNoReturnExperience, 2, 10); + this.mMainTable.Controls.Add(this.mLaunchDelayLabel, 0, 11); + this.mMainTable.Controls.Add(this.mFfaLaunchDelay, 1, 11); + this.mMainTable.Controls.Add(this.mNoReturnLaunchDelay, 2, 11); + this.mMainTable.Controls.Add(this.mButtonsTable, 0, 12); + this.mMainTable.Dock = System.Windows.Forms.DockStyle.Fill; + this.mMainTable.Location = new System.Drawing.Point(3, 3); + this.mMainTable.Name = "mMainTable"; + this.mMainTable.RowCount = 13; + for (int i = 0; i < 12; i++) + { + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + } + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.mMainTable.Size = new System.Drawing.Size(478, 384); + this.mMainTable.TabIndex = 0; + this.mFfaHeaderLabel.Anchor = System.Windows.Forms.AnchorStyles.None; + this.mFfaHeaderLabel.AutoSize = true; + this.mFfaHeaderLabel.Name = "mFfaHeaderLabel"; + this.mFfaHeaderLabel.Text = "Free For All"; + this.mNoReturnHeaderLabel.Anchor = System.Windows.Forms.AnchorStyles.None; + this.mNoReturnHeaderLabel.AutoSize = true; + this.mNoReturnHeaderLabel.Name = "mNoReturnHeaderLabel"; + this.mNoReturnHeaderLabel.Text = "No Return"; + ConfigureRowLabel(this.mMapLabel, "mMapLabel", "Map:"); + ConfigureRowLabel(this.mWeatherLabel, "mWeatherLabel", "Weather:"); + ConfigureRowLabel(this.mTimeOfDayLabel, "mTimeOfDayLabel", "Time Of Day:"); + ConfigureRowLabel(this.mAdvancedDamageLabel, "mAdvancedDamageLabel", "Advanced Damage:"); + ConfigureRowLabel(this.mMissionLengthLabel, "mMissionLengthLabel", "Mission Length:"); + ConfigureRowLabel(this.mMechLabel, "mMechLabel", "Mech:"); + ConfigureRowLabel(this.mCamoLabel, "mCamoLabel", "Camo:"); + ConfigureRowLabel(this.mPatchLabel, "mPatchLabel", "Patch:"); + ConfigureRowLabel(this.mBadgeLabel, "mBadgeLabel", "Badge:"); + ConfigureRowLabel(this.mExperienceLabel, "mExperienceLabel", "Experience:"); + ConfigureRowLabel(this.mLaunchDelayLabel, "mLaunchDelayLabel", "Autotranslocate Delay:"); + ConfigureOptionCombo(this.mFfaMap, "mFfaMap"); + ConfigureOptionCombo(this.mNoReturnMap, "mNoReturnMap"); + ConfigureOptionCombo(this.mFfaWeather, "mFfaWeather"); + ConfigureOptionCombo(this.mNoReturnWeather, "mNoReturnWeather"); + ConfigureOptionCombo(this.mFfaTimeOfDay, "mFfaTimeOfDay"); + ConfigureOptionCombo(this.mNoReturnTimeOfDay, "mNoReturnTimeOfDay"); + ConfigureOptionCombo(this.mFfaAdvancedDamage, "mFfaAdvancedDamage"); + ConfigureOptionCombo(this.mNoReturnAdvancedDamage, "mNoReturnAdvancedDamage"); + ConfigureOptionCombo(this.mFfaVehicle, "mFfaVehicle"); + ConfigureOptionCombo(this.mNoReturnVehicle, "mNoReturnVehicle"); + ConfigureOptionCombo(this.mFfaCamo, "mFfaCamo"); + ConfigureOptionCombo(this.mNoReturnCamo, "mNoReturnCamo"); + ConfigureOptionCombo(this.mFfaPatch, "mFfaPatch"); + ConfigureOptionCombo(this.mNoReturnPatch, "mNoReturnPatch"); + ConfigureOptionCombo(this.mFfaBadge, "mFfaBadge"); + ConfigureOptionCombo(this.mNoReturnBadge, "mNoReturnBadge"); + ConfigureOptionCombo(this.mFfaExperience, "mFfaExperience"); + ConfigureOptionCombo(this.mNoReturnExperience, "mNoReturnExperience"); + this.mFfaMissionLength.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFfaMissionLength.Name = "mFfaMissionLength"; + this.mNoReturnMissionLength.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mNoReturnMissionLength.Name = "mNoReturnMissionLength"; + this.mFfaLaunchDelay.Dock = System.Windows.Forms.DockStyle.Fill; + this.mFfaLaunchDelay.Name = "mFfaLaunchDelay"; + this.mNoReturnLaunchDelay.Dock = System.Windows.Forms.DockStyle.Fill; + this.mNoReturnLaunchDelay.Name = "mNoReturnLaunchDelay"; + this.mButtonsTable.ColumnCount = 2; + this.mMainTable.SetColumnSpan(this.mButtonsTable, 3); + this.mButtonsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.mButtonsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mButtonsTable.Controls.Add(this.mCancel, 1, 0); + this.mButtonsTable.Controls.Add(this.mOK, 0, 0); + this.mButtonsTable.Dock = System.Windows.Forms.DockStyle.Fill; + this.mButtonsTable.Margin = new System.Windows.Forms.Padding(0); + this.mButtonsTable.Name = "mButtonsTable"; + this.mButtonsTable.RowCount = 1; + this.mButtonsTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.mButtonsTable.Size = new System.Drawing.Size(478, 44); + this.mButtonsTable.TabIndex = 27; + this.mCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.mCancel.Name = "mCancel"; + this.mCancel.Size = new System.Drawing.Size(75, 22); + this.mCancel.TabIndex = 1; + this.mCancel.Text = "&Cancel"; + this.mCancel.UseVisualStyleBackColor = true; + this.mCancel.Click += new System.EventHandler(mCancel_Click); + this.mOK.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.mOK.Name = "mOK"; + this.mOK.Size = new System.Drawing.Size(75, 22); + this.mOK.TabIndex = 0; + this.mOK.Text = "&OK"; + this.mOK.UseVisualStyleBackColor = true; + this.mOK.Click += new System.EventHandler(mOK_Click); + base.AcceptButton = this.mOK; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(484, 390); + base.Controls.Add(this.mMainTable); + base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + base.MaximizeBox = false; + base.Name = "BTDefaultsDialog"; + base.Padding = new System.Windows.Forms.Padding(3); + this.Text = "BattleTech Defaults"; + this.mMainTable.ResumeLayout(false); + this.mMainTable.PerformLayout(); + this.mButtonsTable.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)this.mFfaLaunchDelay).EndInit(); + ((System.ComponentModel.ISupportInitialize)this.mNoReturnLaunchDelay).EndInit(); + base.ResumeLayout(false); + } + + private static void ConfigureRowLabel(Label label, string name, string text) + { + label.Anchor = AnchorStyles.Right; + label.AutoSize = true; + label.Name = name; + label.Text = text; + } + + private static void ConfigureOptionCombo(ComboBox comboBox, string name) + { + comboBox.Anchor = AnchorStyles.Left | AnchorStyles.Right; + comboBox.DropDownStyle = ComboBoxStyle.DropDownList; + comboBox.FormattingEnabled = true; + comboBox.Name = name; + } +} diff --git a/Console/TeslaConsole.BattleTech/BTGame.cs b/Console/TeslaConsole.BattleTech/BTGame.cs index 7ed9911..dd26e81 100644 --- a/Console/TeslaConsole.BattleTech/BTGame.cs +++ b/Console/TeslaConsole.BattleTech/BTGame.cs @@ -166,6 +166,10 @@ internal class BTGame : DockContent private ToolStripMenuItem resetToolStripMenuItem; + private Button mPrintLastMission; + + private CheckBox mAutoPrint; + private CheckBox mAutoTranslocateCheckbox; private Button mRandomMap; @@ -807,19 +811,40 @@ internal class BTGame : DockContent } Program.LogExceptionAndShowDialog(ex, "Mission results failed to save.", "Error Saving Mission Results"); } + if (mAutoPrint.Checked) + { + try + { + mLastMissionResults.GetPrintDocument().Print(); + } + catch (Exception ex2) + { + if (Debugger.IsAttached) + { + throw; + } + Program.LogExceptionAndShowDialog(ex2, "Mission results failed to print.", "Error Printing Mission Results"); + } + } + mPrintLastMission.Enabled = true; } - catch (Exception ex2) + catch (Exception ex3) { if (Debugger.IsAttached) { throw; } - Program.LogExceptionAndShowDialog(ex2, "Failed to create mission results.", "Error Creating Mission Results"); + Program.LogExceptionAndShowDialog(ex3, "Failed to create mission results.", "Error Creating Mission Results"); } mMissionRecorder = null; mMissionPlayers = null; } + private void mPrintLastMission_Click(object sender, EventArgs e) + { + mLastMissionResults.GetPrintDocument().Print(); + } + private void SwitchControlsMode(bool editMode) { mMap.Enabled = editMode; @@ -1267,6 +1292,8 @@ internal class BTGame : DockContent this.mGoButton = new System.Windows.Forms.Button(); this.mResetContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); this.resetToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mPrintLastMission = new System.Windows.Forms.Button(); + this.mAutoPrint = new System.Windows.Forms.CheckBox(); this.mAutoTranslocateCheckbox = new System.Windows.Forms.CheckBox(); this.mRandomMap = new System.Windows.Forms.Button(); this.mRandomWeather = new System.Windows.Forms.Button(); @@ -1326,6 +1353,8 @@ internal class BTGame : DockContent this.mMissionPropertiesTable.Controls.Add(this.mTimeOfDayLabel, 0, 2); this.mMissionPropertiesTable.Controls.Add(this.mAdvancedDamage, 3, 0); this.mMissionPropertiesTable.Controls.Add(this.mGoButton, 3, 2); + this.mMissionPropertiesTable.Controls.Add(this.mPrintLastMission, 6, 2); + this.mMissionPropertiesTable.Controls.Add(this.mAutoPrint, 6, 1); this.mMissionPropertiesTable.Controls.Add(this.mAutoTranslocateCheckbox, 6, 0); this.mMissionPropertiesTable.Controls.Add(this.mRandomMap, 2, 0); this.mMissionPropertiesTable.Controls.Add(this.mRandomWeather, 2, 1); @@ -1440,6 +1469,24 @@ internal class BTGame : DockContent this.resetToolStripMenuItem.Size = new System.Drawing.Size(102, 22); this.resetToolStripMenuItem.Text = "&Reset"; this.resetToolStripMenuItem.Click += new System.EventHandler(resetToolStripMenuItem_Click); + this.mPrintLastMission.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mPrintLastMission.AutoSize = true; + this.mPrintLastMission.Enabled = false; + this.mPrintLastMission.Location = new System.Drawing.Point(529, 61); + this.mPrintLastMission.Name = "mPrintLastMission"; + this.mPrintLastMission.Size = new System.Drawing.Size(107, 23); + this.mPrintLastMission.TabIndex = 11; + this.mPrintLastMission.Text = "Print Last Mission"; + this.mPrintLastMission.UseVisualStyleBackColor = true; + this.mPrintLastMission.Click += new System.EventHandler(mPrintLastMission_Click); + this.mAutoPrint.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mAutoPrint.AutoSize = true; + this.mAutoPrint.Location = new System.Drawing.Point(529, 35); + this.mAutoPrint.Name = "mAutoPrint"; + this.mAutoPrint.Size = new System.Drawing.Size(72, 17); + this.mAutoPrint.TabIndex = 9; + this.mAutoPrint.Text = "Auto Print"; + this.mAutoPrint.UseVisualStyleBackColor = true; this.mAutoTranslocateCheckbox.Anchor = System.Windows.Forms.AnchorStyles.Left; this.mAutoTranslocateCheckbox.AutoSize = true; this.mAutoTranslocateCheckbox.Location = new System.Drawing.Point(529, 6); diff --git a/Console/TeslaConsole.BattleTech/BTMissionResults.cs b/Console/TeslaConsole.BattleTech/BTMissionResults.cs index dc6698f..dd99366 100644 --- a/Console/TeslaConsole.BattleTech/BTMissionResults.cs +++ b/Console/TeslaConsole.BattleTech/BTMissionResults.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing.Printing; using System.IO; using System.IO.Compression; using System.Runtime.Serialization.Formatters.Binary; @@ -9,9 +10,8 @@ namespace TeslaConsole.BattleTech; /// /// The recorded outcome of one BattleTech mission, saved as a gzip'd /// BinaryFormatter blob (.btm) exactly like the RP .rpm mechanism. Mirrors -/// RPMissionResults 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. +/// RPMissionResults minus the football branches (BT battle modes have +/// no teams, so every ranking entry is a single pilot). /// [Serializable] internal class BTMissionResults @@ -104,4 +104,41 @@ internal class BTMissionResults } return num; } + + public BTPlayer[][] GetChartOrder() + { + return GetChartOrder(TimeSpan.MaxValue); + } + + public BTPlayer[][] GetChartOrder(TimeSpan gameTime) + { + List> list = new List>(); + foreach (BTPlayer player in Mission.Players) + { + list.Add(new Tuple(new BTPlayer[1] { player }, GetScore(player, gameTime))); + } + for (int i = 0; i < list.Count - 1; i++) + { + for (int j = i + 1; j < list.Count; j++) + { + if (list[i].B < list[j].B || (list[i].B == list[j].B && string.CompareOrdinal(list[i].A[0].Name, list[j].A[0].Name) > 0)) + { + Tuple value = list[i]; + list[i] = list[j]; + list[j] = value; + } + } + } + List list2 = new List(); + foreach (Tuple item in list) + { + list2.Add(item.A); + } + return list2.ToArray(); + } + + public PrintDocument GetPrintDocument() + { + return new BTPrintDocument(this); + } } diff --git a/Console/TeslaConsole.BattleTech/BTPrintDocument.cs b/Console/TeslaConsole.BattleTech/BTPrintDocument.cs new file mode 100644 index 0000000..00dad51 --- /dev/null +++ b/Console/TeslaConsole.BattleTech/BTPrintDocument.cs @@ -0,0 +1,748 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Printing; +using System.Text; + +namespace TeslaConsole.BattleTech; + +/// +/// The BattleTech score-sheet print document: one landscape page per pilot with +/// the mech portrait, placing, kill/death/damage stats, a randomized +/// mission-highlights narrative (), the pilot-vs-pilot +/// damage matrix, and the place-over-time chart. A close mirror of +/// RPPrintDocument minus the football branches and the RP-only +/// lap/boost/score-zone concepts; the chart marks kills and deaths only. +/// +public class BTPrintDocument : PrintDocument +{ + private const int sThinBorderSize = 3; + + private const int sThickBorderSize = 14; + + private const int sChampherSize = 14; + + private const double sSmallRectWidthDropFraction = 0.5721493440968718; + + private const double sSmallRectHeightDropFraction = 245.0 / 372.0; + + private const string sSansSerifFontName = "Helvetica LT Std"; + + private const string sSerifFontName = "Courier New"; + + private readonly BTMissionResults mMissionResults; + + private readonly BTPlayer[][] mFinalRanking; + + private readonly BTPlayer[] mPrintOrder; + + private int mCurrentPlayer; + + private Dictionary> mPlayerLines; + + private int[] mChartRows; + + private Dictionary mChartCols; + + internal BTPrintDocument(BTMissionResults missionResults) + { + mMissionResults = missionResults; + bool noReturn = false; + foreach (BTPlayer player in mMissionResults.Mission.Players) + { + if (player.Role.Key == "NoReturn") + { + noReturn = true; + break; + } + } + base.DocumentName = string.Format("BattleTech {0} mission at {1} on {2}", noReturn ? "No Return" : "Free For All", mMissionResults.MissionStart.ToLongTimeString(), mMissionResults.MissionStart.ToLongDateString()); + mFinalRanking = mMissionResults.GetChartOrder(); + List list = new List(); + for (int i = 0; i < mFinalRanking.Length; i++) + { + list.Add(mFinalRanking[i][0]); + } + for (int j = 0; j < mFinalRanking.Length; j++) + { + for (int k = 1; k < mFinalRanking[j].Length; k++) + { + list.Add(mFinalRanking[j][k]); + } + } + mPrintOrder = list.ToArray(); + } + + protected override void OnQueryPageSettings(QueryPageSettingsEventArgs e) + { + e.PageSettings.Margins = new Margins(50, 50, 50, 50); + e.PageSettings.Landscape = true; + base.OnQueryPageSettings(e); + } + + protected override void OnBeginPrint(PrintEventArgs e) + { + mCurrentPlayer = 0; + base.OnBeginPrint(e); + } + + protected override void OnPrintPage(PrintPageEventArgs e) + { + BTPlayer bTPlayer = mPrintOrder[mCurrentPlayer]; + List list = new List(); + foreach (BTMissionEvent @event in mMissionResults.Events) + { + if (Contains(@event.InvolvedPilots, bTPlayer)) + { + list.Add(@event); + } + } + for (int i = 0; i < list.Count - 1; i++) + { + for (int j = i + 1; j < list.Count; j++) + { + if (list[i].GameTime > list[j].GameTime) + { + BTMissionEvent value = list[i]; + list[i] = list[j]; + list[j] = value; + } + } + } + int score = mMissionResults.GetScore(bTPlayer); + int finalPlace = mMissionResults.GetFinalPlace(bTPlayer); + Size size = new Size(e.PageBounds.Width - 100, e.PageBounds.Height - 100); + if (size.Width > e.MarginBounds.Width) + { + size.Width = e.MarginBounds.Width; + } + if (size.Height > e.MarginBounds.Height) + { + size.Height = e.MarginBounds.Height; + } + Rectangle rectangle = new Rectangle((e.MarginBounds.Width - size.Width) / 2 + e.MarginBounds.Left, (e.MarginBounds.Height - size.Height) / 2 + e.MarginBounds.Top, size.Width, size.Height); + Rectangle inputRect = rectangle; + inputRect.Inflate(-14, -14); + Rectangle rectangle2 = rectangle; + rectangle2.Inflate(-3, -3); + Rectangle inputRect2 = rectangle2; + inputRect2.Inflate(-14, -14); + Rectangle rectangle3 = rectangle2; + rectangle3.Inflate(-7, -7); + Rectangle inputRect3 = rectangle3; + inputRect3.Inflate(-14, -14); + Rectangle rectangle4 = rectangle2; + rectangle4.Inflate(-14, -14); + Rectangle inputRect4 = rectangle4; + inputRect4.Inflate(-14, -14); + Rectangle rectangle5 = rectangle4; + rectangle5.Inflate(-3, -3); + Rectangle inputRect5 = rectangle5; + inputRect5.Inflate(-14, -14); + int widthDrop = (int)(0.5721493440968718 * (double)rectangle3.Width + 0.5); + int heightDrop = (int)(245.0 / 372.0 * (double)rectangle3.Height + 0.5); + Rectangle rectangle6 = RectDrop(rectangle, widthDrop, heightDrop); + Rectangle rectangle7 = RectDrop(inputRect, widthDrop, heightDrop); + Rectangle rectangle8 = RectDrop(rectangle2, widthDrop, heightDrop); + Rectangle rectangle9 = RectDrop(inputRect2, widthDrop, heightDrop); + Rectangle rectangle10 = RectDrop(rectangle3, widthDrop, heightDrop); + Rectangle rectangle11 = RectDrop(inputRect3, widthDrop, heightDrop); + Rectangle rectangle12 = RectDrop(rectangle4, widthDrop, heightDrop); + Rectangle rectangle13 = RectDrop(inputRect4, widthDrop, heightDrop); + Rectangle rectangle14 = RectDrop(rectangle5, widthDrop, heightDrop); + Rectangle rectangle15 = RectDrop(inputRect5, widthDrop, heightDrop); + using (GraphicsPath graphicsPath = new GraphicsPath()) + { + graphicsPath.AddLine(rectangle.Left, inputRect.Bottom, rectangle.Left, inputRect.Top); + graphicsPath.AddLine(inputRect.Left, rectangle.Top, inputRect.Right, rectangle.Top); + graphicsPath.AddLine(rectangle3.Right, rectangle3.Top, inputRect3.Left, rectangle3.Top); + graphicsPath.AddLine(rectangle3.Left, inputRect3.Top, rectangle3.Left, rectangle3.Bottom); + graphicsPath.StartFigure(); + graphicsPath.AddLine(rectangle3.Left, inputRect5.Bottom, rectangle5.Left, inputRect5.Bottom); + graphicsPath.AddLine(inputRect5.Left, rectangle5.Bottom, inputRect5.Right, rectangle5.Bottom); + graphicsPath.AddLine(rectangle5.Right, inputRect5.Bottom, rectangle5.Right, rectangle3.Top); + graphicsPath.AddLine(rectangle3.Right, rectangle3.Top, rectangle3.Right, inputRect3.Bottom); + graphicsPath.AddLine(inputRect3.Right, rectangle3.Bottom, rectangle3.Left, rectangle3.Bottom); + graphicsPath.StartFigure(); + graphicsPath.AddLine(rectangle10.Left, rectangle15.Bottom, rectangle14.Left, rectangle15.Bottom); + graphicsPath.AddLine(rectangle15.Left, rectangle14.Bottom, rectangle15.Right, rectangle14.Bottom); + graphicsPath.AddLine(rectangle14.Right, rectangle15.Bottom, rectangle14.Right, rectangle10.Top); + graphicsPath.AddLine(rectangle10.Right, rectangle10.Top, rectangle10.Right, rectangle11.Bottom); + graphicsPath.AddLine(rectangle11.Right, rectangle10.Bottom, rectangle10.Left, rectangle10.Bottom); + e.Graphics.FillPath(Brushes.LightGray, graphicsPath); + } + using (GraphicsPath graphicsPath2 = new GraphicsPath()) + { + graphicsPath2.AddLine(rectangle2.Left, inputRect2.Bottom, rectangle.Left, inputRect.Bottom); + graphicsPath2.AddLine(inputRect.Left, rectangle.Bottom, inputRect.Right, rectangle.Bottom); + graphicsPath2.AddLine(rectangle.Right, inputRect.Bottom, rectangle.Right, inputRect.Top); + graphicsPath2.AddLine(inputRect.Right, rectangle.Top, inputRect2.Right, rectangle2.Top); + graphicsPath2.AddLine(inputRect2.Right, rectangle3.Top, rectangle3.Right, inputRect3.Top); + graphicsPath2.AddLine(rectangle3.Right, inputRect3.Bottom, inputRect3.Right, rectangle3.Bottom); + graphicsPath2.AddLine(inputRect2.Left, rectangle3.Bottom, rectangle3.Left, inputRect2.Bottom); + graphicsPath2.StartFigure(); + graphicsPath2.AddLine(rectangle12.Left, rectangle13.Bottom, rectangle14.Left, rectangle15.Bottom); + graphicsPath2.AddLine(rectangle14.Left, rectangle15.Top, rectangle15.Left, rectangle14.Top); + graphicsPath2.AddLine(rectangle15.Right, rectangle14.Top, rectangle14.Right, rectangle15.Top); + graphicsPath2.AddLine(rectangle12.Right, rectangle13.Top, rectangle10.Right, rectangle13.Top); + graphicsPath2.AddLine(rectangle10.Right, rectangle10.Top, rectangle11.Left, rectangle10.Top); + graphicsPath2.AddLine(rectangle10.Left, rectangle11.Top, rectangle10.Left, rectangle13.Bottom); + graphicsPath2.StartFigure(); + graphicsPath2.AddLine(rectangle4.Left, inputRect4.Bottom, rectangle5.Left, inputRect5.Bottom); + graphicsPath2.AddLine(rectangle5.Left, rectangle6.Bottom + 14, inputRect5.Left, rectangle6.Bottom); + graphicsPath2.AddLine(rectangle7.Right, rectangle6.Bottom, rectangle6.Right, rectangle7.Bottom); + graphicsPath2.AddLine(rectangle6.Right, inputRect5.Top, rectangle6.Right + 14, rectangle5.Top); + graphicsPath2.AddLine(inputRect5.Right, rectangle5.Top, rectangle5.Right, inputRect5.Top); + graphicsPath2.AddLine(rectangle4.Right, inputRect4.Top, rectangle3.Right, inputRect4.Top); + graphicsPath2.AddLine(rectangle3.Right, inputRect3.Top, inputRect3.Right, rectangle3.Top); + graphicsPath2.AddLine(rectangle10.Right, rectangle3.Top, rectangle10.Right, rectangle11.Bottom); + graphicsPath2.AddLine(rectangle11.Right, rectangle10.Bottom, inputRect3.Left, rectangle10.Bottom); + graphicsPath2.AddLine(rectangle3.Left, rectangle10.Bottom, rectangle3.Left, inputRect4.Bottom); + e.Graphics.FillPath(Brushes.DarkGray, graphicsPath2); + } + using (GraphicsPath graphicsPath3 = new GraphicsPath()) + { + graphicsPath3.AddLine(rectangle2.Left, inputRect2.Bottom, rectangle2.Left, inputRect2.Top); + graphicsPath3.AddLine(inputRect2.Left, rectangle2.Top, inputRect2.Right, rectangle2.Top); + graphicsPath3.AddLine(rectangle2.Right, inputRect2.Top, rectangle2.Right, inputRect2.Bottom); + graphicsPath3.AddLine(inputRect2.Right, rectangle2.Bottom, inputRect2.Left, rectangle2.Bottom); + graphicsPath3.StartFigure(); + graphicsPath3.AddLine(rectangle12.Left, rectangle13.Bottom, rectangle12.Left, rectangle13.Top); + graphicsPath3.AddLine(rectangle13.Left, rectangle12.Top, rectangle13.Right, rectangle12.Top); + graphicsPath3.AddLine(rectangle12.Right, rectangle13.Top, rectangle12.Right, rectangle13.Bottom); + graphicsPath3.AddLine(rectangle13.Right, rectangle12.Bottom, rectangle13.Left, rectangle12.Bottom); + graphicsPath3.StartFigure(); + graphicsPath3.AddLine(rectangle4.Left, inputRect4.Bottom, rectangle4.Left, rectangle8.Bottom + 14); + graphicsPath3.AddLine(rectangle13.Left, rectangle8.Bottom, rectangle9.Right, rectangle8.Bottom); + graphicsPath3.AddLine(rectangle8.Right, rectangle9.Bottom, rectangle8.Right, inputRect4.Top); + graphicsPath3.AddLine(rectangle8.Right + 14, rectangle4.Top, inputRect4.Right, rectangle4.Top); + graphicsPath3.AddLine(rectangle4.Right, inputRect4.Top, rectangle4.Right, inputRect4.Bottom); + graphicsPath3.AddLine(inputRect4.Right, rectangle4.Bottom, inputRect4.Left, rectangle4.Bottom); + e.Graphics.FillPath(Brushes.Gray, graphicsPath3); + } + using Font font3 = new Font("Helvetica LT Std", 12f); + using Font font = new Font("Helvetica LT Std", 8f); + Rectangle rectangle16 = rectangle14; + rectangle16.X += 175; + rectangle16.Width -= 178; + rectangle16.Y += 58; + rectangle16.Height -= 86; + BTVehicle bTVehicle = (BTConfig.Vehicles.ContainsKey(bTPlayer.VehicleKey) ? BTConfig.Vehicles[bTPlayer.VehicleKey] : null); + Image image = bTVehicle?.Image; + if (image != null) + { + e.Graphics.DrawImage(image, GetCenteredScaledRect(rectangle16, image.Size)); + } + else + { + StringFormat stringFormat = new StringFormat(); + stringFormat.LineAlignment = StringAlignment.Center; + stringFormat.Alignment = StringAlignment.Center; + using (StringFormat format = stringFormat) + { + Rectangle rectangle17 = rectangle16; + rectangle17.Inflate(-20, -20); + e.Graphics.DrawString("The tech crew's holo-imager is down for repairs. Recon imagery could not be obtained for this mech.", font, Brushes.Black, rectangle17, format); + } + e.Graphics.DrawRectangle(Pens.Black, rectangle16); + } + using (Font font2 = new Font("Helvetica LT Std", 18f, FontStyle.Bold)) + { + using StringFormat stringFormat2 = new StringFormat(); + stringFormat2.Alignment = StringAlignment.Center; + stringFormat2.LineAlignment = StringAlignment.Far; + e.Graphics.DrawString(bTPlayer.Name, font2, Brushes.Black, rectangle14.Left + rectangle14.Width / 2, rectangle16.Top - 10, stringFormat2); + } + using (StringFormat stringFormat3 = new StringFormat()) + { + stringFormat3.Alignment = StringAlignment.Center; + e.Graphics.DrawString((bTVehicle != null) ? bTVehicle.Name : bTPlayer.VehicleKey, font3, Brushes.Black, rectangle16.Left + rectangle16.Width / 2, rectangle16.Bottom + 5, stringFormat3); + } + int num = rectangle15.Left - 7; + Rectangle rectangle18 = new Rectangle(num, rectangle16.Top, rectangle16.Left - num, rectangle15.Bottom - rectangle16.Top); + using (Font font4 = new Font("Helvetica LT Std", 16f, FontStyle.Bold)) + { + using StringFormat stringFormat4 = new StringFormat(); + stringFormat4.Alignment = StringAlignment.Center; + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(string.Format("{0}{1} Place", finalPlace, finalPlace switch + { + 3 => "rd", + 2 => "nd", + 1 => "st", + _ => "th", + })); + e.Graphics.DrawString(stringBuilder.ToString(), font4, Brushes.Black, rectangle18, stringFormat4); + } + using (StringFormat stringFormat5 = new StringFormat()) + { + stringFormat5.LineAlignment = StringAlignment.Far; + stringFormat5.SetTabStops(0f, new float[1] { 80f }); + int deaths = 0; + int kills = 0; + GetPlayerStats(out deaths, out kills, bTPlayer, list); + StringBuilder stringBuilder2 = new StringBuilder(); + stringBuilder2.AppendLine($"Score:\t{score}"); + stringBuilder2.AppendLine(); + stringBuilder2.AppendLine($"Deaths:\t{deaths}"); + stringBuilder2.AppendLine(); + stringBuilder2.AppendLine($"Kills:\t{kills}"); + e.Graphics.DrawString(stringBuilder2.ToString(), font, Brushes.Black, rectangle18, stringFormat5); + } + int num2 = rectangle5.Top + 40; + Rectangle rectangle19 = new Rectangle(rectangle6.Right + 5, num2, rectangle5.Right - rectangle6.Right - 10, rectangle6.Bottom - 5 - num2); + using (StringFormat stringFormat6 = new StringFormat()) + { + stringFormat6.Alignment = StringAlignment.Center; + e.Graphics.DrawString("Mission Highlights", font3, Brushes.Black, (rectangle19.Right + rectangle19.Left) / 2, rectangle5.Top + 15, stringFormat6); + } + using (Font font5 = new Font("Courier New", 10f)) + { + string text = mMissionResults.MissionStart.ToString() + " "; + string s = text; + List list2 = new List(); + BTPlayer[][] array = mFinalRanking; + foreach (BTPlayer[] array2 in array) + { + list2.Add(array2[0].Name); + } + string text2 = BTStrings.Recap(list2.ToArray()); + Random random = new Random(); + List list3 = new List(list); + SortedList sortedList = new SortedList(); + string text3 = text + text2; + while (e.Graphics.MeasureString(text3, font5, rectangle19.Width).Height < (float)rectangle19.Height) + { + s = text3; + if (list3.Count < 1) + { + break; + } + int index = random.Next(list3.Count); + BTMissionEvent bTMissionEvent = list3[index]; + list3.RemoveAt(index); + string text4 = null; + if (bTMissionEvent is BTDamagedEvent) + { + BTDamagedEvent bTDamagedEvent = (BTDamagedEvent)bTMissionEvent; + if (bTDamagedEvent.DamageLoss > 0) + { + text4 = ((bTDamagedEvent.DamageLoss < 10) ? BTStrings.LightDamage(bTDamagedEvent.GameTime, bTDamagedEvent.ReportingPilot.Name, BTConfig.VehicleNameOrKey(bTDamagedEvent.ReportingPilot.VehicleKey), bTDamagedEvent.Damager.Name) : ((bTDamagedEvent.DamageLoss >= 100) ? BTStrings.HeavyDamage(bTDamagedEvent.GameTime, bTDamagedEvent.ReportingPilot.Name, BTConfig.VehicleNameOrKey(bTDamagedEvent.ReportingPilot.VehicleKey), bTDamagedEvent.Damager.Name) : BTStrings.ModerateDamage(bTDamagedEvent.GameTime, bTDamagedEvent.ReportingPilot.Name, BTConfig.VehicleNameOrKey(bTDamagedEvent.ReportingPilot.VehicleKey), bTDamagedEvent.Damager.Name))); + } + } + else if (bTMissionEvent is BTKilledEvent) + { + BTKilledEvent bTKilledEvent = (BTKilledEvent)bTMissionEvent; + text4 = ((bTKilledEvent.ReportingPilot != bTKilledEvent.Killer) ? BTStrings.Kill(bTKilledEvent.GameTime, bTKilledEvent.ReportingPilot.Name, BTConfig.VehicleNameOrKey(bTKilledEvent.ReportingPilot.VehicleKey), bTKilledEvent.Killer.Name) : BTStrings.DeathWithoutHonor(bTKilledEvent.GameTime, bTKilledEvent.ReportingPilot.Name, BTConfig.VehicleNameOrKey(bTKilledEvent.ReportingPilot.VehicleKey))); + } + else if (bTMissionEvent is BTDeathWithoutHonorEvent) + { + text4 = BTStrings.DeathWithoutHonor(bTMissionEvent.GameTime, bTMissionEvent.ReportingPilot.Name, BTConfig.VehicleNameOrKey(bTMissionEvent.ReportingPilot.VehicleKey)); + } + if (text4 != null) + { + TimeSpan gameTime; + for (gameTime = bTMissionEvent.GameTime; sortedList.ContainsKey(gameTime); gameTime += TimeSpan.FromTicks(1L)) + { + } + sortedList.Add(gameTime, text4); + } + StringBuilder stringBuilder3 = new StringBuilder(text); + foreach (KeyValuePair item in sortedList) + { + if (stringBuilder3.Length > 0) + { + stringBuilder3.Append(" "); + } + stringBuilder3.Append(item.Value); + } + if (stringBuilder3.Length > 0) + { + stringBuilder3.Append(" "); + } + stringBuilder3.Append(text2); + text3 = stringBuilder3.ToString(); + } + e.Graphics.DrawString(s, font5, Brushes.Black, rectangle19); + } + int num3 = rectangle6.Bottom + 5; + Rectangle rectangle20 = new Rectangle(inputRect5.Left, num3, inputRect5.Width, rectangle5.Bottom - num3); + Rectangle rectangle21 = new Rectangle(rectangle20.Right - 250, rectangle20.Top, 250, rectangle20.Height - 20); + Image image2 = (BTConfig.Maps.ContainsKey(mMissionResults.Mission.MapKey) ? BTConfig.Maps[mMissionResults.Mission.MapKey] : null)?.Image; + if (image2 != null) + { + e.Graphics.DrawImage(image2, GetCenteredScaledRect(rectangle21, image2.Size)); + } + else + { + StringFormat stringFormat7 = new StringFormat(); + stringFormat7.LineAlignment = StringAlignment.Center; + stringFormat7.Alignment = StringAlignment.Center; + using (StringFormat format2 = stringFormat7) + { + Rectangle rectangle22 = rectangle21; + rectangle22.Inflate(-20, -20); + e.Graphics.DrawString("The recon satellite is currently on a transitional orbit. Satellite imagery could not be obtained for this arena.", font, Brushes.Black, rectangle22, format2); + } + e.Graphics.DrawRectangle(Pens.Black, rectangle21); + } + Rectangle rect = new Rectangle(rectangle20.Left, rectangle20.Top, rectangle21.Left - 5 - rectangle20.Left, 92); + Dictionary dictionary = new Dictionary(); + foreach (BTPlayer player in mMissionResults.Mission.Players) + { + if (player != bTPlayer) + { + dictionary.Add(player, new int[4]); + } + } + foreach (BTMissionEvent event2 in mMissionResults.Events) + { + if (event2 is BTKilledEvent) + { + BTKilledEvent bTKilledEvent2 = (BTKilledEvent)event2; + if (bTKilledEvent2.ReportingPilot != bTKilledEvent2.Killer) + { + if (bTKilledEvent2.Killer == bTPlayer) + { + dictionary[bTKilledEvent2.ReportingPilot][3]++; + } + else if (bTKilledEvent2.ReportingPilot == bTPlayer) + { + dictionary[bTKilledEvent2.Killer][2]++; + } + } + } + else + { + if (!(event2 is BTDamagedEvent)) + { + continue; + } + BTDamagedEvent bTDamagedEvent2 = (BTDamagedEvent)event2; + if (bTDamagedEvent2.ReportingPilot != bTDamagedEvent2.Damager) + { + if (bTDamagedEvent2.Damager == bTPlayer) + { + dictionary[bTDamagedEvent2.ReportingPilot][1] += bTDamagedEvent2.DamageLoss; + } + else if (bTDamagedEvent2.ReportingPilot == bTPlayer) + { + dictionary[bTDamagedEvent2.Damager][0] += bTDamagedEvent2.DamageLoss; + } + } + } + } + using (Pen pen = new Pen(Color.Gray, 2f)) + { + using Font font6 = new Font("Helvetica LT Std", 6f, FontStyle.Regular); + using StringFormat stringFormat8 = new StringFormat(); + e.Graphics.DrawRectangle(Pens.Gray, rect); + int num4 = 120; + int num5 = 14; + int num6 = rect.Left + 1; + int num7 = rect.Left + num4; + int num8 = (rect.Width - num4) / Math.Max(1, dictionary.Count); + if (dictionary.Count == 0) + { + e.Graphics.DrawLine(pen, num7, rect.Top, num7, rect.Bottom); + } + int num9 = rect.Bottom; + for (int l = 0; l < 5; l++) + { + num9 -= num5; + e.Graphics.DrawLine(Pens.Gray, rect.Left, num9, rect.Right, num9); + } + int num10 = rect.Top + 1; + stringFormat8.LineAlignment = StringAlignment.Near; + e.Graphics.DrawString("Pilot", font6, Brushes.Black, num6, num10, stringFormat8); + int num11 = rect.Bottom - num5 / 2; + int num12 = num11 - num5; + int num13 = num12 - num5; + int num14 = num13 - num5; + int num15 = num14 - num5; + stringFormat8.LineAlignment = StringAlignment.Center; + e.Graphics.DrawString("Score", font6, Brushes.Black, num6, num15, stringFormat8); + e.Graphics.DrawString("Damage You Received From", font6, Brushes.Black, num6, num14, stringFormat8); + e.Graphics.DrawString("Damage You Inflicted On", font6, Brushes.Black, num6, num13, stringFormat8); + e.Graphics.DrawString("Times You Were Killed By", font6, Brushes.Black, num6, num12, stringFormat8); + e.Graphics.DrawString("Times You Killed", font6, Brushes.Black, num6, num11, stringFormat8); + int num16 = num7; + foreach (KeyValuePair item2 in dictionary) + { + int num17 = num16 + 1; + e.Graphics.DrawLine(pen, num16, rect.Top, num16, rect.Bottom); + stringFormat8.LineAlignment = StringAlignment.Near; + e.Graphics.DrawString(item2.Key.Name, font6, Brushes.Black, num17, num10, stringFormat8); + stringFormat8.LineAlignment = StringAlignment.Center; + e.Graphics.DrawString(mMissionResults.GetScore(item2.Key).ToString(), font6, Brushes.Black, num17, num15, stringFormat8); + e.Graphics.DrawString(item2.Value[0].ToString(), font6, Brushes.Black, num17, num14, stringFormat8); + e.Graphics.DrawString(item2.Value[1].ToString(), font6, Brushes.Black, num17, num13, stringFormat8); + e.Graphics.DrawString(item2.Value[2].ToString(), font6, Brushes.Black, num17, num12, stringFormat8); + e.Graphics.DrawString(item2.Value[3].ToString(), font6, Brushes.Black, num17, num11, stringFormat8); + num16 += num8; + } + } + int num18 = rectangle20.Left + 60; + int num19 = rect.Bottom + 120; + Rectangle rectangle23 = new Rectangle(num18, num19, rect.Right - 75 - num18, rectangle20.Bottom - 85 - num19); + if (mPlayerLines == null) + { + mPlayerLines = new Dictionary>(); + BTPlayer[][] array = mFinalRanking; + foreach (BTPlayer[] array3 in array) + { + BTPlayer[] array4 = array3; + foreach (BTPlayer key in array4) + { + mPlayerLines.Add(key, new List()); + } + } + mChartRows = new int[mPlayerLines.Count]; + float num20 = (float)rectangle23.Height / (float)mPlayerLines.Count; + for (int n = 0; n < mChartRows.Length; n++) + { + mChartRows[n] = (int)(num20 * (float)n + 0.5f) + rectangle23.Top; + } + mChartCols = new Dictionary(); + TimeSpan timeSpan = mMissionResults.ActuallMissionTime.Subtract(TimeSpan.FromSeconds(10.0)); + TimeSpan timeSpan2 = TimeSpan.FromSeconds(15.0); + for (TimeSpan timeSpan3 = timeSpan2; timeSpan3 < timeSpan; timeSpan3 += timeSpan2) + { + mChartCols.Add(timeSpan3, (int)((float)timeSpan3.Ticks / (float)mMissionResults.ActuallMissionTime.Ticks * (float)rectangle23.Width + 0.5f) + rectangle23.Left); + } + int num21 = 0; + array = mFinalRanking; + foreach (BTPlayer[] array5 in array) + { + BTPlayer[] array4 = array5; + foreach (BTPlayer key2 in array4) + { + mPlayerLines[key2].Add(new Point(rectangle23.Left, mChartRows[num21++])); + } + } + foreach (KeyValuePair mChartCol in mChartCols) + { + num21 = 0; + array = mMissionResults.GetChartOrder(mChartCol.Key); + foreach (BTPlayer[] array6 in array) + { + BTPlayer[] array4 = array6; + foreach (BTPlayer key3 in array4) + { + mPlayerLines[key3].Add(new Point(mChartCol.Value, mChartRows[num21++])); + } + } + } + num21 = 0; + array = mFinalRanking; + foreach (BTPlayer[] array7 in array) + { + BTPlayer[] array4 = array7; + foreach (BTPlayer key4 in array4) + { + mPlayerLines[key4].Add(new Point(rectangle23.Right, mChartRows[num21++])); + } + } + } + using (Font font7 = new Font("Helvetica LT Std", 8f)) + { + using (StringFormat stringFormat9 = new StringFormat()) + { + stringFormat9.LineAlignment = StringAlignment.Center; + foreach (KeyValuePair> mPlayerLine in mPlayerLines) + { + if (mPlayerLine.Key != bTPlayer) + { + DrawPlayerLine(e.Graphics, mPlayerLine.Key, mPlayerLine.Value.ToArray(), rectangle23.Right + 5, Pens.LightGray, Brushes.LightGray, font7, stringFormat9); + } + } + DrawPlayerLine(e.Graphics, bTPlayer, mPlayerLines[bTPlayer].ToArray(), rectangle23.Right + 5, Pens.Black, Brushes.Black, font7, stringFormat9); + } + using Pen pen2 = new Pen(Color.Black, 1.5f); + using (Font font8 = new Font("Courier New", 8f)) + { + int num22 = 5; + int num23 = 3; + e.Graphics.DrawLines(pen2, new Point[3] + { + new Point(rectangle23.Left, rectangle23.Top), + new Point(rectangle23.Left, rectangle23.Bottom), + new Point(rectangle23.Right, rectangle23.Bottom) + }); + using StringFormat stringFormat10 = new StringFormat(); + stringFormat10.Alignment = StringAlignment.Far; + stringFormat10.LineAlignment = StringAlignment.Center; + int num24 = 0; + BTPlayer[][] array = mFinalRanking; + foreach (BTPlayer[] array8 in array) + { + int num25 = mChartRows[num24]; + e.Graphics.DrawLine(pen2, rectangle23.Left - num22, num25, rectangle23.Left + num22, num25); + Graphics graphics = e.Graphics; + int num26 = ++num24; + graphics.DrawString(num26.ToString(), font8, Brushes.Black, rectangle23.Left - 10, num25, stringFormat10); + _ = array8; + } + int num27 = rectangle23.Bottom + 23; + stringFormat10.Alignment = StringAlignment.Center; + stringFormat10.LineAlignment = StringAlignment.Near; + e.Graphics.DrawString("0", font8, Brushes.Black, rectangle23.Left, num27, stringFormat10); + foreach (KeyValuePair mChartCol2 in mChartCols) + { + if (mChartCol2.Key.Seconds == 0) + { + e.Graphics.DrawLine(pen2, mChartCol2.Value, rectangle23.Bottom - num22, mChartCol2.Value, rectangle23.Bottom + num22); + e.Graphics.DrawString(mChartCol2.Key.Minutes.ToString(), font8, Brushes.Black, mChartCol2.Value, num27, stringFormat10); + } + else + { + e.Graphics.DrawLine(pen2, mChartCol2.Value, rectangle23.Bottom - num23, mChartCol2.Value, rectangle23.Bottom); + } + } + e.Graphics.DrawLine(pen2, rectangle23.Right, rectangle23.Bottom - num22, rectangle23.Right, rectangle23.Bottom + num22); + e.Graphics.DrawString(((int)(mMissionResults.ActuallMissionTime.TotalMinutes + 0.5)).ToString(), font8, Brushes.Black, rectangle23.Right, num27, stringFormat10); + e.Graphics.DrawString("Time", font, Brushes.Black, (rectangle23.Left + rectangle23.Right) / 2, rectangle23.Bottom + 68, stringFormat10); + stringFormat10.LineAlignment = StringAlignment.Center; + e.Graphics.DrawString("P\r\nl\r\na\r\nc\r\ne", font, Brushes.Black, rectangle23.Left - 54, (rectangle23.Bottom + rectangle23.Top) / 2, stringFormat10); + int num28 = rectangle23.Top - 70; + int num29 = rectangle23.Left + 10; + int num30 = (rectangle23.Right + rectangle23.Left) / 2 - 30; + stringFormat10.Alignment = StringAlignment.Near; + DrawKillMarker(e.Graphics, pen2, num29, num28, 15f); + e.Graphics.DrawString("Kill", font7, Brushes.Black, num29 + 15, num28, stringFormat10); + DrawDeathMarker(e.Graphics, pen2, num30, num28, 15f); + e.Graphics.DrawString("Death", font7, Brushes.Black, num30 + 15, num28, stringFormat10); + } + foreach (BTMissionEvent event3 in mMissionResults.Events) + { + if (event3 is BTKilledEvent) + { + BTKilledEvent bTKilledEvent3 = (BTKilledEvent)event3; + if (bTKilledEvent3.Killer != bTKilledEvent3.ReportingPilot) + { + DrawKillMarker(e.Graphics, (bTKilledEvent3.ReportingPilot == bTPlayer || bTKilledEvent3.Killer == bTPlayer) ? Pens.Black : Pens.LightGray, GetMarkerPosition(bTKilledEvent3.GameTime, mPlayerLines[bTKilledEvent3.Killer], rectangle23), 10f); + } + DrawDeathMarker(e.Graphics, (bTKilledEvent3.ReportingPilot == bTPlayer || bTKilledEvent3.Killer == bTPlayer) ? Pens.Black : Pens.LightGray, GetMarkerPosition(bTKilledEvent3.GameTime, mPlayerLines[bTKilledEvent3.ReportingPilot], rectangle23), 10f); + } + else if (event3 is BTDeathWithoutHonorEvent) + { + DrawDeathMarker(e.Graphics, (event3.ReportingPilot == bTPlayer) ? Pens.Black : Pens.LightGray, GetMarkerPosition(event3.GameTime, mPlayerLines[event3.ReportingPilot], rectangle23), 10f); + } + } + } + e.HasMorePages = ++mCurrentPlayer < mPrintOrder.Length; + base.OnPrintPage(e); + } + + private PointF GetMarkerPosition(TimeSpan time, List chartLine, RectangleF chartArea) + { + TimeSpan timeSpan = TimeSpan.Zero; + TimeSpan timeSpan2 = mMissionResults.ActuallMissionTime; + int num = 0; + foreach (KeyValuePair mChartCol in mChartCols) + { + timeSpan2 = mChartCol.Key; + if (mChartCol.Key < time) + { + num++; + timeSpan = timeSpan2; + continue; + } + break; + } + if (timeSpan == timeSpan2) + { + timeSpan2 = mMissionResults.ActuallMissionTime; + } + float num2 = ((float)time.Ticks - (float)timeSpan.Ticks) / ((float)timeSpan2.Ticks - (float)timeSpan.Ticks); + PointF pointF = chartLine[num]; + PointF pointF2 = chartLine[num + 1]; + return new PointF((pointF2.X - pointF.X) * num2 + pointF.X, (pointF2.Y - pointF.Y) * num2 + pointF.Y); + } + + private static void DrawKillMarker(Graphics graphics, Pen pen, PointF point, float size) + { + DrawKillMarker(graphics, pen, point.X, point.Y, size); + } + + private static void DrawKillMarker(Graphics graphics, Pen pen, float x, float y, float size) + { + float y2 = y + size / 2f; + graphics.DrawPolygon(pen, new PointF[3] + { + new PointF(x - size / 2f, y2), + new PointF(x + size / 2f, y2), + new PointF(x, y - size / 2f) + }); + } + + private static void DrawDeathMarker(Graphics graphics, Pen pen, PointF point, float size) + { + DrawDeathMarker(graphics, pen, point.X, point.Y, size); + } + + private static void DrawDeathMarker(Graphics graphics, Pen pen, float x, float y, float size) + { + graphics.DrawRectangle(pen, x - size / 2f, y - size / 2f, size, size); + } + + private static void DrawPlayerLine(Graphics graphics, BTPlayer player, Point[] points, int playerNameX, Pen pen, Brush brush, Font font, StringFormat format) + { + graphics.DrawLines(pen, points); + graphics.DrawString(player.Name, font, brush, playerNameX, points[points.Length - 1].Y, format); + } + + private static void GetPlayerStats(out int deaths, out int kills, BTPlayer player, IEnumerable relaventEventsSorted) + { + deaths = 0; + kills = 0; + foreach (BTMissionEvent item in relaventEventsSorted) + { + if (item.ReportingPilot == player) + { + if (item is BTKilledEvent || item is BTDeathWithoutHonorEvent) + { + deaths++; + } + } + else if (item is BTKilledEvent && ((BTKilledEvent)item).Killer == player) + { + kills++; + } + } + } + + private static Rectangle RectDrop(Rectangle inputRect, int widthDrop, int heightDrop) + { + return new Rectangle(inputRect.X, inputRect.Y, inputRect.Width - widthDrop, inputRect.Height - heightDrop); + } + + private static Rectangle GetCenteredScaledRect(Rectangle bounds, Size originalSize) + { + float num = (float)bounds.Width / (float)bounds.Height; + float num2 = (float)originalSize.Width / (float)originalSize.Height; + Size size = bounds.Size; + if (num > num2) + { + size.Width = (int)((float)size.Height * num2 + 0.5f); + } + else + { + size.Height = (int)((float)size.Width / num2 + 0.5f); + } + return new Rectangle(bounds.X + (bounds.Width - size.Width) / 2, bounds.Y + (bounds.Height - size.Height) / 2, size.Width, size.Height); + } + + private static bool Contains(IEnumerable players, BTPlayer player) + { + foreach (BTPlayer player2 in players) + { + if (player2 == player) + { + return true; + } + } + return false; + } +} diff --git a/Console/TeslaConsole.BattleTech/BTStrings.cs b/Console/TeslaConsole.BattleTech/BTStrings.cs new file mode 100644 index 0000000..a246995 --- /dev/null +++ b/Console/TeslaConsole.BattleTech/BTStrings.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Windows.Forms; +using System.Xml; + +namespace TeslaConsole.BattleTech; + +/// +/// The BattleTech mission-highlight narrative strings, loaded from +/// BattleTech\BTStrings.xml. Mirrors TeslaConsole.RPStrings: each +/// category is a pool of templates and one is picked at random with the +/// {time}/{player}/{vehicle}/{damager} placeholders substituted. The RP-only +/// categories (score/boost/lap) have no BT counterpart; DeathWithoutHonor +/// replaces SelfKill. +/// +internal class BTStrings +{ + private static bool sInitalized = false; + + private static List sLightDamage = new List(); + + private static List sModerateDamage = new List(); + + private static List sHeavyDamage = new List(); + + private static List sKill = new List(); + + private static List sDeathWithoutHonor = new List(); + + private static List sRecap = new List(); + + private static Random sRandom = new Random(); + + private static void Initalize() + { + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.Load(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "BattleTech\\BTStrings.xml")); + if (xmlDocument.DocumentElement.Name != "BTStrings") + { + throw new XmlException("Document element was not a BTStrings node."); + } + foreach (XmlNode childNode in xmlDocument.DocumentElement.ChildNodes) + { + switch (childNode.Name) + { + case "LightDamage": + if (!sLightDamage.Contains(childNode.InnerText)) + { + sLightDamage.Add(childNode.InnerText); + } + break; + case "ModerateDamage": + if (!sModerateDamage.Contains(childNode.InnerText)) + { + sModerateDamage.Add(childNode.InnerText); + } + break; + case "HeavyDamage": + if (!sHeavyDamage.Contains(childNode.InnerText)) + { + sHeavyDamage.Add(childNode.InnerText); + } + break; + case "Kill": + if (!sKill.Contains(childNode.InnerText)) + { + sKill.Add(childNode.InnerText); + } + break; + case "DeathWithoutHonor": + if (!sDeathWithoutHonor.Contains(childNode.InnerText)) + { + sDeathWithoutHonor.Add(childNode.InnerText); + } + break; + case "Recap": + if (!sRecap.Contains(childNode.InnerText)) + { + sRecap.Add(childNode.InnerText); + } + break; + } + } + sInitalized = true; + } + + private static string GetRandomStringAndReplace(List list, TimeSpan time, string player, string vehicle, string damager) + { + if (!sInitalized) + { + Initalize(); + } + if (list.Count == 0) + { + return null; + } + return list[sRandom.Next(list.Count)].Replace("{time}", GetTimeString(time)).Replace("{player}", player).Replace("{vehicle}", vehicle) + .Replace("{damager}", damager); + } + + public static string GetTimeString(TimeSpan time) + { + int num = (int)(time.TotalSeconds + 0.5); + int num2 = num / 60; + return $"{num2:D2}:{num % 60:D2}"; + } + + public static string LightDamage(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sLightDamage, time, player, vehicle, damager); + } + + public static string ModerateDamage(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sModerateDamage, time, player, vehicle, damager); + } + + public static string HeavyDamage(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sHeavyDamage, time, player, vehicle, damager); + } + + public static string Kill(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sKill, time, player, vehicle, damager); + } + + public static string DeathWithoutHonor(TimeSpan time, string player, string vehicle) + { + return GetRandomStringAndReplace(sDeathWithoutHonor, time, player, vehicle, ""); + } + + public static string Recap(string[] playerRanking) + { + StringBuilder stringBuilder = new StringBuilder(playerRanking[0]); + for (int i = 1; i < playerRanking.Length - 1; i++) + { + stringBuilder.AppendFormat(", {0}", playerRanking[i]); + } + return GetRandomStringAndReplace(sRecap, TimeSpan.Zero, "", "", "").Replace("{winners}", stringBuilder.ToString()).Replace("{looser}", (playerRanking.Length > 1) ? playerRanking[playerRanking.Length - 1] : "nobody"); + } +} diff --git a/Console/TeslaConsole.csproj b/Console/TeslaConsole.csproj index e397835..5e7cdfe 100644 --- a/Console/TeslaConsole.csproj +++ b/Console/TeslaConsole.csproj @@ -31,6 +31,7 @@ + diff --git a/Console/TeslaConsole/TeslaConsoleForm.cs b/Console/TeslaConsole/TeslaConsoleForm.cs index 70e802a..95c7d7c 100644 --- a/Console/TeslaConsole/TeslaConsoleForm.cs +++ b/Console/TeslaConsole/TeslaConsoleForm.cs @@ -51,6 +51,12 @@ public class TeslaConsoleForm : Form private ToolStripMenuItem mPrintRpMission; + private ToolStripMenuItem mBtMissionPrintPreview; + + private ToolStripMenuItem mPrintBtMission; + + private OpenFileDialog mBTMissionOpenDialog; + private ToolStripSeparator mSeperator1; private PrintDialog mRPPrintDialog; @@ -73,10 +79,20 @@ public class TeslaConsoleForm : Form private ToolStripMenuItem exportRedPlanetDefaultsToolStripMenuItem; + private ToolStripMenuItem battleTechDefaultsToolStripMenuItem; + + private ToolStripMenuItem importBattleTechDefaultsToolStripMenuItem; + + private ToolStripMenuItem exportBattleTechDefaultsToolStripMenuItem; + private OpenFileDialog mRPDefaultsOpenDialog; private SaveFileDialog mRPDefaultsSaveDialog; + private OpenFileDialog mBTDefaultsOpenDialog; + + private SaveFileDialog mBTDefaultsSaveDialog; + private ToolStripSeparator toolStripSeparator1; private ToolStripMenuItem enableCustomBitmapsToolStripMenuItem; @@ -91,10 +107,12 @@ public class TeslaConsoleForm : Form { InitializeComponent(); mRpMissionPrintPreview.Visible = false; + mBtMissionPrintPreview.Visible = false; mSeperator1.Visible = false; mExceptionsMenu.Visible = false; enableCustomBitmapsToolStripMenuItem.Checked = PlasmaBitmaps.EnableCustomBitmaps; mRPMissionOpenDialog.InitialDirectory = RPMissionResults.GetRPMissionsDirectory(); + mBTMissionOpenDialog.InitialDirectory = BTMissionResults.GetBTMissionsDirectory(); TeslaConsole.Site.LoadFromStore(); PodManager.StartConfigurationServer(); } @@ -323,6 +341,52 @@ public class TeslaConsoleForm : Form } } + private PrintDocument GetBTMissionPrintDocument() + { + if (mBTMissionOpenDialog.ShowDialog() == DialogResult.OK) + { + try + { + BTMissionResults bTMissionResults = BTMissionResults.Load(mBTMissionOpenDialog.FileName); + return bTMissionResults.GetPrintDocument(); + } + catch (Exception ex) + { + MessageBox.Show("The selected document could not be loaded because it appears to be corrupt.", "Error Loading Mission Data", MessageBoxButtons.OK); + Program.LogException(ex, $"Error Loading BT Mission Results (\"{mBTMissionOpenDialog.FileName}\")."); + } + } + return null; + } + + private void btPrintPreviewToolStripMenuItem_Click(object sender, EventArgs e) + { + PrintDocument bTMissionPrintDocument = GetBTMissionPrintDocument(); + if (bTMissionPrintDocument != null) + { + if (mRPPrintPreviewDialog.IsDisposed) + { + mRPPrintPreviewDialog = new PrintPreviewDialog(); + } + mRPPrintPreviewDialog.Document = bTMissionPrintDocument; + mRPPrintPreviewDialog.Show(); + mRPPrintPreviewDialog.DesktopBounds = Screen.PrimaryScreen.WorkingArea; + } + } + + private void printBtMissionToolStripMenuItem_Click(object sender, EventArgs e) + { + PrintDocument bTMissionPrintDocument = GetBTMissionPrintDocument(); + if (bTMissionPrintDocument != null) + { + mRPPrintDialog.Document = bTMissionPrintDocument; + if (mRPPrintDialog.ShowDialog() == DialogResult.OK) + { + mRPPrintDialog.Document.Print(); + } + } + } + private void TeslaConsoleForm_FormClosing(object sender, FormClosingEventArgs e) { if (mSitePanel != null && !mSitePanel.IsDisposed) @@ -374,6 +438,43 @@ public class TeslaConsoleForm : Form } } + private void changeBattleTechDefaultsToolStripMenuItem_Click(object sender, EventArgs e) + { + BTDefaultsDialog.ShowSingleton(); + } + + private void importBattleTechDefaultsToolStripMenuItem_Click(object sender, EventArgs e) + { + if (mBTDefaultsOpenDialog.ShowDialog() == DialogResult.OK) + { + try + { + BTDefaults.Import(mBTDefaultsOpenDialog.FileName); + } + catch (Exception) + { + MessageBox.Show("This BattleTech defaults file appears to be corrupt.", "Error Loading BattleTech Defaults!", MessageBoxButtons.OK); + return; + } + BTDefaults.Save(); + } + } + + private void exportBattleTechDefaultsToolStripMenuItem_Click(object sender, EventArgs e) + { + if (mBTDefaultsSaveDialog.ShowDialog() == DialogResult.OK) + { + try + { + BTDefaults.Export(mBTDefaultsSaveDialog.FileName); + } + catch (Exception) + { + MessageBox.Show("The BattleTech defaults file could not be exported.", "Error Exporting BattleTech Defaults!", MessageBoxButtons.OK); + } + } + } + private void enableCustomBitmapsToolStripMenuItem_Click(object sender, EventArgs e) { enableCustomBitmapsToolStripMenuItem.Checked = (PlasmaBitmaps.EnableCustomBitmaps = !PlasmaBitmaps.EnableCustomBitmaps); @@ -437,6 +538,8 @@ public class TeslaConsoleForm : Form this.mSitePanelSeparator = new System.Windows.Forms.ToolStripSeparator(); this.mRpMissionPrintPreview = new System.Windows.Forms.ToolStripMenuItem(); this.mPrintRpMission = new System.Windows.Forms.ToolStripMenuItem(); + this.mBtMissionPrintPreview = new System.Windows.Forms.ToolStripMenuItem(); + this.mPrintBtMission = new System.Windows.Forms.ToolStripMenuItem(); this.mSeperator1 = new System.Windows.Forms.ToolStripSeparator(); this.mPlasmaEditor = new System.Windows.Forms.ToolStripMenuItem(); this.mPlasmaFontTool = new System.Windows.Forms.ToolStripMenuItem(); @@ -447,6 +550,9 @@ public class TeslaConsoleForm : Form this.redPlanetDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.importRedPlanetDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.exportRedPlanetDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.battleTechDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.importBattleTechDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportBattleTechDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.enableCustomBitmapsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.mGamesMenu = new System.Windows.Forms.ToolStripMenuItem(); @@ -462,8 +568,11 @@ public class TeslaConsoleForm : Form this.mRPPrintPreviewDialog = new System.Windows.Forms.PrintPreviewDialog(); this.mRPPrintDialog = new System.Windows.Forms.PrintDialog(); this.mRPMissionOpenDialog = new System.Windows.Forms.OpenFileDialog(); + this.mBTMissionOpenDialog = new System.Windows.Forms.OpenFileDialog(); this.mRPDefaultsOpenDialog = new System.Windows.Forms.OpenFileDialog(); this.mRPDefaultsSaveDialog = new System.Windows.Forms.SaveFileDialog(); + this.mBTDefaultsOpenDialog = new System.Windows.Forms.OpenFileDialog(); + this.mBTDefaultsSaveDialog = new System.Windows.Forms.SaveFileDialog(); this.mMenu.SuspendLayout(); base.SuspendLayout(); this.mMainDockPanel.ActiveAutoHideContent = null; @@ -525,7 +634,7 @@ public class TeslaConsoleForm : Form this.mMenu.Size = new System.Drawing.Size(1168, 24); this.mMenu.TabIndex = 3; this.mMenu.Text = "menuStrip1"; - this.mFileMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[10] { this.mShowSitePanelMenuItem, this.mSitePanelSeparator, this.mRpMissionPrintPreview, this.mPrintRpMission, this.mSeperator1, this.mPlasmaEditor, this.mPlasmaFontTool, this.mPlasmaBitmapDecoder, this.mSeperator2, this.mExit }); + this.mFileMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[12] { this.mShowSitePanelMenuItem, this.mSitePanelSeparator, this.mRpMissionPrintPreview, this.mPrintRpMission, this.mBtMissionPrintPreview, this.mPrintBtMission, this.mSeperator1, this.mPlasmaEditor, this.mPlasmaFontTool, this.mPlasmaBitmapDecoder, this.mSeperator2, this.mExit }); this.mFileMenu.Name = "mFileMenu"; this.mFileMenu.Size = new System.Drawing.Size(37, 20); this.mFileMenu.Text = "File"; @@ -543,6 +652,14 @@ public class TeslaConsoleForm : Form this.mPrintRpMission.Size = new System.Drawing.Size(204, 22); this.mPrintRpMission.Text = "Print RP Mission"; this.mPrintRpMission.Click += new System.EventHandler(printToolStripMenuItem_Click); + this.mBtMissionPrintPreview.Name = "mBtMissionPrintPreview"; + this.mBtMissionPrintPreview.Size = new System.Drawing.Size(204, 22); + this.mBtMissionPrintPreview.Text = "BT Mission Print Preview"; + this.mBtMissionPrintPreview.Click += new System.EventHandler(btPrintPreviewToolStripMenuItem_Click); + this.mPrintBtMission.Name = "mPrintBtMission"; + this.mPrintBtMission.Size = new System.Drawing.Size(204, 22); + this.mPrintBtMission.Text = "Print BT Mission"; + this.mPrintBtMission.Click += new System.EventHandler(printBtMissionToolStripMenuItem_Click); this.mSeperator1.Name = "mSeperator1"; this.mSeperator1.Size = new System.Drawing.Size(201, 6); this.mPlasmaEditor.Name = "mPlasmaEditor"; @@ -563,7 +680,7 @@ public class TeslaConsoleForm : Form this.mExit.Size = new System.Drawing.Size(204, 22); this.mExit.Text = "Exit"; this.mExit.Click += new System.EventHandler(mExit_Click); - this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[5] { this.redPlanetDefaultsToolStripMenuItem, this.importRedPlanetDefaultsToolStripMenuItem, this.exportRedPlanetDefaultsToolStripMenuItem, this.toolStripSeparator1, this.enableCustomBitmapsToolStripMenuItem }); + this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[8] { this.redPlanetDefaultsToolStripMenuItem, this.importRedPlanetDefaultsToolStripMenuItem, this.exportRedPlanetDefaultsToolStripMenuItem, this.battleTechDefaultsToolStripMenuItem, this.importBattleTechDefaultsToolStripMenuItem, this.exportBattleTechDefaultsToolStripMenuItem, this.toolStripSeparator1, this.enableCustomBitmapsToolStripMenuItem }); this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); this.settingsToolStripMenuItem.Text = "Settings"; @@ -579,6 +696,18 @@ public class TeslaConsoleForm : Form this.exportRedPlanetDefaultsToolStripMenuItem.Size = new System.Drawing.Size(220, 22); this.exportRedPlanetDefaultsToolStripMenuItem.Text = "&Export Red Planet Defaults"; this.exportRedPlanetDefaultsToolStripMenuItem.Click += new System.EventHandler(exportRedPlanetDefaultsToolStripMenuItem_Click); + this.battleTechDefaultsToolStripMenuItem.Name = "battleTechDefaultsToolStripMenuItem"; + this.battleTechDefaultsToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.battleTechDefaultsToolStripMenuItem.Text = "Change &BattleTech Defaults"; + this.battleTechDefaultsToolStripMenuItem.Click += new System.EventHandler(changeBattleTechDefaultsToolStripMenuItem_Click); + this.importBattleTechDefaultsToolStripMenuItem.Name = "importBattleTechDefaultsToolStripMenuItem"; + this.importBattleTechDefaultsToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.importBattleTechDefaultsToolStripMenuItem.Text = "Import BattleTech Defaults"; + this.importBattleTechDefaultsToolStripMenuItem.Click += new System.EventHandler(importBattleTechDefaultsToolStripMenuItem_Click); + this.exportBattleTechDefaultsToolStripMenuItem.Name = "exportBattleTechDefaultsToolStripMenuItem"; + this.exportBattleTechDefaultsToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.exportBattleTechDefaultsToolStripMenuItem.Text = "Export BattleTech Defaults"; + this.exportBattleTechDefaultsToolStripMenuItem.Click += new System.EventHandler(exportBattleTechDefaultsToolStripMenuItem_Click); this.toolStripSeparator1.Name = "toolStripSeparator1"; this.toolStripSeparator1.Size = new System.Drawing.Size(217, 6); this.enableCustomBitmapsToolStripMenuItem.Name = "enableCustomBitmapsToolStripMenuItem"; @@ -634,8 +763,11 @@ public class TeslaConsoleForm : Form this.mRPPrintPreviewDialog.Visible = false; this.mRPPrintDialog.UseEXDialog = true; this.mRPMissionOpenDialog.Filter = "RP Mission (*.rpm)|*.rpm"; + this.mBTMissionOpenDialog.Filter = "BT Mission (*.btm)|*.btm"; this.mRPDefaultsOpenDialog.Filter = "RP Defaults (*.rpd)|*.rpd"; this.mRPDefaultsSaveDialog.Filter = "RP Defaults (*.rpd)|*.rpd"; + this.mBTDefaultsOpenDialog.Filter = "BT Defaults (*.btd)|*.btd"; + this.mBTDefaultsSaveDialog.Filter = "BT Defaults (*.btd)|*.btd"; base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; base.ClientSize = new System.Drawing.Size(1168, 591); diff --git a/Console/tests/TeslaConsole.DiffTests/BTGoldenEggTests.cs b/Console/tests/TeslaConsole.DiffTests/BTGoldenEggTests.cs index 886d6f9..4a9111c 100644 --- a/Console/tests/TeslaConsole.DiffTests/BTGoldenEggTests.cs +++ b/Console/tests/TeslaConsole.DiffTests/BTGoldenEggTests.cs @@ -197,15 +197,18 @@ namespace TeslaConsole.DiffTests { // No Return is not a separate scenario on the wire: both golden eggs say // scenario=freeforall, and the modes differ only by the pilots' role. + // The model is "noreturn" (the Mac ini tag, resolvable against the + // game's BTL4.RES "NoReturn" resource) — NOT the golden eggs' "noretun", + // which was a 4.10 console bug that dropped a character. var args = (string[])CavernArgs.Clone(); args[13] = "NoReturn"; - args[14] = "noretun"; + args[14] = "noreturn"; var egg = ParseEgg(Generate(args)); Assert.Contains("scenario=freeforall", egg["mission"]); Assert.Contains("role=Role::NoReturn", egg["200.0.0.113"]); Assert.True(egg.ContainsKey("Role::NoReturn"), "Missing [Role::NoReturn] block"); - Assert.Contains("model=noretun", egg["Role::NoReturn"]); + Assert.Contains("model=noreturn", egg["Role::NoReturn"]); Assert.False(egg.ContainsKey("Role::Default"), "Unreferenced [Role::Default] emitted"); } @@ -241,7 +244,7 @@ namespace TeslaConsole.DiffTests Assert.Equal(7, ffa.SelectNodes("badge").Count); Assert.Equal(2, ffa.SelectNodes("role").Count); Assert.Equal("dfltrole", ffa.SelectSingleNode("role[@key='Default']").Attributes["model"].InnerText); - Assert.Equal("noretun", ffa.SelectSingleNode("role[@key='NoReturn']").Attributes["model"].InnerText); + Assert.Equal("noreturn", ffa.SelectSingleNode("role[@key='NoReturn']").Attributes["model"].InnerText); } } } diff --git a/Console/tests/TeslaConsole.DiffTests/CatalogTests.cs b/Console/tests/TeslaConsole.DiffTests/CatalogTests.cs index 28e3844..814005e 100644 --- a/Console/tests/TeslaConsole.DiffTests/CatalogTests.cs +++ b/Console/tests/TeslaConsole.DiffTests/CatalogTests.cs @@ -25,8 +25,8 @@ namespace TeslaConsole.DiffTests => _fx.Recovered.Run("CatalogEntry", new[] { _catalog, launchKey, w, h }); [Fact] - public void Catalog_Has_Three_Products_And_Five_Entries() - => Assert.Equal("products=3;entries=5", + public void Catalog_Has_Four_Products_And_Eight_Entries() + => Assert.Equal("products=4;entries=8", _fx.Recovered.Run("CatalogSummary", new[] { _catalog })); [Fact] @@ -73,5 +73,32 @@ namespace TeslaConsole.DiffTests => Assert.Equal( @"BattleTech Firestorm|cc8500ed-a653-45a7-bef8-c332d30371a6|C:\Games\MW4\launcher.exe||C:\Games\MW4|True", Entry("CC8500ED-A653-45a7-BEF8-C332D30371A6")); + + // BattleTech 4.11 is a new product (no original counterpart); these pin the + // catalog entries the console registers on BT pods. + + [Fact] + public void BattleTech_GameClient_Matches_Expected() + => Assert.Equal( + @"BattleTech 4.11|f4c957fd-72f7-4c5f-8971-28095007e8df|C:\Games\BT411\btl4.exe|-net 1501|C:\Games\BT411|True", + Entry("F4C957FD-72F7-4C5F-8971-28095007E8DF")); + + [Fact] + public void BattleTech_GameClient_With_Resolution_Matches_Expected() + => Assert.Equal( + @"BattleTech 4.11|f4c957fd-72f7-4c5f-8971-28095007e8df|C:\Games\BT411\btl4.exe|-net 1501 -res 1024 768|C:\Games\BT411|True", + Entry("F4C957FD-72F7-4C5F-8971-28095007E8DF", "1024", "768")); + + [Fact] + public void BattleTech_LiveCamera_Matches_Expected() + => Assert.Equal( + @"BattleTech 4.11 LC|d393711a-eda0-48b2-82a0-89df12b768af|C:\Games\BT411\btl4.exe|-net 1501 -lc|C:\Games\BT411|True", + Entry("D393711A-EDA0-48B2-82A0-89DF12B768AF")); + + [Fact] + public void BattleTech_MissionReview_Matches_Expected() + => Assert.Equal( + @"BattleTech 4.11 MR|2e9b8628-9c20-42fb-b070-e9c38d521082|C:\Games\BT411\btl4.exe|-net 1501 -mr|C:\Games\BT411|True", + Entry("2E9B8628-9C20-42FB-B070-E9C38D521082")); } }