From 2e08def73cec419bbb34bd04c30e259d4fb9de28 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 31 Oct 2025 16:27:05 +0000 Subject: [PATCH 01/82] New: Initial MSTS consist parser --- OpenBVE.sln | 22 + source/ObjectViewer/ProgramS.cs | 22 +- source/OpenBVE/System/Loading.cs | 2 +- .../OpenBVE/UserInterface/formMain.Start.cs | 54 +- source/OpenBVE/UserInterface/formMain.cs | 2 +- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 911 ++++++++++++++++++ .../Panel/Enums/CabComponentType.cs | 28 + .../Train.MsTs/Panel/Enums/PanelSubject.cs | 43 + source/Plugins/Train.MsTs/Plugin.cs | 76 ++ .../Train.MsTs/Properties/AssemblyInfo.cs | 35 + source/Plugins/Train.MsTs/Sound/SmsParser.cs | 426 ++++++++ .../Plugins/Train.MsTs/Sound/TriggerTypes.cs | 75 ++ source/Plugins/Train.MsTs/Train.MsTs.csproj | 104 ++ .../Plugins/Train.MsTs/Train/ConsistParser.cs | 325 +++++++ .../Train/Enums/BrakeEquipmentType.cs | 34 + .../Train.MsTs/Train/Enums/BrakeSystemType.cs | 34 + .../Plugins/Train.MsTs/Train/VehicleParser.cs | 612 ++++++++++++ source/Plugins/Train.MsTs/app.config | 15 + source/Plugins/Train.MsTs/packages.config | 9 + source/TrainManager/Motor/MSTS/MotorSound.cs | 12 + .../Power/MSTS/MSTSAccelerationCurve.cs | 124 +++ source/TrainManager/Train/Station.cs | 2 +- source/TrainManager/TrainManager.csproj | 4 + 23 files changed, 2956 insertions(+), 15 deletions(-) create mode 100644 source/Plugins/Train.MsTs/Panel/CvfParser.cs create mode 100644 source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs create mode 100644 source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs create mode 100644 source/Plugins/Train.MsTs/Plugin.cs create mode 100644 source/Plugins/Train.MsTs/Properties/AssemblyInfo.cs create mode 100644 source/Plugins/Train.MsTs/Sound/SmsParser.cs create mode 100644 source/Plugins/Train.MsTs/Sound/TriggerTypes.cs create mode 100644 source/Plugins/Train.MsTs/Train.MsTs.csproj create mode 100644 source/Plugins/Train.MsTs/Train/ConsistParser.cs create mode 100644 source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs create mode 100644 source/Plugins/Train.MsTs/Train/Enums/BrakeSystemType.cs create mode 100644 source/Plugins/Train.MsTs/Train/VehicleParser.cs create mode 100644 source/Plugins/Train.MsTs/app.config create mode 100644 source/Plugins/Train.MsTs/packages.config create mode 100644 source/TrainManager/Motor/MSTS/MotorSound.cs create mode 100644 source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs diff --git a/OpenBVE.sln b/OpenBVE.sln index 0ba02dcc9a..340dd0a23b 100644 --- a/OpenBVE.sln +++ b/OpenBVE.sln @@ -28,6 +28,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenBve", "source\OpenBVE\O {7D0D7673-C77A-4140-A5C6-075D825AC11D} = {7D0D7673-C77A-4140-A5C6-075D825AC11D} {8DAA1CF4-A29A-42CE-8649-34E0FBC0D97C} = {8DAA1CF4-A29A-42CE-8649-34E0FBC0D97C} {A2FC4D71-1ED9-40D4-B746-FE6AB3C7D55E} = {A2FC4D71-1ED9-40D4-B746-FE6AB3C7D55E} + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45} = {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45} {ACAFCA35-01B7-479C-AD5F-9BCE0F8A597B} = {ACAFCA35-01B7-479C-AD5F-9BCE0F8A597B} {B520B1D7-3889-4C88-9E0F-CB96802D5CD1} = {B520B1D7-3889-4C88-9E0F-CB96802D5CD1} {C4BE7A1F-9CCD-4E78-8341-741ABDA8E026} = {C4BE7A1F-9CCD-4E78-8341-741ABDA8E026} @@ -176,6 +177,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Train.OpenBve", "source\Plu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Win32PluginProxy", "source\Plugins\Win32PluginProxy\Win32PluginProxy.csproj", "{B9014791-7170-4DCD-B3F1-2B7518A85C83}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Train.MsTs", "source\Plugins\Train.MsTs\Train.MsTs.csproj", "{A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Route.Bve5", "source\Plugins\Route.Bve5\Route.Bve5.csproj", "{C4BE7A1F-9CCD-4E78-8341-741ABDA8E026}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Formats.OpenBve", "source\Plugins\Formats.OpenBve\Formats.OpenBve.csproj", "{7ED7B285-FAE6-4B34-ACC5-87399F27C8BC}" @@ -859,6 +862,24 @@ Global {B9014791-7170-4DCD-B3F1-2B7518A85C83}.Release|Mixed Platforms.Build.0 = Release|Any CPU {B9014791-7170-4DCD-B3F1-2B7518A85C83}.Release|x86.ActiveCfg = Release|Any CPU {B9014791-7170-4DCD-B3F1-2B7518A85C83}.Release|x86.Build.0 = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Debug|x86.Build.0 = Debug|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.GLMenu|Any CPU.ActiveCfg = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.GLMenu|Any CPU.Build.0 = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.GLMenu|Mixed Platforms.ActiveCfg = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.GLMenu|Mixed Platforms.Build.0 = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.GLMenu|x86.ActiveCfg = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.GLMenu|x86.Build.0 = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Release|Any CPU.Build.0 = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Release|x86.ActiveCfg = Release|Any CPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45}.Release|x86.Build.0 = Release|Any CPU {C4BE7A1F-9CCD-4E78-8341-741ABDA8E026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C4BE7A1F-9CCD-4E78-8341-741ABDA8E026}.Debug|Any CPU.Build.0 = Debug|Any CPU {C4BE7A1F-9CCD-4E78-8341-741ABDA8E026}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -930,6 +951,7 @@ Global {7D0D7673-C77A-4140-A5C6-075D825AC11D} = {D75531B7-000E-432E-A168-51846256A9D1} {D3710390-CD0E-4E14-8E4F-80302D797D5E} = {F49789F2-97F3-45B3-BC85-F4E09C0D120D} {B9014791-7170-4DCD-B3F1-2B7518A85C83} = {F49789F2-97F3-45B3-BC85-F4E09C0D120D} + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45} = {F49789F2-97F3-45B3-BC85-F4E09C0D120D} {C4BE7A1F-9CCD-4E78-8341-741ABDA8E026} = {D75531B7-000E-432E-A168-51846256A9D1} {7ED7B285-FAE6-4B34-ACC5-87399F27C8BC} = {16553295-E70F-4596-AA78-848EEA576C4A} EndGlobalSection diff --git a/source/ObjectViewer/ProgramS.cs b/source/ObjectViewer/ProgramS.cs index 809267a014..d48b6330ec 100644 --- a/source/ObjectViewer/ProgramS.cs +++ b/source/ObjectViewer/ProgramS.cs @@ -315,18 +315,22 @@ internal static void RefreshObjects() { try { - if(Files[i].EndsWith(".dat", StringComparison.InvariantCultureIgnoreCase) || Files[i].EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase) || Files[i].EndsWith(".cfg", StringComparison.InvariantCultureIgnoreCase)) + if(Files[i].EndsWith(".dat", StringComparison.InvariantCultureIgnoreCase) || Files[i].EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase) || Files[i].EndsWith(".cfg", StringComparison.InvariantCultureIgnoreCase) || Files[i].EndsWith(".con", StringComparison.InvariantCultureIgnoreCase)) { - string currentTrainFolder = Path.GetDirectoryName(Files[i]); + string currentTrain = Files[i]; + if (currentTrain.EndsWith("extensions.cfg", StringComparison.InvariantCultureIgnoreCase)) + { + currentTrain = System.IO.Path.GetDirectoryName(currentTrain); + } bool canLoad = false; for (int j = 0; j < Program.CurrentHost.Plugins.Length; j++) { - if (Program.CurrentHost.Plugins[j].Train != null && Program.CurrentHost.Plugins[j].Train.CanLoadTrain(currentTrainFolder)) + if (Program.CurrentHost.Plugins[j].Train != null && Program.CurrentHost.Plugins[j].Train.CanLoadTrain(currentTrain)) { Control[] dummyControls = new Control[0]; TrainManager.Trains = new List { new TrainBase(TrainState.Available, TrainType.LocalPlayerTrain) }; AbstractTrain playerTrain = TrainManager.Trains[0]; - Program.CurrentHost.Plugins[j].Train.LoadTrain(Encoding.UTF8, currentTrainFolder, ref playerTrain, ref dummyControls); + Program.CurrentHost.Plugins[j].Train.LoadTrain(Encoding.UTF8, currentTrain, ref playerTrain, ref dummyControls); TrainManager.PlayerTrain = TrainManager.Trains[0]; canLoad = true; break; @@ -423,7 +427,7 @@ internal static void KeyDown(object sender, KeyboardKeyEventArgs e) { CheckFileExists = true, Multiselect = true, - Filter = @"All supported object files|*.csv;*.b3d;*.x;*.animated;extensions.cfg;*.l3dobj;*.l3dgrp;*.obj;*.s;train.xml|openBVE Objects|*.csv;*.b3d;*.x;*.animated;extensions.cfg;train.xml|LokSim 3D Objects|*.l3dobj;*.l3dgrp|Wavefront Objects|*.obj|Microsoft Train Simulator Objects|*.s|All files|*" + Filter = @"All supported object files|*.csv;*.b3d;*.x;*.animated;extensions.cfg;*.l3dobj;*.l3dgrp;*.obj;*.s;train.xml;*.con|openBVE Objects|*.csv;*.b3d;*.x;*.animated;extensions.cfg;train.xml|LokSim 3D Objects|*.l3dobj;*.l3dgrp|Wavefront Objects|*.obj|Microsoft Train Simulator Files|*.s;*.con|All files|*" }; if (Dialog.ShowDialog() == DialogResult.OK) { @@ -431,11 +435,11 @@ internal static void KeyDown(object sender, KeyboardKeyEventArgs e) string[] f = Dialog.FileNames; for (int i = 0; i < f.Length; i++) { - string currentTrainFolder = string.Empty; - if(f[i].EndsWith(".dat", StringComparison.InvariantCultureIgnoreCase) || f[i].EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase) || f[i].EndsWith(".cfg", StringComparison.InvariantCultureIgnoreCase)) + string currentTrain = string.Empty; + if(f[i].EndsWith(".dat", StringComparison.InvariantCultureIgnoreCase) || f[i].EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase) || f[i].EndsWith(".cfg", StringComparison.InvariantCultureIgnoreCase) || f[i].EndsWith(".con", StringComparison.InvariantCultureIgnoreCase)) { // only check to see if it's a train if this is a specified filetype, else we'll start loading the full train from an object in it's folder - currentTrainFolder = Path.GetDirectoryName(f[i]); + currentTrain = f[i].EndsWith(".con", StringComparison.InvariantCultureIgnoreCase) ? f[i] : Path.GetDirectoryName(f[i]); } for (int j = 0; j < Program.CurrentHost.Plugins.Length; j++) { @@ -453,7 +457,7 @@ internal static void KeyDown(object sender, KeyboardKeyEventArgs e) { Files.Add(f[i]); } - if (!string.IsNullOrEmpty(currentTrainFolder) && Program.CurrentHost.Plugins[j].Train != null && Program.CurrentHost.Plugins[j].Train.CanLoadTrain(currentTrainFolder)) + if (!string.IsNullOrEmpty(currentTrain) && Program.CurrentHost.Plugins[j].Train != null && Program.CurrentHost.Plugins[j].Train.CanLoadTrain(currentTrain)) { Files.Add(f[i]); } diff --git a/source/OpenBVE/System/Loading.cs b/source/OpenBVE/System/Loading.cs index e098eded14..78187f2a96 100644 --- a/source/OpenBVE/System/Loading.cs +++ b/source/OpenBVE/System/Loading.cs @@ -472,7 +472,7 @@ private static void LoadEverythingThreaded() { // load plugin for (int i = 0; i < Program.TrainManager.Trains.Count; i++) { if ( Program.TrainManager.Trains[i].State != TrainState.Bogus) { - if ( Program.TrainManager.Trains[i].IsPlayerTrain) { + if (Program.TrainManager.Trains[i].IsPlayerTrain && !string.IsNullOrEmpty(Program.TrainManager.Trains[i].TrainFolder)) { if (Program.TrainManager.Trains[i].Plugin == null && !Program.TrainManager.Trains[i].LoadCustomPlugin(Program.TrainManager.Trains[i].TrainFolder, CurrentTrainEncoding)) { Program.TrainManager.Trains[i].LoadDefaultPlugin(Program.TrainManager.Trains[i].TrainFolder); } diff --git a/source/OpenBVE/UserInterface/formMain.Start.cs b/source/OpenBVE/UserInterface/formMain.Start.cs index 04fc6d208d..54f4622e78 100644 --- a/source/OpenBVE/UserInterface/formMain.Start.cs +++ b/source/OpenBVE/UserInterface/formMain.Start.cs @@ -15,6 +15,7 @@ using OpenBveApi.Interface; using OpenBveApi.Routes; using RouteManager2; +using TrainManager.SafetySystems; using Path = OpenBveApi.Path; namespace OpenBve @@ -660,6 +661,11 @@ private void OnTrainFolderChanged(object sender, EventArgs e) /// Whether this is a packaged content folder private void PopulateTrainList(string selectedFolder, ListView listView, bool packages) { + string error; //ignored in this case, background thread + if (Program.CurrentHost.Plugins == null && !Program.CurrentHost.LoadPlugins(Program.FileSystem, Interface.CurrentOptions, out error, Program.TrainManager, Program.Renderer)) + { + throw new Exception("Unable to load the required plugins- Please reinstall OpenBVE"); + } try { if (selectedFolder.Length == 0) @@ -717,7 +723,9 @@ private void PopulateTrainList(string selectedFolder, ListView listView, bool pa try { string[] Folders = Directory.GetDirectories(selectedFolder); + string[] Files = Directory.GetFiles(Folder); Array.Sort(Folders); + Array.Sort(Files); for (int i = 0; i < Folders.Length; i++) { try @@ -744,6 +752,19 @@ private void PopulateTrainList(string selectedFolder, ListView listView, bool pa //Most likely permissions } } + + for (int i = 0; i < Files.Length; i++) + { + for (int j = 0; j < Program.CurrentHost.Plugins.Length; j++) + { + if (Program.CurrentHost.Plugins[j].Train != null && Program.CurrentHost.Plugins[j].Train.CanLoadTrain(Files[i])) + { + ListViewItem Item = listviewTrainFolders.Items.Add(System.IO.Path.GetFileName(Files[i])); + Item.ImageKey = "train"; + Item.Tag = Files[i]; + } + } + } } catch { @@ -776,7 +797,13 @@ private void listviewTrainFolders_SelectedIndexChanged(object sender, EventArgs return; } if (t != null) { - if (Directory.Exists(t)) { + if (t.EndsWith(".con", StringComparison.InvariantCultureIgnoreCase)) + { + Result.TrainFolder = t; + ShowTrain(false); + } + else if (Directory.Exists(t)) + { try { string File = Path.CombineFile(t, "train.dat"); @@ -1106,8 +1133,29 @@ private void buttonTrainEncodingBig5_Click(object sender, EventArgs e) { private readonly object StartGame = new Object(); private void buttonStart_Click(object sender, EventArgs e) { - if (Result.RouteFile != null & Result.TrainFolder != null) { - if (File.Exists(Result.RouteFile) & Directory.Exists(Result.TrainFolder)) { + if (Result.RouteFile != null & Result.TrainFolder != null) + { + bool canLoadRoute = false, canLoadTrain = false; + for (int i = 0; i < Program.CurrentHost.Plugins.Length; i++) + { + if (canLoadRoute == false) + { + if (Program.CurrentHost.Plugins[i].Route != null && Program.CurrentHost.Plugins[i].Route.CanLoadRoute(Result.RouteFile)) + { + canLoadRoute = true; + } + } + if (canLoadTrain == false) + { + if (Program.CurrentHost.Plugins[i].Train != null && Program.CurrentHost.Plugins[i].Train.CanLoadTrain(Result.TrainFolder)) + { + canLoadTrain = true; + } + } + } + + if (canLoadRoute && canLoadTrain) + { Result.Start = true; buttonClose_Click(StartGame, e); //HACK: Call Application.DoEvents() to force the message pump to process all pending messages when the form closes diff --git a/source/OpenBVE/UserInterface/formMain.cs b/source/OpenBVE/UserInterface/formMain.cs index 5b3fd77f99..deab2b1cc9 100644 --- a/source/OpenBVE/UserInterface/formMain.cs +++ b/source/OpenBVE/UserInterface/formMain.cs @@ -1198,7 +1198,7 @@ private void formMain_FormClosing() string[] a = new string[Interface.CurrentOptions.RecentlyUsedTrains.Length]; for (int i = 0; i < Interface.CurrentOptions.RecentlyUsedTrains.Length; i++) { - if (Directory.Exists(Interface.CurrentOptions.RecentlyUsedTrains[i])) + if ((Interface.CurrentOptions.RecentlyUsedTrains[i].EndsWith(".con", StringComparison.InvariantCultureIgnoreCase) && File.Exists(Interface.CurrentOptions.RecentlyUsedTrains[i])) || Directory.Exists(Interface.CurrentOptions.RecentlyUsedTrains[i])) { a[n] = Interface.CurrentOptions.RecentlyUsedTrains[i]; n++; diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs new file mode 100644 index 0000000000..365de22fc5 --- /dev/null +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -0,0 +1,911 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using LibRender2.Trains; +using OpenBve.Formats.MsTs; +using OpenBveApi.Colors; +using OpenBveApi.FunctionScripting; +using OpenBveApi.Interface; +using OpenBveApi.Math; +using OpenBveApi.Objects; +using OpenBveApi.Textures; +using SharpCompress.Compressors; +using SharpCompress.Compressors.Deflate; +using TrainManager.Car; +using TrainManager.Trains; + +namespace Train.MsTs +{ + class CabviewFileParser + { + // constants + private const double stackDistance = 0.000001; + + /// EyeDistance is required to be 1.0 by UpdateCarSectionElement and by UpdateCameraRestriction, thus cannot be easily changed. + private const double eyeDistance = 1.0; + + private static string currentFolder; + + private static readonly List cabComponents = new List(); + + // parse panel config + internal static bool ParseCabViewFile(string fileName, ref CarBase Car) + { + currentFolder = Path.GetDirectoryName(fileName); + Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read); + + byte[] buffer = new byte[34]; + fb.Read(buffer, 0, 2); + + bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE); + + string headerString; + if (unicode) + { + fb.Read(buffer, 0, 32); + headerString = Encoding.Unicode.GetString(buffer, 0, 16); + } + else + { + fb.Read(buffer, 2, 14); + headerString = Encoding.ASCII.GetString(buffer, 0, 8); + } + + // SIMISA@F means compressed + // SIMISA@@ means uncompressed + if (headerString.StartsWith("SIMISA@F")) + { + fb = new ZlibStream(fb, CompressionMode.Decompress); + } + else if (headerString.StartsWith("\r\nSIMISA")) + { + // ie us1rd2l1000r10d.s, we are going to allow this but warn + Console.Error.WriteLine("Improper header in " + fileName); + fb.Read(buffer, 0, 4); + } + else if (!headerString.StartsWith("SIMISA@@")) + { + Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognized cabview file header " + headerString + " in " + fileName); + return false; + } + + string subHeader; + if (unicode) + { + fb.Read(buffer, 0, 32); + subHeader = Encoding.Unicode.GetString(buffer, 0, 16); + } + else + { + fb.Read(buffer, 0, 16); + subHeader = Encoding.ASCII.GetString(buffer, 0, 8); + } + + if (subHeader[7] == 't') + { + using (BinaryReader reader = new BinaryReader(fb)) + { + byte[] newBytes = reader.ReadBytes((int) (fb.Length - fb.Position)); + string s; + if (unicode) + { + s = Encoding.Unicode.GetString(newBytes); + } + else + { + s = Encoding.ASCII.GetString(newBytes); + } + TextualBlock block = new TextualBlock(s, KujuTokenID.Tr_CabViewFile); + ParseBlock(block); + } + + } + else if (subHeader[7] != 'b') + { + Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognized subHeader " + subHeader + " in " + fileName); + return false; + } + else + { + using (BinaryReader reader = new BinaryReader(fb)) + { + KujuTokenID currentToken = (KujuTokenID) reader.ReadUInt16(); + if (currentToken != KujuTokenID.Tr_CabViewFile) + { + throw new Exception(); //Shape definition + } + + reader.ReadUInt16(); + uint remainingBytes = reader.ReadUInt32(); + byte[] newBytes = reader.ReadBytes((int) remainingBytes); + BinaryBlock block = new BinaryBlock(newBytes, KujuTokenID.Tr_CabViewFile); + ParseBlock(block); + } + } + + //Create panel + //Create camera restriction + double WorldWidth, WorldHeight; + if (Plugin.Renderer.Screen.Width >= Plugin.Renderer.Screen.Height) + { + WorldWidth = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.HorizontalViewingAngle) * eyeDistance; + WorldHeight = WorldWidth / Plugin.Renderer.Screen.AspectRatio; + } + else + { + WorldHeight = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.VerticalViewingAngle) * eyeDistance / Plugin.Renderer.Screen.AspectRatio; + WorldWidth = WorldHeight * Plugin.Renderer.Screen.AspectRatio; + } + + double x0 = (PanelLeft - PanelCenter.X) / PanelResolution; + double x1 = (PanelRight - PanelCenter.X) / PanelResolution; + double y0 = (PanelCenter.Y - PanelBottom) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; + double y1 = (PanelCenter.Y - PanelTop) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; + Car.CameraRestriction.BottomLeft = new Vector3(x0 * WorldWidth, y0 * WorldHeight, eyeDistance); + Car.CameraRestriction.TopRight = new Vector3(x1 * WorldWidth, y1 * WorldHeight, eyeDistance); + Car.DriverYaw = Math.Atan((PanelCenter.X - PanelOrigin.X) * WorldWidth / PanelResolution); + Car.DriverPitch = Math.Atan((PanelOrigin.Y - PanelCenter.Y) * WorldWidth / PanelResolution); + + if (File.Exists(CabViews[0].fileName)) + { + Car.Driver = CabViews[0].position; + Plugin.currentHost.RegisterTexture(CabViews[0].fileName, new TextureParameters(null, null), out Texture tday, true); + PanelBitmapWidth = tday.Width; + PanelBitmapHeight = tday.Height; + CreateElement(ref Car.CarSections[0].Groups[0], 0.0, 0.0, 1024, 768, new Vector2(0.5, 0.5), 0.0, Car.Driver, tday, null, new Color32(255, 255, 255, 255)); + } + else + { + //Main panel image doesn't exist + return false; + } + + int currentLayer = 1; + for (int i = 0; i < cabComponents.Count; i++) + { + cabComponents[i].Create(ref Car, currentLayer, fileName); + + } + + return true; + } + + internal struct CabView + { + internal string fileName; + internal Vector2 topLeft; + internal Vector2 panelSize; + internal Vector3 position; + internal Vector3 direction; + + internal void setCabView(string cabViewFile) + { + cabViewFile = cabViewFile.Replace(@"\\", @"\"); + fileName = OpenBveApi.Path.CombineFile(currentFolder, cabViewFile); + } + } + + private static CabView currentCabView; + + private static List CabViews = new List(); + + private static void ParseBlock(Block block) + { + Block newBlock; + switch (block.Token) + { + case KujuTokenID.CabViewControls: + int controlCount = block.ReadInt16(); + while (controlCount > 0) + { + newBlock = block.ReadSubBlock(); + Component currentComponent = new Component(newBlock); + currentComponent.Parse(); + cabComponents.Add(currentComponent); + controlCount--; + } + + break; + case KujuTokenID.Direction: + currentCabView.direction.X = block.ReadSingle(); + currentCabView.direction.Y = block.ReadSingle(); + currentCabView.direction.Z = block.ReadSingle(); + break; + case KujuTokenID.Position: + currentCabView.position.X = block.ReadSingle(); + currentCabView.position.Y = block.ReadSingle(); + currentCabView.position.Z = block.ReadSingle(); + break; + case KujuTokenID.CabViewWindow: + currentCabView.topLeft.X = block.ReadInt16(); + currentCabView.topLeft.Y = block.ReadInt16(); + currentCabView.panelSize.X = block.ReadInt16(); + currentCabView.panelSize.Y = block.ReadInt16(); + break; + case KujuTokenID.CabViewFile: + case KujuTokenID.CabViewWindowFile: + if (string.IsNullOrEmpty(currentCabView.fileName)) + { + currentCabView.setCabView(block.ReadString()); + } + + break; + case KujuTokenID.CabViewType: + case KujuTokenID.EngineData: + block.Skip((int) block.Length()); + break; + case KujuTokenID.Tr_CabViewFile: + newBlock = block.ReadSubBlock(KujuTokenID.CabViewType); + ParseBlock(newBlock); + //The main front cabview + newBlock = block.ReadSubBlock(KujuTokenID.CabViewFile); + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.CabViewWindow); + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.CabViewWindowFile); //Appears to be a duplicate of CabViewFile, some are empty or v/v + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.Position); //Position within loco X,Y,Z + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT X, ROT Y, ROT Z ?? + ParseBlock(newBlock); + CabViews.Add(currentCabView); + currentCabView = new CabView(); + //View #2, normally L + newBlock = block.ReadSubBlock(KujuTokenID.CabViewFile); + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.CabViewWindow); + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.CabViewWindowFile); //Appears to be a duplicate of CabViewFile, some are empty or v/v + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.Position); //Position within loco X,Y,Z + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT X, ROT Y, ROT Z ?? + ParseBlock(newBlock); + CabViews.Add(currentCabView); + currentCabView = new CabView(); + //View #3, normally R + newBlock = block.ReadSubBlock(KujuTokenID.CabViewFile); + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.CabViewWindow); + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.CabViewWindowFile); //Appears to be a duplicate of CabViewFile, some are empty or v/v + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.Position); //Position within loco X,Y,Z + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT X, ROT Y, ROT Z ?? + ParseBlock(newBlock); + CabViews.Add(currentCabView); + newBlock = block.ReadSubBlock(KujuTokenID.EngineData); + ParseBlock(newBlock); + newBlock = block.ReadSubBlock(KujuTokenID.CabViewControls); + ParseBlock(newBlock); + break; + } + } + + static double PanelResolution = 1024.0; + static double PanelLeft = 0.0, PanelRight = 1024.0; + static double PanelTop = 0.0, PanelBottom = 768.0; + static Vector2 PanelCenter = new Vector2(0, 240); + private static Vector2 PanelOrigin = new Vector2(0, 240); + static double PanelBitmapWidth = 640.0, PanelBitmapHeight = 480.0; + + + + private class Component + { + private CabComponentType Type = CabComponentType.None; + private string TexturePath; + private PanelSubject _panelSubject; + private string Units; + private Vector2 Position = new Vector2(0, 0); + private Vector2 Size = new Vector2(0, 0); + private double PivotPoint; + private double InitialAngle; + private double LastAngle; + private double Maximum; + private double Minimum; + private int TotalFrames; + private int HorizontalFrames; + private int VerticalFrames; + private bool MouseControl; + + internal void Parse() + { + Block newBlock; + if (!Enum.TryParse(myBlock.Token.ToString(), true, out Type)) + { + Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognised CabViewComponent type."); + return; + } + + while (myBlock.Position() < myBlock.Length() - 2) + { + //Components in CVF files are considerably less structured, so read *any* valid block + try + { + newBlock = myBlock.ReadSubBlock(); + ReadSubBlock(newBlock); + } + catch + { + break; + } + + } + } + + internal void Create(ref CarBase Car, int Layer, string fileName) + { + if (File.Exists(TexturePath) && Units != null) + { + //Create and register texture + + //Create element + double rW = 1024.0 / 640.0; + double rH = 768.0 / 480.0; + int wday, hday; + int j; + string f; + CultureInfo Culture = CultureInfo.InvariantCulture; + switch (Type) + { + case CabComponentType.Dial: + Texture tday; + Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(null, null), out tday, true); + //Get final position from the 640px panel (Yuck...) + Position.X *= rW; + Position.Y *= rH; + Size.X *= rW; + Size.Y *= rH; + PivotPoint *= rH; + j = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X, Size.Y, new Vector2((0.5 * Size.X) / (tday.Width * rW), PivotPoint / (tday.Height * rH)), Layer * stackDistance, Car.Driver, tday, null, new Color32(255, 255, 255, 255)); + Car.CarSections[0].Groups[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0); + Car.CarSections[0].Groups[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0); + Car.CarSections[0].Groups[0].Elements[j].RotateYDirection = Vector3.Cross(Car.CarSections[0].Groups[0].Elements[j].RotateZDirection, Car.CarSections[0].Groups[0].Elements[j].RotateXDirection); + f = GetStackLanguageFromSubject(Car.baseTrain, Units, "Dial " + " in " + fileName); + InitialAngle -= 360; + InitialAngle *= 0.0174532925199433; //degrees to radians + LastAngle *= 0.0174532925199433; + double a0 = (InitialAngle * Maximum - LastAngle * Minimum) / (Maximum - Minimum); + double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum); + f += " " + a1.ToString(Culture) + " * " + a0.ToString(Culture) + " +"; + Car.CarSections[0].Groups[0].Elements[j].RotateZFunction = new FunctionScript(Plugin.currentHost, f, false); + //MSTS cab dials are backstopped as standard + Car.CarSections[0].Groups[0].Elements[j].RotateZFunction.Minimum = InitialAngle; + Car.CarSections[0].Groups[0].Elements[j].RotateZFunction.Maximum = LastAngle; + break; + case CabComponentType.Lever: + /* + * TODO: + * Need to revisit the actual position versus frame with MSTS content. + * + * Take the example of the stock Class 50 + * This has a notched brake handle, with 5 physical notches + * + * The cabview has 12 frames for these 5 notches, which appear to be mapped using NumPositions + * Oddly, all frames appear to be distinct. Need to check OR + MSTS handling + * Suspect there's a notch delay or something that should use these. + */ + Position.X *= rW; + Position.Y *= rH; + Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + if (wday > 0 & hday > 0) + { + Texture[] textures = new Texture[TotalFrames]; + int row = 0; + int column = 0; + int frameWidth = wday / HorizontalFrames; + int frameHeight = hday / VerticalFrames; + for (int k = 0; k < TotalFrames; k++) + { + Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); + if (column < HorizontalFrames - 1) + { + column++; + } + else + { + column = 0; + row++; + } + } + + j = -1; + for (int k = 0; k < textures.Length; k++) + { + int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + if (k == 0) j = l; + } + + f = GetStackLanguageFromSubject(Car.baseTrain, Units, "Lever " + " in " + fileName); + Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); + } + + break; + case CabComponentType.TriState: + case CabComponentType.TwoState: + Position.X *= rW; + Position.Y *= rH; + Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + if (wday > 0 & hday > 0) + { + Texture[] textures = new Texture[TotalFrames]; + int row = 0; + int column = 0; + int frameWidth = wday / HorizontalFrames; + int frameHeight = hday / VerticalFrames; + for (int k = 0; k < TotalFrames; k++) + { + Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); + if (column < HorizontalFrames - 1) + { + column++; + } + else + { + column = 0; + row++; + } + } + + j = -1; + for (int k = 0; k < textures.Length; k++) + { + int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + if (k == 0) j = l; + } + + f = Type == CabComponentType.TwoState ? GetStackLanguageFromSubject(Car.baseTrain, Units, "TwoState " + " in " + fileName) : GetStackLanguageFromSubject(Car.baseTrain, Units, "TriState " + " in " + fileName); + + Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); + } + + break; + } + } + } + + private void ReadSubBlock(Block block) + { + switch (block.Token) + { + case KujuTokenID.Pivot: + PivotPoint = block.ReadSingle(); + break; + case KujuTokenID.Units: + string u = block.ReadString(); + switch (u.ToLowerInvariant()) + { + case "amps": + Units = "motor"; + break; + case "miles_per_hour": + Units = "mph"; + break; + case "inches_of_mercury": + case "psi": + //We don't simulate vaccum brakes, so just hook to PSI if vaccum is declared + switch (_panelSubject) + { + case PanelSubject.BrakeCylinder: + Units = "bc_psi"; + break; + case PanelSubject.BrakePipe: + Units = "bp_psi"; + break; + } + + break; + } + + break; + case KujuTokenID.ScalePos: + InitialAngle = block.ReadSingle(); + LastAngle = block.ReadSingle(); + break; + case KujuTokenID.ScaleRange: + if (_panelSubject == PanelSubject.Ammeter) + { + //As we're currently using the BVE ammeter hack, ignore the values + Minimum = 0; + Maximum = 1; + block.Skip((int) block.Length()); + } + + Minimum = block.ReadSingle(); + Maximum = block.ReadSingle(); + break; + case KujuTokenID.States: + //Contains sub-blocks with Style and SwitchVal types + //Doesn't appear to have any frame data + //Examining image appears to show 1-frame V strip + block.Skip((int) block.Length()); + break; + case KujuTokenID.DirIncrease: + //Do we start at 0 or max? + block.Skip((int) block.Length()); + break; + case KujuTokenID.Orientation: + //Flip? + block.Skip((int) block.Length()); + break; + case KujuTokenID.NumValues: + case KujuTokenID.NumPositions: + //notch ==> frame data + //We can skip for basic cabs + block.Skip((int) block.Length()); + break; + case KujuTokenID.NumFrames: + TotalFrames = block.ReadInt16(); + HorizontalFrames = block.ReadInt16(); + VerticalFrames = block.ReadInt16(); + break; + case KujuTokenID.MouseControl: + MouseControl = block.ReadInt16() == 1; + break; + case KujuTokenID.Style: + block.Skip((int) block.Length()); + break; + case KujuTokenID.Graphic: + string s = block.ReadString(); + TexturePath = OpenBveApi.Path.CombineFile(currentFolder, s); + break; + case KujuTokenID.Position: + Position.X = block.ReadSingle(); + Position.Y = block.ReadSingle(); + Size.X = block.ReadSingle(); + Size.Y = block.ReadSingle(); + break; + case KujuTokenID.Type: + string t = block.ReadString(); + while (block.Position() < block.Length() - 2) + { + //Special case: Concated strings + //#2 appears to be the type repeated + //DO NOT RELY ON THIS THOUGH.... + t += @" " + block.ReadString(); + } + + switch (t.ToLowerInvariant()) + { + case "speedometer dial": + _panelSubject = PanelSubject.Speedometer; + break; + case "brake_pipe dial": + _panelSubject = PanelSubject.BrakePipe; + break; + case "brake_cyl dial": + _panelSubject = PanelSubject.BrakeCylinder; + break; + case "ammeter dial": + _panelSubject = PanelSubject.Ammeter; + break; + case "aspect_display cab_signal_display": + _panelSubject = PanelSubject.AWS; + break; + case "horn two_state": + _panelSubject = PanelSubject.Horn; + Units = "klaxon"; + break; + case "direction tri_state": + _panelSubject = PanelSubject.Direction; + Units = "rev"; + break; + case "throttle lever": + _panelSubject = PanelSubject.PowerHandle; + Units = "power"; + break; + case "engine_brake lever": + _panelSubject = PanelSubject.EngineBrakeHandle; + Units = "brake"; + break; + case "train_brake lever": + _panelSubject = PanelSubject.TrainBrakeHandle; + Units = "brake"; + break; + case "whistle two_state": + _panelSubject = PanelSubject.Horn; + Units = "klaxon"; + break; + case "wipers two_state": + _panelSubject = PanelSubject.WiperState; + Units = "wiperstate"; + break; + } + + break; + } + } + + internal Component(Block block) + { + myBlock = block; + } + + private readonly Block myBlock; + } + + + + // get stack language from subject + private static string GetStackLanguageFromSubject(TrainBase Train, string Subject, string ErrorLocation) + { + CultureInfo Culture = CultureInfo.InvariantCulture; + string Suffix = ""; + { + // detect d# suffix + int i; + for (i = Subject.Length - 1; i >= 0; i--) + { + int a = char.ConvertToUtf32(Subject, i); + if (a < 48 | a > 57) break; + } + + if (i >= 0 & i < Subject.Length - 1) + { + if (Subject[i] == 'd' | Subject[i] == 'D') + { + int n; + if (int.TryParse(Subject.Substring(i + 1), NumberStyles.Integer, Culture, out n)) + { + if (n == 0) + { + Suffix = " floor 10 mod"; + } + else + { + string t0 = Math.Pow(10.0, n).ToString(Culture); + string t1 = Math.Pow(10.0, -n).ToString(Culture); + Suffix = " ~ " + t0 + " >= <> " + t1 + " * floor 10 mod 10 ?"; + } + + Subject = Subject.Substring(0, i); + i--; + } + } + } + } + // transform subject + string Code; + switch (Subject.ToLowerInvariant()) + { + case "acc": + Code = "acceleration"; + break; + case "motor": + Code = "accelerationmotor"; + break; + case "true": + Code = "1"; + break; + case "kmph": + Code = "speedometer abs 3.6 *"; + break; + case "mph": + Code = "speedometer abs 2.2369362920544 *"; + break; + case "ms": + Code = "speedometer abs"; + break; + case "bc": + Code = "brakecylinder 0.001 *"; + break; + case "bc_psi": + Code = "brakecylinder 0.000145038 *"; + break; + case "mr": + Code = "mainreservoir 0.001 *"; + break; + case "sap": + Code = "straightairpipe 0.001 *"; + break; + case "bp": + Code = "brakepipe 0.001 *"; + break; + case "bp_psi": + Code = "brakepipe 0.000145038 *"; + break; + case "er": + Code = "equalizingreservoir 0.001 *"; + break; + case "door": + Code = "1 doors -"; + break; + case "csc": + Code = "constSpeed"; + break; + case "power": + Code = "brakeNotchLinear 0 powerNotch ?"; + break; + case "brake": + Code = "brakeNotchLinear"; + break; + case "rev": + Code = "reverserNotch ++"; + break; + case "hour": + Code = "0.000277777777777778 time * 24 mod floor"; + break; + case "min": + Code = "0.0166666666666667 time * 60 mod floor"; + break; + case "sec": + Code = "time 60 mod floor"; + break; + case "atc": + Code = "271 pluginstate"; + break; + case "klaxon": + Code = "klaxon"; + break; + case "wiperstate": + Code = "wiperstate"; + break; + default: + { + Code = "0"; + bool unsupported = true; + if (Subject.StartsWith("ats", StringComparison.OrdinalIgnoreCase)) + { + string a = Subject.Substring(3); + int n; + if (int.TryParse(a, NumberStyles.Integer, CultureInfo.InvariantCulture, out n)) + { + if (n >= 0 & n <= 255) + { + Code = n.ToString(Culture) + " pluginstate"; + unsupported = false; + } + } + } + else if (Subject.StartsWith("doorl", StringComparison.OrdinalIgnoreCase)) + { + string a = Subject.Substring(5); + int n; + if (int.TryParse(a, NumberStyles.Integer, CultureInfo.InvariantCulture, out n)) + { + if (n >= 0 & n < Train.Cars.Length) + { + Code = n.ToString(Culture) + " leftdoorsindex ceiling"; + unsupported = false; + } + else + { + Code = "2"; + unsupported = false; + } + } + } + else if (Subject.StartsWith("doorr", StringComparison.OrdinalIgnoreCase)) + { + string a = Subject.Substring(5); + int n; + if (int.TryParse(a, NumberStyles.Integer, CultureInfo.InvariantCulture, out n)) + { + if (n >= 0 & n < Train.Cars.Length) + { + Code = n.ToString(Culture) + " rightdoorsindex ceiling"; + unsupported = false; + } + else + { + Code = "2"; + unsupported = false; + } + } + } + + if (unsupported) + { + Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid subject " + Subject + " encountered in " + ErrorLocation); + } + } + break; + } + + return Code + Suffix; + } + + internal static int CreateElement(ref ElementsGroup Group, double Left, double Top, double Width, double Height, Vector2 RelativeRotationCenter, double Distance, Vector3 Driver, Texture DaytimeTexture, Texture NighttimeTexture, Color32 Color, bool AddStateToLastElement = false) + { + if (Width == 0 || Height == 0) + { + Plugin.currentHost.AddMessage(MessageType.Error, false, "Attempted to create an invalid size element"); + } + + double WorldWidth, WorldHeight; + if (Plugin.Renderer.Screen.Width >= Plugin.Renderer.Screen.Height) + { + WorldWidth = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.HorizontalViewingAngle) * eyeDistance; + WorldHeight = WorldWidth / Plugin.Renderer.Screen.AspectRatio; + } + else + { + WorldHeight = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.VerticalViewingAngle) * eyeDistance / Plugin.Renderer.Screen.AspectRatio; + WorldWidth = WorldHeight * Plugin.Renderer.Screen.AspectRatio; + } + + double x0 = Left / PanelResolution; + double x1 = (Left + Width) / PanelResolution; + double y0 = (PanelBottom - Top) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; + double y1 = (PanelBottom - (Top + Height)) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; + double xd = 0.5 - PanelCenter.X / PanelResolution; + x0 += xd; + x1 += xd; + double yt = PanelBottom - PanelResolution / Plugin.Renderer.Screen.AspectRatio; + double yd = (PanelCenter.Y - yt) / (PanelBottom - yt) - 0.5; + y0 += yd; + y1 += yd; + x0 = (x0 - 0.5) * WorldWidth; + x1 = (x1 - 0.5) * WorldWidth; + y0 = (y0 - 0.5) * WorldHeight; + y1 = (y1 - 0.5) * WorldHeight; + double xm = x0 * (1.0 - RelativeRotationCenter.X) + x1 * RelativeRotationCenter.X; + double ym = y0 * (1.0 - RelativeRotationCenter.Y) + y1 * RelativeRotationCenter.Y; + Vector3[] v = new Vector3[4]; + v[0] = new Vector3(x0 - xm, y1 - ym, 0); + v[1] = new Vector3(x0 - xm, y0 - ym, 0); + v[2] = new Vector3(x1 - xm, y0 - ym, 0); + v[3] = new Vector3(x1 - xm, y1 - ym, 0); + Vertex t0 = new Vertex(v[0], new Vector2(0.0f, 1.0f)); + Vertex t1 = new Vertex(v[1], new Vector2(0.0f, 0.0f)); + Vertex t2 = new Vertex(v[2], new Vector2(1.0f, 0.0f)); + Vertex t3 = new Vertex(v[3], new Vector2(1.0f, 1.0f)); + StaticObject Object = new StaticObject(Plugin.currentHost); + Object.Mesh.Vertices = new VertexTemplate[] {t0, t1, t2, t3}; + Object.Mesh.Faces = new[] {new MeshFace(new[] {0, 1, 2, 0, 2, 3}, FaceFlags.Triangles)}; //Must create as a single face like this to avoid Z-sort issues with overlapping bits + Object.Mesh.Materials = new MeshMaterial[1]; + Object.Mesh.Materials[0].Flags = new MaterialFlags(); + if (DaytimeTexture != null) + { + Object.Mesh.Materials[0].Flags |= MaterialFlags.TransparentColor; + + if (NighttimeTexture != null) + { + // In BVE4 and versions of OpenBVE prior to v1.7.1.0, elements with NighttimeImage defined are rendered with lighting disabled. + Object.Mesh.Materials[0].Flags |= MaterialFlags.DisableLighting; + } + } + + Object.Mesh.Materials[0].Color = Color; + Object.Mesh.Materials[0].TransparentColor = Color24.Blue; + Object.Mesh.Materials[0].DaytimeTexture = DaytimeTexture; + Object.Mesh.Materials[0].NighttimeTexture = NighttimeTexture; + Object.Dynamic = true; + // calculate offset + Vector3 o; + o.X = xm + Driver.X; + o.Y = ym + Driver.Y; + o.Z = eyeDistance - Distance + Driver.Z; + // add object + if (AddStateToLastElement) + { + int n = Group.Elements.Length - 1; + int j = Group.Elements[n].States.Length; + Array.Resize(ref Group.Elements[n].States, j + 1); + Group.Elements[n].States[j] = new ObjectState + { + Translation = Matrix4D.CreateTranslation(o.X, o.Y, -o.Z), + Prototype = Object + }; + return n; + } + else + { + int n = Group.Elements.Length; + Array.Resize(ref Group.Elements, n + 1); + Group.Elements[n] = new AnimatedObject(Plugin.currentHost); + Group.Elements[n].States = new[] {new ObjectState()}; + Group.Elements[n].States[0].Translation = Matrix4D.CreateTranslation(o.X, o.Y, -o.Z); + Group.Elements[n].States[0].Prototype = Object; + Group.Elements[n].CurrentState = 0; + Group.Elements[n].internalObject = new ObjectState {Prototype = Object}; + Plugin.currentHost.CreateDynamicObject(ref Group.Elements[n].internalObject); + return n; + } + } + } +} diff --git a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs new file mode 100644 index 0000000000..2cc762b78f --- /dev/null +++ b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs @@ -0,0 +1,28 @@ +namespace Train.MsTs +{ + internal enum CabComponentType + { + /// None + None = 0, + /// Dial based control + Dial = 1, + /// Lever based control + Lever = 2, + /// Dial based gauge + Gauge = 3, + /// Two-state based control + TwoState = 4, + /// Tri-state based control + TriState = 5, + /// A display capable of displaying N states + MultiStateDisplay = 6, + /// In cab signalling / safety system (e.g. AWS) + CabSignalDisplay = 7, + /// Steam locomotive firebox animation + Firebox = 8, + /// A digital display + Digital = 9, + /// A digital clock + DigitalClock = 10 + } +} diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs new file mode 100644 index 0000000000..891d252f52 --- /dev/null +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -0,0 +1,43 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +namespace Train.MsTs +{ + internal enum PanelSubject + { + Alerter_Display, + Ammeter, + Aspect_Display, + AWS, + Bell, + Brake_Cyl, + Brake_Pipe, + Clock, + CP_Handle, + CPH_Display, + Direction, + Direction_Display, + Engine_Brake, + Emergency_Brake, + Eq_Res, + Friction_Braking, + Front_Hlight, + Horn, + Load_Meter, + Main_Res, + Overspeed, + Pantograph, + Panto_Display, + Penalty_App, + Reset, + Sanders, + Sanding, + Speedlim_Display, + Speedometer, + Throttle, + Traction_Braking, + Train_Brake, + Wheelslip, + Whistle, + Wipers, + } +} diff --git a/source/Plugins/Train.MsTs/Plugin.cs b/source/Plugins/Train.MsTs/Plugin.cs new file mode 100644 index 0000000000..9db7773950 --- /dev/null +++ b/source/Plugins/Train.MsTs/Plugin.cs @@ -0,0 +1,76 @@ +using System.Text; +using LibRender2; +using OpenBveApi; +using OpenBveApi.FileSystem; +using OpenBveApi.Hosts; +using OpenBveApi.Interface; +using OpenBveApi.Trains; +using TrainManager.Trains; + +namespace Train.MsTs +{ + public class Plugin : TrainInterface + { + internal static ConsistParser ConsistParser; + + internal static WagonParser WagonParser; + + internal static HostInterface currentHost; + + internal static BaseRenderer Renderer; + + internal static bool PreviewOnly; + public Plugin() + { + ConsistParser = new ConsistParser(this); + WagonParser = new WagonParser(this); + } + + public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions Options, object rendererReference) + { + currentHost = host; + Renderer = (BaseRenderer) rendererReference; + } + + public override bool CanLoadTrain(string path) + { + if (path.ToLowerInvariant().EndsWith(".con")) + { + return true; + } + return false; + } + + public override bool LoadTrain(Encoding Encoding, string trainPath, ref AbstractTrain train, ref Control[] currentControls) + { + PreviewOnly = false; + try + { + ConsistParser.ReadConsist(trainPath, ref train); + } + catch + { + return false; + } + + return true; + } + + public override string GetDescription(string trainPath, Encoding userSelectedEncoding = null) + { + PreviewOnly = true; + AbstractTrain train = new TrainBase(TrainState.Pending, TrainType.LocalPlayerTrain); + ConsistParser.ReadConsist(trainPath, ref train); + if(train is TrainBase trainBase && trainBase.Cars.Length != 0) + { + return trainBase.Cars[train.DriverCar].Description; + } + return string.Empty; + } + + public override string GetImage(string trainPath) + { + return string.Empty; + } + } +} diff --git a/source/Plugins/Train.MsTs/Properties/AssemblyInfo.cs b/source/Plugins/Train.MsTs/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..256878fa3a --- /dev/null +++ b/source/Plugins/Train.MsTs/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Train.MsTs")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Train.MsTs")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a6e3d875-ddfa-446a-aaf5-bfaff3c9ef45")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs new file mode 100644 index 0000000000..78b483df6c --- /dev/null +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -0,0 +1,426 @@ +using OpenBve.Formats.MsTs; +using SharpCompress.Compressors.Deflate; +using System.IO; +using System; +using System.Text; +using OpenBveApi.Interface; +using SharpCompress.Compressors; +using TrainManager.Car; +using SoundManager; + +namespace Train.MsTs +{ + class SoundModelSystemParser + { + private static string currentFolder; + + private static string currentFile; + + internal static bool ParseSoundFile(string fileName, ref CarBase Car) + { + currentFile = fileName; + currentFolder = Path.GetDirectoryName(fileName); + Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read); + + byte[] buffer = new byte[34]; + fb.Read(buffer, 0, 2); + + bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE); + + string headerString; + if (unicode) + { + fb.Read(buffer, 0, 32); + headerString = Encoding.Unicode.GetString(buffer, 0, 16); + } + else + { + fb.Read(buffer, 2, 14); + headerString = Encoding.ASCII.GetString(buffer, 0, 8); + } + + // SIMISA@F means compressed + // SIMISA@@ means uncompressed + if (headerString.StartsWith("SIMISA@F")) + { + fb = new ZlibStream(fb, CompressionMode.Decompress); + } + else if (headerString.StartsWith("\r\nSIMISA")) + { + // ie us1rd2l1000r10d.s, we are going to allow this but warn + Console.Error.WriteLine("Improper header in " + fileName); + fb.Read(buffer, 0, 4); + } + else if (!headerString.StartsWith("SIMISA@@")) + { + Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognized SMS file header " + headerString + " in " + fileName); + return false; + } + + string subHeader; + if (unicode) + { + fb.Read(buffer, 0, 32); + subHeader = Encoding.Unicode.GetString(buffer, 0, 16); + } + else + { + fb.Read(buffer, 0, 16); + subHeader = Encoding.ASCII.GetString(buffer, 0, 8); + } + SoundSet soundSet = new SoundSet(); + if (subHeader[7] == 't') + { + using (BinaryReader reader = new BinaryReader(fb)) + { + byte[] newBytes = reader.ReadBytes((int)(fb.Length - fb.Position)); + string s; + if (unicode) + { + s = Encoding.Unicode.GetString(newBytes); + } + else + { + s = Encoding.ASCII.GetString(newBytes); + } + + TextualBlock block = new TextualBlock(s, KujuTokenID.Tr_SMS); + ParseBlock(block, ref soundSet, ref Car); + } + + } + else if (subHeader[7] != 'b') + { + Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognized subHeader " + subHeader + " in " + fileName); + return false; + } + else + { + using (BinaryReader reader = new BinaryReader(fb)) + { + KujuTokenID currentToken = (KujuTokenID)reader.ReadUInt16(); + if (currentToken != KujuTokenID.Tr_SMS) + { + return false; + } + + reader.ReadUInt16(); + uint remainingBytes = reader.ReadUInt32(); + byte[] newBytes = reader.ReadBytes((int)remainingBytes); + BinaryBlock block = new BinaryBlock(newBytes, KujuTokenID.Tr_SMS); + ParseBlock(block, ref soundSet, ref Car); + } + } + + return false; + } + + + internal struct SoundSet + { + internal bool Activation; + internal double ActivationDistance; + internal double DeactivationDistance; + internal bool CamCam; + internal bool PassengerCam; + internal bool ExternalCam; + internal double Priority; + + } + + private static SoundTrigger currentTrigger; + private static KujuTokenID currentSoundType; + + private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref CarBase car) + { + Block newBlock; + switch (block.Token) + { + case KujuTokenID.Tr_SMS: + // file root + while (block.Position() < block.Length()) + { + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + break; + case KujuTokenID.ScalabiltyGroup: + // root container for sound groups + block.ReadSingle(); // number of groups + while (block.Position() < block.Length()) + { + try + { + newBlock = block.ReadSubBlock(true); + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + break; + case KujuTokenID.Activation: + // control the conditions under which the sounds in the group are activated + currentSoundSet.Activation = true; + while (block.Position() < block.Length()) + { + newBlock = block.ReadSubBlock(true); + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + break; + case KujuTokenID.Deactivation: + // control the conditions under which the sounds in the group are deactivated + currentSoundSet.Activation = false; + while (block.Position() < block.Length() - 1) + { + newBlock = block.ReadSubBlock(true); + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + break; + case KujuTokenID.Distance: + // absolute distance, presumably to camera + if (currentSoundSet.Activation) + { + currentSoundSet.ActivationDistance = block.ReadSingle(); + } + else + { + currentSoundSet.DeactivationDistance = block.ReadSingle(); + } + break; + case KujuTokenID.ExternalCam: + case KujuTokenID.CabCam: + case KujuTokenID.PassengerCam: + currentSoundSet.ExternalCam = currentSoundSet.Activation; + break; + case KujuTokenID.Streams: + // each stream represents a unique sound + int numStreams = block.ReadInt32(); + for (int i = 0; i < numStreams; i++) + { + newBlock = block.ReadSubBlock(new[] { KujuTokenID.Stream, KujuTokenID.Skip }); + if (block.Token == KujuTokenID.Skip) + { + i--; + } + else + { + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + if (block.Length() - block.Position() <= 3) + { + // WARN: incorrect number of streams supplied + break; + } + } + break; + case KujuTokenID.Stream: + while (block.Position() < block.Length() - 3) + { + newBlock = block.ReadSubBlock(new[] { KujuTokenID.Priority, KujuTokenID.Triggers, KujuTokenID.Volume, KujuTokenID.VolumeCurve, KujuTokenID.FrequencyCurve, KujuTokenID.Granularity }); + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + break; + case KujuTokenID.Priority: + currentSoundSet.Priority = block.ReadSingle(); + break; + case KujuTokenID.Triggers: + int numTriggers = block.ReadInt32(); + for (int i = 0; i < numTriggers; i++) + { + // two triggers per sound set (start + stop) + newBlock = block.ReadSubBlock(new [] {KujuTokenID.Variable_Trigger, KujuTokenID.Initial_Trigger, KujuTokenID.Discrete_Trigger, KujuTokenID.Random_Trigger, KujuTokenID.Dist_Travelled_Trigger}); + ParseBlock(newBlock, ref currentSoundSet, ref car); + if (block.Length() - block.Position() <= 3) + { + // WARN: incorrect number of triggers supplied + break; + } + } + break; + case KujuTokenID.Initial_Trigger: + // when initially appears, hence nothing other than StartLoop should be valid + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.DisableTrigger }); + ParseBlock(newBlock, ref currentSoundSet, ref car); + break; + case KujuTokenID.StartLoop: + numStreams = block.ReadInt32(); + for (int i = 0; i < numStreams; i++) + { + newBlock = block.ReadSubBlock(KujuTokenID.File); + ParseBlock(newBlock, ref currentSoundSet, ref car); + newBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + break; + case KujuTokenID.File: + if (block.ReadPath(currentFolder, out string soundFile)) + { + // n.b. MSTS does not distinguish between increase / decrease sounds for handles etc. + switch (currentTrigger) + { + case SoundTrigger.ReverserChange: + if (currentSoundType == KujuTokenID.PlayOneShot) + { + car.baseTrain.Handles.Reverser.EngageSound = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Reverser.ReleaseSound = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + } + break; + case SoundTrigger.ThrottleChange: + if (currentSoundType == KujuTokenID.PlayOneShot) + { + car.baseTrain.Handles.Power.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.Increase = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.IncreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.Min = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.Max = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + } + break; + case SoundTrigger.TrainBrakeChange: + if (currentSoundType == KujuTokenID.PlayOneShot) + { + car.baseTrain.Handles.Brake.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.Increase = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.IncreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.Min = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.Max = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + } + break; + case SoundTrigger.EngineBrakeChange: + if (currentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.Handles.LocoBrake != null) + { + car.baseTrain.Handles.LocoBrake.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.Increase = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.IncreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.Min = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.Max = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + } + break; + } + } + else + { + Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Sound File " + soundFile + " was not found in SMS " + currentFile); + } + int checkDigit = block.ReadInt32(); + if (checkDigit != -1) + { + // Unknown purpose at the minute- set to -1 everywhere + throw new Exception(); + } + + break; + case KujuTokenID.SelectionMethod: + KujuTokenID token = block.ReadEnumValue(default(KujuTokenID)); + switch (token) + { + case KujuTokenID.SequentialSelection: + break; + case KujuTokenID.RandomSelection: + break; + } + break; + case KujuTokenID.Discrete_Trigger: + currentSoundType = block.Token; + currentTrigger = (SoundTrigger)block.ReadInt32(); // stored as integer + newBlock = block.ReadSubBlock(new[] { KujuTokenID.PlayOneShot, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump }); + ParseBlock(newBlock, ref currentSoundSet, ref car); + break; + case KujuTokenID.Variable_Trigger: + currentSoundType = block.Token; + token = block.ReadEnumValue(default(KujuTokenID)); + switch (token) + { + case KujuTokenID.StartLoop: + break; + case KujuTokenID.Speed_Inc_Past: + double speedValue = block.ReadSingle(); + break; + case KujuTokenID.Speed_Dec_Past: + speedValue = block.ReadSingle(); + break; + case KujuTokenID.SpeedControlled: + break; + case KujuTokenID.DistanceControlled: + case KujuTokenID.Distance_Inc_Past: + case KujuTokenID.Distance_Dec_Past: + break; + case KujuTokenID.Variable1_Inc_Past: + case KujuTokenID.Variable1_Dec_Past: + case KujuTokenID.Variable1Controlled: + break; + case KujuTokenID.Variable2_Inc_Past: + case KujuTokenID.Variable2_Dec_Past: + case KujuTokenID.Variable2Controlled: + break; + case KujuTokenID.Variable3_Inc_Past: + case KujuTokenID.Variable3_Dec_Past: + case KujuTokenID.Variable3Controlled: + break; + default: + throw new Exception("Unexpected enum value " + token + " encounted in SMS file " + currentFile); + } + break; + case KujuTokenID.PlayOneShot: + currentSoundType = block.Token; + int numSounds = block.ReadInt16(); + for (int i = 0; i < numSounds; i++) + { + newBlock = block.ReadSubBlock(KujuTokenID.File); + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + break; + case KujuTokenID.StartLoopRelease: + numStreams = block.ReadInt16(); + for (int i = 0; i < numStreams; i++) + { + newBlock = block.ReadSubBlock(KujuTokenID.File); + ParseBlock(newBlock, ref currentSoundSet, ref car); + } + newBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); + ParseBlock(newBlock, ref currentSoundSet, ref car); + break; + case KujuTokenID.ReleaseLoopRelease: + // empty block expected + break; + case KujuTokenID.Volume: + double volume = block.ReadSingle(); + break; + case KujuTokenID.VolumeCurve: + token = block.ReadEnumValue(default(KujuTokenID)); + switch (token) + { + case KujuTokenID.SpeedControlled: + newBlock = block.ReadSubBlock(KujuTokenID.CurvePoints); + ParseBlock(newBlock, ref currentSoundSet, ref car); + break; + case KujuTokenID.DistanceControlled: + break; + case KujuTokenID.Variable1Controlled: + case KujuTokenID.Variable2Controlled: + case KujuTokenID.Variable3Controlled: + break; + default: + throw new Exception("Unexpected enum value " + token + " encounted in SMS file " + currentFile); + } + break; + case KujuTokenID.CurvePoints: + int numPoints = block.ReadInt32(); + for (int i = 0; i < numPoints; i++) + { + double refenceValue = block.ReadSingle(); + double volumeValue = block.ReadSingle(); + } + break; + case KujuTokenID.Granularity: + // presuming this is the step in km/h + break; + } + } + } +} diff --git a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs new file mode 100644 index 0000000000..456a62d337 --- /dev/null +++ b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs @@ -0,0 +1,75 @@ +// ReSharper disable UnusedMember.Global +namespace Train.MsTs +{ + internal enum SoundTrigger + { + DynamicBrakeIncrease = 2, + DynamicBrakeOff = 3, + SanderOn = 4, + SanderOff = 5, + WiperOn = 6, + WiperOff = 7, + HornOn= 8, + HornOff = 9, + BellOn = 10, + BellOff = 11, + CompressorOn = 12, + CompressorOff = 13, + TrainBrakePressureIncrease = 14, + ReverserChange = 15, + ThrottleChange = 16, + TrainBrakeChange = 17, + EngineBrakeChange = 18, + // 19 not listed + DynamicBrakeChange = 20, + EngineBrakePressureIncrease = 21, + EngineBrakePressureDecrease = 22, + EnginePowerOn = 23, + EnginePowerOff = 24, + // 25 + 26 not listed + SteamEjector2On = 27, + SteamEjector2Off = 28, + //29 + 30 not listed + SteamEjector1On = 30, + SteamEjector1Off = 31, + DamperChange = 32, + BlowerChange = 33, + CylinderCocksToggle = 34, + // 35 not listed + FireboxDoorChange = 36, + LightSwitchToggle = 37, + WaterScoopDown = 38, + WaterScoopUp = 39, + // 40 not listed + FireboxDoorClose = 41, + SteamSafetyValveOn = 42, + SteamSafetyValveOff = 43, + SteamHeatChange = 44, + Pantograph1Up = 45, + Pantograph1Down = 46, + Pantograph1Toggle = 47, + VigilanceAlarmReset = 48, + // 49 - 53 not listed + TrainBrakePressureDecrease = 54, + //55 not listed + VigilanceAlarmOn = 56, + VigilanceAlarmOff = 57, + Couple = 58, + CoupleB = 59, + CoupleC = 60, + Uncouple = 61, + UncoupleB = 62, + UncoupleC = 63, + // 64 + 65 not listed + Pantograph2Up = 66, + Pantograph2Down = 67 + /* + * NOTE: + * https://open-rails.readthedocs.io/en/latest/sound.html + * Considerably more ORTS specific triggers + * + * Not looking to handle these at the minute. + */ + + } +} diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj new file mode 100644 index 0000000000..91b4e0978d --- /dev/null +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {A6E3D875-DDFA-446A-AAF5-BFAFF3C9EF45} + Library + Properties + Train.MsTs + Train.MsTs + v4.6.1 + 512 + true + + + true + full + false + ..\..\..\bin_debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\..\bin_release\ + TRACE + prompt + 4 + + + + ..\..\..\packages\SharpCompress.0.32.2\lib\net461\SharpCompress.dll + + + + ..\..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + + ..\..\..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + + ..\..\..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\..\..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\..\..\packages\System.Text.Encoding.CodePages.6.0.0\lib\net461\System.Text.Encoding.CodePages.dll + + + + + + + + + + + + + + + + + + {68215476-302C-49F2-9F7E-AAE20A2B6B12} + LibRender2 + + + {27134980-4415-4375-A564-40A9014DFA5F} + OpenBveApi + + + {90ABFA0C-ABCA-444E-ADEF-9A299AED6524} + SoundManager + + + {D0FCA2C5-FF75-42D8-AE80-310280A61FB1} + TrainManager + + + {e81b7bd8-a326-47d3-b7ee-e9c7d4d119fa} + Formats.Msts + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs new file mode 100644 index 0000000000..ce56a908b9 --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using LibRender2.Trains; +using OpenBve.Formats.MsTs; +using OpenBveApi.Interface; +using OpenBveApi.Routes; +using OpenBveApi.Trains; +using SharpCompress.Compressors; +using SharpCompress.Compressors.Deflate; +using SoundManager; +using TrainManager.Car; +using TrainManager.Handles; +using TrainManager.Motor; +using TrainManager.Power; +using TrainManager.Trains; + +namespace Train.MsTs +{ + internal class ConsistParser + { + internal readonly Plugin plugin; + + internal string currentFolder; + + internal ConsistParser(Plugin Plugin) + { + plugin = Plugin; + } + + internal void ReadConsist(string fileName, ref AbstractTrain Train) + { + currentCarIndex = -1; + TrainBase train = Train as TrainBase; + train.Handles.Reverser = new ReverserHandle(train); + train.Handles.EmergencyBrake = new EmergencyHandle(train); + train.Handles.Power = new PowerHandle(8, 8, new double[] { }, new double[] { }, train); + train.Handles.Brake = new BrakeHandle(8, 8, train.Handles.EmergencyBrake, new double[] { }, new double[] { }, train); + train.Handles.LocoBrake = new LocoBrakeHandle(0, train.Handles.EmergencyBrake, new double[] {}, new double[] {}, train); + train.Handles.LocoBrakeType = LocoBrakeType.Independant; + train.Handles.HasLocoBrake = false; + train.Handles.HoldBrake = new HoldBrakeHandle(train); + train.Specs.AveragesPressureDistribution = true; + train.SafetySystems.Headlights = new LightSource(train, 2); + currentFolder = Path.GetDirectoryName(fileName); + DirectoryInfo d = Directory.GetParent(currentFolder); + if(!d.Name.Equals("TRAINS", StringComparison.InvariantCultureIgnoreCase)) + { + //FIXME: Better finding of the trainset folder (set in options?) + throw new Exception("Unable to find the TRAINS folder"); + } + + currentFolder = d.FullName; + + Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); + + byte[] buffer = new byte[34]; + fb.Read(buffer, 0, 2); + + bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE); + + string headerString; + if (unicode) + { + fb.Read(buffer, 0, 32); + headerString = Encoding.Unicode.GetString(buffer, 0, 16); + } + else + { + fb.Read(buffer, 2, 14); + headerString = Encoding.ASCII.GetString(buffer, 0, 8); + } + + // SIMISA@F means compressed + // SIMISA@@ means uncompressed + if (headerString.StartsWith("SIMISA@F")) + { + fb = new ZlibStream(fb, CompressionMode.Decompress); + } + else if (headerString.StartsWith("\r\nSIMISA")) + { + // ie us1rd2l1000r10d.s, we are going to allow this but warn + Console.Error.WriteLine("Improper header in " + fileName); + fb.Read(buffer, 0, 4); + } + else if (!headerString.StartsWith("SIMISA@@")) + { + throw new Exception("Unrecognized shape file header " + headerString + " in " + fileName); + } + + string subHeader; + if (unicode) + { + fb.Read(buffer, 0, 32); + subHeader = Encoding.Unicode.GetString(buffer, 0, 16); + } + else + { + fb.Read(buffer, 0, 16); + subHeader = Encoding.ASCII.GetString(buffer, 0, 8); + } + if (subHeader[7] == 't') + { + using (BinaryReader reader = new BinaryReader(fb)) + { + byte[] newBytes = reader.ReadBytes((int)(fb.Length - fb.Position)); + string s = unicode ? Encoding.Unicode.GetString(newBytes) : Encoding.ASCII.GetString(newBytes); + TextualBlock block = new TextualBlock(s, KujuTokenID.Train); + ParseBlock(block, ref train); + } + + } + else if (subHeader[7] != 'b') + { + throw new Exception("Unrecognized subHeader \"" + subHeader + "\" in " + fileName); + } + else + { + using (BinaryReader reader = new BinaryReader(fb)) + { + KujuTokenID currentToken = (KujuTokenID) reader.ReadUInt16(); + if (currentToken != KujuTokenID.Train) + { + throw new Exception(); //Shape definition + } + reader.ReadUInt16(); + uint remainingBytes = reader.ReadUInt32(); + byte[] newBytes = reader.ReadBytes((int) remainingBytes); + BinaryBlock block = new BinaryBlock(newBytes, KujuTokenID.Train); + ParseBlock(block, ref train); + } + } + + bool hasCabview = false; + //create couplers & other necessary properties for the thing to load + //TODO: Pull out MSTS properties + for (int i = 0; i < train.Cars.Length; i++) + { + train.Cars[i].Coupler = new Coupler(0.9 * 0.3, 1.1 * 0.3, train.Cars[i / 2], train.Cars.Length > 1 ? train.Cars[i / 2 + 1] : null); + train.Cars[i].CurrentCarSection = -1; + train.Cars[i].ChangeCarSection(CarSectionType.NotVisible); + train.Cars[i].FrontBogie.ChangeSection(-1); + train.Cars[i].RearBogie.ChangeSection(-1); + train.Cars[i].Coupler.ChangeSection(-1); + train.Cars[i].Specs.ExposedFrontalArea = 0.6 * train.Cars[i].Width * train.Cars[i].Height; + train.Cars[i].Specs.UnexposedFrontalArea = 0.2 * train.Cars[i].Width * train.Cars[i].Height; + train.Cars[i].Specs.CenterOfGravityHeight = 1.6; + train.Cars[i].Specs.CriticalTopplingAngle = 0.5 * Math.PI - Math.Atan(2 * train.Cars[i].Specs.CenterOfGravityHeight / train.Cars[i].Width); + if (train.Cars[i].HasInteriorView && hasCabview == false) + { + // For the minute at least, let's set our driver car to be the first car which has an interior view + hasCabview = true; + train.DriverCar = i; + } + } + + train.Cars[train.DriverCar].Windscreen = new Windscreen(256, 10.0, train.Cars[train.DriverCar]); + train.Cars[train.DriverCar].Windscreen.Wipers = new WindscreenWiper(train.Cars[Train.DriverCar].Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 0.0, true); // hack: zero hold time so they act as fast with two states + train.PlaceCars(0.0); + } + + private int currentCarIndex = -1; + private CarBase currentCar; + private bool reverseCurentCar; + private void ParseBlock(Block block, ref TrainBase Train) + { + Block newBlock; + switch (block.Token) + { + default: + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, ref Train); + break; + case KujuTokenID.Default: + // presumably used internally by MSTS, not useful + block.Skip((int)block.Length()); + break; + case KujuTokenID.Name: + string consistName = block.ReadString(); // displayed name for consist in-game + break; + case KujuTokenID.TrainCfg: + string trainName = block.ReadString(); // we're unlikely to want this, as this is just the MSTS internal dictionary key + while (block.Length() - block.Position() > 2) + { + try + { + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, ref Train); + } + catch + { + // ignore + } + } + break; + case KujuTokenID.Serial: + /* + * This identifies the revision number of the consist (e.g. how many times it's been edited using the built-in MSTS tools) + * OpenRails ignores it. + * Not really helpful for our use-case at the minute. + */ + break; + case KujuTokenID.MaxVelocity: + // Presumably max speed of the consist (derails over this or something?) + break; + case KujuTokenID.NextWagonUID: + /* + * This seems to identify the *next* wagon UID to be used when a train is coupled + * Again, OpenRails ignores it. + */ + break; + case KujuTokenID.Durability: + // Dunno- Presumably used for derailment / similar physics somewhere + break; + case KujuTokenID.Engine: + case KujuTokenID.Wagon: + newBlock = block.ReadSubBlock(new[] {KujuTokenID.EngineData, KujuTokenID.WagonData, KujuTokenID.UiD, KujuTokenID.Flip}); + Block secondBlock = block.ReadSubBlock(new[] {KujuTokenID.EngineData, KujuTokenID.WagonData, KujuTokenID.UiD}); + //Must have 2x blocks, car UiD and car name. Order doesn't matter however, so we've gotta DIY as we need the car number + if (newBlock.Token == KujuTokenID.UiD) + { + ParseBlock(newBlock, ref Train); + ParseBlock(secondBlock, ref Train); + } + else + { + ParseBlock(secondBlock, ref Train); + ParseBlock(newBlock, ref Train); + } + + currentCar.Doors = new[] + { + new Door(-1, 1000.0, 0), + new Door(1, 1000.0, 0) + }; + if (reverseCurentCar) + { + currentCar.Reverse(); + reverseCurentCar = false; + } + currentCar.Breaker = new Breaker(currentCar); + currentCar.Sounds.Plugin = new Dictionary(); + currentCar.Sounds.Motor = new MSTSMotorSound(currentCar); + Train.Cars[currentCarIndex] = currentCar; + /* + * FIXME: Needs removing or sorting when the car is created + */ + Train.Cars[currentCarIndex].FrontAxle.Follower.TriggerType = currentCarIndex == 0 ? EventTriggerType.FrontCarFrontAxle : EventTriggerType.OtherCarFrontAxle; + Train.Cars[currentCarIndex].RearAxle.Follower.TriggerType = currentCarIndex == Train.Cars.Length - 1 ? EventTriggerType.RearCarRearAxle : EventTriggerType.OtherCarRearAxle; + Train.Cars[currentCarIndex].BeaconReceiver.TriggerType = currentCarIndex == 0 ? EventTriggerType.TrainFront : EventTriggerType.None; + Train.Cars[currentCarIndex].BeaconReceiverPosition = 0.5 * Train.Cars[currentCarIndex].Length; + Train.Cars[currentCarIndex].FrontAxle.Position = 0.4 * Train.Cars[currentCarIndex].Length; + Train.Cars[currentCarIndex].RearAxle.Position = -0.4 * Train.Cars[currentCarIndex].Length; + break; + // Engine / wagon block + case KujuTokenID.UiD: + // Unique ID of engine / wagon within consist + // For the minute, let's just create a new car and advance our car number + Array.Resize(ref Train.Cars, Train.Cars.Length + 1); + currentCarIndex++; + break; + case KujuTokenID.WagonData: + case KujuTokenID.EngineData: + /* + * FIXME: All this needs to be pulled from the eng properties, or fixed so it doesn't matter + */ + currentCar = new CarBase(Train, currentCarIndex, 0.35, 0.0025, 1.1); + currentCar.HoldBrake = new CarHoldBrake(currentCar); + //FIXME END + + /* + * Pull out the wagon path bits from the block next + * From the available documentation and experience this *appears* to be as follows: + * [0] - Name of the wagon to search for + * [1] - Search path relative to the TRAINS\trainset directory, if not found in DB + * + * If WagonName that is already in the database, but with a different folder is supplied + * then the original will be returned + * https://digital-rails.com/wordpress/2018/11/18/duplicate-wagons/ + * + * HOWEVER: + * http://www.elvastower.com/forums/index.php?/topic/34187-or-consist-format/ + * OpenRails seems to treat these as: + * [0] - WagonFileName => Must add approprite eng / wag extension + * [1] - Search path relative to the TRAINS\trainset directory + * + * Going to match MSTS for the minute, but possibly needs an OpenRails detection mechanism(?) + * Note that in all / most cases, both should be the same anyways. + */ + + string[] wagonFiles = block.ReadStringArray(); + switch (wagonFiles.Length) + { + case 0: + Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: Unable to determine WagonFile to load."); + break; + case 1: + //Just a WagonName- This is likely invalid, but let's ignore + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(currentFolder, "trainset"), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: No WagonFolder supplied, searching entire trainset folder."); + break; + case 2: + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(currentFolder, "trainset\\" + wagonFiles[1]), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + break; + default: + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(currentFolder, "trainset"), wagonFiles[1], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: Two parameters were expected- Check for correct escaping of strings."); + break; + } + break; + case KujuTokenID.Flip: + // Allows a car to be reversed within a consist + reverseCurentCar = true; + break; + } + + } + + internal string GetDescription(string fileName) + { + return string.Empty; + } + } +} diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs new file mode 100644 index 0000000000..2a8fa6b8b6 --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -0,0 +1,34 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +namespace Train.MsTs +{ + enum BrakeEquipmentType + { + /// Handbrake is fitted + Handbrake, + /// Manual brake fitted. + Manual_Brake, + /// 3 position retaining valve is fitted. + Retainer_3_Position, + /// 4 position retaining valve is fitted + /// This is meant for freight wagons only + Retainer_4_Position, + /// Vacuum brake is fitted. + Vacuum_Brake, + /// Standard triple valve is fitted. + Triple_Valve, + /// Triple valve that permits partial releasing of the brakes. + Graduated_Release_Triple_Valve, + /// Electrically controlled brake system is fitted. Release and application of the brakes are independently controlled. + EP_Brake, + /// Same functionality as ep_brake + ECP_Brake, + /// Air tank used for normal service brake applications. This is required for all brake systems. + Auxilary_Reservoir, + /// Air tank used for emergency applications. + /// This is optional. + Emergency_Brake_Reservoir, + /// Electronic or computer controller on the vehicle that can be set to independently control any parameter of the braking system. + Distributor + } +} diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeSystemType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeSystemType.cs new file mode 100644 index 0000000000..f13c57081b --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeSystemType.cs @@ -0,0 +1,34 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +namespace Train.MsTs +{ + enum BrakeSystemType + { + /// One pipe controls and supplies the air brakes. + Air_single_pipe, + /// Two pipes are used. One to control the brakes, the other to charge the reserviors. + Air_twin_pipe, + /// The car uses a manual braking system. + Manual_Braking, + /// One pipe is used to supply and control the vacuum brakes. + Vacuum_single_pipe, + Vaccum_single_pipe = Vacuum_single_pipe, + Vacumn_single_pipe = Vaccum_single_pipe, + /// Two pipes are used. One controls the vacuum brakes, the other supply the vacuum reservior. + Vacuum_twin_pipe, + Vaccum_twin_pipe = Vacuum_twin_pipe, + /// The brakes are controlled by a computer or complex electrical control system. + ECP, + /// The brakes are a combination of standard air brakes and electrical control signals. + EP, + EP_Brake = EP, + /// The vehicle has no brakes + /// Air pipe pressure will be passed through this vehicle + Air_Piped, + /// The vehicle has no brakes + /// Vacuum pipe pressure will be passed through this vehicle + Vacuum_Piped, + /// The vehicle has a handbrake + Handbrake + } +} diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs new file mode 100644 index 0000000000..5e88669d15 --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -0,0 +1,612 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using LibRender2.Trains; +using OpenBve.Formats.Msts; +using OpenBve.Formats.MsTs; +using OpenBveApi.Graphics; +using OpenBveApi.Interface; +using OpenBveApi.Objects; +using OpenBveApi.Trains; +using OpenBveApi.World; +using SharpCompress.Compressors; +using SharpCompress.Compressors.Deflate; +using TrainManager.BrakeSystems; +using TrainManager.Car; +using TrainManager.Handles; +using TrainManager.Power; +using TrainManager.Trains; + +namespace Train.MsTs +{ + internal class WagonParser + { + private readonly Plugin plugin; + + private readonly Dictionary wagonCache; + private readonly Dictionary engineCache; + private string[] wagonFiles; + private int wheelRadiusNum; + private double wheelRadius; + + internal WagonParser(Plugin Plugin) + { + plugin = Plugin; + wagonCache = new Dictionary(); + engineCache = new Dictionary(); + } + + internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, ref CarBase Car, ref TrainBase train) + { + wheelRadiusNum = 1; + wagonFiles = Directory.GetFiles(trainSetDirectory, isEngine ? "*.eng" : "*.wag", SearchOption.AllDirectories); + /* + * MSTS maintains an internal database, as opposed to using full paths + * Unfortunately, this means we've got to do an approximation of the same thing! + * (TrainStore is / was an early MSTS attempt to deal with the same problem by moving + * excess eng, wag and con files out from the MSTS directory) + * + * Unclear at the minute as to whether an eng can refer to a *separate* wag file, but + * unless documentation specifically states otherwise, we'll assume it can + * + * So, the *first* thing we need to do is to read the engine (as this may + * refer to a different sub-wagon): + */ + if (isEngine) + { + if (engineCache.ContainsKey(wagonName)) + { + ReadWagonData(engineCache[wagonName], ref wagonName, true, ref Car, ref train); + } + else + { + for (int i = 0; i < wagonFiles.Length; i++) + { + if (ReadWagonData(wagonFiles[i], ref wagonName, true, ref Car, ref train)) + { + break; + } + } + } + } + /* + * We've now found the engine properties- + * Now, we need to read the wagon properties to find the visual wagon to display + * (The Engine only holds the physics data) + */ + if (wagonCache.ContainsKey(wagonName)) + { + ReadWagonData(wagonCache[wagonName], ref wagonName, false, ref Car, ref train); + } + else + { + for (int i = 0; i < wagonFiles.Length; i++) + { + if (ReadWagonData(wagonFiles[i], ref wagonName, false, ref Car, ref train)) + { + break; + } + } + } + } + + internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) + { + Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); + + byte[] buffer = new byte[34]; + fb.Read(buffer, 0, 2); + + bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE); + + string headerString; + if (unicode) + { + fb.Read(buffer, 0, 32); + headerString = Encoding.Unicode.GetString(buffer, 0, 16); + } + else + { + fb.Read(buffer, 2, 14); + headerString = Encoding.ASCII.GetString(buffer, 0, 8); + } + + // SIMISA@F means compressed + // SIMISA@@ means uncompressed + if (headerString.StartsWith("SIMISA@F")) + { + fb = new ZlibStream(fb, CompressionMode.Decompress); + } + else if (headerString.StartsWith("\r\nSIMISA")) + { + // ie us1rd2l1000r10d.s, we are going to allow this but warn + Console.Error.WriteLine("Improper header in " + fileName); + fb.Read(buffer, 0, 4); + } + else if (!headerString.StartsWith("SIMISA@@")) + { + throw new Exception("Unrecognized vehicle file header " + headerString + " in " + fileName); + } + + string subHeader; + if (unicode) + { + fb.Read(buffer, 0, 32); + subHeader = Encoding.Unicode.GetString(buffer, 0, 16); + } + else + { + fb.Read(buffer, 0, 16); + subHeader = Encoding.ASCII.GetString(buffer, 0, 8); + } + if (subHeader[7] == 't') + { + using (BinaryReader reader = new BinaryReader(fb)) + { + byte[] newBytes = reader.ReadBytes((int)(fb.Length - fb.Position)); + string s = unicode ? Encoding.Unicode.GetString(newBytes) : Encoding.ASCII.GetString(newBytes); + + /* + * Engine files contain two blocks, not in an enclosing block + * Assume that these can be of arbritrary order, so read using a dictionary + */ + Dictionary blocks = TextualBlock.ReadBlocks(s); + if (!blocks.ContainsKey(KujuTokenID.Wagon)) + { + //Not found any wagon data in this file + return false; + } + if (isEngine && blocks.ContainsKey(KujuTokenID.Engine)) + { + return ParseBlock(blocks[KujuTokenID.Engine], fileName, ref wagonName, true, ref car, ref train); + } + if (!isEngine && blocks.ContainsKey(KujuTokenID.Wagon)) + { + return ParseBlock(blocks[KujuTokenID.Wagon], fileName, ref wagonName, false, ref car, ref train); + } + return false; + } + + } + if (subHeader[7] != 'b') + { + throw new Exception("Unrecognized subHeader \"" + subHeader + "\" in " + fileName); + } + else + { + using (BinaryReader reader = new BinaryReader(fb)) + { + KujuTokenID currentToken = (KujuTokenID) reader.ReadUInt16(); + if (currentToken != KujuTokenID.Wagon) + { + throw new Exception(); //Shape definition + } + reader.ReadUInt16(); + uint remainingBytes = reader.ReadUInt32(); + byte[] newBytes = reader.ReadBytes((int) remainingBytes); + BinaryBlock block = new BinaryBlock(newBytes, KujuTokenID.Wagon); + try + { + ParseBlock(block, fileName, ref wagonName, isEngine, ref car, ref train); + } + catch (InvalidDataException) + { + return false; + } + + } + } + return true; + } + + private double maxForce = 0; + private double maxBrakeForce = 0; + private BrakeSystemType[] brakeSystemTypes; + + private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) + { + Block newBlock; + switch (block.Token) + { + case KujuTokenID.Wagon: + string name = block.ReadString().Trim(); + if (isEngine) + { + // Within an Engine block, the Wagon block defines the visual wagon to display + wagonName = name; + } + else + { + if (!name.Equals(wagonName, StringComparison.InvariantCultureIgnoreCase)) + { + if (!wagonCache.ContainsKey(name)) + { + // CHECK: How do MSTS / OR mediate between files with the same key + wagonCache.Add(name, fileName); + } + return false; + } + while (block.Length() - block.Position() > 2) + { + try + { + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); + } + catch + { + //ignore + } + } + if (brakeSystemTypes == null) + { + break; + } + // Add brakes last, as we need the acceleration values + if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_piped)) + { + /* + * FIXME: Need to implement vac braked / air piped and vice-versa, but for the minute, we'll assume that if one or the other is present + * then the vehicle has no brakes + */ + car.CarBrake = new ThroughPiped(car); + } + else + { + if (brakeSystemTypes.Contains(BrakeSystemType.EP)) + { + // Combined air brakes and control signals + // Assume equivilant to ElectromagneticStraightAirBrake + car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car); + } + else if (brakeSystemTypes.Contains(BrakeSystemType.ECP)) + { + // Complex computer control + // Assume equivialant to ElectricCommandBrake at the minute + car.CarBrake = new ElectricCommandBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + } + else if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) + { + // The car contains no control gear, but is air / vac braked + // Assume equivilant to AutomaticAirBrake + // NOTE: This must be last in the else-if chain to enure that a vehicle with EP / ECP and these declared is setup correctly + car.CarBrake = new AutomaticAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + } + + car.CarBrake.mainReservoir = new MainReservoir(690000.0, 780000.0, 0.01, 0.075 / train.Cars.Length); + car.CarBrake.airCompressor = new Compressor(5000.0, car.CarBrake.mainReservoir, car); + car.CarBrake.equalizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); + car.CarBrake.equalizingReservoir.NormalPressure = 1.005 * 490000.0; + double r = 200000.0 / 440000.0 - 1.0; + if (r < 0.1) r = 0.1; + if (r > 1.0) r = 1.0; + car.CarBrake.auxiliaryReservoir = new AuxiliaryReservoir(0.975 * 490000.0, 200000.0, 0.5, r); + car.CarBrake.brakeCylinder = new BrakeCylinder(440000.0, 440000.0, 0.3 * 300000.0, 300000.0, 200000.0); + car.CarBrake.straightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); + + } + + car.CarBrake.brakePipe = new BrakePipe(490000.0, 10000000.0, 1500000.0, 5000000.0, true); + car.CarBrake.JerkUp = 10; + car.CarBrake.JerkDown = 10; + } + break; + case KujuTokenID.Engine: + name = block.ReadString().Trim(); + if (!name.Equals(wagonName, StringComparison.InvariantCultureIgnoreCase)) + { + if (!engineCache.ContainsKey(name)) + { + // CHECK: How do MSTS / OR mediate between files with the same key + engineCache.Add(name, fileName); + } + return false; + } + while (block.Length() - block.Position() > 2) + { + try + { + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); + } + catch + { + //ignore + } + } + + if (brakeSystemTypes == null) + { + break; + } + // Add brakes last, as we need the acceleration values + if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_piped)) + { + /* + * FIXME: Need to implement vac braked / air piped and vice-versa, but for the minute, we'll assume that if one or the other is present + * then the vehicle has no brakes + */ + car.CarBrake = new ThroughPiped(car); + } + else + { + if (brakeSystemTypes.Contains(BrakeSystemType.EP)) + { + // Combined air brakes and control signals + // Assume equivilant to ElectromagneticStraightAirBrake + car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + } + else if (brakeSystemTypes.Contains(BrakeSystemType.ECP)) + { + // Complex computer control + // Assume equivialant to ElectricCommandBrake at the minute + car.CarBrake = new ElectricCommandBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + } + else if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) + { + // The car contains no control gear, but is air / vac braked + // Assume equivilant to AutomaticAirBrake + // NOTE: This must be last in the else-if chain to enure that a vehicle with EP / ECP and these declared is setup correctly + car.CarBrake = new AutomaticAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + } + + car.CarBrake.mainReservoir = new MainReservoir(690000.0, 780000.0, 0.01, 0.075 / train.Cars.Length); + car.CarBrake.airCompressor = new Compressor(5000.0, car.CarBrake.mainReservoir, car); + car.CarBrake.equalizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); + car.CarBrake.equalizingReservoir.NormalPressure = 1.005 * 490000.0; + double r = 200000.0 / 440000.0 - 1.0; + if (r < 0.1) r = 0.1; + if (r > 1.0) r = 1.0; + car.CarBrake.auxiliaryReservoir = new AuxiliaryReservoir(0.975 * 490000.0, 200000.0, 0.5, r); + car.CarBrake.brakeCylinder = new BrakeCylinder(440000.0, 440000.0, 0.3 * 300000.0, 300000.0, 200000.0); + car.CarBrake.straightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); + + } + + car.CarBrake.brakePipe = new BrakePipe(490000.0, 10000000.0, 1500000.0, 5000000.0, true); + car.CarBrake.JerkUp = 10; + car.CarBrake.JerkDown = 10; + break; + case KujuTokenID.Type: + if (isEngine) + { + //Will load engine type + } + else + { + try + { + WagonType type = block.ReadEnumValue(default(WagonType)); + } + catch + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vechicle Parser: Invalid vehicle type specified."); + } + } + break; + case KujuTokenID.WagonShape: + if(Plugin.PreviewOnly) + { + break; + } + // Loads exterior object + string objectFile = OpenBveApi.Path.CombineFile(Path.GetDirectoryName(fileName), block.ReadString()); + if (!File.Exists(objectFile)) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle object file " + objectFile + " was not found"); + return true; + } + + for (int i = 0; i < Plugin.currentHost.Plugins.Length; i++) + { + + if (Plugin.currentHost.Plugins[i].Object != null && Plugin.currentHost.Plugins[i].Object.CanLoadObject(objectFile)) + { + Plugin.currentHost.Plugins[i].Object.LoadObject(objectFile, Encoding.Default, out UnifiedObject carObject); + car.LoadCarSections(carObject, false); + break; + } + } + break; + case KujuTokenID.Size: + // Physical size of the car + car.Width = block.ReadSingle(UnitOfLength.Meter); + if (car.Width == 0) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle width is invalid."); + car.Width = 2; + } + car.Height = block.ReadSingle(UnitOfLength.Meter); + if (car.Height == 0) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle height is invalid."); + car.Height = 2; + } + car.Length = block.ReadSingle(UnitOfLength.Meter); + if (car.Length == 0) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle length is invalid."); + car.Length = 25; + } + break; + case KujuTokenID.Mass: + // Sets the empty mass of the car + car.EmptyMass = block.ReadSingle(UnitOfWeight.Kilograms); + break; + case KujuTokenID.BrakeEquipmentType: + // Determines the brake equipment types available + BrakeEquipmentType[] brakeEquipmentTypes = block.ReadEnumArray(default(BrakeEquipmentType)); + break; + case KujuTokenID.BrakeSystemType: + // Determines the brake system types available + brakeSystemTypes = block.ReadEnumArray(default(BrakeSystemType)); + break; + case KujuTokenID.CabView: + // Loads cab view file + if (Plugin.PreviewOnly) + { + break; + } + string cabViewFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "CABVIEW"), block.ReadString()); + if (!File.Exists(cabViewFile)) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Cab view file " + cabViewFile + " was not found"); + return true; + } + + if (car.CarSections.Length == 0) + { + car.CarSections = new CarSection[1]; + } + else if (car.CarSections.Length > 0) + { + // Cab View must always be at CarSection zero, but the order is not guaranteed within an eng / wag + CarSection[] move = new CarSection[car.CarSections.Length + 1]; + for (int i = 0; i < car.CarSections.Length; i++) + { + move[i + 1] = car.CarSections[i]; + } + car.CarSections = move; + } + car.CarSections[0] = new CarSection(Plugin.currentHost, ObjectType.Overlay, true, car); + car.CameraRestrictionMode = CameraRestrictionMode.On; + Plugin.Renderer.Camera.CurrentRestriction = CameraRestrictionMode.On; + CabviewFileParser.ParseCabViewFile(cabViewFile, ref car); + car.HasInteriorView = true; + break; + case KujuTokenID.Description: + /* + * Only I believe valid in ENG files + * NOTE: For some reason, the array appears to be as lines, however it also contains the newline character + * Binary format?? + */ + string[] strings = block.ReadStringArray(); + car.Description = string.Join("", strings).Replace(@"\n", Environment.NewLine); + break; + case KujuTokenID.Comment: + if(car.Description == string.Empty) + { + // WAG files often have a comment block with a basic description + strings = block.ReadStringArray(); + car.Description = string.Join("", strings); + } + break; + case KujuTokenID.MaxPower: + // maximum continous power at the rails provided to the wheels + break; + case KujuTokenID.MaxForce: + // maximum force applied when starting + if (!isEngine) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine force is not expected to be present in a wagon block."); + break; + } + maxForce = block.ReadSingle(UnitOfForce.Newton); + car.Specs.AccelerationCurves = new AccelerationCurve[] + { + new MSTSAccelerationCurve(car, maxForce) + }; + // FIXME: Default BVE values + car.Specs.JerkPowerUp = 10.0; + car.Specs.JerkPowerDown = 10.0; + break; + case KujuTokenID.MaxBrakeForce: + maxBrakeForce = block.ReadSingle(UnitOfForce.Newton); + break; + case KujuTokenID.MaxContinuousForce: + // Maximum continuous force + break; + case KujuTokenID.RunUpTimeToMaxForce: + // + break; + case KujuTokenID.WheelRadius: + wheelRadius = block.ReadSingle(UnitOfLength.Meter); + break; + case KujuTokenID.NumWheels: + int numWheels = block.ReadInt32(); + if (numWheels < 2) + { + // NumWheels *should* be divisible by two (to get axles), but some content uses a single wheel, e.g. stock Class 50 + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Invalid number of wheels."); + numWheels = 1; + } + else + { + numWheels /= 2; + } + + if (numWheels == 1) + { + car.Wheels.Add("WHEELS" + wheelRadiusNum, new Wheels(2, "WHEELS" + wheelRadiusNum, wheelRadius)); + } + else + { + car.Wheels.Add("WHEELS" + wheelRadiusNum, new Wheels(2, "WHEELS" + wheelRadiusNum, wheelRadius)); + for (int i = 1; i < numWheels + 1; i++) + { + car.Wheels.Add("WHEELS" + wheelRadiusNum + i, new Wheels(2, "WHEELS1" + wheelRadiusNum + i, wheelRadius)); + } + } + wheelRadiusNum++; + break; + case KujuTokenID.Sound: + string soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), block.ReadString()); + if (File.Exists(soundFile)) + { + SoundModelSystemParser.ParseSoundFile(soundFile, ref car); + } + break; + case KujuTokenID.EngineControllers: + if (!isEngine) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine controllers are not expected to be present in a wagon block."); + break; + } + + while (block.Position() < block.Length() - 2) + { + // large number of potential controls when including diesel + steam, so allow *any* block here + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, fileName, ref wagonName, true, ref car, ref train); + } + break; + case KujuTokenID.Throttle: + case KujuTokenID.Brake_Train: + // NOTE: Throttle is valid for DIESEL + ELECTRIC only + block.ReadSingle(); // minimum + block.ReadSingle(); // maxiumum + block.ReadSingle(); // power step per notch + block.ReadSingle(); // default value (at start of simulation presumably) + newBlock = block.ReadSubBlock(KujuTokenID.NumNotches); + ParseBlock(newBlock, fileName, ref wagonName, true, ref car, ref train); + break; + case KujuTokenID.NumNotches: + // n.b. totalNotches value includes zero in MSTS + int totalNotches = block.ReadInt16(); + for (int i = 0; i < totalNotches; i++) + { + newBlock = block.ReadSubBlock(KujuTokenID.Notch); + ParseBlock(newBlock, fileName, ref wagonName, true, ref car, ref train); + } + switch (block.ParentBlock.Token) + { + case KujuTokenID.Throttle: + train.Handles.Power = new PowerHandle(totalNotches - 1, train); + break; + case KujuTokenID.Brake_Train: + train.Handles.Brake = new BrakeHandle(totalNotches - 1, totalNotches - 1, train.Handles.EmergencyBrake, new double[] { }, new double[] { }, train); + break; + } + break; + case KujuTokenID.Notch: + double powerValue = block.ReadSingle(); + double graduationValue = block.ReadSingle(); + string notchToken = block.ReadString(); + break; + } + return true; + } + } +} diff --git a/source/Plugins/Train.MsTs/app.config b/source/Plugins/Train.MsTs/app.config new file mode 100644 index 0000000000..a1e2bff25d --- /dev/null +++ b/source/Plugins/Train.MsTs/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/Plugins/Train.MsTs/packages.config b/source/Plugins/Train.MsTs/packages.config new file mode 100644 index 0000000000..5d2acb5f31 --- /dev/null +++ b/source/Plugins/Train.MsTs/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/source/TrainManager/Motor/MSTS/MotorSound.cs b/source/TrainManager/Motor/MSTS/MotorSound.cs new file mode 100644 index 0000000000..3f894c50e0 --- /dev/null +++ b/source/TrainManager/Motor/MSTS/MotorSound.cs @@ -0,0 +1,12 @@ +using TrainManager.Car; + +namespace TrainManager.Motor +{ + /// Represents a MSTS motor sound + public class MSTSMotorSound : AbstractMotorSound + { + public MSTSMotorSound(CarBase car) : base(car) + { + } + } +} diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs new file mode 100644 index 0000000000..2c8b94eb32 --- /dev/null +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -0,0 +1,124 @@ +// ReSharper disable InconsistentNaming + +using TrainManager.Car; +using TrainManager.Trains; + +namespace TrainManager.Power +{ + /// Represents a MSTS acceleration curve + public class MSTSAccelerationCurve : AccelerationCurve + { + /// Holds a reference to the car + /// Can't store the train reference directly as we may couple to another + private readonly CarBase baseCar; + /// The maximum force supplied by the engine + private readonly double MaxForce; + + public MSTSAccelerationCurve(CarBase car, double maxForce) + { + baseCar = car; + MaxForce = maxForce; + } + + public override double GetAccelerationOutput(double Speed) + { + /* + * According to Newton's second law, Acceleration = Force / Mass + * For the minute at least, we'll assume that the Force value specified in an + * ENG file is proportionate across power notches, and remains constant throughout + * the speed range. In practice, MSTS will actually have internally simulated this + * via engine RPM, boiler pressure etc. etc. but at present we obviously don't + * have these available. + * + * We don't in this case need to take the speed or loading values into account, but + * retain them as legacy + * REFACTOR: Store the train reference in BVE acceleration curves??? + */ + double totalMass = 0; + + TrainBase baseTrain = baseCar.baseTrain; + for (int i = 0; i < baseTrain.Cars.Length; i++) + { + totalMass += baseTrain.Cars[i].CurrentMass; + } + + if (baseTrain.Handles.EmergencyBrake.Actual) + { + return totalMass / MaxForce / 3.6; + } + + if (baseTrain.Handles.Brake.Actual > 0) + { + return ((baseTrain.Handles.Brake.Actual / (double)baseTrain.Handles.Brake.MaximumNotch) * (totalMass / MaxForce)) / 3.6; + } + + return ((baseTrain.Handles.Power.Actual / (double)baseTrain.Handles.Power.MaximumNotch) * (totalMass / MaxForce)) / 3.6; + + } + + public override double MaximumAcceleration + { + get + { + double totalMass = 0; + TrainBase baseTrain = baseCar.baseTrain; + for (int i = 0; i < baseTrain.Cars.Length; i++) + { + totalMass += baseTrain.Cars[i].CurrentMass; + } + + return totalMass / MaxForce; + } + } + + } + + public class MSTSDecelerationCurve : AccelerationCurve + { + private readonly TrainBase Train; + /// The maximum force supplied by the engine + private readonly double MaxForce; + + public MSTSDecelerationCurve(TrainBase train, double maxForce) + { + Train = train; + MaxForce = maxForce; + } + public override double GetAccelerationOutput(double Speed) + { + /* + * According to Newton's second law, Acceleration = Force / Mass + * For the minute at least, we'll assume that the Force value specified in an + * ENG file is proportionate across power notches, and remains constant throughout + * the speed range. In practice, MSTS will actually have internally simulated this + * via engine RPM, boiler pressure etc. etc. but at present we obviously don't + * have these available. + * + * We don't in this case need to take the speed or loading values into account, but + * retain them as legacy + * REFACTOR: Store the train reference in BVE acceleration curves??? + */ + double totalMass = 0; + for (int i = 0; i < Train.Cars.Length; i++) + { + totalMass += Train.Cars[i].CurrentMass; + } + + return totalMass / MaxForce / 3.6; ; + } + + public override double MaximumAcceleration + { + get + { + double totalMass = 0; + for (int i = 0; i < Train.Cars.Length; i++) + { + totalMass += Train.Cars[i].CurrentMass; + } + + return totalMass / MaxForce; + } + } + } +} diff --git a/source/TrainManager/Train/Station.cs b/source/TrainManager/Train/Station.cs index 3cad38dcba..625141ce68 100644 --- a/source/TrainManager/Train/Station.cs +++ b/source/TrainManager/Train/Station.cs @@ -1,4 +1,4 @@ -using System; +using System; using OpenBveApi; using OpenBveApi.Colors; using OpenBveApi.Hosts; diff --git a/source/TrainManager/TrainManager.csproj b/source/TrainManager/TrainManager.csproj index 256b2791de..b454623344 100644 --- a/source/TrainManager/TrainManager.csproj +++ b/source/TrainManager/TrainManager.csproj @@ -127,6 +127,8 @@ + + @@ -136,9 +138,11 @@ + + From 1c6db531fd41e0cb37817ce0bfeab6e9dc2f47fd Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 10 Mar 2025 13:23:32 +0000 Subject: [PATCH 02/82] Hook in bell, horn and headlights Only panel simulation for headlights at the minute, need to think about lighting cones and stuff... --- source/Plugins/Train.MsTs/Misc/Units.cs | 15 + source/Plugins/Train.MsTs/Panel/CvfParser.cs | 327 ++++--------------- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 59 ++-- source/Plugins/Train.MsTs/Train.MsTs.csproj | 1 + 4 files changed, 107 insertions(+), 295 deletions(-) create mode 100644 source/Plugins/Train.MsTs/Misc/Units.cs diff --git a/source/Plugins/Train.MsTs/Misc/Units.cs b/source/Plugins/Train.MsTs/Misc/Units.cs new file mode 100644 index 0000000000..56d181ddd2 --- /dev/null +++ b/source/Plugins/Train.MsTs/Misc/Units.cs @@ -0,0 +1,15 @@ +// ReSharper disable UnusedMember.Global +// ReSharper disable InconsistentNaming +namespace Train.MsTs +{ + internal enum Units + { + Unknown = 0, + Amps, + Inches_Of_Mercury, + Kilometers_Per_Hour, + Miles_Per_Hour, + PSI, + Kilo_Lbs + } +} diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 365de22fc5..fc2a48206a 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -88,15 +88,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) using (BinaryReader reader = new BinaryReader(fb)) { byte[] newBytes = reader.ReadBytes((int) (fb.Length - fb.Position)); - string s; - if (unicode) - { - s = Encoding.Unicode.GetString(newBytes); - } - else - { - s = Encoding.ASCII.GetString(newBytes); - } + string s = unicode ? Encoding.Unicode.GetString(newBytes) : Encoding.ASCII.GetString(newBytes); TextualBlock block = new TextualBlock(s, KujuTokenID.Tr_CabViewFile); ParseBlock(block); } @@ -152,8 +144,6 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) { Car.Driver = CabViews[0].position; Plugin.currentHost.RegisterTexture(CabViews[0].fileName, new TextureParameters(null, null), out Texture tday, true); - PanelBitmapWidth = tday.Width; - PanelBitmapHeight = tday.Height; CreateElement(ref Car.CarSections[0].Groups[0], 0.0, 0.0, 1024, 768, new Vector2(0.5, 0.5), 0.0, Car.Driver, tday, null, new Color32(255, 255, 255, 255)); } else @@ -165,7 +155,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) int currentLayer = 1; for (int i = 0; i < cabComponents.Count; i++) { - cabComponents[i].Create(ref Car, currentLayer, fileName); + cabComponents[i].Create(ref Car, currentLayer); } @@ -290,16 +280,13 @@ private static void ParseBlock(Block block) static double PanelTop = 0.0, PanelBottom = 768.0; static Vector2 PanelCenter = new Vector2(0, 240); private static Vector2 PanelOrigin = new Vector2(0, 240); - static double PanelBitmapWidth = 640.0, PanelBitmapHeight = 480.0; - - private class Component { private CabComponentType Type = CabComponentType.None; private string TexturePath; - private PanelSubject _panelSubject; - private string Units; + private PanelSubject panelSubject; + private Units Units; private Vector2 Position = new Vector2(0, 0); private Vector2 Size = new Vector2(0, 0); private double PivotPoint; @@ -314,7 +301,6 @@ private class Component internal void Parse() { - Block newBlock; if (!Enum.TryParse(myBlock.Token.ToString(), true, out Type)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognised CabViewComponent type."); @@ -326,7 +312,7 @@ internal void Parse() //Components in CVF files are considerably less structured, so read *any* valid block try { - newBlock = myBlock.ReadSubBlock(); + Block newBlock = myBlock.ReadSubBlock(); ReadSubBlock(newBlock); } catch @@ -337,9 +323,9 @@ internal void Parse() } } - internal void Create(ref CarBase Car, int Layer, string fileName) + internal void Create(ref CarBase Car, int Layer) { - if (File.Exists(TexturePath) && Units != null) + if (File.Exists(TexturePath)) { //Create and register texture @@ -353,8 +339,7 @@ internal void Create(ref CarBase Car, int Layer, string fileName) switch (Type) { case CabComponentType.Dial: - Texture tday; - Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(null, null), out tday, true); + Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(null, null), out Texture tday, true); //Get final position from the 640px panel (Yuck...) Position.X *= rW; Position.Y *= rH; @@ -365,7 +350,7 @@ internal void Create(ref CarBase Car, int Layer, string fileName) Car.CarSections[0].Groups[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0); Car.CarSections[0].Groups[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0); Car.CarSections[0].Groups[0].Elements[j].RotateYDirection = Vector3.Cross(Car.CarSections[0].Groups[0].Elements[j].RotateZDirection, Car.CarSections[0].Groups[0].Elements[j].RotateXDirection); - f = GetStackLanguageFromSubject(Car.baseTrain, Units, "Dial " + " in " + fileName); + f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); InitialAngle -= 360; InitialAngle *= 0.0174532925199433; //degrees to radians LastAngle *= 0.0174532925199433; @@ -420,7 +405,7 @@ internal void Create(ref CarBase Car, int Layer, string fileName) if (k == 0) j = l; } - f = GetStackLanguageFromSubject(Car.baseTrain, Units, "Lever " + " in " + fileName); + f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); } @@ -458,8 +443,7 @@ internal void Create(ref CarBase Car, int Layer, string fileName) if (k == 0) j = l; } - f = Type == CabComponentType.TwoState ? GetStackLanguageFromSubject(Car.baseTrain, Units, "TwoState " + " in " + fileName) : GetStackLanguageFromSubject(Car.baseTrain, Units, "TriState " + " in " + fileName); - + f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); } @@ -476,38 +460,14 @@ private void ReadSubBlock(Block block) PivotPoint = block.ReadSingle(); break; case KujuTokenID.Units: - string u = block.ReadString(); - switch (u.ToLowerInvariant()) - { - case "amps": - Units = "motor"; - break; - case "miles_per_hour": - Units = "mph"; - break; - case "inches_of_mercury": - case "psi": - //We don't simulate vaccum brakes, so just hook to PSI if vaccum is declared - switch (_panelSubject) - { - case PanelSubject.BrakeCylinder: - Units = "bc_psi"; - break; - case PanelSubject.BrakePipe: - Units = "bp_psi"; - break; - } - - break; - } - + Units = block.ReadEnumValue(default(Units)); break; case KujuTokenID.ScalePos: InitialAngle = block.ReadSingle(); LastAngle = block.ReadSingle(); break; case KujuTokenID.ScaleRange: - if (_panelSubject == PanelSubject.Ammeter) + if (panelSubject == PanelSubject.Ammeter) { //As we're currently using the BVE ammeter hack, ignore the values Minimum = 0; @@ -560,61 +520,8 @@ private void ReadSubBlock(Block block) Size.Y = block.ReadSingle(); break; case KujuTokenID.Type: - string t = block.ReadString(); - while (block.Position() < block.Length() - 2) - { - //Special case: Concated strings - //#2 appears to be the type repeated - //DO NOT RELY ON THIS THOUGH.... - t += @" " + block.ReadString(); - } - - switch (t.ToLowerInvariant()) - { - case "speedometer dial": - _panelSubject = PanelSubject.Speedometer; - break; - case "brake_pipe dial": - _panelSubject = PanelSubject.BrakePipe; - break; - case "brake_cyl dial": - _panelSubject = PanelSubject.BrakeCylinder; - break; - case "ammeter dial": - _panelSubject = PanelSubject.Ammeter; - break; - case "aspect_display cab_signal_display": - _panelSubject = PanelSubject.AWS; - break; - case "horn two_state": - _panelSubject = PanelSubject.Horn; - Units = "klaxon"; - break; - case "direction tri_state": - _panelSubject = PanelSubject.Direction; - Units = "rev"; - break; - case "throttle lever": - _panelSubject = PanelSubject.PowerHandle; - Units = "power"; - break; - case "engine_brake lever": - _panelSubject = PanelSubject.EngineBrakeHandle; - Units = "brake"; - break; - case "train_brake lever": - _panelSubject = PanelSubject.TrainBrakeHandle; - Units = "brake"; - break; - case "whistle two_state": - _panelSubject = PanelSubject.Horn; - Units = "klaxon"; - break; - case "wipers two_state": - _panelSubject = PanelSubject.WiperState; - Units = "wiperstate"; - break; - } + panelSubject = block.ReadEnumValue(default(PanelSubject)); + break; } @@ -628,185 +535,71 @@ internal Component(Block block) private readonly Block myBlock; } - - // get stack language from subject - private static string GetStackLanguageFromSubject(TrainBase Train, string Subject, string ErrorLocation) + private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject subject, Units subjectUnits) { - CultureInfo Culture = CultureInfo.InvariantCulture; - string Suffix = ""; - { - // detect d# suffix - int i; - for (i = Subject.Length - 1; i >= 0; i--) - { - int a = char.ConvertToUtf32(Subject, i); - if (a < 48 | a > 57) break; - } - - if (i >= 0 & i < Subject.Length - 1) - { - if (Subject[i] == 'd' | Subject[i] == 'D') - { - int n; - if (int.TryParse(Subject.Substring(i + 1), NumberStyles.Integer, Culture, out n)) - { - if (n == 0) - { - Suffix = " floor 10 mod"; - } - else - { - string t0 = Math.Pow(10.0, n).ToString(Culture); - string t1 = Math.Pow(10.0, -n).ToString(Culture); - Suffix = " ~ " + t0 + " >= <> " + t1 + " * floor 10 mod 10 ?"; - } - - Subject = Subject.Substring(0, i); - i--; - } - } - } - } // transform subject - string Code; - switch (Subject.ToLowerInvariant()) + string Code = string.Empty; + switch (subject) { - case "acc": + case PanelSubject.Ammeter: Code = "acceleration"; break; - case "motor": - Code = "accelerationmotor"; - break; - case "true": - Code = "1"; - break; - case "kmph": - Code = "speedometer abs 3.6 *"; - break; - case "mph": - Code = "speedometer abs 2.2369362920544 *"; - break; - case "ms": - Code = "speedometer abs"; - break; - case "bc": - Code = "brakecylinder 0.001 *"; - break; - case "bc_psi": - Code = "brakecylinder 0.000145038 *"; - break; - case "mr": - Code = "mainreservoir 0.001 *"; + case PanelSubject.Brake_Cyl: + switch (subjectUnits) + { + case Units.Inches_Of_Mercury: + case Units.PSI: + Code = "brakecylinder 0.001 *"; + break; + } break; - case "sap": - Code = "straightairpipe 0.001 *"; + case PanelSubject.Brake_Pipe: + switch (subjectUnits) + { + case Units.Inches_Of_Mercury: + case Units.PSI: + Code = "brakepipe 0.001 *"; + break; + } break; - case "bp": - Code = "brakepipe 0.001 *"; + case PanelSubject.Direction: + Code = "reverserNotch ++"; break; - case "bp_psi": - Code = "brakepipe 0.000145038 *"; + case PanelSubject.Engine_Brake: + Code = "locoBrakeNotch"; break; - case "er": - Code = "equalizingreservoir 0.001 *"; + case PanelSubject.Front_Hlight: + Code = "headlights"; break; - case "door": - Code = "1 doors -"; + case PanelSubject.Horn: + Code = "horn"; break; - case "csc": - Code = "constSpeed"; + case PanelSubject.Speedometer: + switch (subjectUnits) + { + case Units.Miles_Per_Hour: + Code = "speedometer abs 2.2369362920544 *"; + break; + case Units.Kilometers_Per_Hour: + Code = "speedometer abs 3.6 *"; + break; + } break; - case "power": + case PanelSubject.Throttle: Code = "brakeNotchLinear 0 powerNotch ?"; break; - case "brake": + case PanelSubject.Train_Brake: Code = "brakeNotchLinear"; break; - case "rev": - Code = "reverserNotch ++"; - break; - case "hour": - Code = "0.000277777777777778 time * 24 mod floor"; - break; - case "min": - Code = "0.0166666666666667 time * 60 mod floor"; - break; - case "sec": - Code = "time 60 mod floor"; - break; - case "atc": - Code = "271 pluginstate"; - break; - case "klaxon": - Code = "klaxon"; - break; - case "wiperstate": + case PanelSubject.Wipers: Code = "wiperstate"; break; default: - { Code = "0"; - bool unsupported = true; - if (Subject.StartsWith("ats", StringComparison.OrdinalIgnoreCase)) - { - string a = Subject.Substring(3); - int n; - if (int.TryParse(a, NumberStyles.Integer, CultureInfo.InvariantCulture, out n)) - { - if (n >= 0 & n <= 255) - { - Code = n.ToString(Culture) + " pluginstate"; - unsupported = false; - } - } - } - else if (Subject.StartsWith("doorl", StringComparison.OrdinalIgnoreCase)) - { - string a = Subject.Substring(5); - int n; - if (int.TryParse(a, NumberStyles.Integer, CultureInfo.InvariantCulture, out n)) - { - if (n >= 0 & n < Train.Cars.Length) - { - Code = n.ToString(Culture) + " leftdoorsindex ceiling"; - unsupported = false; - } - else - { - Code = "2"; - unsupported = false; - } - } - } - else if (Subject.StartsWith("doorr", StringComparison.OrdinalIgnoreCase)) - { - string a = Subject.Substring(5); - int n; - if (int.TryParse(a, NumberStyles.Integer, CultureInfo.InvariantCulture, out n)) - { - if (n >= 0 & n < Train.Cars.Length) - { - Code = n.ToString(Culture) + " rightdoorsindex ceiling"; - unsupported = false; - } - else - { - Code = "2"; - unsupported = false; - } - } - } - - if (unsupported) - { - Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid subject " + Subject + " encountered in " + ErrorLocation); - } - } break; } - - return Code + Suffix; + return Code; } internal static int CreateElement(ref ElementsGroup Group, double Left, double Top, double Width, double Height, Vector2 RelativeRotationCenter, double Distance, Vector3 Driver, Texture DaytimeTexture, Texture NighttimeTexture, Color32 Color, bool AddStateToLastElement = false) @@ -862,12 +655,6 @@ internal static int CreateElement(ref ElementsGroup Group, double Left, double T if (DaytimeTexture != null) { Object.Mesh.Materials[0].Flags |= MaterialFlags.TransparentColor; - - if (NighttimeTexture != null) - { - // In BVE4 and versions of OpenBVE prior to v1.7.1.0, elements with NighttimeImage defined are rendered with lighting disabled. - Object.Mesh.Materials[0].Flags |= MaterialFlags.DisableLighting; - } } Object.Mesh.Materials[0].Color = Color; diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 78b483df6c..a0bfb367ee 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -74,16 +74,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase Car) using (BinaryReader reader = new BinaryReader(fb)) { byte[] newBytes = reader.ReadBytes((int)(fb.Length - fb.Position)); - string s; - if (unicode) - { - s = Encoding.Unicode.GetString(newBytes); - } - else - { - s = Encoding.ASCII.GetString(newBytes); - } - + string s = unicode ? Encoding.Unicode.GetString(newBytes) : Encoding.ASCII.GetString(newBytes); TextualBlock block = new TextualBlock(s, KujuTokenID.Tr_SMS); ParseBlock(block, ref soundSet, ref Car); } @@ -245,7 +236,15 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.DisableTrigger }); ParseBlock(newBlock, ref currentSoundSet, ref car); break; + case KujuTokenID.StartLoopRelease: case KujuTokenID.StartLoop: + /* StartLoopRelease - Loop stops when key is released + * StartLoop - Loop continues when key is released + * --------------------------------------------------- + * NOTE: Handle these on a per-sound trigger, as where possible + * map to existing subsystems + */ + currentSoundType = block.Token; numStreams = block.ReadInt32(); for (int i = 0; i < numStreams; i++) { @@ -255,6 +254,10 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca ParseBlock(newBlock, ref currentSoundSet, ref car); } break; + case KujuTokenID.ReleaseLoopRelease: + // empty block expected + // appear to be paired with StartLoopRelease + break; case KujuTokenID.File: if (block.ReadPath(currentFolder, out string soundFile)) { @@ -301,6 +304,27 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca car.baseTrain.Handles.LocoBrake.Max = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); } break; + case SoundTrigger.LightSwitchToggle: + if (currentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.SafetySystems.Headlights != null) + { + Plugin.currentHost.RegisterSound(soundFile, 2.0, out var soundHandle); + car.baseTrain.SafetySystems.Headlights.SwitchSoundBuffer = soundHandle as SoundBuffer; + } + break; + case SoundTrigger.HornOn: + if (currentSoundType == KujuTokenID.StartLoopRelease && car.Horns[0] != null) + { + Plugin.currentHost.RegisterSound(soundFile, 2.0, out var soundHandle); + car.Horns[0].LoopSound = soundHandle as SoundBuffer; + } + break; + case SoundTrigger.BellOn: + if (currentSoundType == KujuTokenID.StartLoopRelease && car.Horns[2] != null) + { + Plugin.currentHost.RegisterSound(soundFile, 2.0, out var soundHandle); + car.Horns[0].LoopSound = soundHandle as SoundBuffer; + } + break; } } else @@ -326,13 +350,11 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca } break; case KujuTokenID.Discrete_Trigger: - currentSoundType = block.Token; currentTrigger = (SoundTrigger)block.ReadInt32(); // stored as integer newBlock = block.ReadSubBlock(new[] { KujuTokenID.PlayOneShot, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump }); ParseBlock(newBlock, ref currentSoundSet, ref car); break; case KujuTokenID.Variable_Trigger: - currentSoundType = block.Token; token = block.ReadEnumValue(default(KujuTokenID)); switch (token) { @@ -375,19 +397,6 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca ParseBlock(newBlock, ref currentSoundSet, ref car); } break; - case KujuTokenID.StartLoopRelease: - numStreams = block.ReadInt16(); - for (int i = 0; i < numStreams; i++) - { - newBlock = block.ReadSubBlock(KujuTokenID.File); - ParseBlock(newBlock, ref currentSoundSet, ref car); - } - newBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); - ParseBlock(newBlock, ref currentSoundSet, ref car); - break; - case KujuTokenID.ReleaseLoopRelease: - // empty block expected - break; case KujuTokenID.Volume: double volume = block.ReadSingle(); break; diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index 91b4e0978d..895c6795d2 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -55,6 +55,7 @@ + From b23682f670d7022e10c9d7d7d5dad0da5f6f7239 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 7 Nov 2025 12:14:51 +0000 Subject: [PATCH 03/82] Start to hook in diesel simulation to MSTS properties --- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 35 +++---- source/Plugins/Train.MsTs/Train.MsTs.csproj | 1 + .../Train/Enums/BrakeEquipmentType.cs | 4 +- .../Train.MsTs/Train/Enums/EngineTypes.cs | 10 ++ .../Plugins/Train.MsTs/Train/VehicleParser.cs | 91 +++++++++++++++++-- .../TrainManager/Power/AccelerationCurve.cs | 2 +- .../Power/MSTS/MSTSAccelerationCurve.cs | 10 +- source/TrainManager/TrainManager.csproj | 1 - 8 files changed, 119 insertions(+), 35 deletions(-) create mode 100644 source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index fc2a48206a..3428be56d9 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -298,6 +298,7 @@ private class Component private int HorizontalFrames; private int VerticalFrames; private bool MouseControl; + private bool DirIncrease; internal void Parse() { @@ -351,16 +352,12 @@ internal void Create(ref CarBase Car, int Layer) Car.CarSections[0].Groups[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0); Car.CarSections[0].Groups[0].Elements[j].RotateYDirection = Vector3.Cross(Car.CarSections[0].Groups[0].Elements[j].RotateZDirection, Car.CarSections[0].Groups[0].Elements[j].RotateXDirection); f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); - InitialAngle -= 360; - InitialAngle *= 0.0174532925199433; //degrees to radians - LastAngle *= 0.0174532925199433; + InitialAngle = InitialAngle.ToRadians(); + LastAngle = LastAngle.ToRadians(); double a0 = (InitialAngle * Maximum - LastAngle * Minimum) / (Maximum - Minimum); double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum); f += " " + a1.ToString(Culture) + " * " + a0.ToString(Culture) + " +"; Car.CarSections[0].Groups[0].Elements[j].RotateZFunction = new FunctionScript(Plugin.currentHost, f, false); - //MSTS cab dials are backstopped as standard - Car.CarSections[0].Groups[0].Elements[j].RotateZFunction.Minimum = InitialAngle; - Car.CarSections[0].Groups[0].Elements[j].RotateZFunction.Maximum = LastAngle; break; case CabComponentType.Lever: /* @@ -467,14 +464,6 @@ private void ReadSubBlock(Block block) LastAngle = block.ReadSingle(); break; case KujuTokenID.ScaleRange: - if (panelSubject == PanelSubject.Ammeter) - { - //As we're currently using the BVE ammeter hack, ignore the values - Minimum = 0; - Maximum = 1; - block.Skip((int) block.Length()); - } - Minimum = block.ReadSingle(); Maximum = block.ReadSingle(); break; @@ -486,7 +475,7 @@ private void ReadSubBlock(Block block) break; case KujuTokenID.DirIncrease: //Do we start at 0 or max? - block.Skip((int) block.Length()); + DirIncrease = block.ReadInt16() == 1; break; case KujuTokenID.Orientation: //Flip? @@ -543,23 +532,29 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject switch (subject) { case PanelSubject.Ammeter: - Code = "acceleration"; + Code = "amps"; break; case PanelSubject.Brake_Cyl: switch (subjectUnits) { - case Units.Inches_Of_Mercury: case Units.PSI: - Code = "brakecylinder 0.001 *"; + Code = "brakecylinder 0.000145038 *"; break; } break; case PanelSubject.Brake_Pipe: switch (subjectUnits) { - case Units.Inches_Of_Mercury: case Units.PSI: - Code = "brakepipe 0.001 *"; + Code = "brakecylinder 0.000145038 *"; + break; + } + break; + case PanelSubject.Main_Res: + switch (subjectUnits) + { + case Units.PSI: + Code = "mainreservoir 0.000145038 *"; break; } break; diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index 895c6795d2..c227286feb 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -66,6 +66,7 @@ + diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index 2a8fa6b8b6..095f49e372 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -29,6 +29,8 @@ enum BrakeEquipmentType /// This is optional. Emergency_Brake_Reservoir, /// Electronic or computer controller on the vehicle that can be set to independently control any parameter of the braking system. - Distributor + Distributor, + /// One pipe controls and supplies the air brakes. + Air_Single_Pipe } } diff --git a/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs b/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs new file mode 100644 index 0000000000..aa055975cf --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs @@ -0,0 +1,10 @@ +namespace Train.MsTs +{ + internal enum EngineType + { + NoEngine = 0, + Diesel, + Steam, + Electric + } +} diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 5e88669d15..986434df30 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -16,6 +16,7 @@ using TrainManager.BrakeSystems; using TrainManager.Car; using TrainManager.Handles; +using TrainManager.Motor; using TrainManager.Power; using TrainManager.Trains; @@ -42,6 +43,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r { wheelRadiusNum = 1; wagonFiles = Directory.GetFiles(trainSetDirectory, isEngine ? "*.eng" : "*.wag", SearchOption.AllDirectories); + currentEngineType = EngineType.NoEngine; /* * MSTS maintains an internal database, as opposed to using full paths * Unfortunately, this means we've got to do an approximation of the same thing! @@ -90,6 +92,29 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r } } } + + Car.Specs.AccelerationCurveMaximum = maxForce / Car.CurrentMass; + // as properties may not be in order, set this stuff last + if (isEngine) + { + switch (currentEngineType) + { + case EngineType.Diesel: + Car.Engine = new DieselEngine(Car, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); + Car.Engine.FuelTank = new FuelTank(dieselCapacity, 0, dieselCapacity); + Car.Engine.IsRunning = true; + + if (maxBrakeAmps > 0 && maxEngineAmps > 0) + { + Car.Engine.Components.Add(EngineComponent.RegenerativeTractionMotor, new RegenerativeTractionMotor(Car.Engine, maxEngineAmps, maxBrakeAmps)); + } + else if (maxEngineAmps > 0) + { + Car.Engine.Components.Add(EngineComponent.TractionMotor, new TractionMotor(Car.Engine, maxEngineAmps)); + } + break; + } + } } internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) @@ -204,6 +229,19 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private double maxForce = 0; private double maxBrakeForce = 0; private BrakeSystemType[] brakeSystemTypes; + private EngineType currentEngineType; + private double dieselIdleRPM; + private double dieselMaxRPM; + private double dieselRPMChangeRate; + private double dieselIdleUse; + private double dieselMaxUse; + private double dieselCapacity; + private double maxEngineAmps; + private double maxBrakeAmps; + private double mainReservoirMinimumPressure; + private double mainReservoirMaximumPressure; + private double brakeCylinderMaximumPressure; + private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { @@ -245,7 +283,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; } // Add brakes last, as we need the acceleration values - if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_piped)) + if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_Piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_Piped)) { /* * FIXME: Need to implement vac braked / air piped and vice-versa, but for the minute, we'll assume that if one or the other is present @@ -272,7 +310,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool // The car contains no control gear, but is air / vac braked // Assume equivilant to AutomaticAirBrake // NOTE: This must be last in the else-if chain to enure that a vehicle with EP / ECP and these declared is setup correctly - car.CarBrake = new AutomaticAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0,0,0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); } car.CarBrake.mainReservoir = new MainReservoir(690000.0, 780000.0, 0.01, 0.075 / train.Cars.Length); @@ -322,7 +360,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; } // Add brakes last, as we need the acceleration values - if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_piped)) + if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_Piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_Piped)) { /* * FIXME: Need to implement vac braked / air piped and vice-versa, but for the minute, we'll assume that if one or the other is present @@ -352,27 +390,27 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool car.CarBrake = new AutomaticAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); } - car.CarBrake.mainReservoir = new MainReservoir(690000.0, 780000.0, 0.01, 0.075 / train.Cars.Length); + car.CarBrake.mainReservoir = new MainReservoir(mainReservoirMinimumPressure, mainReservoirMaximumPressure, 0.01, 0.075 / train.Cars.Length); car.CarBrake.airCompressor = new Compressor(5000.0, car.CarBrake.mainReservoir, car); car.CarBrake.equalizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - car.CarBrake.equalizingReservoir.NormalPressure = 1.005 * 490000.0; + car.CarBrake.equalizingReservoir.NormalPressure = 1.005 * brakeCylinderMaximumPressure; double r = 200000.0 / 440000.0 - 1.0; if (r < 0.1) r = 0.1; if (r > 1.0) r = 1.0; - car.CarBrake.auxiliaryReservoir = new AuxiliaryReservoir(0.975 * 490000.0, 200000.0, 0.5, r); - car.CarBrake.brakeCylinder = new BrakeCylinder(440000.0, 440000.0, 0.3 * 300000.0, 300000.0, 200000.0); + car.CarBrake.auxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, r); + car.CarBrake.brakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure, 0.3 * 300000.0, 300000.0, 200000.0); car.CarBrake.straightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); } - car.CarBrake.brakePipe = new BrakePipe(490000.0, 10000000.0, 1500000.0, 5000000.0, true); + car.CarBrake.brakePipe = new BrakePipe(brakeCylinderMaximumPressure, 10000000.0, 1500000.0, 5000000.0, true); car.CarBrake.JerkUp = 10; car.CarBrake.JerkDown = 10; break; case KujuTokenID.Type: if (isEngine) { - //Will load engine type + currentEngineType = block.ReadEnumValue(default(EngineType)); } else { @@ -605,6 +643,41 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool double graduationValue = block.ReadSingle(); string notchToken = block.ReadString(); break; + case KujuTokenID.DieselEngineIdleRPM: + dieselIdleRPM = block.ReadSingle(); + break; + case KujuTokenID.DieselEngineMaxRPM: + dieselMaxRPM = block.ReadSingle(); + break; + case KujuTokenID.DieselEngineMaxRPMChangeRate: + dieselRPMChangeRate = block.ReadSingle(); + break; + case KujuTokenID.DieselUsedPerHourAtIdle: + dieselIdleUse = block.ReadSingle(UnitOfVolume.Litres); + dieselIdleUse /= 3600; + break; + case KujuTokenID.DieselUsedPerHourAtMaxPower: + dieselMaxUse = block.ReadSingle(UnitOfVolume.Litres); + dieselMaxUse /= 3600; + break; + case KujuTokenID.MaxDieselLevel: + dieselCapacity = block.ReadSingle(UnitOfVolume.Litres); + break; + case KujuTokenID.MaxCurrent: + maxEngineAmps = block.ReadSingle(UnitOfCurrent.Amps); + break; + case KujuTokenID.DynamicBrakesResistorCurrentLimit: + maxBrakeAmps = block.ReadSingle(UnitOfCurrent.Amps); + break; + case KujuTokenID.AirBrakesMainMinResAirPressure: + mainReservoirMinimumPressure = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + break; + case KujuTokenID.AirBrakesMainMaxAirPressure: + mainReservoirMaximumPressure = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + break; + case KujuTokenID.BrakeCylinderPressureForMaxBrakeBrakeForce: + brakeCylinderMaximumPressure = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + break; } return true; } diff --git a/source/TrainManager/Power/AccelerationCurve.cs b/source/TrainManager/Power/AccelerationCurve.cs index 3545fa5f9f..838fd02ac6 100644 --- a/source/TrainManager/Power/AccelerationCurve.cs +++ b/source/TrainManager/Power/AccelerationCurve.cs @@ -5,7 +5,7 @@ public abstract class AccelerationCurve { /// Gets the acceleration output for this curve /// The current speed - /// The acceleration output + /// The acceleration output in km/h/s public abstract double GetAccelerationOutput(double Speed); /// Gets the maximum possible acceleration output for this curve diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs index 2c8b94eb32..8ae8ab7891 100644 --- a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -1,6 +1,7 @@ // ReSharper disable InconsistentNaming using TrainManager.Car; +using TrainManager.Motor; using TrainManager.Trains; namespace TrainManager.Power @@ -32,7 +33,6 @@ public override double GetAccelerationOutput(double Speed) * * We don't in this case need to take the speed or loading values into account, but * retain them as legacy - * REFACTOR: Store the train reference in BVE acceleration curves??? */ double totalMass = 0; @@ -49,11 +49,15 @@ public override double GetAccelerationOutput(double Speed) if (baseTrain.Handles.Brake.Actual > 0) { - return ((baseTrain.Handles.Brake.Actual / (double)baseTrain.Handles.Brake.MaximumNotch) * (totalMass / MaxForce)) / 3.6; + return ((baseTrain.Handles.Brake.Actual / (double)baseTrain.Handles.Brake.MaximumNotch) * (totalMass / MaxForce)); } - return ((baseTrain.Handles.Power.Actual / (double)baseTrain.Handles.Power.MaximumNotch) * (totalMass / MaxForce)) / 3.6; + if (baseCar.Engine is DieselEngine dieselEngine) + { + return dieselEngine.CurrentPower * (totalMass / MaxForce); + } + return ((baseTrain.Handles.Power.Actual / (double)baseTrain.Handles.Power.MaximumNotch) * (totalMass / MaxForce)); } public override double MaximumAcceleration diff --git a/source/TrainManager/TrainManager.csproj b/source/TrainManager/TrainManager.csproj index b454623344..5e3df5a269 100644 --- a/source/TrainManager/TrainManager.csproj +++ b/source/TrainManager/TrainManager.csproj @@ -127,7 +127,6 @@ - From 0e9569e0f0fc66d1ea4c9dfc3f33072b4aa44add Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 26 Mar 2025 16:58:41 +0000 Subject: [PATCH 04/82] Read application / release rate from ENG --- source/Plugins/Train.MsTs/Train/VehicleParser.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 986434df30..8775e32661 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -241,6 +241,8 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private double mainReservoirMinimumPressure; private double mainReservoirMaximumPressure; private double brakeCylinderMaximumPressure; + private double emergencyRate; + private double releaseRate; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) @@ -678,6 +680,12 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.BrakeCylinderPressureForMaxBrakeBrakeForce: brakeCylinderMaximumPressure = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); break; + case KujuTokenID.TrainBrakesControllerEmergencyApplicationRate: + emergencyRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + break; + case KujuTokenID.TrainBrakesControllerMaxReleaseRate: + releaseRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + break; } return true; } From 975da835d808c83f12c1820f2d4201d9eb452519 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 31 Mar 2025 15:15:04 +0100 Subject: [PATCH 05/82] Hook in MaxVelocity and AntiSlip --- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 24 +++++++++++++------ .../Power/MSTS/MSTSAccelerationCurve.cs | 9 ++++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 8775e32661..18f3dceaef 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -97,6 +97,14 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r // as properties may not be in order, set this stuff last if (isEngine) { + Car.Specs.AccelerationCurves = new AccelerationCurve[] + { + new MSTSAccelerationCurve(Car, maxForce, maxVelocity) + }; + // FIXME: Default BVE values + Car.Specs.JerkPowerUp = 10.0; + Car.Specs.JerkPowerDown = 10.0; + Car.ReAdhesionDevice = new BveReAdhesionDevice(Car, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); switch (currentEngineType) { case EngineType.Diesel: @@ -243,6 +251,8 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private double brakeCylinderMaximumPressure; private double emergencyRate; private double releaseRate; + private double maxVelocity; + private bool hasAntiSlipDevice; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) @@ -544,13 +554,9 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; } maxForce = block.ReadSingle(UnitOfForce.Newton); - car.Specs.AccelerationCurves = new AccelerationCurve[] - { - new MSTSAccelerationCurve(car, maxForce) - }; - // FIXME: Default BVE values - car.Specs.JerkPowerUp = 10.0; - car.Specs.JerkPowerDown = 10.0; + break; + case KujuTokenID.MaxVelocity: + maxVelocity = block.ReadSingle(UnitOfVelocity.MetersPerSecond); break; case KujuTokenID.MaxBrakeForce: maxBrakeForce = block.ReadSingle(UnitOfForce.Newton); @@ -686,6 +692,10 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.TrainBrakesControllerMaxReleaseRate: releaseRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); break; + case KujuTokenID.AntiSlip: + // if any value in this block, car has wheelslip detection control + hasAntiSlipDevice = true; + break; } return true; } diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs index 8ae8ab7891..fd5752b8b0 100644 --- a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -14,8 +14,10 @@ public class MSTSAccelerationCurve : AccelerationCurve private readonly CarBase baseCar; /// The maximum force supplied by the engine private readonly double MaxForce; + /// The maximum velocity attainable + private readonly double MaxVelocity; - public MSTSAccelerationCurve(CarBase car, double maxForce) + public MSTSAccelerationCurve(CarBase car, double maxForce, double maxVelocity) { baseCar = car; MaxForce = maxForce; @@ -23,6 +25,11 @@ public MSTSAccelerationCurve(CarBase car, double maxForce) public override double GetAccelerationOutput(double Speed) { + if (baseCar.Specs.PerceivedSpeed > MaxVelocity) + { + return 0; + } + /* * According to Newton's second law, Acceleration = Force / Mass * For the minute at least, we'll assume that the Force value specified in an From 1df1254470b3b6333576a613e0eab7844a1b0420 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 31 Oct 2025 16:14:42 +0000 Subject: [PATCH 06/82] Implement basic electric engine --- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 13 ++++++++++- .../Power/MSTS/MSTSAccelerationCurve.cs | 22 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 18f3dceaef..a86ba53567 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -99,7 +99,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r { Car.Specs.AccelerationCurves = new AccelerationCurve[] { - new MSTSAccelerationCurve(Car, maxForce, maxVelocity) + new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) }; // FIXME: Default BVE values Car.Specs.JerkPowerUp = 10.0; @@ -121,6 +121,10 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r Car.Engine.Components.Add(EngineComponent.TractionMotor, new TractionMotor(Car.Engine, maxEngineAmps)); } break; + case EngineType.Electric: + Car.Engine = new ElectricEngine(Car); + Car.Engine.Components.Add(EngineComponent.Pantograph, new Pantograph(Car.Engine)); + break; } } } @@ -235,6 +239,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine } private double maxForce = 0; + private double maxContinuousForce = 0; private double maxBrakeForce = 0; private BrakeSystemType[] brakeSystemTypes; private EngineType currentEngineType; @@ -563,6 +568,12 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; case KujuTokenID.MaxContinuousForce: // Maximum continuous force + if (!isEngine) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine force is not expected to be present in a wagon block."); + break; + } + maxContinuousForce = block.ReadSingle(UnitOfForce.Newton); break; case KujuTokenID.RunUpTimeToMaxForce: // diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs index fd5752b8b0..6ba164ada9 100644 --- a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -17,10 +17,14 @@ public class MSTSAccelerationCurve : AccelerationCurve /// The maximum velocity attainable private readonly double MaxVelocity; - public MSTSAccelerationCurve(CarBase car, double maxForce, double maxVelocity) + private readonly double MaxContinuousForce; + + public MSTSAccelerationCurve(CarBase car, double maxForce, double maxContinuousForce, double maxVelocity) { baseCar = car; MaxForce = maxForce; + MaxContinuousForce = maxContinuousForce; + MaxVelocity = maxVelocity; } public override double GetAccelerationOutput(double Speed) @@ -61,9 +65,23 @@ public override double GetAccelerationOutput(double Speed) if (baseCar.Engine is DieselEngine dieselEngine) { + // diesel engine uses simulated power level of MaxForce return dieselEngine.CurrentPower * (totalMass / MaxForce); } + if (baseCar.Engine is ElectricEngine electricEngine) + { + if (baseCar.Specs.PerceivedSpeed < 3.61111 || baseCar.Specs.PerceivedSpeed > 7.22222) + { + // for electrics, MaxContinuousForce is used between 13km/h and 26km/h as per original Kuju physics model + return electricEngine.CurrentPower * (totalMass / MaxContinuousForce); + } + else + { + return electricEngine.CurrentPower * (totalMass / MaxForce); + } + } + return ((baseTrain.Handles.Power.Actual / (double)baseTrain.Handles.Power.MaximumNotch) * (totalMass / MaxForce)); } @@ -115,7 +133,7 @@ public override double GetAccelerationOutput(double Speed) totalMass += Train.Cars[i].CurrentMass; } - return totalMass / MaxForce / 3.6; ; + return totalMass / MaxForce / 3.6; } public override double MaximumAcceleration From 30c115fd127beed9961826bcfe6d5335045b9b43 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 31 Mar 2025 17:20:41 +0100 Subject: [PATCH 07/82] Add some minor bits --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 8 ++++---- .../Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs | 4 +++- source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index a0bfb367ee..15a2b26374 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -129,7 +129,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca { case KujuTokenID.Tr_SMS: // file root - while (block.Position() < block.Length()) + while (block.Position() < block.Length() - 3) { newBlock = block.ReadSubBlock(); ParseBlock(newBlock, ref currentSoundSet, ref car); @@ -155,7 +155,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca case KujuTokenID.Activation: // control the conditions under which the sounds in the group are activated currentSoundSet.Activation = true; - while (block.Position() < block.Length()) + while (block.Position() < block.Length() - 3) { newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, ref currentSoundSet, ref car); @@ -164,7 +164,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca case KujuTokenID.Deactivation: // control the conditions under which the sounds in the group are deactivated currentSoundSet.Activation = false; - while (block.Position() < block.Length() - 1) + while (block.Position() < block.Length() - 3) { newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, ref currentSoundSet, ref car); @@ -351,7 +351,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca break; case KujuTokenID.Discrete_Trigger: currentTrigger = (SoundTrigger)block.ReadInt32(); // stored as integer - newBlock = block.ReadSubBlock(new[] { KujuTokenID.PlayOneShot, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.PlayOneShot, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.SetStreamVolume }); ParseBlock(newBlock, ref currentSoundSet, ref car); break; case KujuTokenID.Variable_Trigger: diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index 095f49e372..d6200c446c 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -31,6 +31,8 @@ enum BrakeEquipmentType /// Electronic or computer controller on the vehicle that can be set to independently control any parameter of the braking system. Distributor, /// One pipe controls and supplies the air brakes. - Air_Single_Pipe + Air_Single_Pipe, + /// One pipe for control air, one pipe for supply air + Air_Twin_Pipe } } diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs index 6ba164ada9..43c6b7c8c0 100644 --- a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -16,7 +16,8 @@ public class MSTSAccelerationCurve : AccelerationCurve private readonly double MaxForce; /// The maximum velocity attainable private readonly double MaxVelocity; - + /// The maximum continuous force supplied by the engine + /// Used by electric model private readonly double MaxContinuousForce; public MSTSAccelerationCurve(CarBase car, double maxForce, double maxContinuousForce, double maxVelocity) From 72255ac37f9d4b1804bc2f3429723aecf9930dc8 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 31 Mar 2025 17:39:44 +0100 Subject: [PATCH 08/82] Start to parse safety systems from ENG --- source/Plugins/Train.MsTs/Train.MsTs.csproj | 6 + .../Plugins/Train.MsTs/Train/VehicleParser.cs | 16 ++- .../Train.MsTs/Vigilance/AWSMonitor.cs | 6 + .../Vigilance/AbstractVigilanceDevice.cs | 135 ++++++++++++++++++ .../Vigilance/EmergencyStopMonitor.cs | 6 + .../Train.MsTs/Vigilance/OverspeedMonitor.cs | 6 + .../Train.MsTs/Vigilance/ResetCondition.cs | 19 +++ .../Train.MsTs/Vigilance/VigilanceMonitor.cs | 6 + 8 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 source/Plugins/Train.MsTs/Vigilance/AWSMonitor.cs create mode 100644 source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs create mode 100644 source/Plugins/Train.MsTs/Vigilance/EmergencyStopMonitor.cs create mode 100644 source/Plugins/Train.MsTs/Vigilance/OverspeedMonitor.cs create mode 100644 source/Plugins/Train.MsTs/Vigilance/ResetCondition.cs create mode 100644 source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index c227286feb..2c7400ab6e 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -68,6 +68,12 @@ + + + + + + diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index a86ba53567..bb166d3278 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -132,7 +132,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); - + vigilanceDevices = new List(); byte[] buffer = new byte[34]; fb.Read(buffer, 0, 2); @@ -258,6 +258,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private double releaseRate; private double maxVelocity; private bool hasAntiSlipDevice; + private List vigilanceDevices; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) @@ -707,6 +708,19 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool // if any value in this block, car has wheelslip detection control hasAntiSlipDevice = true; break; + case KujuTokenID.AWSMonitor: + case KujuTokenID.EmergencyStopMonitor: + case KujuTokenID.OverspeedMonitor: + case KujuTokenID.VigilanceMonitor: + // MSTS safety systems + VigilanceDevice device = VigilanceDevice.CreateVigilanceDevice(block.Token); + while (block.Position() < block.Length() - 2) + { + newBlock = block.ReadSubBlock(); + device.ParseBlock(newBlock); + } + vigilanceDevices.Add(device); + break; } return true; } diff --git a/source/Plugins/Train.MsTs/Vigilance/AWSMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/AWSMonitor.cs new file mode 100644 index 0000000000..2e2ea0bc0a --- /dev/null +++ b/source/Plugins/Train.MsTs/Vigilance/AWSMonitor.cs @@ -0,0 +1,6 @@ +namespace Train.MsTs +{ + internal class AWSMonitor : VigilanceDevice + { + } +} diff --git a/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs b/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs new file mode 100644 index 0000000000..9c39e2903d --- /dev/null +++ b/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs @@ -0,0 +1,135 @@ +using System.IO; +using OpenBve.Formats.MsTs; + +namespace Train.MsTs +{ + internal abstract class VigilanceDevice + { + /// The time limit before this device alarms + /// Set to -1 for triggered devices + internal double TimeLimit; + /// The time limit before the device intervenes in alarm state + internal double AlarmTimeLimit; + /// The time limit before the device applies a penalty + internal double PenaltyTimeLimit; + /// The critical level of the trigger + internal double CriticalLevel; + /// The reset level of the trigger + internal double ResetLevel; + /// Whether full brake is applied on intervention + internal bool AppliesFullBrake; + /// Whether emergency brake is applied on intervention + internal bool AppliesEmergencyBrake; + /// Whether power is cut on invervention + internal bool CutsPower; + /// Whether the engine is shut down on intervention + internal bool ShutsDownEngine; + /// The reset conditions + internal ResetCondition ResetConditions; + + internal static VigilanceDevice CreateVigilanceDevice(KujuTokenID token) + { + switch (token) + { + case KujuTokenID.AWSMonitor: + return new AWSMonitor(); + case KujuTokenID.EmergencyStopMonitor: + return new EmergencyStopMonitor(); + case KujuTokenID.OverspeedMonitor: + return new OverspeedMonitor(); + case KujuTokenID.VigilanceMonitor: + return new VigilanceMonitor(); + default: + throw new InvalidDataException("Not a valid vigilance device"); + } + } + + internal void ParseBlock(Block block) + { + switch (block.Token) + { + case KujuTokenID.MonitoringDeviceMonitorTimeLimit: + TimeLimit = block.ReadSingle(); + break; + case KujuTokenID.MonitoringDeviceAlarmTimeLimit: + AlarmTimeLimit = block.ReadSingle(); + break; + case KujuTokenID.MonitoringDevicePenaltyTimeLimit: + PenaltyTimeLimit = block.ReadSingle(); + break; + case KujuTokenID.MonitoringDeviceCriticalLevel: + CriticalLevel = block.ReadSingle(); + break; + case KujuTokenID.MonitoringDeviceResetLevel: + ResetLevel = block.ReadSingle(); + break; + case KujuTokenID.MonitoringDeviceAppliesFullBrake: + AppliesFullBrake = block.ReadInt32() == 1; + break; + case KujuTokenID.MonitoringDeviceAppliesEmergencyBrake: + AppliesEmergencyBrake = block.ReadInt32() == 1; + break; + case KujuTokenID.MonitoringDeviceAppliesCutsPower: + CutsPower = block.ReadInt32() == 1; + break; + case KujuTokenID.MonitoringDeviceAppliesShutsDownEngine: + ShutsDownEngine = block.ReadInt32() == 1; + break; + case KujuTokenID.MonitoringDeviceResetOnZeroSpeed: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.ZeroSpeed; + } + break; + case KujuTokenID.MonitoringDeviceResetOnZeroThrottle: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.ZeroThrottle; + } + break; + case KujuTokenID.MonitoringDeviceResetOnDirectionNeutral: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.DirectionNeutral; + } + break; + case KujuTokenID.MonitoringDeviceResetOnEngineAtIdle: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.EngineAtIdle; + } + break; + case KujuTokenID.MonitoringDeviceResetOnBrakeOff: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.BrakeOff; + } + break; + case KujuTokenID.MonitoringDeviceResetOnBrakeFullyOn: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.BrakeFullyOn; + } + break; + case KujuTokenID.MonitoringDeviceResetOnDynamicBrakeOff: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.DynamicBrakeOff; + } + break; + case KujuTokenID.MonitoringDeviceResetOnDynamicBrakeOn: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.DynamicBrakeOn; + } + break; + case KujuTokenID.MonitoringDeviceResetOnResetButton: + if (block.ReadInt32() == 1) + { + ResetConditions |= ResetCondition.ResetButton; + } + break; + } + } + } +} diff --git a/source/Plugins/Train.MsTs/Vigilance/EmergencyStopMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/EmergencyStopMonitor.cs new file mode 100644 index 0000000000..44d7e86c96 --- /dev/null +++ b/source/Plugins/Train.MsTs/Vigilance/EmergencyStopMonitor.cs @@ -0,0 +1,6 @@ +namespace Train.MsTs +{ + internal class EmergencyStopMonitor : VigilanceDevice + { + } +} diff --git a/source/Plugins/Train.MsTs/Vigilance/OverspeedMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/OverspeedMonitor.cs new file mode 100644 index 0000000000..5e6d884495 --- /dev/null +++ b/source/Plugins/Train.MsTs/Vigilance/OverspeedMonitor.cs @@ -0,0 +1,6 @@ +namespace Train.MsTs +{ + internal class OverspeedMonitor : VigilanceDevice + { + } +} diff --git a/source/Plugins/Train.MsTs/Vigilance/ResetCondition.cs b/source/Plugins/Train.MsTs/Vigilance/ResetCondition.cs new file mode 100644 index 0000000000..578aa040cf --- /dev/null +++ b/source/Plugins/Train.MsTs/Vigilance/ResetCondition.cs @@ -0,0 +1,19 @@ +using System; + +namespace Train.MsTs +{ + [Flags] + public enum ResetCondition + { + None = 0, + ZeroSpeed = 1, + ZeroThrottle= 2, + DirectionNeutral = 4, + EngineAtIdle = 8, + BrakeOff = 16, + BrakeFullyOn = 32, + DynamicBrakeOff = 64, + DynamicBrakeOn = 128, + ResetButton = 256, + } +} diff --git a/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs new file mode 100644 index 0000000000..f666e9b427 --- /dev/null +++ b/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs @@ -0,0 +1,6 @@ +namespace Train.MsTs +{ + internal class VigilanceMonitor : VigilanceDevice + { + } +} From f9643ed3a51e78bfa7b01656128d6e00903a3077 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 31 Mar 2025 18:43:02 +0100 Subject: [PATCH 09/82] More work --- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 2 -- .../Plugins/Train.MsTs/Train/ConsistParser.cs | 20 +++++++------------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 3428be56d9..09f7e883a1 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -510,8 +510,6 @@ private void ReadSubBlock(Block block) break; case KujuTokenID.Type: panelSubject = block.ReadEnumValue(default(PanelSubject)); - - break; } } diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index ce56a908b9..617fe20844 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -215,20 +215,13 @@ private void ParseBlock(Block block, ref TrainBase Train) break; case KujuTokenID.Engine: case KujuTokenID.Wagon: - newBlock = block.ReadSubBlock(new[] {KujuTokenID.EngineData, KujuTokenID.WagonData, KujuTokenID.UiD, KujuTokenID.Flip}); - Block secondBlock = block.ReadSubBlock(new[] {KujuTokenID.EngineData, KujuTokenID.WagonData, KujuTokenID.UiD}); - //Must have 2x blocks, car UiD and car name. Order doesn't matter however, so we've gotta DIY as we need the car number - if (newBlock.Token == KujuTokenID.UiD) - { - ParseBlock(newBlock, ref Train); - ParseBlock(secondBlock, ref Train); - } - else + Array.Resize(ref Train.Cars, Train.Cars.Length + 1); + currentCarIndex++; + while (block.Length() - block.Position() > 2) { - ParseBlock(secondBlock, ref Train); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.EngineData, KujuTokenID.WagonData, KujuTokenID.UiD, KujuTokenID.Flip, KujuTokenID.EngineVariables }); ParseBlock(newBlock, ref Train); } - currentCar.Doors = new[] { new Door(-1, 1000.0, 0), @@ -257,8 +250,6 @@ private void ParseBlock(Block block, ref TrainBase Train) case KujuTokenID.UiD: // Unique ID of engine / wagon within consist // For the minute, let's just create a new car and advance our car number - Array.Resize(ref Train.Cars, Train.Cars.Length + 1); - currentCarIndex++; break; case KujuTokenID.WagonData: case KujuTokenID.EngineData: @@ -313,6 +304,9 @@ private void ParseBlock(Block block, ref TrainBase Train) // Allows a car to be reversed within a consist reverseCurentCar = true; break; + case KujuTokenID.EngineVariables: + // Sets properties of the train when loaded, ignore for the minute + break; } } From 8a8f1b1dafe17782edb0381761731a427aef462d Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 2 Apr 2025 12:16:11 +0100 Subject: [PATCH 10/82] New: Create variable handle type, implement MSTS per notch figures --- source/Plugins/Train.MsTs/Train.MsTs.csproj | 1 + .../Plugins/Train.MsTs/Train/VehicleParser.cs | 37 ++++--------------- .../Power/MSTS/MSTSAccelerationCurve.cs | 8 +++- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index 2c7400ab6e..5cbdb6ff3e 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -55,6 +55,7 @@ + diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index bb166d3278..38b4ffedb3 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -15,14 +15,13 @@ using SharpCompress.Compressors.Deflate; using TrainManager.BrakeSystems; using TrainManager.Car; -using TrainManager.Handles; using TrainManager.Motor; using TrainManager.Power; using TrainManager.Trains; namespace Train.MsTs { - internal class WagonParser + internal partial class WagonParser { private readonly Plugin plugin; @@ -631,37 +630,15 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } break; case KujuTokenID.Throttle: - case KujuTokenID.Brake_Train: - // NOTE: Throttle is valid for DIESEL + ELECTRIC only - block.ReadSingle(); // minimum - block.ReadSingle(); // maxiumum - block.ReadSingle(); // power step per notch - block.ReadSingle(); // default value (at start of simulation presumably) - newBlock = block.ReadSubBlock(KujuTokenID.NumNotches); - ParseBlock(newBlock, fileName, ref wagonName, true, ref car, ref train); - break; - case KujuTokenID.NumNotches: - // n.b. totalNotches value includes zero in MSTS - int totalNotches = block.ReadInt16(); - for (int i = 0; i < totalNotches; i++) - { - newBlock = block.ReadSubBlock(KujuTokenID.Notch); - ParseBlock(newBlock, fileName, ref wagonName, true, ref car, ref train); - } - switch (block.ParentBlock.Token) + if (currentEngineType == EngineType.Steam) { - case KujuTokenID.Throttle: - train.Handles.Power = new PowerHandle(totalNotches - 1, train); - break; - case KujuTokenID.Brake_Train: - train.Handles.Brake = new BrakeHandle(totalNotches - 1, totalNotches - 1, train.Handles.EmergencyBrake, new double[] { }, new double[] { }, train); - break; + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: A throttle is not valid for a Steam Locomotive."); + break; } + train.Handles.Power = ParseHandle(block, train); break; - case KujuTokenID.Notch: - double powerValue = block.ReadSingle(); - double graduationValue = block.ReadSingle(); - string notchToken = block.ReadString(); + case KujuTokenID.Brake_Train: + train.Handles.Brake = ParseHandle(block, train); break; case KujuTokenID.DieselEngineIdleRPM: dieselIdleRPM = block.ReadSingle(); diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs index 43c6b7c8c0..ab90fcec6a 100644 --- a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -1,6 +1,7 @@ // ReSharper disable InconsistentNaming using TrainManager.Car; +using TrainManager.Handles; using TrainManager.Motor; using TrainManager.Trains; @@ -134,7 +135,12 @@ public override double GetAccelerationOutput(double Speed) totalMass += Train.Cars[i].CurrentMass; } - return totalMass / MaxForce / 3.6; + double maxBrakeForce = totalMass / MaxForce / 3.6; + if (Train.Handles.Brake is VariableHandle variableHandle) + { + maxBrakeForce *= variableHandle.GetPowerModifier; + } + return maxBrakeForce; } public override double MaximumAcceleration From aa05e957640c7a9be8ac0fdbfc8a371afbb4d4f5 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 3 Apr 2025 20:51:17 +0100 Subject: [PATCH 11/82] More WIP --- source/Plugins/Train.MsTs/Misc/Units.cs | 1 + source/Plugins/Train.MsTs/Panel/CvfParser.cs | 9 +++++++++ .../Train.MsTs/Panel/Enums/PanelSubject.cs | 19 +++++++++++++++++++ source/Plugins/Train.MsTs/Sound/SmsParser.cs | 4 ++-- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/source/Plugins/Train.MsTs/Misc/Units.cs b/source/Plugins/Train.MsTs/Misc/Units.cs index 56d181ddd2..8951c8caa7 100644 --- a/source/Plugins/Train.MsTs/Misc/Units.cs +++ b/source/Plugins/Train.MsTs/Misc/Units.cs @@ -6,6 +6,7 @@ internal enum Units { Unknown = 0, Amps, + Gallons, Inches_Of_Mercury, Kilometers_Per_Hour, Miles_Per_Hour, diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 09f7e883a1..0c855ec187 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -538,6 +538,9 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject case Units.PSI: Code = "brakecylinder 0.000145038 *"; break; + case Units.Inches_Of_Mercury: + Code = "0"; + break; } break; case PanelSubject.Brake_Pipe: @@ -546,6 +549,9 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject case Units.PSI: Code = "brakecylinder 0.000145038 *"; break; + case Units.Inches_Of_Mercury: + Code = "0"; + break; } break; case PanelSubject.Main_Res: @@ -554,6 +560,9 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject case Units.PSI: Code = "mainreservoir 0.000145038 *"; break; + case Units.Inches_Of_Mercury: + Code = "0"; + break; } break; case PanelSubject.Direction: diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs index 891d252f52..edb164fed3 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -9,16 +9,22 @@ internal enum PanelSubject Aspect_Display, AWS, Bell, + Blower, Brake_Cyl, Brake_Pipe, Clock, CP_Handle, CPH_Display, + Cutoff, + Cyl_Cocks, + Dampers_Front, Direction, Direction_Display, Engine_Brake, Emergency_Brake, Eq_Res, + Firebox, + Firehole, Friction_Braking, Front_Hlight, Horn, @@ -28,14 +34,27 @@ internal enum PanelSubject Pantograph, Panto_Display, Penalty_App, + Regulator, Reset, + Reverser_Plate, Sanders, Sanding, + Small_Ejector, Speedlim_Display, Speedometer, + Steamchest_Pr, + Steamheat_Pressure, + Steam_Inj1, + Steam_Inj2, + Steam_Pr, + Tender_Water, + Boiler_Water, Throttle, Traction_Braking, Train_Brake, + Vacuum_Reservoir_Pressure, + Water_Injector1, + Water_Injector2, Wheelslip, Whistle, Wipers, diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 15a2b26374..f9f51947d7 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -250,9 +250,9 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca { newBlock = block.ReadSubBlock(KujuTokenID.File); ParseBlock(newBlock, ref currentSoundSet, ref car); - newBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); - ParseBlock(newBlock, ref currentSoundSet, ref car); } + newBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); + ParseBlock(newBlock, ref currentSoundSet, ref car); break; case KujuTokenID.ReleaseLoopRelease: // empty block expected From 07d5590b03134aba5922d2fdf7e80c2beaa84dc4 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 3 Apr 2025 21:03:22 +0100 Subject: [PATCH 12/82] Fix rotation glitch, animate pantographs --- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 5 +++++ source/Plugins/Train.MsTs/Train/VehicleParser.cs | 15 +++++---------- source/TrainManager/TrainManager.csproj | 1 - 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 0c855ec187..6ad0431f28 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -462,6 +462,11 @@ private void ReadSubBlock(Block block) case KujuTokenID.ScalePos: InitialAngle = block.ReadSingle(); LastAngle = block.ReadSingle(); + // correct angle position if appropriate + if (InitialAngle > LastAngle) + { + InitialAngle = -(365 - InitialAngle); + } break; case KujuTokenID.ScaleRange: Minimum = block.ReadSingle(); diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 38b4ffedb3..3f84bb7750 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -8,6 +8,7 @@ using OpenBve.Formats.MsTs; using OpenBveApi.Graphics; using OpenBveApi.Interface; +using OpenBveApi.Motor; using OpenBveApi.Objects; using OpenBveApi.Trains; using OpenBveApi.World; @@ -28,7 +29,6 @@ internal partial class WagonParser private readonly Dictionary wagonCache; private readonly Dictionary engineCache; private string[] wagonFiles; - private int wheelRadiusNum; private double wheelRadius; internal WagonParser(Plugin Plugin) @@ -40,7 +40,7 @@ internal WagonParser(Plugin Plugin) internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, ref CarBase Car, ref TrainBase train) { - wheelRadiusNum = 1; + exteriorLoaded = false; wagonFiles = Directory.GetFiles(trainSetDirectory, isEngine ? "*.eng" : "*.wag", SearchOption.AllDirectories); currentEngineType = EngineType.NoEngine; /* @@ -594,19 +594,14 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool numWheels /= 2; } - if (numWheels == 1) + if (block.ParentBlock.Token == KujuTokenID.Engine) { - car.Wheels.Add("WHEELS" + wheelRadiusNum, new Wheels(2, "WHEELS" + wheelRadiusNum, wheelRadius)); + car.DrivingWheels.Add(new Wheels(numWheels == 1 ? 2 : numWheels, wheelRadius)); } else { - car.Wheels.Add("WHEELS" + wheelRadiusNum, new Wheels(2, "WHEELS" + wheelRadiusNum, wheelRadius)); - for (int i = 1; i < numWheels + 1; i++) - { - car.Wheels.Add("WHEELS" + wheelRadiusNum + i, new Wheels(2, "WHEELS1" + wheelRadiusNum + i, wheelRadius)); - } + car.TrailingWheels.Add(new Wheels(numWheels == 1 ? 2 : numWheels, wheelRadius)); } - wheelRadiusNum++; break; case KujuTokenID.Sound: string soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), block.ReadString()); diff --git a/source/TrainManager/TrainManager.csproj b/source/TrainManager/TrainManager.csproj index 5e3df5a269..6c2c037317 100644 --- a/source/TrainManager/TrainManager.csproj +++ b/source/TrainManager/TrainManager.csproj @@ -127,7 +127,6 @@ - From a9bf729976fe474c8bb6036ada67e9c9b4f0a27f Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 7 Apr 2025 17:43:05 +0100 Subject: [PATCH 13/82] More fixes to MSTS vehicle parser --- source/Plugins/Train.MsTs/Handles/Handle.cs | 4 ++-- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 2 +- source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/Plugins/Train.MsTs/Handles/Handle.cs b/source/Plugins/Train.MsTs/Handles/Handle.cs index 79cf20f144..4db9a055a4 100644 --- a/source/Plugins/Train.MsTs/Handles/Handle.cs +++ b/source/Plugins/Train.MsTs/Handles/Handle.cs @@ -23,8 +23,8 @@ internal AbstractHandle ParseHandle(Block block, TrainBase train) HandleMaximum = (int)(block.ReadSingle() * 100); HandleStep = (int)(block.ReadSingle() * 100); HandleStartingPosition = (int)(block.ReadSingle() * 100); - - Block notchDescriptions = block.ReadSubBlock(KujuTokenID.NumNotches); + // some handles seem to have extra numbers here, I believe ignored by the MSTS parser + Block notchDescriptions = block.GetSubBlock(KujuTokenID.NumNotches); ParseNotchDescriptionBlock(notchDescriptions); return new VariableHandle(train, NotchDescriptions); } diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index f9f51947d7..76d616ef9c 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -233,7 +233,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca break; case KujuTokenID.Initial_Trigger: // when initially appears, hence nothing other than StartLoop should be valid - newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.DisableTrigger }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.DisableTrigger }); ParseBlock(newBlock, ref currentSoundSet, ref car); break; case KujuTokenID.StartLoopRelease: diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index d6200c446c..3ba86b2bb3 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -24,7 +24,8 @@ enum BrakeEquipmentType /// Same functionality as ep_brake ECP_Brake, /// Air tank used for normal service brake applications. This is required for all brake systems. - Auxilary_Reservoir, + Auxiliary_Reservoir = 9, + Auxilary_Reservoir = 9, /// Air tank used for emergency applications. /// This is optional. Emergency_Brake_Reservoir, From 889b308c95e3475d4c01331a37e30d076e91183a Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 9 Apr 2025 09:39:27 +0100 Subject: [PATCH 14/82] Implement FreightAnim --- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 3f84bb7750..49c8cc5300 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -30,6 +30,7 @@ internal partial class WagonParser private readonly Dictionary engineCache; private string[] wagonFiles; private double wheelRadius; + private bool exteriorLoaded = false; internal WagonParser(Plugin Plugin) { @@ -459,11 +460,20 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool if (Plugin.currentHost.Plugins[i].Object != null && Plugin.currentHost.Plugins[i].Object.CanLoadObject(objectFile)) { - Plugin.currentHost.Plugins[i].Object.LoadObject(objectFile, Encoding.Default, out UnifiedObject carObject); - car.LoadCarSections(carObject, false); + Plugin.currentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject carObject); + if (exteriorLoaded) + { + car.CarSections[car.CarSections.Length -1].AppendObject(Plugin.currentHost, car, carObject); + } + else + { + car.LoadCarSections(carObject, false); + } break; } } + + exteriorLoaded = true; break; case KujuTokenID.Size: // Physical size of the car @@ -693,6 +703,32 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } vigilanceDevices.Add(device); break; + case KujuTokenID.FreightAnim: + objectFile = OpenBveApi.Path.CombineFile(Path.GetDirectoryName(fileName), block.ReadString()); + if (!File.Exists(objectFile)) + { + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle object file " + objectFile + " was not found"); + break; + } + + for (int i = 0; i < Plugin.currentHost.Plugins.Length; i++) + { + + if (Plugin.currentHost.Plugins[i].Object != null && Plugin.currentHost.Plugins[i].Object.CanLoadObject(objectFile)) + { + Plugin.currentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject freightObject); + if (exteriorLoaded) + { + car.CarSections[car.CarSections.Length - 1].AppendObject(Plugin.currentHost, car, freightObject); + } + else + { + car.LoadCarSections(freightObject, false); + } + break; + } + } + break; } return true; } From e5001e4d40e53dd4ee2f45f3178ad4cac92ec072 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 9 Apr 2025 11:54:04 +0100 Subject: [PATCH 15/82] Ignore sound file validation for trigger of -1 Appears to have the same effect as skip --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 5 ++++- source/Plugins/Train.MsTs/Sound/TriggerTypes.cs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 76d616ef9c..a9351d5afe 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -329,7 +329,10 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca } else { - Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Sound File " + soundFile + " was not found in SMS " + currentFile); + if (currentTrigger != SoundTrigger.Skip) + { + Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Sound File " + soundFile + " was not found in SMS " + currentFile); + } } int checkDigit = block.ReadInt32(); if (checkDigit != -1) diff --git a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs index 456a62d337..c51d31c467 100644 --- a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs +++ b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs @@ -3,6 +3,10 @@ namespace Train.MsTs { internal enum SoundTrigger { + // -1 appears to be used to skip the sound + // unclear as to why it's not just left out, copy + paste or internal MSTS validation? + // some of the time, these exist, but a lot of the time the sound file is missing + Skip = -1, DynamicBrakeIncrease = 2, DynamicBrakeOff = 3, SanderOn = 4, From 59f47fb2c884b32c5a22ea46b2023e0af7e61bce Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 31 Oct 2025 16:36:20 +0000 Subject: [PATCH 16/82] Start to implement SoundStreams --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 145 ++++++++++++------ .../Plugins/Train.MsTs/Sound/TriggerTypes.cs | 1 + .../Plugins/Train.MsTs/Train/ConsistParser.cs | 1 - .../Plugins/Train.MsTs/Train/VehicleParser.cs | 32 ++-- source/SoundManager/SoundManager.csproj | 2 + source/TrainManager/Car/CarSounds.cs | 6 +- .../Motor/ElectricEngine/ElectricEngine.cs | 4 +- source/TrainManager/Motor/MSTS/MotorSound.cs | 12 -- .../Sounds/MSTS/FrequencyCurve.cs | 55 +++++++ .../TrainManager/Sounds/MSTS/SoundStream.cs | 37 +++++ .../Sounds/MSTS/SoundTrigger.Speed.cs | 77 ++++++++++ .../MSTS/SoundTrigger.VariableControlled.cs | 86 +++++++++++ .../TrainManager/Sounds/MSTS/SoundTrigger.cs | 25 +++ .../TrainManager/Sounds/MSTS/VolumeCurve.cs | 50 ++++++ source/TrainManager/Train/TrainBase.cs | 7 +- source/TrainManager/TrainManager.csproj | 11 +- 16 files changed, 476 insertions(+), 75 deletions(-) delete mode 100644 source/TrainManager/Motor/MSTS/MotorSound.cs create mode 100644 source/TrainManager/Sounds/MSTS/FrequencyCurve.cs create mode 100644 source/TrainManager/Sounds/MSTS/SoundStream.cs create mode 100644 source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs create mode 100644 source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs create mode 100644 source/TrainManager/Sounds/MSTS/SoundTrigger.cs create mode 100644 source/TrainManager/Sounds/MSTS/VolumeCurve.cs diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index a9351d5afe..d626bcae41 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -4,9 +4,11 @@ using System; using System.Text; using OpenBveApi.Interface; +using OpenBveApi.World; using SharpCompress.Compressors; using TrainManager.Car; using SoundManager; +using TrainManager.MsTsSounds; namespace Train.MsTs { @@ -15,6 +17,8 @@ class SoundModelSystemParser private static string currentFolder; private static string currentFile; + + private static Tuple[] curvePoints; internal static bool ParseSoundFile(string fileName, ref CarBase Car) { @@ -69,6 +73,8 @@ internal static bool ParseSoundFile(string fileName, ref CarBase Car) subHeader = Encoding.ASCII.GetString(buffer, 0, 8); } SoundSet soundSet = new SoundSet(); + SoundStream soundStream = new SoundStream(); + if (subHeader[7] == 't') { using (BinaryReader reader = new BinaryReader(fb)) @@ -76,7 +82,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase Car) byte[] newBytes = reader.ReadBytes((int)(fb.Length - fb.Position)); string s = unicode ? Encoding.Unicode.GetString(newBytes) : Encoding.ASCII.GetString(newBytes); TextualBlock block = new TextualBlock(s, KujuTokenID.Tr_SMS); - ParseBlock(block, ref soundSet, ref Car); + ParseBlock(block, ref soundSet, ref soundStream, ref Car); } } @@ -99,7 +105,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase Car) uint remainingBytes = reader.ReadUInt32(); byte[] newBytes = reader.ReadBytes((int)remainingBytes); BinaryBlock block = new BinaryBlock(newBytes, KujuTokenID.Tr_SMS); - ParseBlock(block, ref soundSet, ref Car); + ParseBlock(block, ref soundSet, ref soundStream, ref Car); } } @@ -116,13 +122,17 @@ internal struct SoundSet internal bool PassengerCam; internal bool ExternalCam; internal double Priority; - + + internal SoundTrigger currentTrigger; + internal KujuTokenID currentSoundType; + internal KujuTokenID variableTriggerType; + internal double variableValue; + } - private static SoundTrigger currentTrigger; - private static KujuTokenID currentSoundType; + - private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref CarBase car) + private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref SoundStream currentSoundStream, ref CarBase car) { Block newBlock; switch (block.Token) @@ -132,7 +142,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca while (block.Position() < block.Length() - 3) { newBlock = block.ReadSubBlock(); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } break; case KujuTokenID.ScalabiltyGroup: @@ -143,7 +153,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca try { newBlock = block.ReadSubBlock(true); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } catch (Exception e) { @@ -158,7 +168,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca while (block.Position() < block.Length() - 3) { newBlock = block.ReadSubBlock(true); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } break; case KujuTokenID.Deactivation: @@ -167,7 +177,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca while (block.Position() < block.Length() - 3) { newBlock = block.ReadSubBlock(true); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } break; case KujuTokenID.Distance: @@ -198,7 +208,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca } else { - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } if (block.Length() - block.Position() <= 3) { @@ -211,7 +221,13 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca while (block.Position() < block.Length() - 3) { newBlock = block.ReadSubBlock(new[] { KujuTokenID.Priority, KujuTokenID.Triggers, KujuTokenID.Volume, KujuTokenID.VolumeCurve, KujuTokenID.FrequencyCurve, KujuTokenID.Granularity }); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); + } + + if (currentSoundStream.Triggers.Count > 0) + { + car.Sounds.ControlledSounds.Add(currentSoundStream); + currentSoundStream = new SoundStream(); } break; case KujuTokenID.Priority: @@ -223,7 +239,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca { // two triggers per sound set (start + stop) newBlock = block.ReadSubBlock(new [] {KujuTokenID.Variable_Trigger, KujuTokenID.Initial_Trigger, KujuTokenID.Discrete_Trigger, KujuTokenID.Random_Trigger, KujuTokenID.Dist_Travelled_Trigger}); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); if (block.Length() - block.Position() <= 3) { // WARN: incorrect number of triggers supplied @@ -234,7 +250,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca case KujuTokenID.Initial_Trigger: // when initially appears, hence nothing other than StartLoop should be valid newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.DisableTrigger }); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.StartLoopRelease: case KujuTokenID.StartLoop: @@ -244,15 +260,15 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca * NOTE: Handle these on a per-sound trigger, as where possible * map to existing subsystems */ - currentSoundType = block.Token; + currentSoundSet.currentSoundType = block.Token; numStreams = block.ReadInt32(); for (int i = 0; i < numStreams; i++) { newBlock = block.ReadSubBlock(KujuTokenID.File); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } newBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.ReleaseLoopRelease: // empty block expected @@ -262,17 +278,37 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca if (block.ReadPath(currentFolder, out string soundFile)) { // n.b. MSTS does not distinguish between increase / decrease sounds for handles etc. - switch (currentTrigger) + switch (currentSoundSet.currentTrigger) { + case SoundTrigger.VariableControlled: + // hack + Plugin.currentHost.RegisterSound(soundFile, 5.0, out var soundHandle); + switch (currentSoundSet.variableTriggerType) + { + case KujuTokenID.Speed_Inc_Past: + currentSoundStream.Triggers.Add(new SpeedIncPast(car, soundHandle as SoundBuffer, currentSoundSet.variableValue, true)); + break; + case KujuTokenID.Speed_Dec_Past: + currentSoundStream.Triggers.Add(new SpeedDecPast(car, soundHandle as SoundBuffer, currentSoundSet.variableValue, true)); + break; + case KujuTokenID.Variable2_Inc_Past: + currentSoundStream.Triggers.Add(new Variable2IncPast(car, soundHandle as SoundBuffer, currentSoundSet.variableValue, true)); + break; + case KujuTokenID.Variable2_Dec_Past: + currentSoundStream.Triggers.Add(new Variable2DecPast(car, soundHandle as SoundBuffer, currentSoundSet.variableValue, true)); + break; + + } + break; case SoundTrigger.ReverserChange: - if (currentSoundType == KujuTokenID.PlayOneShot) + if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot) { car.baseTrain.Handles.Reverser.EngageSound = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); car.baseTrain.Handles.Reverser.ReleaseSound = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); } break; case SoundTrigger.ThrottleChange: - if (currentSoundType == KujuTokenID.PlayOneShot) + if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot) { car.baseTrain.Handles.Power.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); car.baseTrain.Handles.Power.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); @@ -283,7 +319,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca } break; case SoundTrigger.TrainBrakeChange: - if (currentSoundType == KujuTokenID.PlayOneShot) + if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot) { car.baseTrain.Handles.Brake.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); car.baseTrain.Handles.Brake.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); @@ -294,7 +330,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca } break; case SoundTrigger.EngineBrakeChange: - if (currentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.Handles.LocoBrake != null) + if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.Handles.LocoBrake != null) { car.baseTrain.Handles.LocoBrake.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); car.baseTrain.Handles.LocoBrake.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); @@ -305,23 +341,23 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca } break; case SoundTrigger.LightSwitchToggle: - if (currentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.SafetySystems.Headlights != null) + if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.SafetySystems.Headlights != null) { - Plugin.currentHost.RegisterSound(soundFile, 2.0, out var soundHandle); + Plugin.currentHost.RegisterSound(soundFile, 2.0, out soundHandle); car.baseTrain.SafetySystems.Headlights.SwitchSoundBuffer = soundHandle as SoundBuffer; } break; case SoundTrigger.HornOn: - if (currentSoundType == KujuTokenID.StartLoopRelease && car.Horns[0] != null) + if (currentSoundSet.currentSoundType == KujuTokenID.StartLoopRelease && car.Horns[0] != null) { - Plugin.currentHost.RegisterSound(soundFile, 2.0, out var soundHandle); + Plugin.currentHost.RegisterSound(soundFile, 2.0, out soundHandle); car.Horns[0].LoopSound = soundHandle as SoundBuffer; } break; case SoundTrigger.BellOn: - if (currentSoundType == KujuTokenID.StartLoopRelease && car.Horns[2] != null) + if (currentSoundSet.currentSoundType == KujuTokenID.StartLoopRelease && car.Horns[2] != null) { - Plugin.currentHost.RegisterSound(soundFile, 2.0, out var soundHandle); + Plugin.currentHost.RegisterSound(soundFile, 2.0, out soundHandle); car.Horns[0].LoopSound = soundHandle as SoundBuffer; } break; @@ -329,7 +365,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca } else { - if (currentTrigger != SoundTrigger.Skip) + if (currentSoundSet.currentTrigger != SoundTrigger.Skip) { Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Sound File " + soundFile + " was not found in SMS " + currentFile); } @@ -353,25 +389,26 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca } break; case KujuTokenID.Discrete_Trigger: - currentTrigger = (SoundTrigger)block.ReadInt32(); // stored as integer + currentSoundSet.currentTrigger = (SoundTrigger)block.ReadInt32(); // stored as integer newBlock = block.ReadSubBlock(new[] { KujuTokenID.PlayOneShot, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.SetStreamVolume }); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.Variable_Trigger: - token = block.ReadEnumValue(default(KujuTokenID)); - switch (token) + currentSoundSet.variableTriggerType = block.ReadEnumValue(default(KujuTokenID)); + switch (currentSoundSet.variableTriggerType) { case KujuTokenID.StartLoop: break; case KujuTokenID.Speed_Inc_Past: - double speedValue = block.ReadSingle(); - break; case KujuTokenID.Speed_Dec_Past: - speedValue = block.ReadSingle(); + currentSoundSet.variableValue = block.ReadSingle(UnitOfVelocity.KilometersPerHour, UnitOfVelocity.MetersPerSecond); // speed in m/s + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.PlayOneShot }); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.SpeedControlled: break; case KujuTokenID.DistanceControlled: + break; case KujuTokenID.Distance_Inc_Past: case KujuTokenID.Distance_Dec_Past: break; @@ -381,6 +418,10 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca break; case KujuTokenID.Variable2_Inc_Past: case KujuTokenID.Variable2_Dec_Past: + currentSoundSet.variableValue = block.ReadSingle(); // power value + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump }); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); + break; case KujuTokenID.Variable2Controlled: break; case KujuTokenID.Variable3_Inc_Past: @@ -388,16 +429,16 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca case KujuTokenID.Variable3Controlled: break; default: - throw new Exception("Unexpected enum value " + token + " encounted in SMS file " + currentFile); + throw new Exception("Unexpected enum value " + currentSoundSet.variableTriggerType + " encounted in SMS file " + currentFile); } break; case KujuTokenID.PlayOneShot: - currentSoundType = block.Token; + currentSoundSet.currentSoundType = block.Token; int numSounds = block.ReadInt16(); for (int i = 0; i < numSounds; i++) { newBlock = block.ReadSubBlock(KujuTokenID.File); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } break; case KujuTokenID.Volume: @@ -408,25 +449,43 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref Ca switch (token) { case KujuTokenID.SpeedControlled: + case KujuTokenID.DistanceControlled: + case KujuTokenID.Variable1Controlled: + case KujuTokenID.Variable2Controlled: + case KujuTokenID.Variable3Controlled: newBlock = block.ReadSubBlock(KujuTokenID.CurvePoints); - ParseBlock(newBlock, ref currentSoundSet, ref car); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; + default: + throw new Exception("Unexpected enum value " + token + " encounted in SMS file " + currentFile); + } + + currentSoundStream.VolumeCurve = new MsTsVolumeCurve(car, token, curvePoints); + break; + case KujuTokenID.FrequencyCurve: + token = block.ReadEnumValue(default(KujuTokenID)); + switch (token) + { + case KujuTokenID.SpeedControlled: case KujuTokenID.DistanceControlled: - break; case KujuTokenID.Variable1Controlled: case KujuTokenID.Variable2Controlled: case KujuTokenID.Variable3Controlled: + newBlock = block.ReadSubBlock(KujuTokenID.CurvePoints); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; default: throw new Exception("Unexpected enum value " + token + " encounted in SMS file " + currentFile); } + + currentSoundStream.FrequencyCurve = new MsTsFrequencyCurve(car, token, curvePoints); break; case KujuTokenID.CurvePoints: int numPoints = block.ReadInt32(); + curvePoints = new Tuple[numPoints]; for (int i = 0; i < numPoints; i++) { - double refenceValue = block.ReadSingle(); - double volumeValue = block.ReadSingle(); + curvePoints[i] = new Tuple(block.ReadSingle(), block.ReadSingle()); } break; case KujuTokenID.Granularity: diff --git a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs index c51d31c467..c70a78cdec 100644 --- a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs +++ b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs @@ -7,6 +7,7 @@ internal enum SoundTrigger // unclear as to why it's not just left out, copy + paste or internal MSTS validation? // some of the time, these exist, but a lot of the time the sound file is missing Skip = -1, + VariableControlled = 0, DynamicBrakeIncrease = 2, DynamicBrakeOff = 3, SanderOn = 4, diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index 617fe20844..62ecf2d576 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -234,7 +234,6 @@ private void ParseBlock(Block block, ref TrainBase Train) } currentCar.Breaker = new Breaker(currentCar); currentCar.Sounds.Plugin = new Dictionary(); - currentCar.Sounds.Motor = new MSTSMotorSound(currentCar); Train.Cars[currentCarIndex] = currentCar; /* * FIXME: Needs removing or sorting when the car is created diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 49c8cc5300..f8e1bc99c5 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -73,6 +73,10 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r } } } + else + { + Car.TractionModel = new BVETrailerCar(Car); + } /* * We've now found the engine properties- * Now, we need to read the wagon properties to find the visual wagon to display @@ -90,42 +94,38 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r { break; } - } + } } - Car.Specs.AccelerationCurveMaximum = maxForce / Car.CurrentMass; // as properties may not be in order, set this stuff last if (isEngine) { - Car.Specs.AccelerationCurves = new AccelerationCurve[] - { - new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) - }; // FIXME: Default BVE values Car.Specs.JerkPowerUp = 10.0; Car.Specs.JerkPowerDown = 10.0; - Car.ReAdhesionDevice = new BveReAdhesionDevice(Car, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); + switch (currentEngineType) { case EngineType.Diesel: - Car.Engine = new DieselEngine(Car, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); - Car.Engine.FuelTank = new FuelTank(dieselCapacity, 0, dieselCapacity); - Car.Engine.IsRunning = true; + Car.TractionModel = new DieselEngine(Car, new AccelerationCurve[] { new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) }, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); + Car.TractionModel.FuelTank = new FuelTank(dieselCapacity, 0, dieselCapacity); + Car.TractionModel.IsRunning = true; if (maxBrakeAmps > 0 && maxEngineAmps > 0) { - Car.Engine.Components.Add(EngineComponent.RegenerativeTractionMotor, new RegenerativeTractionMotor(Car.Engine, maxEngineAmps, maxBrakeAmps)); + Car.TractionModel.Components.Add(EngineComponent.RegenerativeTractionMotor, new RegenerativeTractionMotor(Car.TractionModel, maxEngineAmps, maxBrakeAmps)); } else if (maxEngineAmps > 0) { - Car.Engine.Components.Add(EngineComponent.TractionMotor, new TractionMotor(Car.Engine, maxEngineAmps)); + Car.TractionModel.Components.Add(EngineComponent.TractionMotor, new TractionMotor(Car.TractionModel, maxEngineAmps)); } break; case EngineType.Electric: - Car.Engine = new ElectricEngine(Car); - Car.Engine.Components.Add(EngineComponent.Pantograph, new Pantograph(Car.Engine)); + Car.TractionModel = new ElectricEngine(Car, new AccelerationCurve[] { new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) }); + Car.TractionModel.Components.Add(EngineComponent.Pantograph, new Pantograph(Car.TractionModel)); break; } + Car.ReAdhesionDevice = new BveReAdhesionDevice(Car, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); } } @@ -704,6 +704,10 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool vigilanceDevices.Add(device); break; case KujuTokenID.FreightAnim: + if (Plugin.PreviewOnly) + { + break; + } objectFile = OpenBveApi.Path.CombineFile(Path.GetDirectoryName(fileName), block.ReadString()); if (!File.Exists(objectFile)) { diff --git a/source/SoundManager/SoundManager.csproj b/source/SoundManager/SoundManager.csproj index 86e28e3270..857bdfaad0 100644 --- a/source/SoundManager/SoundManager.csproj +++ b/source/SoundManager/SoundManager.csproj @@ -47,6 +47,7 @@ prompt + ..\..\packages\OpenTK-OpenBVE.1.0.4\lib\net20\OpenTK.dll @@ -78,5 +79,6 @@ + \ No newline at end of file diff --git a/source/TrainManager/Car/CarSounds.cs b/source/TrainManager/Car/CarSounds.cs index a36b226a59..e14b1464f5 100644 --- a/source/TrainManager/Car/CarSounds.cs +++ b/source/TrainManager/Car/CarSounds.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using SoundManager; +using TrainManager.Motor; +using TrainManager.MsTsSounds; namespace TrainManager.Car { @@ -8,6 +10,8 @@ public class CarSounds { /// The loop sound public CarSound Loop; + + public List ControlledSounds = new List(); /// The sounds triggered by the train's plugin public Dictionary Plugin = new Dictionary(); /// The sounds triggered by a request stop diff --git a/source/TrainManager/Motor/ElectricEngine/ElectricEngine.cs b/source/TrainManager/Motor/ElectricEngine/ElectricEngine.cs index 0353b04617..31d5b377cd 100644 --- a/source/TrainManager/Motor/ElectricEngine/ElectricEngine.cs +++ b/source/TrainManager/Motor/ElectricEngine/ElectricEngine.cs @@ -1,4 +1,4 @@ -//Simplified BSD License (BSD-2-Clause) +//Simplified BSD License (BSD-2-Clause) // //Copyright (c) 2025, Christopher Lees, The OpenBVE Project // @@ -32,7 +32,7 @@ namespace TrainManager.Motor { public class ElectricEngine : TractionModel { - public ElectricEngine(CarBase car) : base(car) + public ElectricEngine(CarBase car, AccelerationCurve[] accelerationCurves) : base(car, accelerationCurves, true) { } diff --git a/source/TrainManager/Motor/MSTS/MotorSound.cs b/source/TrainManager/Motor/MSTS/MotorSound.cs deleted file mode 100644 index 3f894c50e0..0000000000 --- a/source/TrainManager/Motor/MSTS/MotorSound.cs +++ /dev/null @@ -1,12 +0,0 @@ -using TrainManager.Car; - -namespace TrainManager.Motor -{ - /// Represents a MSTS motor sound - public class MSTSMotorSound : AbstractMotorSound - { - public MSTSMotorSound(CarBase car) : base(car) - { - } - } -} diff --git a/source/TrainManager/Sounds/MSTS/FrequencyCurve.cs b/source/TrainManager/Sounds/MSTS/FrequencyCurve.cs new file mode 100644 index 0000000000..a05f3bcf4a --- /dev/null +++ b/source/TrainManager/Sounds/MSTS/FrequencyCurve.cs @@ -0,0 +1,55 @@ +using System; +using OpenBve.Formats.MsTs; +using TrainManager.Car; + +namespace TrainManager.MsTsSounds +{ + public class MsTsFrequencyCurve + { + private readonly CarBase car; + private readonly Tuple[] frequencyPoints; + + private readonly KujuTokenID controller; + + public MsTsFrequencyCurve(CarBase car, KujuTokenID controller, Tuple[] points) + { + this.car = car; + this.controller = controller; + frequencyPoints = new Tuple[points.Length]; + for (int i = 0; i < frequencyPoints.Length; i++) + { + frequencyPoints[i] = new Tuple(points[i].Item1, points[i].Item2 / 11025); // MSTS base sound frequency is 11025hz, convert to percentage + } + } + + public double Pitch + { + get + { + switch (controller) + { + case KujuTokenID.SpeedControlled: + for (int i = frequencyPoints.Length - 1; i >= 0; i--) + { + if (Math.Abs(car.CurrentSpeed) >= frequencyPoints[i].Item1) + { + return frequencyPoints[i].Item2; + } + } + break; + case KujuTokenID.Variable2Controlled: + for (int i = frequencyPoints.Length - 1; i >= 0; i--) + { + if (car.TractionModel.CurrentPower >= frequencyPoints[i].Item1) + { + return frequencyPoints[i].Item2; + } + } + break; + + } + return 0; + } + } + } +} diff --git a/source/TrainManager/Sounds/MSTS/SoundStream.cs b/source/TrainManager/Sounds/MSTS/SoundStream.cs new file mode 100644 index 0000000000..318ae5bf99 --- /dev/null +++ b/source/TrainManager/Sounds/MSTS/SoundStream.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace TrainManager.MsTsSounds +{ + public class SoundStream + { + public List Triggers; + + public MsTsVolumeCurve VolumeCurve; + + public MsTsFrequencyCurve FrequencyCurve; + + public SoundStream() + { + Triggers = new List(); + } + + public void Update(double timeElapsed) + { + double volume = 1.0, pitch = 1.0; + if (VolumeCurve != null) + { + volume = VolumeCurve.Volume; + } + + if (FrequencyCurve != null) + { + pitch = FrequencyCurve.Pitch; + } + + for (int i = 0; i < Triggers.Count; i++) + { + Triggers[i].Update(timeElapsed, pitch, volume); + } + } + } +} diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs new file mode 100644 index 0000000000..cb1add86eb --- /dev/null +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs @@ -0,0 +1,77 @@ +using System; +using OpenBveApi.Math; +using SoundManager; +using TrainManager.Car; + +namespace TrainManager.MsTsSounds +{ + public class SpeedIncPast : SoundTrigger + { + private readonly double speedValue; + + private readonly bool soundLoops; + + private bool triggered; + + public SpeedIncPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) + { + this.speedValue = speedValue; + this.soundLoops = soundLoops; + } + + public override void Update(double timeElapsed, double pitchValue, double volumeValue) + { + if (Math.Abs(Car.CurrentSpeed) >= speedValue) + { + if (Buffer != null) + { + if (triggered == false) + { + this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; + } + } + triggered = true; + } + else + { + triggered = false; + Source?.Stop(); + } + } + } + + public class SpeedDecPast : SoundTrigger + { + private readonly double speedValue; + + private readonly bool soundLoops; + + private bool triggered; + + public SpeedDecPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) + { + this.speedValue = speedValue; + this.soundLoops = soundLoops; + } + + public override void Update(double timeElapsed, double pitchValue, double volumeValue) + { + if (Math.Abs(Car.CurrentSpeed) <= speedValue) + { + if (Buffer != null) + { + if (triggered == false) + { + this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; + } + } + triggered = true; + } + else + { + triggered = false; + Source?.Stop(); + } + } + } +} diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs new file mode 100644 index 0000000000..207e5084d1 --- /dev/null +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs @@ -0,0 +1,86 @@ +using OpenBveApi.Math; +using SoundManager; +using TrainManager.Car; + +namespace TrainManager.MsTsSounds +{ + public class Variable2IncPast : SoundTrigger + { + private readonly double speedValue; + + private readonly bool soundLoops; + + private bool triggered; + + public Variable2IncPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) + { + this.speedValue = speedValue; + this.soundLoops = soundLoops; + } + + public override void Update(double timeElapsed, double pitchValue, double volumeValue) + { + if (Car.TractionModel.CurrentPower >= speedValue) + { + if (Buffer != null) + { + if (triggered == false) + { + this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; + } + else + { + this.Source.Pitch = pitchValue; + this.Source.Volume = volumeValue; + } + } + triggered = true; + } + else + { + triggered = false; + Source?.Stop(); + } + } + } + + public class Variable2DecPast : SoundTrigger + { + private readonly double speedValue; + + private readonly bool soundLoops; + + private bool triggered; + + public Variable2DecPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) + { + this.speedValue = speedValue; + this.soundLoops = soundLoops; + } + + public override void Update(double timeElapsed, double pitchValue, double volumeValue) + { + if (Car.TractionModel.CurrentPower <= speedValue) + { + if (Buffer != null) + { + if (triggered == false) + { + this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; + } + else + { + this.Source.Pitch = pitchValue; + this.Source.Volume = volumeValue; + } + } + triggered = true; + } + else + { + triggered = false; + Source?.Stop(); + } + } + } +} diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs new file mode 100644 index 0000000000..d345976926 --- /dev/null +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs @@ -0,0 +1,25 @@ +using SoundManager; +using TrainManager.Car; + +namespace TrainManager.MsTsSounds +{ + public abstract class SoundTrigger + { + internal readonly SoundBuffer Buffer; + + internal SoundSource Source; + + internal readonly CarBase Car; + + internal SoundTrigger(CarBase car, SoundBuffer buffer) + { + Buffer = buffer; + Car = car; + } + + public virtual void Update(double timeElapsed, double pitchValue, double volumeValue) + { + + } + } +} diff --git a/source/TrainManager/Sounds/MSTS/VolumeCurve.cs b/source/TrainManager/Sounds/MSTS/VolumeCurve.cs new file mode 100644 index 0000000000..a5a587240a --- /dev/null +++ b/source/TrainManager/Sounds/MSTS/VolumeCurve.cs @@ -0,0 +1,50 @@ +using OpenBve.Formats.MsTs; +using System; +using TrainManager.Car; + +namespace TrainManager.MsTsSounds +{ + public class MsTsVolumeCurve + { + private readonly CarBase car; + private readonly Tuple[] volumePoints; + + private readonly KujuTokenID controller; + + public MsTsVolumeCurve(CarBase car, KujuTokenID controller, Tuple[] points) + { + this.car = car as CarBase; + this.controller = controller; + volumePoints = points; + } + + public double Volume + { + get + { + switch (controller) + { + case KujuTokenID.SpeedControlled: + for (int i = volumePoints.Length - 1; i >= 0; i--) + { + if (Math.Abs(car.CurrentSpeed) >= volumePoints[i].Item1) + { + return volumePoints[i].Item2; + } + } + break; + case KujuTokenID.Variable2Controlled: + for (int i = volumePoints.Length - 1; i >= 0; i--) + { + if (car.TractionModel.CurrentPower >= volumePoints[i].Item1) + { + return volumePoints[i].Item2; + } + } + break; + } + return 0; + } + } + } +} diff --git a/source/TrainManager/Train/TrainBase.cs b/source/TrainManager/Train/TrainBase.cs index 6e6a99bdff..055c4506ef 100644 --- a/source/TrainManager/Train/TrainBase.cs +++ b/source/TrainManager/Train/TrainBase.cs @@ -522,7 +522,12 @@ private void UpdatePhysicsAndControls(double timeElapsed) // Update Run and Motor sounds for (int i = 0; i < Cars.Length; i++) { - Cars[i].Run.Update(timeElapsed); + Cars[i].Run.Update(TimeElapsed); + Cars[i].Sounds.Motor?.Update(TimeElapsed); + for (int j = 0; j < Cars[i].Sounds.ControlledSounds.Count; j++) + { + Cars[i].Sounds.ControlledSounds[j].Update(TimeElapsed); + } } // safety system diff --git a/source/TrainManager/TrainManager.csproj b/source/TrainManager/TrainManager.csproj index 6c2c037317..d9235ee0d1 100644 --- a/source/TrainManager/TrainManager.csproj +++ b/source/TrainManager/TrainManager.csproj @@ -136,7 +136,6 @@ - @@ -170,6 +169,12 @@ + + + + + + @@ -196,6 +201,10 @@ {27134980-4415-4375-a564-40a9014dfa5f} OpenBveApi + + {e81b7bd8-a326-47d3-b7ee-e9c7d4d119fa} + Formats.Msts + {4ef680d7-df17-4978-9872-133edc169b4b} RouteManager2 From 647b3a50bd64102556e0c7820243fa060a0ce986 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 16 Apr 2025 18:18:50 +0100 Subject: [PATCH 17/82] Basic implementation of view based sounds --- .../Runtime/System/CameraViewMode.cs | 21 ++++++++---- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 32 ++++++++++++++--- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 4 +++ source/SoundManager/Sounds.CarSound.cs | 5 ++- .../TrainManager/Sounds/MSTS/SoundStream.cs | 34 +++++++++++++++++-- .../Sounds/MSTS/SoundTrigger.Speed.cs | 18 ++++------ .../MSTS/SoundTrigger.VariableControlled.cs | 20 ++++------- .../TrainManager/Sounds/MSTS/SoundTrigger.cs | 8 +++++ 8 files changed, 102 insertions(+), 40 deletions(-) diff --git a/source/OpenBveApi/Runtime/System/CameraViewMode.cs b/source/OpenBveApi/Runtime/System/CameraViewMode.cs index 6f1715b063..70e5d25968 100644 --- a/source/OpenBveApi/Runtime/System/CameraViewMode.cs +++ b/source/OpenBveApi/Runtime/System/CameraViewMode.cs @@ -1,19 +1,26 @@ -namespace OpenBveApi.Runtime +using System; + +namespace OpenBveApi.Runtime { + /// Represents the available camera view modes. + [Flags] public enum CameraViewMode { + /// Unknown + /// Used for bitwise operations + NotDefined = 0, /// The interior of a 2D cab - Interior, + Interior = 2, /// The interior of a 3D cab - InteriorLookAhead, + InteriorLookAhead = 4, /// An exterior camera attached to a train - Exterior, + Exterior = 8, /// A camera attached to the track - Track, + Track = 16, /// A fly-by camera attached to a point on the track - FlyBy, + FlyBy = 32, /// A fly-by zooming camera attached to a point on the track - FlyByZooming + FlyByZooming = 64 } } diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index d626bcae41..a06792a932 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -4,6 +4,7 @@ using System; using System.Text; using OpenBveApi.Interface; +using OpenBveApi.Runtime; using OpenBveApi.World; using SharpCompress.Compressors; using TrainManager.Car; @@ -118,9 +119,6 @@ internal struct SoundSet internal bool Activation; internal double ActivationDistance; internal double DeactivationDistance; - internal bool CamCam; - internal bool PassengerCam; - internal bool ExternalCam; internal double Priority; internal SoundTrigger currentTrigger; @@ -192,9 +190,33 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So } break; case KujuTokenID.ExternalCam: + if (currentSoundSet.Activation) + { + currentSoundStream.ActivationCameraModes |= CameraViewMode.Exterior; + currentSoundStream.ActivationCameraModes |= CameraViewMode.Track; + currentSoundStream.ActivationCameraModes |= CameraViewMode.FlyBy; + currentSoundStream.ActivationCameraModes |= CameraViewMode.FlyByZooming; + } + else + { + currentSoundStream.DeactivationCameraModes |= CameraViewMode.Exterior; + currentSoundStream.DeactivationCameraModes |= CameraViewMode.Track; + currentSoundStream.DeactivationCameraModes |= CameraViewMode.FlyBy; + currentSoundStream.DeactivationCameraModes |= CameraViewMode.FlyByZooming; + } + break; case KujuTokenID.CabCam: + if (currentSoundSet.Activation) + { + currentSoundStream.ActivationCameraModes |= CameraViewMode.Interior; + } + else + { + currentSoundStream.DeactivationCameraModes |= CameraViewMode.Interior; + } + break; case KujuTokenID.PassengerCam: - currentSoundSet.ExternalCam = currentSoundSet.Activation; + // FIXME: Passenger cam not currently distinguished from interior cam break; case KujuTokenID.Streams: // each stream represents a unique sound @@ -282,7 +304,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So { case SoundTrigger.VariableControlled: // hack - Plugin.currentHost.RegisterSound(soundFile, 5.0, out var soundHandle); + Plugin.currentHost.RegisterSound(soundFile, currentSoundSet.ActivationDistance, out var soundHandle); switch (currentSoundSet.variableTriggerType) { case KujuTokenID.Speed_Inc_Past: diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index f8e1bc99c5..d77d6c3c59 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -124,6 +124,10 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r Car.TractionModel = new ElectricEngine(Car, new AccelerationCurve[] { new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) }); Car.TractionModel.Components.Add(EngineComponent.Pantograph, new Pantograph(Car.TractionModel)); break; + case EngineType.Steam: + // NOT YET IMPLEMENTED + Car.TractionModel = new BVEMotorCar(Car, new AccelerationCurve[] { new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) }); + break; } Car.ReAdhesionDevice = new BveReAdhesionDevice(Car, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); } diff --git a/source/SoundManager/Sounds.CarSound.cs b/source/SoundManager/Sounds.CarSound.cs index c28c2c2bc9..f6f9e63b51 100644 --- a/source/SoundManager/Sounds.CarSound.cs +++ b/source/SoundManager/Sounds.CarSound.cs @@ -3,8 +3,9 @@ using OpenBveApi.Hosts; using OpenBveApi.Interface; using OpenBveApi.Math; -using OpenBveApi.Sounds; +using OpenBveApi.Runtime; using OpenBveApi.Trains; +using SoundHandle = OpenBveApi.Sounds.SoundHandle; namespace SoundManager { @@ -21,6 +22,8 @@ public class CarSound /// Used when crossfading between multiple sounds of the same type public double TargetVolume; + public CameraViewMode ViewModes; + public CarSound(HostInterface currentHost, string trainFolder, string soundFile, double radius, Vector3 position) : this(currentHost, trainFolder, string.Empty, -1, soundFile, radius, position) { } diff --git a/source/TrainManager/Sounds/MSTS/SoundStream.cs b/source/TrainManager/Sounds/MSTS/SoundStream.cs index 318ae5bf99..249d27d2b3 100644 --- a/source/TrainManager/Sounds/MSTS/SoundStream.cs +++ b/source/TrainManager/Sounds/MSTS/SoundStream.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using OpenBveApi.Runtime; +using System.Collections.Generic; namespace TrainManager.MsTsSounds { @@ -9,10 +10,16 @@ public class SoundStream public MsTsVolumeCurve VolumeCurve; public MsTsFrequencyCurve FrequencyCurve; + /// The camera modes in which this sound stream is active + public CameraViewMode ActivationCameraModes; + /// The modes in which this sound stream is not active + public CameraViewMode DeactivationCameraModes; public SoundStream() { Triggers = new List(); + ActivationCameraModes = CameraViewMode.NotDefined; + DeactivationCameraModes = CameraViewMode.NotDefined; } public void Update(double timeElapsed) @@ -28,9 +35,32 @@ public void Update(double timeElapsed) pitch = FrequencyCurve.Pitch; } + bool canActivate = true; + if (ActivationCameraModes != CameraViewMode.NotDefined) + { + canActivate = (ActivationCameraModes & TrainManagerBase.Renderer.Camera.CurrentMode) != 0; + } + + if (DeactivationCameraModes != CameraViewMode.NotDefined) + { + if (canActivate) + { + canActivate = (DeactivationCameraModes & TrainManagerBase.Renderer.Camera.CurrentMode) == 0; + } + } + + for (int i = 0; i < Triggers.Count; i++) { - Triggers[i].Update(timeElapsed, pitch, volume); + if (canActivate) + { + Triggers[i].Update(timeElapsed, pitch, volume); + } + else + { + Triggers[i].Stop(); + } + } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs index cb1add86eb..581431d093 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs @@ -11,8 +11,6 @@ public class SpeedIncPast : SoundTrigger private readonly bool soundLoops; - private bool triggered; - public SpeedIncPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) { this.speedValue = speedValue; @@ -25,17 +23,16 @@ public override void Update(double timeElapsed, double pitchValue, double volume { if (Buffer != null) { - if (triggered == false) + if (Triggered == false) { this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; } } - triggered = true; + Triggered = true; } else { - triggered = false; - Source?.Stop(); + Stop(); } } } @@ -46,8 +43,6 @@ public class SpeedDecPast : SoundTrigger private readonly bool soundLoops; - private bool triggered; - public SpeedDecPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) { this.speedValue = speedValue; @@ -60,17 +55,16 @@ public override void Update(double timeElapsed, double pitchValue, double volume { if (Buffer != null) { - if (triggered == false) + if (Triggered == false) { this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; } } - triggered = true; + Triggered = true; } else { - triggered = false; - Source?.Stop(); + Stop(); } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs index 207e5084d1..13d58953c9 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs @@ -9,9 +9,7 @@ public class Variable2IncPast : SoundTrigger private readonly double speedValue; private readonly bool soundLoops; - - private bool triggered; - + public Variable2IncPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) { this.speedValue = speedValue; @@ -24,7 +22,7 @@ public override void Update(double timeElapsed, double pitchValue, double volume { if (Buffer != null) { - if (triggered == false) + if (Triggered == false) { this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; } @@ -34,12 +32,11 @@ public override void Update(double timeElapsed, double pitchValue, double volume this.Source.Volume = volumeValue; } } - triggered = true; + Triggered = true; } else { - triggered = false; - Source?.Stop(); + Stop(); } } } @@ -50,8 +47,6 @@ public class Variable2DecPast : SoundTrigger private readonly bool soundLoops; - private bool triggered; - public Variable2DecPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) { this.speedValue = speedValue; @@ -64,7 +59,7 @@ public override void Update(double timeElapsed, double pitchValue, double volume { if (Buffer != null) { - if (triggered == false) + if (Triggered == false) { this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; } @@ -74,12 +69,11 @@ public override void Update(double timeElapsed, double pitchValue, double volume this.Source.Volume = volumeValue; } } - triggered = true; + Triggered = true; } else { - triggered = false; - Source?.Stop(); + Stop(); } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs index d345976926..4bdcd8d615 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs @@ -11,6 +11,8 @@ public abstract class SoundTrigger internal readonly CarBase Car; + internal bool Triggered; + internal SoundTrigger(CarBase car, SoundBuffer buffer) { Buffer = buffer; @@ -21,5 +23,11 @@ public virtual void Update(double timeElapsed, double pitchValue, double volumeV { } + + internal void Stop() + { + Source?.Stop(); + Triggered = false; + } } } From ed10c5b2110819e2de092aff88fe13f0df5b829b Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 17 Apr 2025 18:14:10 +0100 Subject: [PATCH 18/82] Correct dials with anti-clockwise rotation --- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 6ad0431f28..11ce734ea7 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -341,6 +341,11 @@ internal void Create(ref CarBase Car, int Layer) { case CabComponentType.Dial: Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(null, null), out Texture tday, true); + // correct angle position if appropriate + if (!DirIncrease && InitialAngle > LastAngle) + { + InitialAngle = -(365 - InitialAngle); + } //Get final position from the 640px panel (Yuck...) Position.X *= rW; Position.Y *= rH; @@ -349,7 +354,7 @@ internal void Create(ref CarBase Car, int Layer) PivotPoint *= rH; j = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X, Size.Y, new Vector2((0.5 * Size.X) / (tday.Width * rW), PivotPoint / (tday.Height * rH)), Layer * stackDistance, Car.Driver, tday, null, new Color32(255, 255, 255, 255)); Car.CarSections[0].Groups[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0); - Car.CarSections[0].Groups[0].Elements[j].RotateXDirection = new Vector3(1.0, 0.0, 0.0); + Car.CarSections[0].Groups[0].Elements[j].RotateXDirection = DirIncrease ? new Vector3(1.0, 0.0, 0.0) : new Vector3(-1.0, 0.0, 0.0); Car.CarSections[0].Groups[0].Elements[j].RotateYDirection = Vector3.Cross(Car.CarSections[0].Groups[0].Elements[j].RotateZDirection, Car.CarSections[0].Groups[0].Elements[j].RotateXDirection); f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); InitialAngle = InitialAngle.ToRadians(); @@ -462,11 +467,6 @@ private void ReadSubBlock(Block block) case KujuTokenID.ScalePos: InitialAngle = block.ReadSingle(); LastAngle = block.ReadSingle(); - // correct angle position if appropriate - if (InitialAngle > LastAngle) - { - InitialAngle = -(365 - InitialAngle); - } break; case KujuTokenID.ScaleRange: Minimum = block.ReadSingle(); @@ -479,7 +479,7 @@ private void ReadSubBlock(Block block) block.Skip((int) block.Length()); break; case KujuTokenID.DirIncrease: - //Do we start at 0 or max? + // rotates Clockwise (0) or AntiClockwise (1) DirIncrease = block.ReadInt16() == 1; break; case KujuTokenID.Orientation: @@ -552,7 +552,7 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject switch (subjectUnits) { case Units.PSI: - Code = "brakecylinder 0.000145038 *"; + Code = "brakepipe 0.000145038 *"; break; case Units.Inches_Of_Mercury: Code = "0"; From f8cedb983e5979a255cd1dd751bc92f860d7c5ec Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 17 Apr 2025 18:28:22 +0100 Subject: [PATCH 19/82] Add PantographState instruction --- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 11ce734ea7..29a37bd9b2 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -414,6 +414,7 @@ internal void Create(ref CarBase Car, int Layer) break; case CabComponentType.TriState: case CabComponentType.TwoState: + case CabComponentType.MultiStateDisplay: Position.X *= rW; Position.Y *= rH; Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); @@ -444,11 +445,9 @@ internal void Create(ref CarBase Car, int Layer) int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, textures[k], null, new Color32(255, 255, 255, 255), k != 0); if (k == 0) j = l; } - f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); } - break; } } @@ -474,9 +473,9 @@ private void ReadSubBlock(Block block) break; case KujuTokenID.States: //Contains sub-blocks with Style and SwitchVal types - //Doesn't appear to have any frame data - //Examining image appears to show 1-frame V strip - block.Skip((int) block.Length()); + TotalFrames = block.ReadInt16(); + HorizontalFrames = block.ReadInt16(); + VerticalFrames = block.ReadInt16(); break; case KujuTokenID.DirIncrease: // rotates Clockwise (0) or AntiClockwise (1) @@ -602,6 +601,10 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject case PanelSubject.Wipers: Code = "wiperstate"; break; + case PanelSubject.Panto_Display: + case PanelSubject.Pantograph: + Code = "pantographstate"; + break; default: Code = "0"; break; From 1593ff4628c89746e0f45305b3048d8175db8764 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 28 Apr 2025 16:55:02 +0100 Subject: [PATCH 20/82] Fix for incorrectly set rear axle properties --- source/Plugins/Train.MsTs/Train/ConsistParser.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index 62ecf2d576..34283f2b11 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -153,8 +153,12 @@ internal void ReadConsist(string fileName, ref AbstractTrain Train) hasCabview = true; train.DriverCar = i; } + + train.Cars[train.Cars.Length - 1].RearAxle.Follower.TriggerType = i == train.Cars.Length - 1 ? EventTriggerType.RearCarRearAxle : EventTriggerType.OtherCarRearAxle; } + train.Cars[train.Cars.Length - 1].RearAxle.Follower.TriggerType = EventTriggerType.RearCarRearAxle; + train.Cars[train.DriverCar].Windscreen = new Windscreen(256, 10.0, train.Cars[train.DriverCar]); train.Cars[train.DriverCar].Windscreen.Wipers = new WindscreenWiper(train.Cars[Train.DriverCar].Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 0.0, true); // hack: zero hold time so they act as fast with two states train.PlaceCars(0.0); @@ -239,7 +243,6 @@ private void ParseBlock(Block block, ref TrainBase Train) * FIXME: Needs removing or sorting when the car is created */ Train.Cars[currentCarIndex].FrontAxle.Follower.TriggerType = currentCarIndex == 0 ? EventTriggerType.FrontCarFrontAxle : EventTriggerType.OtherCarFrontAxle; - Train.Cars[currentCarIndex].RearAxle.Follower.TriggerType = currentCarIndex == Train.Cars.Length - 1 ? EventTriggerType.RearCarRearAxle : EventTriggerType.OtherCarRearAxle; Train.Cars[currentCarIndex].BeaconReceiver.TriggerType = currentCarIndex == 0 ? EventTriggerType.TrainFront : EventTriggerType.None; Train.Cars[currentCarIndex].BeaconReceiverPosition = 0.5 * Train.Cars[currentCarIndex].Length; Train.Cars[currentCarIndex].FrontAxle.Position = 0.4 * Train.Cars[currentCarIndex].Length; From 494a3ef38e96939b2196b0411472df650e42cc92 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 31 Oct 2025 15:15:21 +0000 Subject: [PATCH 21/82] Hook in smoke generator --- source/LibRender2/BaseRenderer.cs | 20 +++++----- source/Plugins/Train.MsTs/Effects/Exhaust.cs | 21 ++++++++++ source/Plugins/Train.MsTs/Train.MsTs.csproj | 1 + .../Plugins/Train.MsTs/Train/VehicleParser.cs | 38 ++++++++++++++++++- .../Power/MSTS/MSTSAccelerationCurve.cs | 4 +- source/TrainManager/Train/TrainBase.cs | 7 ++-- 6 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 source/Plugins/Train.MsTs/Effects/Exhaust.cs diff --git a/source/LibRender2/BaseRenderer.cs b/source/LibRender2/BaseRenderer.cs index c4eb469bad..22e2e84064 100644 --- a/source/LibRender2/BaseRenderer.cs +++ b/source/LibRender2/BaseRenderer.cs @@ -692,19 +692,19 @@ public void InitializeVisibility() { for (int i = 0; i < StaticObjectStates.Count; i++) { - VAOExtensions.CreateVAO(StaticObjectStates[i].Prototype?.Mesh, false, DefaultShader.VertexLayout, this); - if (StaticObjectStates[i].Matricies != null) - { - GL.CreateBuffers(1, out StaticObjectStates[i].MatrixBufferIndex); - } + VAOExtensions.CreateVAO(StaticObjectStates[i].Prototype.Mesh, false, DefaultShader.VertexLayout, this); + /* + * n.b. + * Only create the actual matrix buffer at first frame render time + * I can't find why at the minute, but Object Viewer otherwise doesn't show them, and attempting + * to retrieve previously set matricies from the shader shows all zeros + * + * Probably a timing issue, but it works doing it that way :/ + */ } for (int i = 0; i < DynamicObjectStates.Count; i++) { - VAOExtensions.CreateVAO(DynamicObjectStates[i].Prototype?.Mesh, false, DefaultShader.VertexLayout, this); - if (DynamicObjectStates[i].Matricies != null) - { - GL.CreateBuffers(1, out DynamicObjectStates[i].MatrixBufferIndex); - } + VAOExtensions.CreateVAO(DynamicObjectStates[i].Prototype.Mesh, false, DefaultShader.VertexLayout, this); } } ObjectsSortedByStart = StaticObjectStates.Select((x, i) => new { Index = i, Distance = x.StartingDistance }).OrderBy(x => x.Distance).Select(x => x.Index).ToArray(); diff --git a/source/Plugins/Train.MsTs/Effects/Exhaust.cs b/source/Plugins/Train.MsTs/Effects/Exhaust.cs new file mode 100644 index 0000000000..7def98fbe8 --- /dev/null +++ b/source/Plugins/Train.MsTs/Effects/Exhaust.cs @@ -0,0 +1,21 @@ +using OpenBveApi.Math; + +namespace Train.MsTs +{ + /// Describes the exhaust animation for a MSTS model + internal struct Exhaust + { + /// The offset from the center of the model + internal Vector3 Offset; + /// The direction of the exhaust emissions + internal Vector3 Direction; + /// The size of the exhaust outlet (controls initial particle size) + internal double Size; + /// The maximum expanded size of a smoke particle + internal double SmokeMaxMagnitude; + /// The rate of particle emissions at idle + internal double SmokeInitialRate; + /// The rate of particle emissions at maximum power + internal double SmokeMaxRate; + } +} diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index 5cbdb6ff3e..5f784aaade 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -55,6 +55,7 @@ + diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index d77d6c3c59..7afc87a8fe 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -3,11 +3,13 @@ using System.IO; using System.Linq; using System.Text; +using LibRender2.Smoke; using LibRender2.Trains; using OpenBve.Formats.Msts; using OpenBve.Formats.MsTs; using OpenBveApi.Graphics; using OpenBveApi.Interface; +using OpenBveApi.Math; using OpenBveApi.Motor; using OpenBveApi.Objects; using OpenBveApi.Trains; @@ -130,6 +132,12 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r break; } Car.ReAdhesionDevice = new BveReAdhesionDevice(Car, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); + + if (Exhaust.Size > 0) + { + Exhaust.Offset.Z -= 0.5 * Car.Length; + Car.ParticleSources.Add(new ParticleSource(Plugin.Renderer, Car, Exhaust.Offset, Exhaust.Size, Exhaust.SmokeMaxMagnitude, Exhaust.Direction)); + } } } @@ -263,7 +271,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private double maxVelocity; private bool hasAntiSlipDevice; private List vigilanceDevices; - + private Exhaust Exhaust; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { @@ -737,6 +745,34 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } } break; + case KujuTokenID.Effects: + while (block.Position() < block.Length() - 2) + { + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); + } + break; + case KujuTokenID.DieselSpecialEffects: + while (block.Position() < block.Length() - 2) + { + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); + } + break; + case KujuTokenID.Exhaust1: + Exhaust.Offset = new Vector3(block.ReadSingle(), block.ReadSingle(), block.ReadSingle()); + Exhaust.Direction = new Vector3(block.ReadSingle(), block.ReadSingle(), block.ReadSingle()); + Exhaust.Size = block.ReadSingle(); + break; + case KujuTokenID.DieselSmokeEffectMaxMagnitude: + Exhaust.SmokeMaxMagnitude = block.ReadSingle(); + break; + case KujuTokenID.DieselSmokeEffectInitialSmokeRate: + Exhaust.SmokeInitialRate = block.ReadSingle(); + break; + case KujuTokenID.DieselSmokeEffectMaxSmokeRate: + Exhaust.SmokeMaxRate = block.ReadSingle(); + break; } return true; } diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs index ab90fcec6a..ba6cf54b86 100644 --- a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -65,13 +65,13 @@ public override double GetAccelerationOutput(double Speed) return ((baseTrain.Handles.Brake.Actual / (double)baseTrain.Handles.Brake.MaximumNotch) * (totalMass / MaxForce)); } - if (baseCar.Engine is DieselEngine dieselEngine) + if (baseCar.TractionModel is DieselEngine dieselEngine) { // diesel engine uses simulated power level of MaxForce return dieselEngine.CurrentPower * (totalMass / MaxForce); } - if (baseCar.Engine is ElectricEngine electricEngine) + if (baseCar.TractionModel is ElectricEngine electricEngine) { if (baseCar.Specs.PerceivedSpeed < 3.61111 || baseCar.Specs.PerceivedSpeed > 7.22222) { diff --git a/source/TrainManager/Train/TrainBase.cs b/source/TrainManager/Train/TrainBase.cs index 055c4506ef..296a58236d 100644 --- a/source/TrainManager/Train/TrainBase.cs +++ b/source/TrainManager/Train/TrainBase.cs @@ -522,11 +522,10 @@ private void UpdatePhysicsAndControls(double timeElapsed) // Update Run and Motor sounds for (int i = 0; i < Cars.Length; i++) { - Cars[i].Run.Update(TimeElapsed); - Cars[i].Sounds.Motor?.Update(TimeElapsed); + Cars[i].Run.Update(timeElapsed); for (int j = 0; j < Cars[i].Sounds.ControlledSounds.Count; j++) { - Cars[i].Sounds.ControlledSounds[j].Update(TimeElapsed); + Cars[i].Sounds.ControlledSounds[j].Update(timeElapsed); } } @@ -606,7 +605,7 @@ private void UpdateSpeeds(double timeElapsed) CenterOfCarPositions[i] = 0.5 * (pr + pf); CenterOfMassPosition += CenterOfCarPositions[i] * Cars[i].CurrentMass; TrainMass += Cars[i].CurrentMass; - // update engine + // update engine etc. if (Cars[i].TractionModel.ProvidesPower && Cars[i].TractionModel != null) { Cars[i].TractionModel.Update(timeElapsed); From 6954a3bae92a15fad4e4784be8dc6dd46a2f93ef Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Sun, 20 Apr 2025 12:49:00 +0100 Subject: [PATCH 22/82] Implementation of digital number displays and FrameMapping Convert digital speedometer / limit displays, improve unlimited Work on multi-color textual displays Only speedometer and speed limit at the minute --- assets/Compatibility/numbers.png | Bin 0 -> 1943 bytes source/Plugins/Train.MsTs/Misc/Units.cs | 1 + .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 165 ++++++++++++++ source/Plugins/Train.MsTs/Panel/CvfParser.cs | 210 +++++++++++++++++- .../Plugins/Train.MsTs/Panel/FrameMapping.cs | 11 + source/Plugins/Train.MsTs/Plugin.cs | 3 + source/Plugins/Train.MsTs/Train.MsTs.csproj | 5 +- 7 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 assets/Compatibility/numbers.png create mode 100644 source/Plugins/Train.MsTs/Panel/CvfAnimation.cs create mode 100644 source/Plugins/Train.MsTs/Panel/FrameMapping.cs diff --git a/assets/Compatibility/numbers.png b/assets/Compatibility/numbers.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3914036b83b0cfb9a5605add6c8a3b20007f37 GIT binary patch literal 1943 zcmZux2{=@17@i4Dj9rS-T;eJs$#idGu56i+J0>$v3nejWNYO$skL)!RvR2A$=FA;4NOjNiKJR(o_y7Oz`_6gJbHZ>~3kk6eVh{*K0)sZQ;oYSn z5GZ7g5Kl!#uJ(gKR)yfK2#aBhxEr-O95 zfO!IBFn$1&3G%^R5+IB96Syl9E}IUr=>p~nJ{TZ-u_jkge7V$;_+FgJT&T8`gDg59 zj1_{X7r^DSKns||Wb!c21B0=EB`XLQm-rh5J6AA+!(7BXlg@*{&htVRKj-tJT}qZL zb79XSc$zuCY#9vBkKj$UqA73M<-Us@S0vy2{u~NUvG~UY!z|3XWdB$2Lct{bcfw)N z7qIl0i}im^zK^#&=YjxP%+IJwUEYV-b?8zW#y1x9?~}!|y+gjE;RApO~DQp853o%k0;0b94ri#h&MY8TTMG2t;TCV`fb7 zwx3Fxz!N-`fai^}%8DhWL1c%VTV6WF6(>?sj~qNmkR_Md9nOz?gG=fYGl4v+Yd=eZ zCKFe|aZur|Iv*jgq*&y0mo()C7)4TwhzZfX)?ow zk&Sw!3Tr%f4WNfUL=MxGJdJbRjMvXpAJOU`FHX;uZZi++K#3ec>Y$UmHN4Cu%tD_U z2Y<*Q$>kU@M)t^H3)Lj8yhA>1nIO%4EuBI*CJrQ{%1f}HM?5^Xw&eGQ`hZ=ANl0I3 zbUSL>B`jtCWT>;-&a-$?*T_CU@xr3(o9trt4P!~v1_N<}UXg8zMMISCdwQi~uUo0M zJJ!zKOcd{*_NQ7|wFlTZxd0i-oNh~EerD-KGf%h8}*i}I-I{=to^FYNx6BZ^1dv8-3HdO+JPV_&;Lwpw(bl+LPm!BJUap)|a>Q=Nojm7oUIWma z8P0!7lf~@W8?Txpos|0#2@t(Uh|}=c=#6%SDH9FAFw5qYC(Tk;m!65BOC1?@LBw(g z3LD1RTWerrMZM%A;$Wk^g?QV3Mx?2WGM;?7fK06^Q9~OyJX)RAQWZ+Id2HqB*B&5R zRgZv1TS%X=$Uq?KE>>uESo>*cSI49_<^W?351kEQW!c2L;bU3vDWXr}%pTIDVF{ZC zx_rE4!*plHG7%b>+a9M>6i`|ou1AAogkeSYlbu`T48DvvA!Y21)~iz&?i(pE6zJ)iwd z-Y?L<)4y5ez{eY0k1-|t*1ukgNJjYMH_W}(e6FLS&iRCPm^4!lF~Yzs+&$0eaVmXj zgKP=6ob$pkg1l2+m8!eeCTfHUnits encountered in a MSTS CVF internal enum Units { Unknown = 0, diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs new file mode 100644 index 0000000000..ca7df0ac11 --- /dev/null +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -0,0 +1,165 @@ +using System; +using OpenBveApi.FunctionScripting; +using OpenBveApi.Math; +using OpenBveApi.Trains; + +namespace Train.MsTs +{ + /// Animation class handling CVF elements with keyframe mappings + internal class CvfAnimation : AnimationScript + { + internal readonly PanelSubject Subject; + + internal readonly FrameMapping[] FrameMapping; + + internal readonly int Digit; + + internal readonly double UnitConversionFactor; + + private int lastResult; + + internal CvfAnimation(PanelSubject subject, FrameMapping[] frameMapping) + { + Subject = subject; + FrameMapping = frameMapping; + Minimum = 0; + Maximum = FrameMapping.Length; + Digit = -1; + } + + internal CvfAnimation(PanelSubject subject, Units unit, int digit) + { + Subject= subject; + switch (unit) + { + case Units.Miles_Per_Hour: + UnitConversionFactor = 2.2369362920544; + break; + case Units.Kilometers_Per_Hour: + UnitConversionFactor = 3.6; + break; + } + Digit = digit; + } + + internal CvfAnimation(PanelSubject subject, Units unit, FrameMapping[] frameMapping) + { + Subject = subject; + switch (unit) + { + case Units.Miles_Per_Hour: + UnitConversionFactor = 2.2369362920544; + break; + case Units.Kilometers_Per_Hour: + UnitConversionFactor = 3.6; + break; + } + + Digit = -1; + FrameMapping = frameMapping; + } + + public double ExecuteScript(AbstractTrain Train, int CarIndex, Vector3 Position, double TrackPosition, int SectionIndex, bool IsPartOfTrain, double TimeElapsed, int CurrentState) + { + dynamic dynamicTrain = Train; + switch (Subject) + { + case PanelSubject.Throttle: + for (int i = 0; i < FrameMapping.Length; i++) + { + if (FrameMapping[i].MappingValue >= (double)dynamicTrain.Handles.Power.Actual / dynamicTrain.Handles.Power.MaximumNotch) + { + lastResult = FrameMapping[i].FrameKey; + break; + } + } + break; + case PanelSubject.Train_Brake: + for (int i = 0; i < FrameMapping.Length; i++) + { + if (FrameMapping[i].MappingValue >= (double)dynamicTrain.Handles.Brake.Actual / dynamicTrain.Handles.Brake.MaximumNotch) + { + lastResult = FrameMapping[i].FrameKey; + break; + } + } + break; + case PanelSubject.Direction: + lastResult = (int)dynamicTrain.Handles.Reverser.Actual + 1; + break; + case PanelSubject.Speedlim_Display: + double speedLim = Math.Min(Train.CurrentRouteLimit, Train.CurrentSectionLimit) * UnitConversionFactor; + if (Digit == -1) + { + // color + for (int i = 0; i < FrameMapping.Length; i++) + { + if (FrameMapping[i].MappingValue <= speedLim) + { + lastResult = FrameMapping[i].FrameKey; + break; + } + } + } + else + { + // digit + if (speedLim == double.PositiveInfinity) + { + lastResult = -1; // cheat to hide + } + else + { + lastResult = (int)(speedLim / (int)Math.Pow(10, Digit) % 10); + } + } + break; + case PanelSubject.Speedometer: + double currentSpeed = Math.Abs(Train.CurrentSpeed) * UnitConversionFactor; + if (Digit == -1) + { + // color + for (int i = FrameMapping.Length - 1; i > 0; i--) + { + if (FrameMapping[i].MappingValue <= currentSpeed) + { + lastResult = FrameMapping[i].FrameKey; + break; + } + } + } + else + { + // digit + lastResult = (int)(currentSpeed / (int)Math.Pow(10, Digit) % 10); + } + break; + } + + return lastResult; + } + + public AnimationScript Clone() + { + return new CvfAnimation(Subject, FrameMapping); + } + + public double LastResult + { + get => lastResult; + set { } + } + + public double Maximum + { + get; + set; + } + + public double Minimum + { + get; + set; + } + } +} diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 29a37bd9b2..9b00d10c1e 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -299,6 +299,13 @@ private class Component private int VerticalFrames; private bool MouseControl; private bool DirIncrease; + private int LeadingZeros; + private FrameMapping[] FrameMappings = new FrameMapping[0]; + + private Tuple[] PositiveColors; + private bool HasPositiveColor = false; + private Tuple[] NegativeColors; + private bool HasNegativeColor = false; internal void Parse() { @@ -326,10 +333,20 @@ internal void Parse() internal void Create(ref CarBase Car, int Layer) { - if (File.Exists(TexturePath)) + if (File.Exists(TexturePath) || Type == CabComponentType.Digital) { - //Create and register texture + if (FrameMappings.Length == 0 && TotalFrames > 1) + { + // e.g. Acela power handle has 25 frames for total power value of 100% but no mappings specified + FrameMappings = new FrameMapping[TotalFrames]; + // frame 0 is always mapping value 0 + for (int i = 1; i < TotalFrames; i++) + { + FrameMappings[i].MappingValue = (double)i / TotalFrames; + FrameMappings[i].FrameKey = i; + } + } //Create element double rW = 1024.0 / 640.0; double rH = 768.0 / 480.0; @@ -408,7 +425,17 @@ internal void Create(ref CarBase Car, int Layer) } f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); - Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); + switch (panelSubject) + { + case PanelSubject.Throttle: + case PanelSubject.Train_Brake: + Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); + break; + default: + Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); + break; + } + } break; @@ -445,9 +472,79 @@ internal void Create(ref CarBase Car, int Layer) int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, textures[k], null, new Color32(255, 255, 255, 255), k != 0); if (k == 0) j = l; } + f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); - Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); + switch (panelSubject) + { + case PanelSubject.Direction: + case PanelSubject.Direction_Display: + Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); + break; + default: + Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); + break; + } + + + } + break; + case CabComponentType.Digital: + if (panelSubject != PanelSubject.Speedometer && panelSubject != PanelSubject.Speedlim_Display) + { + break; + } + Position.X *= rW; + Position.Y *= rH; + + Color24 textColor = PositiveColors[0].Item2; + + Texture[] frameTextures = new Texture[11]; + TexturePath = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.DataFolder, "Compatibility"), "numbers.png"); // arial 9.5pt + Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + + for (int i = 0; i < 10; i++) + { + Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, i * 24, 16, 24), null), out frameTextures[i], true); + } + Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, 0, 16, 24), null), out frameTextures[10], true); // repeated zero [check vice MSTS] + + int numMaxDigits = (int)Math.Floor(Math.Log10(Maximum) + 1); + int numMinDigits = (int)Math.Floor(Math.Log10(Minimum) + 1); + + int totalDigits = Math.Max(numMinDigits, numMaxDigits) + LeadingZeros; + j = -1; + double digitWidth = Size.X / totalDigits; + for (int currentDigit = 0; currentDigit < totalDigits; currentDigit++) + { + for (int k = 0; k < frameTextures.Length; k++) + { + int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y, digitWidth * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, frameTextures[k], null, textColor, k != 0); + if (k == 0) j = l; + } + + // build color arrays and mappings + Car.CarSections[0].Groups[0].Elements[j].Colors = new Color24[NegativeColors.Length + PositiveColors.Length]; + FrameMappings = new FrameMapping[PositiveColors.Length + NegativeColors.Length]; + for (int i = 0; i < NegativeColors.Length; i++) + { + FrameMappings[i].MappingValue = NegativeColors[i].Item1; + FrameMappings[i].FrameKey = i; + Car.CarSections[0].Groups[0].Elements[j].Colors[i] = NegativeColors[i].Item2; + } + + for (int i = 0; i < PositiveColors.Length; i++) + { + FrameMappings[i + NegativeColors.Length].MappingValue = PositiveColors[i].Item1; + FrameMappings[i + NegativeColors.Length].FrameKey = i + NegativeColors.Length; + Car.CarSections[0].Groups[0].Elements[j].Colors[i + NegativeColors.Length] = PositiveColors[i].Item2; + } + + // create color and digit functions + Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, Units, currentDigit); + Car.CarSections[0].Groups[0].Elements[j].ColorFunction = new CvfAnimation(panelSubject, Units, FrameMappings); } + + break; } } @@ -487,9 +584,23 @@ private void ReadSubBlock(Block block) break; case KujuTokenID.NumValues: case KujuTokenID.NumPositions: - //notch ==> frame data - //We can skip for basic cabs - block.Skip((int) block.Length()); + int numValues = block.ReadInt16(); + if (FrameMappings.Length < numValues) + { + Array.Resize(ref FrameMappings, numValues); + } + + for (int i = 0; i < numValues; i++) + { + if (block.Token == KujuTokenID.NumValues) + { + FrameMappings[i].MappingValue = block.ReadSingle(); + } + else + { + FrameMappings[i].FrameKey = block.ReadInt16(); + } + } break; case KujuTokenID.NumFrames: TotalFrames = block.ReadInt16(); @@ -515,6 +626,76 @@ private void ReadSubBlock(Block block) case KujuTokenID.Type: panelSubject = block.ReadEnumValue(default(PanelSubject)); break; + case KujuTokenID.LeadingZeros: + LeadingZeros = block.ReadInt16(); + break; + case KujuTokenID.PositiveColour: + int numColors = block.ReadInt16(); + if (numColors == 0) + { + PositiveColors = new[] + { + new Tuple(0, Color24.White) + + }; + if (block.Length() - block.Position() > 3) + { + var subBlock = block.ReadSubBlock(KujuTokenID.ControlColour); + PositiveColors[0] = new Tuple(0, new Color24((byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16())); + HasPositiveColor = true; + } + } + else + { + HasPositiveColor = true; + PositiveColors = new Tuple[numColors]; + double value = 0; + for (int i = 0; i < numColors; i++) + { + var subBlock = block.ReadSubBlock(KujuTokenID.ControlColour); + Color24 color = new Color24((byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16()); + PositiveColors[i] = new Tuple(value, color); + if (i < numColors - 1) + { + subBlock = block.ReadSubBlock(KujuTokenID.SwitchVal); + value = subBlock.ReadSingle(); + } + } + } + break; + case KujuTokenID.NegativeColour: + numColors = block.ReadInt16(); + if (numColors == 0) + { + NegativeColors = new[] + { + new Tuple(double.NegativeInfinity, Color24.White) + + }; + if (block.Length() - block.Position() > 3) + { + var subBlock = block.ReadSubBlock(KujuTokenID.ControlColour); + NegativeColors[0] = new Tuple(double.NegativeInfinity, new Color24((byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16())); + HasNegativeColor = true; + } + } + else + { + NegativeColors = new Tuple[numColors]; + double value = double.NegativeInfinity; + for (int i = 0; i < numColors; i++) + { + var subBlock = block.ReadSubBlock(KujuTokenID.ControlColour); + Color24 color = new Color24((byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16()); + NegativeColors[i] = new Tuple(value, color); + if (i < numColors - 1) + { + subBlock = block.ReadSubBlock(KujuTokenID.SwitchVal); + value = subBlock.ReadSingle(); + } + } + } + break; } } @@ -527,7 +708,7 @@ internal Component(Block block) } // get stack language from subject - private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject subject, Units subjectUnits) + private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject subject, Units subjectUnits, string suffix = "") { // transform subject string Code = string.Empty; @@ -605,11 +786,22 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject case PanelSubject.Pantograph: Code = "pantographstate"; break; + case PanelSubject.Speedlim_Display: + switch (subjectUnits) + { + case Units.Miles_Per_Hour: + Code = "routelimit sectionlimit max 1 Minus == 1 Minus routelimit sectionlimit max 2.2369362920544 * ?"; + break; + case Units.Kilometers_Per_Hour: + Code = "routelimit sectionlimit max 1 Minus == 1 Minus routelimit sectionlimit max 3.6 * ?"; + break; + } + break; default: Code = "0"; break; } - return Code; + return Code + suffix; } internal static int CreateElement(ref ElementsGroup Group, double Left, double Top, double Width, double Height, Vector2 RelativeRotationCenter, double Distance, Vector3 Driver, Texture DaytimeTexture, Texture NighttimeTexture, Color32 Color, bool AddStateToLastElement = false) diff --git a/source/Plugins/Train.MsTs/Panel/FrameMapping.cs b/source/Plugins/Train.MsTs/Panel/FrameMapping.cs new file mode 100644 index 0000000000..76690ca27c --- /dev/null +++ b/source/Plugins/Train.MsTs/Panel/FrameMapping.cs @@ -0,0 +1,11 @@ +namespace Train.MsTs +{ + /// Maps a CVF animation frame to the key value + internal struct FrameMapping + { + /// The frame key + internal int FrameKey; + /// The key value + internal double MappingValue; + } +} diff --git a/source/Plugins/Train.MsTs/Plugin.cs b/source/Plugins/Train.MsTs/Plugin.cs index 9db7773950..258178a930 100644 --- a/source/Plugins/Train.MsTs/Plugin.cs +++ b/source/Plugins/Train.MsTs/Plugin.cs @@ -19,6 +19,8 @@ public class Plugin : TrainInterface internal static BaseRenderer Renderer; + internal static FileSystem FileSystem; + internal static bool PreviewOnly; public Plugin() { @@ -29,6 +31,7 @@ public Plugin() public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions Options, object rendererReference) { currentHost = host; + FileSystem = fileSystem; Renderer = (BaseRenderer) rendererReference; } diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index 5f784aaade..63232e4cb1 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -1,4 +1,4 @@ - + @@ -31,6 +31,7 @@ 4 + ..\..\..\packages\SharpCompress.0.32.2\lib\net461\SharpCompress.dll @@ -58,9 +59,11 @@ + + From 351f6b1f6ecd8b4e34daac807216bbfb15b193c2 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 1 May 2025 12:58:15 +0100 Subject: [PATCH 23/82] Fix: Issues for MSTS content using AI AI should attempt to raise the pantograph if required Correct acceleration units --- source/TrainManager/Brake/CarBrake.cs | 5 +++++ source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/TrainManager/Brake/CarBrake.cs b/source/TrainManager/Brake/CarBrake.cs index 78d2ba7056..eacf627852 100644 --- a/source/TrainManager/Brake/CarBrake.cs +++ b/source/TrainManager/Brake/CarBrake.cs @@ -101,6 +101,11 @@ public double DecelerationAtServiceMaximumPressure(int Notch, double currentSpee { return 0; } + + if (decelerationCurves[0] is MSTSDecelerationCurve dec) + { + return dec.MaximumAcceleration; + } if (Notch == 0) { return this.DecelerationCurves[0].GetAccelerationOutput(currentSpeed); diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs index ba6cf54b86..3bb7af082a 100644 --- a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -153,7 +153,7 @@ public override double MaximumAcceleration totalMass += Train.Cars[i].CurrentMass; } - return totalMass / MaxForce; + return totalMass / MaxForce / 3.6; } } } From 20484984e527b91e9973e5df2b659f2899b7b479 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Tue, 6 May 2025 20:16:49 +0100 Subject: [PATCH 24/82] Fix Variable2 controlled sounds in electric traction --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 14 ++++++++++++-- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 17 ++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index a06792a932..6c02cccde0 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -1,4 +1,4 @@ -using OpenBve.Formats.MsTs; +using OpenBve.Formats.MsTs; using SharpCompress.Compressors.Deflate; using System.IO; using System; @@ -9,6 +9,7 @@ using SharpCompress.Compressors; using TrainManager.Car; using SoundManager; +using TrainManager.Motor; using TrainManager.MsTsSounds; namespace Train.MsTs @@ -507,7 +508,16 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So curvePoints = new Tuple[numPoints]; for (int i = 0; i < numPoints; i++) { - curvePoints[i] = new Tuple(block.ReadSingle(), block.ReadSingle()); + // Normalise Variable2 values to be consistant across traction models + // MSTS yuck... + if (car.TractionModel is ElectricEngine) + { + curvePoints[i] = new Tuple(block.ReadSingle() / 100, block.ReadSingle()); + } + else + { + curvePoints[i] = new Tuple(block.ReadSingle(), block.ReadSingle()); + } } break; case KujuTokenID.Granularity: diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 7afc87a8fe..4e4dee5fca 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -30,6 +30,8 @@ internal partial class WagonParser private readonly Dictionary wagonCache; private readonly Dictionary engineCache; + private string soundFile; + private bool hasSounds; private string[] wagonFiles; private double wheelRadius; private bool exteriorLoaded = false; @@ -139,6 +141,12 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r Car.ParticleSources.Add(new ParticleSource(Plugin.Renderer, Car, Exhaust.Offset, Exhaust.Size, Exhaust.SmokeMaxMagnitude, Exhaust.Direction)); } } + + if (hasSounds) + { + SoundModelSystemParser.ParseSoundFile(soundFile, ref Car); + hasSounds = false; + } } internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) @@ -626,11 +634,14 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } break; case KujuTokenID.Sound: - string soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), block.ReadString()); - if (File.Exists(soundFile)) + // parse the sounds *after* we've loaded the traction model though + soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), block.ReadString()); + if (!File.Exists(soundFile)) { - SoundModelSystemParser.ParseSoundFile(soundFile, ref car); + Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: SMS file " + soundFile + " was not found."); + break; } + hasSounds = true; break; case KujuTokenID.EngineControllers: if (!isEngine) From dd2361e6a1a08f0f01d3207a8411e8beee96f58b Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 17 Apr 2025 12:13:46 +0100 Subject: [PATCH 25/82] Add comments and headers to some new files Improve comment block skipping, add some BSD headers --- source/Plugins/Train.MsTs/Effects/Exhaust.cs | 2 +- .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 26 +++++++++- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 24 +++++++++ .../Plugins/Train.MsTs/Panel/FrameMapping.cs | 26 +++++++++- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 35 +++++++++---- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 24 +++++++++ .../Sounds/MSTS/FrequencyCurve.cs | 26 +++++++++- .../Sounds/MSTS/SoundTrigger.Speed.cs | 28 +++++++++- .../MSTS/SoundTrigger.VariableControlled.cs | 52 +++++++++++++++---- .../TrainManager/Sounds/MSTS/SoundTrigger.cs | 26 +++++++++- .../TrainManager/Sounds/MSTS/VolumeCurve.cs | 28 +++++++++- 11 files changed, 271 insertions(+), 26 deletions(-) diff --git a/source/Plugins/Train.MsTs/Effects/Exhaust.cs b/source/Plugins/Train.MsTs/Effects/Exhaust.cs index 7def98fbe8..c577d530e8 100644 --- a/source/Plugins/Train.MsTs/Effects/Exhaust.cs +++ b/source/Plugins/Train.MsTs/Effects/Exhaust.cs @@ -2,7 +2,7 @@ namespace Train.MsTs { - /// Describes the exhaust animation for a MSTS model + /// Describes the properties of an exhaust animation for a MSTS model internal struct Exhaust { /// The offset from the center of the model diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index ca7df0ac11..4aae574599 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -1,4 +1,28 @@ -using System; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System; using OpenBveApi.FunctionScripting; using OpenBveApi.Math; using OpenBveApi.Trains; diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 9b00d10c1e..80eb6f3915 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -1,3 +1,27 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + using System; using System.Collections.Generic; using System.Globalization; diff --git a/source/Plugins/Train.MsTs/Panel/FrameMapping.cs b/source/Plugins/Train.MsTs/Panel/FrameMapping.cs index 76690ca27c..a8ee298287 100644 --- a/source/Plugins/Train.MsTs/Panel/FrameMapping.cs +++ b/source/Plugins/Train.MsTs/Panel/FrameMapping.cs @@ -1,4 +1,28 @@ -namespace Train.MsTs +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +namespace Train.MsTs { /// Maps a CVF animation frame to the key value internal struct FrameMapping diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 6c02cccde0..803264fd68 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -1,3 +1,27 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + using OpenBve.Formats.MsTs; using SharpCompress.Compressors.Deflate; using System.IO; @@ -224,15 +248,8 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So int numStreams = block.ReadInt32(); for (int i = 0; i < numStreams; i++) { - newBlock = block.ReadSubBlock(new[] { KujuTokenID.Stream, KujuTokenID.Skip }); - if (block.Token == KujuTokenID.Skip) - { - i--; - } - else - { - ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); - } + newBlock = block.ReadSubBlock(new[] { KujuTokenID.Stream}); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); if (block.Length() - block.Position() <= 3) { // WARN: incorrect number of streams supplied diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 4e4dee5fca..b04b49c4f6 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -1,3 +1,27 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + using System; using System.Collections.Generic; using System.IO; diff --git a/source/TrainManager/Sounds/MSTS/FrequencyCurve.cs b/source/TrainManager/Sounds/MSTS/FrequencyCurve.cs index a05f3bcf4a..eeca176bfc 100644 --- a/source/TrainManager/Sounds/MSTS/FrequencyCurve.cs +++ b/source/TrainManager/Sounds/MSTS/FrequencyCurve.cs @@ -1,4 +1,28 @@ -using System; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System; using OpenBve.Formats.MsTs; using TrainManager.Car; diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs index 581431d093..d117ca0ad7 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs @@ -1,10 +1,35 @@ -using System; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System; using OpenBveApi.Math; using SoundManager; using TrainManager.Car; namespace TrainManager.MsTsSounds { + /// A sound trigger that activates when the train's speed increases past the set value public class SpeedIncPast : SoundTrigger { private readonly double speedValue; @@ -37,6 +62,7 @@ public override void Update(double timeElapsed, double pitchValue, double volume } } + /// A sound trigger that activates when the train's speed decreases past the set value public class SpeedDecPast : SoundTrigger { private readonly double speedValue; diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs index 13d58953c9..37b1f099b7 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs @@ -1,24 +1,53 @@ -using OpenBveApi.Math; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using OpenBveApi.Math; using SoundManager; using TrainManager.Car; namespace TrainManager.MsTsSounds { + /// A sound trigger controlled by Variable2 increasing past the setpoint + /// Variable2 represents the proportion of power the car's TractionModel is currently generating + /// Diesel- EngineRPM + /// Electric- TractiveForce + /// Steam- CylinderPressure public class Variable2IncPast : SoundTrigger { - private readonly double speedValue; + private readonly double variableValue; private readonly bool soundLoops; - public Variable2IncPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) + public Variable2IncPast(CarBase car, SoundBuffer buffer, double variableValue, bool soundLoops) : base(car, buffer) { - this.speedValue = speedValue; + this.variableValue = variableValue; this.soundLoops = soundLoops; } public override void Update(double timeElapsed, double pitchValue, double volumeValue) { - if (Car.TractionModel.CurrentPower >= speedValue) + if (Car.TractionModel.CurrentPower >= variableValue) { if (Buffer != null) { @@ -41,21 +70,26 @@ public override void Update(double timeElapsed, double pitchValue, double volume } } + /// A sound trigger controlled by Variable2 decreasing past the setpoint + /// Variable2 represents the proportion of power the car's TractionModel is currently generating + /// Diesel- EngineRPM + /// Electric- TractiveForce + /// Steam- CylinderPressure public class Variable2DecPast : SoundTrigger { - private readonly double speedValue; + private readonly double variableValue; private readonly bool soundLoops; - public Variable2DecPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) + public Variable2DecPast(CarBase car, SoundBuffer buffer, double variableValue, bool soundLoops) : base(car, buffer) { - this.speedValue = speedValue; + this.variableValue = variableValue; this.soundLoops = soundLoops; } public override void Update(double timeElapsed, double pitchValue, double volumeValue) { - if (Car.TractionModel.CurrentPower <= speedValue) + if (Car.TractionModel.CurrentPower <= variableValue) { if (Buffer != null) { diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs index 4bdcd8d615..eac199d44e 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs @@ -1,4 +1,28 @@ -using SoundManager; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using SoundManager; using TrainManager.Car; namespace TrainManager.MsTsSounds diff --git a/source/TrainManager/Sounds/MSTS/VolumeCurve.cs b/source/TrainManager/Sounds/MSTS/VolumeCurve.cs index a5a587240a..ca881a5a1e 100644 --- a/source/TrainManager/Sounds/MSTS/VolumeCurve.cs +++ b/source/TrainManager/Sounds/MSTS/VolumeCurve.cs @@ -1,4 +1,28 @@ -using OpenBve.Formats.MsTs; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using OpenBve.Formats.MsTs; using System; using TrainManager.Car; @@ -13,7 +37,7 @@ public class MsTsVolumeCurve public MsTsVolumeCurve(CarBase car, KujuTokenID controller, Tuple[] points) { - this.car = car as CarBase; + this.car = car; this.controller = controller; volumePoints = points; } From ab4a6c91c6c8e6e1b90744894efc4acfda98d7af Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 7 May 2025 12:47:45 +0100 Subject: [PATCH 26/82] Better handling for multiple SMS files per vehicle Implement more SMS stream related bits --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 80 ++++++++++++------- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 17 ++-- .../Sounds/MSTS/SoundTrigger.Speed.cs | 27 ++++++- .../MSTS/SoundTrigger.VariableControlled.cs | 14 +++- .../TrainManager/Sounds/MSTS/SoundTrigger.cs | 38 ++++++++- 5 files changed, 137 insertions(+), 39 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 803264fd68..df743d6a5c 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -23,16 +23,18 @@ //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using OpenBve.Formats.MsTs; -using SharpCompress.Compressors.Deflate; -using System.IO; -using System; -using System.Text; using OpenBveApi.Interface; using OpenBveApi.Runtime; +using OpenBveApi.Sounds; using OpenBveApi.World; using SharpCompress.Compressors; -using TrainManager.Car; +using SharpCompress.Compressors.Deflate; using SoundManager; +using System; +using System.IO; +using System.Runtime.ConstrainedExecution; +using System.Text; +using TrainManager.Car; using TrainManager.Motor; using TrainManager.MsTsSounds; @@ -150,7 +152,28 @@ internal struct SoundSet internal KujuTokenID currentSoundType; internal KujuTokenID variableTriggerType; internal double variableValue; + internal SoundBuffer[] soundBuffers; + internal int currentBuffer; + internal void Create(CarBase car, SoundStream currentSoundStream, KujuTokenID selectionMethod) + { + switch (variableTriggerType) + { + case KujuTokenID.Speed_Inc_Past: + currentSoundStream.Triggers.Add(new SpeedIncPast(car, soundBuffers, selectionMethod, variableValue, currentSoundType != KujuTokenID.PlayOneShot)); + break; + case KujuTokenID.Speed_Dec_Past: + currentSoundStream.Triggers.Add(new SpeedDecPast(car, soundBuffers, selectionMethod, variableValue, currentSoundType != KujuTokenID.PlayOneShot)); + break; + case KujuTokenID.Variable2_Inc_Past: + currentSoundStream.Triggers.Add(new Variable2IncPast(car, soundBuffers, selectionMethod, variableValue, currentSoundType != KujuTokenID.PlayOneShot)); + break; + case KujuTokenID.Variable2_Dec_Past: + currentSoundStream.Triggers.Add(new Variable2DecPast(car, soundBuffers, selectionMethod, variableValue, currentSoundType != KujuTokenID.PlayOneShot)); + break; + + } + } } @@ -301,14 +324,22 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So * map to existing subsystems */ currentSoundSet.currentSoundType = block.Token; - numStreams = block.ReadInt32(); - for (int i = 0; i < numStreams; i++) + + int numSounds = block.ReadInt32(); + currentSoundSet.soundBuffers = new SoundBuffer[numSounds]; + for (int i = 0; i < numSounds; i++) { newBlock = block.ReadSubBlock(KujuTokenID.File); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } - newBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); - ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); + KujuTokenID selectionMethod = KujuTokenID.SequentialSelection; + // Attempt to read selection method if at least one sound file, and some data remaining in the block + if (numSounds > 1 && block.Position() < block.Length() - 4) + { + Block subBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); + selectionMethod = subBlock.ReadEnumValue(default(KujuTokenID)); + } + currentSoundSet.Create(car, currentSoundStream, selectionMethod); break; case KujuTokenID.ReleaseLoopRelease: // empty block expected @@ -323,22 +354,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case SoundTrigger.VariableControlled: // hack Plugin.currentHost.RegisterSound(soundFile, currentSoundSet.ActivationDistance, out var soundHandle); - switch (currentSoundSet.variableTriggerType) - { - case KujuTokenID.Speed_Inc_Past: - currentSoundStream.Triggers.Add(new SpeedIncPast(car, soundHandle as SoundBuffer, currentSoundSet.variableValue, true)); - break; - case KujuTokenID.Speed_Dec_Past: - currentSoundStream.Triggers.Add(new SpeedDecPast(car, soundHandle as SoundBuffer, currentSoundSet.variableValue, true)); - break; - case KujuTokenID.Variable2_Inc_Past: - currentSoundStream.Triggers.Add(new Variable2IncPast(car, soundHandle as SoundBuffer, currentSoundSet.variableValue, true)); - break; - case KujuTokenID.Variable2_Dec_Past: - currentSoundStream.Triggers.Add(new Variable2DecPast(car, soundHandle as SoundBuffer, currentSoundSet.variableValue, true)); - break; - - } + currentSoundSet.soundBuffers[currentSoundSet.currentBuffer] = soundHandle as SoundBuffer; break; case SoundTrigger.ReverserChange: if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot) @@ -442,7 +458,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Speed_Inc_Past: case KujuTokenID.Speed_Dec_Past: currentSoundSet.variableValue = block.ReadSingle(UnitOfVelocity.KilometersPerHour, UnitOfVelocity.MetersPerSecond); // speed in m/s - newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.PlayOneShot }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.PlayOneShot, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.SpeedControlled: @@ -474,12 +490,22 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So break; case KujuTokenID.PlayOneShot: currentSoundSet.currentSoundType = block.Token; - int numSounds = block.ReadInt16(); + numSounds = block.ReadInt16(); + currentSoundSet.soundBuffers = new SoundBuffer[numSounds]; for (int i = 0; i < numSounds; i++) { newBlock = block.ReadSubBlock(KujuTokenID.File); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } + + selectionMethod = KujuTokenID.SequentialSelection; + // Attempt to read selection method if at least one sound file, and some data remaining in the block + if (numSounds > 1 && block.Position() < block.Length() - 4) + { + Block subBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); + selectionMethod = subBlock.ReadEnumValue(default(KujuTokenID)); + } + currentSoundSet.Create(car, currentSoundStream, selectionMethod); break; case KujuTokenID.Volume: double volume = block.ReadSingle(); diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index b04b49c4f6..d4d6d66cc7 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -54,8 +54,7 @@ internal partial class WagonParser private readonly Dictionary wagonCache; private readonly Dictionary engineCache; - private string soundFile; - private bool hasSounds; + private List soundFiles; private string[] wagonFiles; private double wheelRadius; private bool exteriorLoaded = false; @@ -65,6 +64,7 @@ internal WagonParser(Plugin Plugin) plugin = Plugin; wagonCache = new Dictionary(); engineCache = new Dictionary(); + soundFiles = new List(); } internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, ref CarBase Car, ref TrainBase train) @@ -166,10 +166,13 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r } } - if (hasSounds) + if (soundFiles.Count > 0) { - SoundModelSystemParser.ParseSoundFile(soundFile, ref Car); - hasSounds = false; + for (int i = 0; i < soundFiles.Count; i++) + { + SoundModelSystemParser.ParseSoundFile(soundFiles[i], ref Car); + } + soundFiles.Clear(); } } @@ -659,13 +662,13 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; case KujuTokenID.Sound: // parse the sounds *after* we've loaded the traction model though - soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), block.ReadString()); + string soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), block.ReadString()); if (!File.Exists(soundFile)) { Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: SMS file " + soundFile + " was not found."); break; } - hasSounds = true; + soundFiles.Add(soundFile); break; case KujuTokenID.EngineControllers: if (!isEngine) diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs index d117ca0ad7..9c1313fb63 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs @@ -23,6 +23,7 @@ //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using System; +using OpenBve.Formats.MsTs; using OpenBveApi.Math; using SoundManager; using TrainManager.Car; @@ -36,6 +37,14 @@ public class SpeedIncPast : SoundTrigger private readonly bool soundLoops; + private double previousSpeed; + + public SpeedIncPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(car, buffers, selectionMethod) + { + this.speedValue = speedValue; + this.soundLoops = soundLoops; + } + public SpeedIncPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) { this.speedValue = speedValue; @@ -44,7 +53,8 @@ public SpeedIncPast(CarBase car, SoundBuffer buffer, double speedValue, bool sou public override void Update(double timeElapsed, double pitchValue, double volumeValue) { - if (Math.Abs(Car.CurrentSpeed) >= speedValue) + double speed = Math.Abs(Car.CurrentSpeed); + if (speed >= speedValue && speed > previousSpeed) { if (Buffer != null) { @@ -59,6 +69,8 @@ public override void Update(double timeElapsed, double pitchValue, double volume { Stop(); } + + previousSpeed = speed; } } @@ -69,6 +81,14 @@ public class SpeedDecPast : SoundTrigger private readonly bool soundLoops; + private double previousSpeed; + + public SpeedDecPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(car, buffers, selectionMethod) + { + this.speedValue = speedValue; + this.soundLoops = soundLoops; + } + public SpeedDecPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) { this.speedValue = speedValue; @@ -77,7 +97,8 @@ public SpeedDecPast(CarBase car, SoundBuffer buffer, double speedValue, bool sou public override void Update(double timeElapsed, double pitchValue, double volumeValue) { - if (Math.Abs(Car.CurrentSpeed) <= speedValue) + double speed = Math.Abs(Car.CurrentSpeed); + if (speed <= speedValue && speed < previousSpeed) { if (Buffer != null) { @@ -92,6 +113,8 @@ public override void Update(double timeElapsed, double pitchValue, double volume { Stop(); } + + previousSpeed = speed; } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs index 37b1f099b7..48587376bc 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs @@ -22,6 +22,7 @@ //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +using OpenBve.Formats.MsTs; using OpenBveApi.Math; using SoundManager; using TrainManager.Car; @@ -38,7 +39,13 @@ public class Variable2IncPast : SoundTrigger private readonly double variableValue; private readonly bool soundLoops; - + + public Variable2IncPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double variableValue, bool soundLoops) : base(car, buffers, selectionMethod) + { + this.variableValue = variableValue; + this.soundLoops = soundLoops; + } + public Variable2IncPast(CarBase car, SoundBuffer buffer, double variableValue, bool soundLoops) : base(car, buffer) { this.variableValue = variableValue; @@ -81,6 +88,11 @@ public class Variable2DecPast : SoundTrigger private readonly bool soundLoops; + public Variable2DecPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double variableValue, bool soundLoops) : base(car, buffers, selectionMethod) + { + this.variableValue = variableValue; + this.soundLoops = soundLoops; + } public Variable2DecPast(CarBase car, SoundBuffer buffer, double variableValue, bool soundLoops) : base(car, buffer) { this.variableValue = variableValue; diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs index eac199d44e..6ebc6273b0 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs @@ -22,6 +22,7 @@ //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +using OpenBve.Formats.MsTs; using SoundManager; using TrainManager.Car; @@ -29,7 +30,33 @@ namespace TrainManager.MsTsSounds { public abstract class SoundTrigger { - internal readonly SoundBuffer Buffer; + internal readonly SoundBuffer[] Buffers; + + private readonly KujuTokenID bufferSelectionMethod; + + + private int bufferIndex; + + internal SoundBuffer Buffer + { + get + { + switch (bufferSelectionMethod) + { + case KujuTokenID.SequentialSelection: + bufferIndex++; + if (bufferIndex > Buffers.Length - 1) + { + bufferIndex = 0; + } + break; + case KujuTokenID.RandomSelection: + bufferIndex = TrainManagerBase.RandomNumberGenerator.Next(0, Buffers.Length - 1); + break; + } + return Buffers[bufferIndex]; + } + } internal SoundSource Source; @@ -39,8 +66,15 @@ public abstract class SoundTrigger internal SoundTrigger(CarBase car, SoundBuffer buffer) { - Buffer = buffer; Car = car; + Buffers = new[] { buffer }; + } + + internal SoundTrigger(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod) + { + Car = car; + Buffers = buffers; + bufferSelectionMethod = selectionMethod; } public virtual void Update(double timeElapsed, double pitchValue, double volumeValue) From 741d210d4454afe9da17f4408e654ab78ef5ecb0 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 8 May 2025 15:54:44 +0100 Subject: [PATCH 27/82] Implement CabSignal, Overspeed and EmergencyBrake displays --- .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 24 ++++++++++ source/Plugins/Train.MsTs/Panel/CvfParser.cs | 44 ++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 4aae574599..89eb9a85f0 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -42,6 +42,23 @@ internal class CvfAnimation : AnimationScript private int lastResult; + internal CvfAnimation(PanelSubject subject) + { + Subject = subject; + switch (subject) + { + case PanelSubject.Aspect_Display: + Minimum = 0; + Maximum = 7; + break; + default: + Minimum = 0; + Maximum = double.MaxValue; + break; + } + Digit = -1; + } + internal CvfAnimation(PanelSubject subject, FrameMapping[] frameMapping) { Subject = subject; @@ -158,6 +175,13 @@ public double ExecuteScript(AbstractTrain Train, int CarIndex, Vector3 Position, lastResult = (int)(currentSpeed / (int)Math.Pow(10, Digit) % 10); } break; + case PanelSubject.Aspect_Display: + lastResult = Train.CurrentSignalAspect; + break; + case PanelSubject.Overspeed: + double currentLimit = Math.Min(Train.CurrentRouteLimit, Train.CurrentSectionLimit); + lastResult = Math.Abs(Train.CurrentSpeed) > currentLimit ? 1 : 0; + break; } return lastResult; diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 80eb6f3915..679631cd04 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -502,6 +502,7 @@ internal void Create(ref CarBase Car, int Layer) { case PanelSubject.Direction: case PanelSubject.Direction_Display: + case PanelSubject.Overspeed: Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); break; default: @@ -567,8 +568,46 @@ internal void Create(ref CarBase Car, int Layer) Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, Units, currentDigit); Car.CarSections[0].Groups[0].Elements[j].ColorFunction = new CvfAnimation(panelSubject, Units, FrameMappings); } + break; + case CabComponentType.CabSignalDisplay: + TotalFrames = 8; + HorizontalFrames = 4; + VerticalFrames = 2; + Position.X *= rW; + Position.Y *= rH; + Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + if (wday > 0 & hday > 0) + { + Texture[] textures = new Texture[8]; + // 4 h-frames, 2 v-frames + int row = 0; + int column = 0; + int frameWidth = wday / HorizontalFrames; + int frameHeight = hday / VerticalFrames; + for (int k = 0; k < TotalFrames; k++) + { + Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); + if (column < HorizontalFrames - 1) + { + column++; + } + else + { + column = 0; + row++; + } + } - + j = -1; + for (int k = 0; k < textures.Length; k++) + { + int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + if (k == 0) j = l; + } + + Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject); + + } break; } } @@ -821,6 +860,9 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject break; } break; + case PanelSubject.Emergency_Brake: + Code = "emergencybrake"; + break; default: Code = "0"; break; From aff286dc16be3c9e52c422d35d17cef547dc4034 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 7 May 2025 12:09:24 +0100 Subject: [PATCH 28/82] Pantograph, headout and wipers work --- .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 38 +- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 638 +++--------------- .../Panel/Enums/CabComponentType.cs | 3 +- .../Train.MsTs/Panel/Enums/PanelSubject.cs | 1 + .../Plugins/Train.MsTs/Panel/FrameMapping.cs | 2 +- source/Plugins/Train.MsTs/Plugin.cs | 8 +- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 171 +++-- .../Plugins/Train.MsTs/Sound/TriggerTypes.cs | 4 + source/Plugins/Train.MsTs/Train.MsTs.csproj | 2 + .../Plugins/Train.MsTs/Train/ConsistParser.cs | 42 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 76 +-- .../TrainManager/Sounds/MSTS/SoundStream.cs | 5 +- .../Sounds/MSTS/SoundTrigger.Speed.cs | 30 +- .../TrainManager/Sounds/MSTS/SoundTrigger.cs | 24 +- 14 files changed, 324 insertions(+), 720 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 89eb9a85f0..a1b3607709 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -1,6 +1,6 @@ //Simplified BSD License (BSD-2-Clause) // -//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project // //Redistribution and use in source and binary forms, with or without //modification, are permitted provided that the following conditions are met: @@ -22,10 +22,13 @@ //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -using System; using OpenBveApi.FunctionScripting; using OpenBveApi.Math; +using OpenBveApi.Motor; using OpenBveApi.Trains; +using System; +using OpenBveApi; +using TrainManager.Motor; namespace Train.MsTs { @@ -100,9 +103,9 @@ internal CvfAnimation(PanelSubject subject, Units unit, FrameMapping[] frameMapp FrameMapping = frameMapping; } - public double ExecuteScript(AbstractTrain Train, int CarIndex, Vector3 Position, double TrackPosition, int SectionIndex, bool IsPartOfTrain, double TimeElapsed, int CurrentState) + public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, double trackPosition, int sectionIndex, bool isPartOfTrain, double timeElapsed, int currentState) { - dynamic dynamicTrain = Train; + dynamic dynamicTrain = train; switch (Subject) { case PanelSubject.Throttle: @@ -129,7 +132,7 @@ public double ExecuteScript(AbstractTrain Train, int CarIndex, Vector3 Position, lastResult = (int)dynamicTrain.Handles.Reverser.Actual + 1; break; case PanelSubject.Speedlim_Display: - double speedLim = Math.Min(Train.CurrentRouteLimit, Train.CurrentSectionLimit) * UnitConversionFactor; + double speedLim = Math.Min(train.CurrentRouteLimit, train.CurrentSectionLimit) * UnitConversionFactor; if (Digit == -1) { // color @@ -156,7 +159,7 @@ public double ExecuteScript(AbstractTrain Train, int CarIndex, Vector3 Position, } break; case PanelSubject.Speedometer: - double currentSpeed = Math.Abs(Train.CurrentSpeed) * UnitConversionFactor; + double currentSpeed = Math.Abs(train.CurrentSpeed) * UnitConversionFactor; if (Digit == -1) { // color @@ -176,11 +179,28 @@ public double ExecuteScript(AbstractTrain Train, int CarIndex, Vector3 Position, } break; case PanelSubject.Aspect_Display: - lastResult = Train.CurrentSignalAspect; + lastResult = train.CurrentSignalAspect; break; case PanelSubject.Overspeed: - double currentLimit = Math.Min(Train.CurrentRouteLimit, Train.CurrentSectionLimit); - lastResult = Math.Abs(Train.CurrentSpeed) > currentLimit ? 1 : 0; + double currentLimit = Math.Min(train.CurrentRouteLimit, train.CurrentSectionLimit); + lastResult = Math.Abs(train.CurrentSpeed) > currentLimit ? 1 : 0; + break; + case PanelSubject.Front_Hlight: + lastResult = dynamicTrain.SafetySystems.Headlights.CurrentState; + break; + case PanelSubject.Pantograph: + case PanelSubject.Panto_Display: + int pantographState = 0; + for (int k = 0; k < dynamicTrain.Cars.Length; k++) + { + if (dynamicTrain.Cars[k].TractionModel is ElectricEngine electricEngine && + electricEngine.Components.TryGetTypedValue(EngineComponent.Pantograph, out Pantograph pantograph)) + { + pantographState = (int)pantograph.State; + break; + } + } + lastResult = pantographState; break; } diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 679631cd04..78897bc8c9 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -1,6 +1,6 @@ //Simplified BSD License (BSD-2-Clause) // -//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project // //Redistribution and use in source and binary forms, with or without //modification, are permitted provided that the following conditions are met: @@ -22,21 +22,21 @@ //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; using LibRender2.Trains; using OpenBve.Formats.MsTs; using OpenBveApi.Colors; -using OpenBveApi.FunctionScripting; +using OpenBveApi.Graphics; using OpenBveApi.Interface; using OpenBveApi.Math; using OpenBveApi.Objects; using OpenBveApi.Textures; using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using OpenBveApi.World; using TrainManager.Car; using TrainManager.Trains; @@ -45,19 +45,19 @@ namespace Train.MsTs class CabviewFileParser { // constants - private const double stackDistance = 0.000001; + internal const double StackDistance = 0.000001; /// EyeDistance is required to be 1.0 by UpdateCarSectionElement and by UpdateCameraRestriction, thus cannot be easily changed. private const double eyeDistance = 1.0; - private static string currentFolder; + internal static string CurrentFolder; - private static readonly List cabComponents = new List(); + private static readonly List cabComponents = new List(); // parse panel config internal static bool ParseCabViewFile(string fileName, ref CarBase Car) { - currentFolder = Path.GetDirectoryName(fileName); + CurrentFolder = Path.GetDirectoryName(fileName); Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read); byte[] buffer = new byte[34]; @@ -91,7 +91,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) } else if (!headerString.StartsWith("SIMISA@@")) { - Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognized cabview file header " + headerString + " in " + fileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Unrecognized cabview file header " + headerString + " in " + fileName); return false; } @@ -120,7 +120,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) } else if (subHeader[7] != 'b') { - Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognized subHeader " + subHeader + " in " + fileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Unrecognized subHeader " + subHeader + " in " + fileName); return false; } else @@ -143,39 +143,58 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) //Create panel //Create camera restriction - double WorldWidth, WorldHeight; + double worldWidth, worldHeight; if (Plugin.Renderer.Screen.Width >= Plugin.Renderer.Screen.Height) { - WorldWidth = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.HorizontalViewingAngle) * eyeDistance; - WorldHeight = WorldWidth / Plugin.Renderer.Screen.AspectRatio; + worldWidth = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.HorizontalViewingAngle) * eyeDistance; + worldHeight = worldWidth / Plugin.Renderer.Screen.AspectRatio; } else { - WorldHeight = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.VerticalViewingAngle) * eyeDistance / Plugin.Renderer.Screen.AspectRatio; - WorldWidth = WorldHeight * Plugin.Renderer.Screen.AspectRatio; + worldHeight = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.VerticalViewingAngle) * eyeDistance / Plugin.Renderer.Screen.AspectRatio; + worldWidth = worldHeight * Plugin.Renderer.Screen.AspectRatio; } - double x0 = (PanelLeft - PanelCenter.X) / PanelResolution; - double x1 = (PanelRight - PanelCenter.X) / PanelResolution; - double y0 = (PanelCenter.Y - PanelBottom) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; - double y1 = (PanelCenter.Y - PanelTop) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; - Car.CameraRestriction.BottomLeft = new Vector3(x0 * WorldWidth, y0 * WorldHeight, eyeDistance); - Car.CameraRestriction.TopRight = new Vector3(x1 * WorldWidth, y1 * WorldHeight, eyeDistance); - Car.DriverYaw = Math.Atan((PanelCenter.X - PanelOrigin.X) * WorldWidth / PanelResolution); - Car.DriverPitch = Math.Atan((PanelOrigin.Y - PanelCenter.Y) * WorldWidth / PanelResolution); + double x0 = -PanelCenter.X / PanelResolution; + double x1 = (PanelSize.X - PanelCenter.X) / PanelResolution; + double y0 = (PanelCenter.Y - PanelSize.Y) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; + double y1 = (PanelCenter.Y) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; + Car.CameraRestriction.BottomLeft = new Vector3(x0 * worldWidth, y0 * worldHeight, eyeDistance); + Car.CameraRestriction.TopRight = new Vector3(x1 * worldWidth, y1 * worldHeight, eyeDistance); + Car.DriverYaw = Math.Atan((PanelCenter.X - PanelOrigin.X) * worldWidth / PanelResolution); + Car.DriverPitch = Math.Atan((PanelOrigin.Y - PanelCenter.Y) * worldWidth / PanelResolution); - if (File.Exists(CabViews[0].fileName)) + if(CabViews.Count == 0 || !File.Exists(CabViews[0].FileName)) { - Car.Driver = CabViews[0].position; - Plugin.currentHost.RegisterTexture(CabViews[0].fileName, new TextureParameters(null, null), out Texture tday, true); - CreateElement(ref Car.CarSections[0].Groups[0], 0.0, 0.0, 1024, 768, new Vector2(0.5, 0.5), 0.0, Car.Driver, tday, null, new Color32(255, 255, 255, 255)); + return false; } - else + Car.CameraRestrictionMode = CameraRestrictionMode.On; + Plugin.Renderer.Camera.CurrentRestriction = CameraRestrictionMode.On; + Car.Driver = CabViews[0].Position; + for (int i = 0; i < CabViews.Count; i++) { - //Main panel image doesn't exist - return false; + + Plugin.CurrentHost.RegisterTexture(CabViews[i].FileName, new TextureParameters(null, null), out Texture tday, true); + switch (i) + { + case 0: + Car.CarSections.Add(CarSectionType.Interior, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, Car)); + CreateElement(ref Car.CarSections[CarSectionType.Interior].Groups[0], Vector2.Null, PanelSize, new Vector2(0.5, 0.5), 0.0, CabViews[0].Position, tday, null, new Color32(255, 255, 255, 255)); + Car.CarSections[CarSectionType.Interior].ViewDirection = new Transformation(CabViews[0].Direction.Y.ToRadians(), -CabViews[0].Direction.X.ToRadians(), -CabViews[0].Direction.Z.ToRadians()); + break; + case 1: + Car.CarSections.Add(CarSectionType.HeadOutLeft, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, Car)); + CreateElement(ref Car.CarSections[CarSectionType.HeadOutLeft].Groups[0], Vector2.Null, PanelSize, new Vector2(0.5, 0.5), 0.0, CabViews[1].Position, tday, null, new Color32(255, 255, 255, 255)); + Car.CarSections[CarSectionType.HeadOutLeft].ViewDirection = new Transformation(CabViews[1].Direction.Y.ToRadians(), -CabViews[1].Direction.X.ToRadians(), -CabViews[1].Direction.Z.ToRadians()); + break; + case 2: + Car.CarSections.Add(CarSectionType.HeadOutRight, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, Car)); + CreateElement(ref Car.CarSections[CarSectionType.HeadOutRight].Groups[0], Vector2.Null, PanelSize, new Vector2(0.5, 0.5), 0.0, CabViews[2].Position, tday, null, new Color32(255, 255, 255, 255)); + Car.CarSections[CarSectionType.HeadOutRight].ViewDirection = new Transformation(CabViews[2].Direction.Y.ToRadians(), -CabViews[2].Direction.X.ToRadians(), -CabViews[2].Direction.Z.ToRadians()); + break; + } } - + int currentLayer = 1; for (int i = 0; i < cabComponents.Count; i++) { @@ -185,25 +204,10 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) return true; } - - internal struct CabView - { - internal string fileName; - internal Vector2 topLeft; - internal Vector2 panelSize; - internal Vector3 position; - internal Vector3 direction; - - internal void setCabView(string cabViewFile) - { - cabViewFile = cabViewFile.Replace(@"\\", @"\"); - fileName = OpenBveApi.Path.CombineFile(currentFolder, cabViewFile); - } - } - + private static CabView currentCabView; - private static List CabViews = new List(); + private static readonly List CabViews = new List(); private static void ParseBlock(Block block) { @@ -215,7 +219,7 @@ private static void ParseBlock(Block block) while (controlCount > 0) { newBlock = block.ReadSubBlock(); - Component currentComponent = new Component(newBlock); + CabComponent currentComponent = new CabComponent(newBlock, currentCabView.Position); currentComponent.Parse(); cabComponents.Add(currentComponent); controlCount--; @@ -223,26 +227,26 @@ private static void ParseBlock(Block block) break; case KujuTokenID.Direction: - currentCabView.direction.X = block.ReadSingle(); - currentCabView.direction.Y = block.ReadSingle(); - currentCabView.direction.Z = block.ReadSingle(); + currentCabView.Direction.X = block.ReadSingle(); + currentCabView.Direction.Y = block.ReadSingle(); + currentCabView.Direction.Z = block.ReadSingle(); break; case KujuTokenID.Position: - currentCabView.position.X = block.ReadSingle(); - currentCabView.position.Y = block.ReadSingle(); - currentCabView.position.Z = block.ReadSingle(); + currentCabView.Position.X = block.ReadSingle(); + currentCabView.Position.Y = block.ReadSingle(); + currentCabView.Position.Z = block.ReadSingle(); break; case KujuTokenID.CabViewWindow: - currentCabView.topLeft.X = block.ReadInt16(); - currentCabView.topLeft.Y = block.ReadInt16(); - currentCabView.panelSize.X = block.ReadInt16(); - currentCabView.panelSize.Y = block.ReadInt16(); + currentCabView.TopLeft.X = block.ReadInt16(); + currentCabView.TopLeft.Y = block.ReadInt16(); + currentCabView.PanelSize.X = block.ReadInt16(); + currentCabView.PanelSize.Y = block.ReadInt16(); break; case KujuTokenID.CabViewFile: case KujuTokenID.CabViewWindowFile: - if (string.IsNullOrEmpty(currentCabView.fileName)) + if (string.IsNullOrEmpty(currentCabView.FileName)) { - currentCabView.setCabView(block.ReadString()); + currentCabView.SetCabView(CurrentFolder, block.ReadString()); } break; @@ -262,7 +266,7 @@ private static void ParseBlock(Block block) ParseBlock(newBlock); newBlock = block.ReadSubBlock(KujuTokenID.Position); //Position within loco X,Y,Z ParseBlock(newBlock); - newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT X, ROT Y, ROT Z ?? + newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT Y, ROT X, ROT Z ParseBlock(newBlock); CabViews.Add(currentCabView); currentCabView = new CabView(); @@ -275,7 +279,7 @@ private static void ParseBlock(Block block) ParseBlock(newBlock); newBlock = block.ReadSubBlock(KujuTokenID.Position); //Position within loco X,Y,Z ParseBlock(newBlock); - newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT X, ROT Y, ROT Z ?? + newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT Y, ROT X, ROT Z ParseBlock(newBlock); CabViews.Add(currentCabView); currentCabView = new CabView(); @@ -288,7 +292,7 @@ private static void ParseBlock(Block block) ParseBlock(newBlock); newBlock = block.ReadSubBlock(KujuTokenID.Position); //Position within loco X,Y,Z ParseBlock(newBlock); - newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT X, ROT Y, ROT Z ?? + newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT Y, ROT X, ROT Z ParseBlock(newBlock); CabViews.Add(currentCabView); newBlock = block.ReadSubBlock(KujuTokenID.EngineData); @@ -299,479 +303,13 @@ private static void ParseBlock(Block block) } } - static double PanelResolution = 1024.0; - static double PanelLeft = 0.0, PanelRight = 1024.0; - static double PanelTop = 0.0, PanelBottom = 768.0; - static Vector2 PanelCenter = new Vector2(0, 240); - private static Vector2 PanelOrigin = new Vector2(0, 240); - - private class Component - { - private CabComponentType Type = CabComponentType.None; - private string TexturePath; - private PanelSubject panelSubject; - private Units Units; - private Vector2 Position = new Vector2(0, 0); - private Vector2 Size = new Vector2(0, 0); - private double PivotPoint; - private double InitialAngle; - private double LastAngle; - private double Maximum; - private double Minimum; - private int TotalFrames; - private int HorizontalFrames; - private int VerticalFrames; - private bool MouseControl; - private bool DirIncrease; - private int LeadingZeros; - private FrameMapping[] FrameMappings = new FrameMapping[0]; - - private Tuple[] PositiveColors; - private bool HasPositiveColor = false; - private Tuple[] NegativeColors; - private bool HasNegativeColor = false; - - internal void Parse() - { - if (!Enum.TryParse(myBlock.Token.ToString(), true, out Type)) - { - Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognised CabViewComponent type."); - return; - } - - while (myBlock.Position() < myBlock.Length() - 2) - { - //Components in CVF files are considerably less structured, so read *any* valid block - try - { - Block newBlock = myBlock.ReadSubBlock(); - ReadSubBlock(newBlock); - } - catch - { - break; - } - - } - } - - internal void Create(ref CarBase Car, int Layer) - { - if (File.Exists(TexturePath) || Type == CabComponentType.Digital) - { - if (FrameMappings.Length == 0 && TotalFrames > 1) - { - // e.g. Acela power handle has 25 frames for total power value of 100% but no mappings specified - FrameMappings = new FrameMapping[TotalFrames]; - // frame 0 is always mapping value 0 - for (int i = 1; i < TotalFrames; i++) - { - FrameMappings[i].MappingValue = (double)i / TotalFrames; - FrameMappings[i].FrameKey = i; - } - - } - //Create element - double rW = 1024.0 / 640.0; - double rH = 768.0 / 480.0; - int wday, hday; - int j; - string f; - CultureInfo Culture = CultureInfo.InvariantCulture; - switch (Type) - { - case CabComponentType.Dial: - Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(null, null), out Texture tday, true); - // correct angle position if appropriate - if (!DirIncrease && InitialAngle > LastAngle) - { - InitialAngle = -(365 - InitialAngle); - } - //Get final position from the 640px panel (Yuck...) - Position.X *= rW; - Position.Y *= rH; - Size.X *= rW; - Size.Y *= rH; - PivotPoint *= rH; - j = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X, Size.Y, new Vector2((0.5 * Size.X) / (tday.Width * rW), PivotPoint / (tday.Height * rH)), Layer * stackDistance, Car.Driver, tday, null, new Color32(255, 255, 255, 255)); - Car.CarSections[0].Groups[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0); - Car.CarSections[0].Groups[0].Elements[j].RotateXDirection = DirIncrease ? new Vector3(1.0, 0.0, 0.0) : new Vector3(-1.0, 0.0, 0.0); - Car.CarSections[0].Groups[0].Elements[j].RotateYDirection = Vector3.Cross(Car.CarSections[0].Groups[0].Elements[j].RotateZDirection, Car.CarSections[0].Groups[0].Elements[j].RotateXDirection); - f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); - InitialAngle = InitialAngle.ToRadians(); - LastAngle = LastAngle.ToRadians(); - double a0 = (InitialAngle * Maximum - LastAngle * Minimum) / (Maximum - Minimum); - double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum); - f += " " + a1.ToString(Culture) + " * " + a0.ToString(Culture) + " +"; - Car.CarSections[0].Groups[0].Elements[j].RotateZFunction = new FunctionScript(Plugin.currentHost, f, false); - break; - case CabComponentType.Lever: - /* - * TODO: - * Need to revisit the actual position versus frame with MSTS content. - * - * Take the example of the stock Class 50 - * This has a notched brake handle, with 5 physical notches - * - * The cabview has 12 frames for these 5 notches, which appear to be mapped using NumPositions - * Oddly, all frames appear to be distinct. Need to check OR + MSTS handling - * Suspect there's a notch delay or something that should use these. - */ - Position.X *= rW; - Position.Y *= rH; - Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - if (wday > 0 & hday > 0) - { - Texture[] textures = new Texture[TotalFrames]; - int row = 0; - int column = 0; - int frameWidth = wday / HorizontalFrames; - int frameHeight = hday / VerticalFrames; - for (int k = 0; k < TotalFrames; k++) - { - Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); - if (column < HorizontalFrames - 1) - { - column++; - } - else - { - column = 0; - row++; - } - } - - j = -1; - for (int k = 0; k < textures.Length; k++) - { - int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, textures[k], null, new Color32(255, 255, 255, 255), k != 0); - if (k == 0) j = l; - } - - f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); - switch (panelSubject) - { - case PanelSubject.Throttle: - case PanelSubject.Train_Brake: - Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); - break; - default: - Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); - break; - } - - } - - break; - case CabComponentType.TriState: - case CabComponentType.TwoState: - case CabComponentType.MultiStateDisplay: - Position.X *= rW; - Position.Y *= rH; - Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - if (wday > 0 & hday > 0) - { - Texture[] textures = new Texture[TotalFrames]; - int row = 0; - int column = 0; - int frameWidth = wday / HorizontalFrames; - int frameHeight = hday / VerticalFrames; - for (int k = 0; k < TotalFrames; k++) - { - Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); - if (column < HorizontalFrames - 1) - { - column++; - } - else - { - column = 0; - row++; - } - } - - j = -1; - for (int k = 0; k < textures.Length; k++) - { - int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, textures[k], null, new Color32(255, 255, 255, 255), k != 0); - if (k == 0) j = l; - } - - f = GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); - switch (panelSubject) - { - case PanelSubject.Direction: - case PanelSubject.Direction_Display: - case PanelSubject.Overspeed: - Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); - break; - default: - Car.CarSections[0].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.currentHost, f, false); - break; - } - - - } - break; - case CabComponentType.Digital: - if (panelSubject != PanelSubject.Speedometer && panelSubject != PanelSubject.Speedlim_Display) - { - break; - } - Position.X *= rW; - Position.Y *= rH; - - Color24 textColor = PositiveColors[0].Item2; - - Texture[] frameTextures = new Texture[11]; - TexturePath = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.DataFolder, "Compatibility"), "numbers.png"); // arial 9.5pt - Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - - for (int i = 0; i < 10; i++) - { - Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, i * 24, 16, 24), null), out frameTextures[i], true); - } - Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, 0, 16, 24), null), out frameTextures[10], true); // repeated zero [check vice MSTS] - - int numMaxDigits = (int)Math.Floor(Math.Log10(Maximum) + 1); - int numMinDigits = (int)Math.Floor(Math.Log10(Minimum) + 1); - - int totalDigits = Math.Max(numMinDigits, numMaxDigits) + LeadingZeros; - j = -1; - double digitWidth = Size.X / totalDigits; - for (int currentDigit = 0; currentDigit < totalDigits; currentDigit++) - { - for (int k = 0; k < frameTextures.Length; k++) - { - int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y, digitWidth * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, frameTextures[k], null, textColor, k != 0); - if (k == 0) j = l; - } - - // build color arrays and mappings - Car.CarSections[0].Groups[0].Elements[j].Colors = new Color24[NegativeColors.Length + PositiveColors.Length]; - FrameMappings = new FrameMapping[PositiveColors.Length + NegativeColors.Length]; - for (int i = 0; i < NegativeColors.Length; i++) - { - FrameMappings[i].MappingValue = NegativeColors[i].Item1; - FrameMappings[i].FrameKey = i; - Car.CarSections[0].Groups[0].Elements[j].Colors[i] = NegativeColors[i].Item2; - } - - for (int i = 0; i < PositiveColors.Length; i++) - { - FrameMappings[i + NegativeColors.Length].MappingValue = PositiveColors[i].Item1; - FrameMappings[i + NegativeColors.Length].FrameKey = i + NegativeColors.Length; - Car.CarSections[0].Groups[0].Elements[j].Colors[i + NegativeColors.Length] = PositiveColors[i].Item2; - } - - // create color and digit functions - Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, Units, currentDigit); - Car.CarSections[0].Groups[0].Elements[j].ColorFunction = new CvfAnimation(panelSubject, Units, FrameMappings); - } - break; - case CabComponentType.CabSignalDisplay: - TotalFrames = 8; - HorizontalFrames = 4; - VerticalFrames = 2; - Position.X *= rW; - Position.Y *= rH; - Plugin.currentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - if (wday > 0 & hday > 0) - { - Texture[] textures = new Texture[8]; - // 4 h-frames, 2 v-frames - int row = 0; - int column = 0; - int frameWidth = wday / HorizontalFrames; - int frameHeight = hday / VerticalFrames; - for (int k = 0; k < TotalFrames; k++) - { - Plugin.currentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); - if (column < HorizontalFrames - 1) - { - column++; - } - else - { - column = 0; - row++; - } - } - - j = -1; - for (int k = 0; k < textures.Length; k++) - { - int l = CreateElement(ref Car.CarSections[0].Groups[0], Position.X, Position.Y, Size.X * rW, Size.Y * rH, new Vector2(0.5, 0.5), Layer * stackDistance, Car.Driver, textures[k], null, new Color32(255, 255, 255, 255), k != 0); - if (k == 0) j = l; - } - - Car.CarSections[0].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject); - - } - break; - } - } - } - - private void ReadSubBlock(Block block) - { - switch (block.Token) - { - case KujuTokenID.Pivot: - PivotPoint = block.ReadSingle(); - break; - case KujuTokenID.Units: - Units = block.ReadEnumValue(default(Units)); - break; - case KujuTokenID.ScalePos: - InitialAngle = block.ReadSingle(); - LastAngle = block.ReadSingle(); - break; - case KujuTokenID.ScaleRange: - Minimum = block.ReadSingle(); - Maximum = block.ReadSingle(); - break; - case KujuTokenID.States: - //Contains sub-blocks with Style and SwitchVal types - TotalFrames = block.ReadInt16(); - HorizontalFrames = block.ReadInt16(); - VerticalFrames = block.ReadInt16(); - break; - case KujuTokenID.DirIncrease: - // rotates Clockwise (0) or AntiClockwise (1) - DirIncrease = block.ReadInt16() == 1; - break; - case KujuTokenID.Orientation: - //Flip? - block.Skip((int) block.Length()); - break; - case KujuTokenID.NumValues: - case KujuTokenID.NumPositions: - int numValues = block.ReadInt16(); - if (FrameMappings.Length < numValues) - { - Array.Resize(ref FrameMappings, numValues); - } - - for (int i = 0; i < numValues; i++) - { - if (block.Token == KujuTokenID.NumValues) - { - FrameMappings[i].MappingValue = block.ReadSingle(); - } - else - { - FrameMappings[i].FrameKey = block.ReadInt16(); - } - } - break; - case KujuTokenID.NumFrames: - TotalFrames = block.ReadInt16(); - HorizontalFrames = block.ReadInt16(); - VerticalFrames = block.ReadInt16(); - break; - case KujuTokenID.MouseControl: - MouseControl = block.ReadInt16() == 1; - break; - case KujuTokenID.Style: - block.Skip((int) block.Length()); - break; - case KujuTokenID.Graphic: - string s = block.ReadString(); - TexturePath = OpenBveApi.Path.CombineFile(currentFolder, s); - break; - case KujuTokenID.Position: - Position.X = block.ReadSingle(); - Position.Y = block.ReadSingle(); - Size.X = block.ReadSingle(); - Size.Y = block.ReadSingle(); - break; - case KujuTokenID.Type: - panelSubject = block.ReadEnumValue(default(PanelSubject)); - break; - case KujuTokenID.LeadingZeros: - LeadingZeros = block.ReadInt16(); - break; - case KujuTokenID.PositiveColour: - int numColors = block.ReadInt16(); - if (numColors == 0) - { - PositiveColors = new[] - { - new Tuple(0, Color24.White) - - }; - if (block.Length() - block.Position() > 3) - { - var subBlock = block.ReadSubBlock(KujuTokenID.ControlColour); - PositiveColors[0] = new Tuple(0, new Color24((byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16())); - HasPositiveColor = true; - } - } - else - { - HasPositiveColor = true; - PositiveColors = new Tuple[numColors]; - double value = 0; - for (int i = 0; i < numColors; i++) - { - var subBlock = block.ReadSubBlock(KujuTokenID.ControlColour); - Color24 color = new Color24((byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16()); - PositiveColors[i] = new Tuple(value, color); - if (i < numColors - 1) - { - subBlock = block.ReadSubBlock(KujuTokenID.SwitchVal); - value = subBlock.ReadSingle(); - } - } - } - break; - case KujuTokenID.NegativeColour: - numColors = block.ReadInt16(); - if (numColors == 0) - { - NegativeColors = new[] - { - new Tuple(double.NegativeInfinity, Color24.White) - - }; - if (block.Length() - block.Position() > 3) - { - var subBlock = block.ReadSubBlock(KujuTokenID.ControlColour); - NegativeColors[0] = new Tuple(double.NegativeInfinity, new Color24((byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16())); - HasNegativeColor = true; - } - } - else - { - NegativeColors = new Tuple[numColors]; - double value = double.NegativeInfinity; - for (int i = 0; i < numColors; i++) - { - var subBlock = block.ReadSubBlock(KujuTokenID.ControlColour); - Color24 color = new Color24((byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16(), (byte)subBlock.ReadInt16()); - NegativeColors[i] = new Tuple(value, color); - if (i < numColors - 1) - { - subBlock = block.ReadSubBlock(KujuTokenID.SwitchVal); - value = subBlock.ReadSingle(); - } - } - } - break; - } - } - - internal Component(Block block) - { - myBlock = block; - } - - private readonly Block myBlock; - } + const double PanelResolution = 1024.0; + private static readonly Vector2 PanelSize = new Vector2(1024, 768); + private static readonly Vector2 PanelCenter = new Vector2(0, 240); + private static readonly Vector2 PanelOrigin = new Vector2(0, 240); // get stack language from subject - private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject subject, Units subjectUnits, string suffix = "") + internal static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject subject, Units subjectUnits, string suffix = "") { // transform subject string Code = string.Empty; @@ -870,11 +408,11 @@ private static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject return Code + suffix; } - internal static int CreateElement(ref ElementsGroup Group, double Left, double Top, double Width, double Height, Vector2 RelativeRotationCenter, double Distance, Vector3 Driver, Texture DaytimeTexture, Texture NighttimeTexture, Color32 Color, bool AddStateToLastElement = false) + internal static int CreateElement(ref ElementsGroup Group, Vector2 TopLeft, Vector2 Size, Vector2 RelativeRotationCenter, double Distance, Vector3 Driver, Texture DaytimeTexture, Texture NighttimeTexture, Color32 Color, bool AddStateToLastElement = false) { - if (Width == 0 || Height == 0) + if (Size.X == 0 || Size.Y == 0) { - Plugin.currentHost.AddMessage(MessageType.Error, false, "Attempted to create an invalid size element"); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Attempted to create an invalid size element"); } double WorldWidth, WorldHeight; @@ -889,15 +427,15 @@ internal static int CreateElement(ref ElementsGroup Group, double Left, double T WorldWidth = WorldHeight * Plugin.Renderer.Screen.AspectRatio; } - double x0 = Left / PanelResolution; - double x1 = (Left + Width) / PanelResolution; - double y0 = (PanelBottom - Top) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; - double y1 = (PanelBottom - (Top + Height)) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; + double x0 = TopLeft.X / PanelResolution; + double x1 = (TopLeft.X + Size.X) / PanelResolution; + double y0 = (PanelSize.Y - TopLeft.Y) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; + double y1 = (PanelSize.Y - (TopLeft.Y + Size.Y)) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; double xd = 0.5 - PanelCenter.X / PanelResolution; x0 += xd; x1 += xd; - double yt = PanelBottom - PanelResolution / Plugin.Renderer.Screen.AspectRatio; - double yd = (PanelCenter.Y - yt) / (PanelBottom - yt) - 0.5; + double yt = PanelSize.Y - PanelResolution / Plugin.Renderer.Screen.AspectRatio; + double yd = (PanelCenter.Y - yt) / (PanelSize.Y - yt) - 0.5; y0 += yd; y1 += yd; x0 = (x0 - 0.5) * WorldWidth; @@ -915,7 +453,7 @@ internal static int CreateElement(ref ElementsGroup Group, double Left, double T Vertex t1 = new Vertex(v[1], new Vector2(0.0f, 0.0f)); Vertex t2 = new Vertex(v[2], new Vector2(1.0f, 0.0f)); Vertex t3 = new Vertex(v[3], new Vector2(1.0f, 1.0f)); - StaticObject Object = new StaticObject(Plugin.currentHost); + StaticObject Object = new StaticObject(Plugin.CurrentHost); Object.Mesh.Vertices = new VertexTemplate[] {t0, t1, t2, t3}; Object.Mesh.Faces = new[] {new MeshFace(new[] {0, 1, 2, 0, 2, 3}, FaceFlags.Triangles)}; //Must create as a single face like this to avoid Z-sort issues with overlapping bits Object.Mesh.Materials = new MeshMaterial[1]; @@ -952,13 +490,13 @@ internal static int CreateElement(ref ElementsGroup Group, double Left, double T { int n = Group.Elements.Length; Array.Resize(ref Group.Elements, n + 1); - Group.Elements[n] = new AnimatedObject(Plugin.currentHost); + Group.Elements[n] = new AnimatedObject(Plugin.CurrentHost); Group.Elements[n].States = new[] {new ObjectState()}; Group.Elements[n].States[0].Translation = Matrix4D.CreateTranslation(o.X, o.Y, -o.Z); Group.Elements[n].States[0].Prototype = Object; Group.Elements[n].CurrentState = 0; Group.Elements[n].internalObject = new ObjectState {Prototype = Object}; - Plugin.currentHost.CreateDynamicObject(ref Group.Elements[n].internalObject); + Plugin.CurrentHost.CreateDynamicObject(ref Group.Elements[n].internalObject); return n; } } diff --git a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs index 2cc762b78f..d78ce03256 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs @@ -1,4 +1,5 @@ -namespace Train.MsTs +// ReSharper disable UnusedMember.Global +namespace Train.MsTs { internal enum CabComponentType { diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs index edb164fed3..1dea4f6f2a 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -27,6 +27,7 @@ internal enum PanelSubject Firehole, Friction_Braking, Front_Hlight, + Gears, Horn, Load_Meter, Main_Res, diff --git a/source/Plugins/Train.MsTs/Panel/FrameMapping.cs b/source/Plugins/Train.MsTs/Panel/FrameMapping.cs index a8ee298287..719558ed61 100644 --- a/source/Plugins/Train.MsTs/Panel/FrameMapping.cs +++ b/source/Plugins/Train.MsTs/Panel/FrameMapping.cs @@ -1,6 +1,6 @@ //Simplified BSD License (BSD-2-Clause) // -//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project // //Redistribution and use in source and binary forms, with or without //modification, are permitted provided that the following conditions are met: diff --git a/source/Plugins/Train.MsTs/Plugin.cs b/source/Plugins/Train.MsTs/Plugin.cs index 258178a930..8866132c80 100644 --- a/source/Plugins/Train.MsTs/Plugin.cs +++ b/source/Plugins/Train.MsTs/Plugin.cs @@ -15,7 +15,7 @@ public class Plugin : TrainInterface internal static WagonParser WagonParser; - internal static HostInterface currentHost; + internal static HostInterface CurrentHost; internal static BaseRenderer Renderer; @@ -28,9 +28,9 @@ public Plugin() WagonParser = new WagonParser(this); } - public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions Options, object rendererReference) + public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions options, object rendererReference) { - currentHost = host; + CurrentHost = host; FileSystem = fileSystem; Renderer = (BaseRenderer) rendererReference; } @@ -44,7 +44,7 @@ public override bool CanLoadTrain(string path) return false; } - public override bool LoadTrain(Encoding Encoding, string trainPath, ref AbstractTrain train, ref Control[] currentControls) + public override bool LoadTrain(Encoding encoding, string trainPath, ref AbstractTrain train, ref Control[] currentControls) { PreviewOnly = false; try diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index df743d6a5c..85559aac26 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -1,6 +1,6 @@ //Simplified BSD License (BSD-2-Clause) // -//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project // //Redistribution and use in source and binary forms, with or without //modification, are permitted provided that the following conditions are met: @@ -23,16 +23,17 @@ //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using OpenBve.Formats.MsTs; +using OpenBveApi; using OpenBveApi.Interface; +using OpenBveApi.Math; +using OpenBveApi.Motor; using OpenBveApi.Runtime; -using OpenBveApi.Sounds; using OpenBveApi.World; using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; using SoundManager; using System; using System.IO; -using System.Runtime.ConstrainedExecution; using System.Text; using TrainManager.Car; using TrainManager.Motor; @@ -51,7 +52,7 @@ class SoundModelSystemParser internal static bool ParseSoundFile(string fileName, ref CarBase Car) { currentFile = fileName; - currentFolder = Path.GetDirectoryName(fileName); + currentFolder = System.IO.Path.GetDirectoryName(fileName); Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read); byte[] buffer = new byte[34]; @@ -85,7 +86,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase Car) } else if (!headerString.StartsWith("SIMISA@@")) { - Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognized SMS file header " + headerString + " in " + fileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Unrecognized SMS file header " + headerString + " in " + fileName); return false; } @@ -116,8 +117,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase Car) } else if (subHeader[7] != 'b') { - Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognized subHeader " + subHeader + " in " + fileName); - return false; + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Unrecognized subHeader " + subHeader + " in " + fileName); } else { @@ -148,28 +148,28 @@ internal struct SoundSet internal double DeactivationDistance; internal double Priority; - internal SoundTrigger currentTrigger; - internal KujuTokenID currentSoundType; - internal KujuTokenID variableTriggerType; - internal double variableValue; - internal SoundBuffer[] soundBuffers; - internal int currentBuffer; + internal SoundTrigger CurrentTrigger; + internal KujuTokenID CurrentSoundType; + internal KujuTokenID VariableTriggerType; + internal double VariableValue; + internal SoundBuffer[] SoundBuffers; + internal int CurrentBuffer; internal void Create(CarBase car, SoundStream currentSoundStream, KujuTokenID selectionMethod) { - switch (variableTriggerType) + switch (VariableTriggerType) { case KujuTokenID.Speed_Inc_Past: - currentSoundStream.Triggers.Add(new SpeedIncPast(car, soundBuffers, selectionMethod, variableValue, currentSoundType != KujuTokenID.PlayOneShot)); + currentSoundStream.Triggers.Add(new SpeedIncPast(car, SoundBuffers, selectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; case KujuTokenID.Speed_Dec_Past: - currentSoundStream.Triggers.Add(new SpeedDecPast(car, soundBuffers, selectionMethod, variableValue, currentSoundType != KujuTokenID.PlayOneShot)); + currentSoundStream.Triggers.Add(new SpeedDecPast(car, SoundBuffers, selectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; case KujuTokenID.Variable2_Inc_Past: - currentSoundStream.Triggers.Add(new Variable2IncPast(car, soundBuffers, selectionMethod, variableValue, currentSoundType != KujuTokenID.PlayOneShot)); + currentSoundStream.Triggers.Add(new Variable2IncPast(car, SoundBuffers, selectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; case KujuTokenID.Variable2_Dec_Past: - currentSoundStream.Triggers.Add(new Variable2DecPast(car, soundBuffers, selectionMethod, variableValue, currentSoundType != KujuTokenID.PlayOneShot)); + currentSoundStream.Triggers.Add(new Variable2DecPast(car, SoundBuffers, selectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; } @@ -187,7 +187,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So // file root while (block.Position() < block.Length() - 3) { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } break; @@ -323,10 +323,10 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So * NOTE: Handle these on a per-sound trigger, as where possible * map to existing subsystems */ - currentSoundSet.currentSoundType = block.Token; + currentSoundSet.CurrentSoundType = block.Token; int numSounds = block.ReadInt32(); - currentSoundSet.soundBuffers = new SoundBuffer[numSounds]; + currentSoundSet.SoundBuffers = new SoundBuffer[numSounds]; for (int i = 0; i < numSounds; i++) { newBlock = block.ReadSubBlock(KujuTokenID.File); @@ -349,81 +349,124 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So if (block.ReadPath(currentFolder, out string soundFile)) { // n.b. MSTS does not distinguish between increase / decrease sounds for handles etc. - switch (currentSoundSet.currentTrigger) + // sound radii are also fudged based upon BVE values; most MSTS content just seems to use massive radii + switch (currentSoundSet.CurrentTrigger) { case SoundTrigger.VariableControlled: // hack - Plugin.currentHost.RegisterSound(soundFile, currentSoundSet.ActivationDistance, out var soundHandle); - currentSoundSet.soundBuffers[currentSoundSet.currentBuffer] = soundHandle as SoundBuffer; + Plugin.CurrentHost.RegisterSound(soundFile, currentSoundSet.ActivationDistance, out var soundHandle); + currentSoundSet.SoundBuffers[currentSoundSet.CurrentBuffer] = soundHandle as SoundBuffer; break; case SoundTrigger.ReverserChange: - if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot) + if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot) { - car.baseTrain.Handles.Reverser.EngageSound = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Reverser.ReleaseSound = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Reverser.EngageSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Reverser.ReleaseSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; case SoundTrigger.ThrottleChange: - if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot) + if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot) { - car.baseTrain.Handles.Power.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Power.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Power.Increase = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Power.IncreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Power.Min = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Power.Max = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.Decrease = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.DecreaseFast = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.Increase = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.IncreaseFast = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.Min = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Power.Max = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; case SoundTrigger.TrainBrakeChange: - if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot) + if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot) { - car.baseTrain.Handles.Brake.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Brake.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Brake.Increase = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Brake.IncreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Brake.Min = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.Brake.Max = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.Decrease = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.DecreaseFast = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.Increase = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.IncreaseFast = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.Min = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.Brake.Max = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; case SoundTrigger.EngineBrakeChange: - if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.Handles.LocoBrake != null) + if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.Handles.LocoBrake != null) { - car.baseTrain.Handles.LocoBrake.Decrease = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.LocoBrake.DecreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.LocoBrake.Increase = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.LocoBrake.IncreaseFast = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.LocoBrake.Min = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); - car.baseTrain.Handles.LocoBrake.Max = new CarSound(Plugin.currentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.Decrease = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.DecreaseFast = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.Increase = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.IncreaseFast = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.Min = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.baseTrain.Handles.LocoBrake.Max = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; case SoundTrigger.LightSwitchToggle: - if (currentSoundSet.currentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.SafetySystems.Headlights != null) + if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot && car.baseTrain.SafetySystems.Headlights != null) { - Plugin.currentHost.RegisterSound(soundFile, 2.0, out soundHandle); + Plugin.CurrentHost.RegisterSound(soundFile, 2.0, out soundHandle); car.baseTrain.SafetySystems.Headlights.SwitchSoundBuffer = soundHandle as SoundBuffer; } break; case SoundTrigger.HornOn: - if (currentSoundSet.currentSoundType == KujuTokenID.StartLoopRelease && car.Horns[0] != null) + if (currentSoundSet.CurrentSoundType == KujuTokenID.StartLoopRelease && car.Horns[0] != null) { - Plugin.currentHost.RegisterSound(soundFile, 2.0, out soundHandle); + Plugin.CurrentHost.RegisterSound(soundFile, 2.0, out soundHandle); car.Horns[0].LoopSound = soundHandle as SoundBuffer; } break; case SoundTrigger.BellOn: - if (currentSoundSet.currentSoundType == KujuTokenID.StartLoopRelease && car.Horns[2] != null) + if (currentSoundSet.CurrentSoundType == KujuTokenID.StartLoopRelease && car.Horns[2] != null) { - Plugin.currentHost.RegisterSound(soundFile, 2.0, out soundHandle); + Plugin.CurrentHost.RegisterSound(soundFile, 2.0, out soundHandle); car.Horns[0].LoopSound = soundHandle as SoundBuffer; } break; + case SoundTrigger.Pantograph1Up: + case SoundTrigger.Pantograph1Down: + case SoundTrigger.Pantograph1Toggle: + if (car.TractionModel.Components.TryGetTypedValue(EngineComponent.Pantograph, out Pantograph pantograph)) + { + if (currentSoundSet.CurrentTrigger == SoundTrigger.Pantograph1Up) + { + pantograph.RaiseSound = new CarSound(Plugin.CurrentHost, soundFile, 100, Vector3.Zero); + } + else if(currentSoundSet.CurrentTrigger == SoundTrigger.Pantograph1Down) + { + pantograph.LowerSound = new CarSound(Plugin.CurrentHost, soundFile, 100, Vector3.Zero); + } + else + { + pantograph.SwitchToggle = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + } + else + { + // n.b. A WAG file may link to a model containing pantograph animations, or a SMS with pantograph sounds, but does not need to mention + // that it exists, so we may need to add it here. + Pantograph newPantograph = new Pantograph(car.TractionModel); + if (currentSoundSet.CurrentTrigger == SoundTrigger.Pantograph1Up) + { + newPantograph.RaiseSound = new CarSound(Plugin.CurrentHost, soundFile, 100, Vector3.Zero); + } + else if (currentSoundSet.CurrentTrigger == SoundTrigger.Pantograph1Down) + { + newPantograph.LowerSound = new CarSound(Plugin.CurrentHost, soundFile, 100, Vector3.Zero); + } + else + { + newPantograph.SwitchToggle = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + car.TractionModel.Components.Add(EngineComponent.Pantograph, newPantograph); + } + break; + case SoundTrigger.WiperOn: + case SoundTrigger.WiperOff: + car.Windscreen.Wipers.SwitchSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + break; } } else { - if (currentSoundSet.currentTrigger != SoundTrigger.Skip) + if (currentSoundSet.CurrentTrigger != SoundTrigger.Skip) { - Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Sound File " + soundFile + " was not found in SMS " + currentFile); + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Sound File " + soundFile + " was not found in SMS " + currentFile); } } int checkDigit = block.ReadInt32(); @@ -445,19 +488,19 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So } break; case KujuTokenID.Discrete_Trigger: - currentSoundSet.currentTrigger = (SoundTrigger)block.ReadInt32(); // stored as integer + currentSoundSet.CurrentTrigger = (SoundTrigger)block.ReadInt32(); // stored as integer newBlock = block.ReadSubBlock(new[] { KujuTokenID.PlayOneShot, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.SetStreamVolume }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.Variable_Trigger: - currentSoundSet.variableTriggerType = block.ReadEnumValue(default(KujuTokenID)); - switch (currentSoundSet.variableTriggerType) + currentSoundSet.VariableTriggerType = block.ReadEnumValue(default(KujuTokenID)); + switch (currentSoundSet.VariableTriggerType) { case KujuTokenID.StartLoop: break; case KujuTokenID.Speed_Inc_Past: case KujuTokenID.Speed_Dec_Past: - currentSoundSet.variableValue = block.ReadSingle(UnitOfVelocity.KilometersPerHour, UnitOfVelocity.MetersPerSecond); // speed in m/s + currentSoundSet.VariableValue = block.ReadSingle(UnitOfVelocity.KilometersPerHour, UnitOfVelocity.MetersPerSecond); // speed in m/s newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.PlayOneShot, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; @@ -474,7 +517,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So break; case KujuTokenID.Variable2_Inc_Past: case KujuTokenID.Variable2_Dec_Past: - currentSoundSet.variableValue = block.ReadSingle(); // power value + currentSoundSet.VariableValue = block.ReadSingle(); // power value newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; @@ -485,13 +528,13 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Variable3Controlled: break; default: - throw new Exception("Unexpected enum value " + currentSoundSet.variableTriggerType + " encounted in SMS file " + currentFile); + throw new Exception("Unexpected enum value " + currentSoundSet.VariableTriggerType + " encounted in SMS file " + currentFile); } break; case KujuTokenID.PlayOneShot: - currentSoundSet.currentSoundType = block.Token; + currentSoundSet.CurrentSoundType = block.Token; numSounds = block.ReadInt16(); - currentSoundSet.soundBuffers = new SoundBuffer[numSounds]; + currentSoundSet.SoundBuffers = new SoundBuffer[numSounds]; for (int i = 0; i < numSounds; i++) { newBlock = block.ReadSubBlock(KujuTokenID.File); diff --git a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs index c70a78cdec..2e71f6125f 100644 --- a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs +++ b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs @@ -55,6 +55,10 @@ internal enum SoundTrigger Pantograph1Toggle = 47, VigilanceAlarmReset = 48, // 49 - 53 not listed + + // TODO: CHECK 53 / 54: + // acelaeng.sms has 53 commented as Brake Normal Apply and 54 as BrakeEmergencyApply + TrainBrakePressureDecrease = 54, //55 not listed VigilanceAlarmOn = 56, diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index 63232e4cb1..af5b120bb1 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -56,9 +56,11 @@ + + diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index 34283f2b11..610608495b 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -12,7 +12,6 @@ using SoundManager; using TrainManager.Car; using TrainManager.Handles; -using TrainManager.Motor; using TrainManager.Power; using TrainManager.Trains; @@ -20,13 +19,13 @@ namespace Train.MsTs { internal class ConsistParser { - internal readonly Plugin plugin; + internal readonly Plugin Plugin; - internal string currentFolder; + internal string CurrentFolder; - internal ConsistParser(Plugin Plugin) + internal ConsistParser(Plugin plugin) { - plugin = Plugin; + Plugin = plugin; } internal void ReadConsist(string fileName, ref AbstractTrain Train) @@ -43,15 +42,19 @@ internal void ReadConsist(string fileName, ref AbstractTrain Train) train.Handles.HoldBrake = new HoldBrakeHandle(train); train.Specs.AveragesPressureDistribution = true; train.SafetySystems.Headlights = new LightSource(train, 2); - currentFolder = Path.GetDirectoryName(fileName); - DirectoryInfo d = Directory.GetParent(currentFolder); - if(!d.Name.Equals("TRAINS", StringComparison.InvariantCultureIgnoreCase)) + CurrentFolder = Path.GetDirectoryName(fileName); + if (CurrentFolder == null) + { + throw new Exception("Unable to determine the MSTS consist working directory."); + } + DirectoryInfo d = Directory.GetParent(CurrentFolder); + if(d == null || !d.Name.Equals("TRAINS", StringComparison.InvariantCultureIgnoreCase)) { //FIXME: Better finding of the trainset folder (set in options?) - throw new Exception("Unable to find the TRAINS folder"); + throw new Exception("Unable to find the MSTS TRAINS folder."); } - currentFolder = d.FullName; + CurrentFolder = d.FullName; Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); @@ -138,7 +141,7 @@ internal void ReadConsist(string fileName, ref AbstractTrain Train) for (int i = 0; i < train.Cars.Length; i++) { train.Cars[i].Coupler = new Coupler(0.9 * 0.3, 1.1 * 0.3, train.Cars[i / 2], train.Cars.Length > 1 ? train.Cars[i / 2 + 1] : null); - train.Cars[i].CurrentCarSection = -1; + train.Cars[i].CurrentCarSection = CarSectionType.NotVisible; train.Cars[i].ChangeCarSection(CarSectionType.NotVisible); train.Cars[i].FrontBogie.ChangeSection(-1); train.Cars[i].RearBogie.ChangeSection(-1); @@ -286,19 +289,19 @@ private void ParseBlock(Block block, ref TrainBase Train) switch (wagonFiles.Length) { case 0: - Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: Unable to determine WagonFile to load."); + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: Unable to determine WagonFile to load."); break; case 1: //Just a WagonName- This is likely invalid, but let's ignore - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(currentFolder, "trainset"), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); - Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: No WagonFolder supplied, searching entire trainset folder."); + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: No WagonFolder supplied, searching entire trainset folder."); break; case 2: - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(currentFolder, "trainset\\" + wagonFiles[1]), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset\\" + wagonFiles[1]), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); break; default: - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(currentFolder, "trainset"), wagonFiles[1], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); - Plugin.currentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: Two parameters were expected- Check for correct escaping of strings."); + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[1], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: Two parameters were expected- Check for correct escaping of strings."); break; } break; @@ -312,10 +315,5 @@ private void ParseBlock(Block block, ref TrainBase Train) } } - - internal string GetDescription(string fileName) - { - return string.Empty; - } } } diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index d4d6d66cc7..8eb8ef926f 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -1,6 +1,6 @@ //Simplified BSD License (BSD-2-Clause) // -//Copyright (c) 2020, Christopher Lees, The OpenBVE Project +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project // //Redistribution and use in source and binary forms, with or without //modification, are permitted provided that the following conditions are met: @@ -31,7 +31,6 @@ using LibRender2.Trains; using OpenBve.Formats.Msts; using OpenBve.Formats.MsTs; -using OpenBveApi.Graphics; using OpenBveApi.Interface; using OpenBveApi.Math; using OpenBveApi.Motor; @@ -54,7 +53,7 @@ internal partial class WagonParser private readonly Dictionary wagonCache; private readonly Dictionary engineCache; - private List soundFiles; + private readonly List soundFiles; private string[] wagonFiles; private double wheelRadius; private bool exteriorLoaded = false; @@ -158,6 +157,8 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r break; } Car.ReAdhesionDevice = new BveReAdhesionDevice(Car, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); + Car.Windscreen = new Windscreen(0, 0, Car); + Car.Windscreen.Wipers = new WindscreenWiper(Car.Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 1.0); if (Exhaust.Size > 0) { @@ -485,7 +486,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } catch { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vechicle Parser: Invalid vehicle type specified."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vechicle Parser: Invalid vehicle type specified."); } } break; @@ -498,23 +499,25 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool string objectFile = OpenBveApi.Path.CombineFile(Path.GetDirectoryName(fileName), block.ReadString()); if (!File.Exists(objectFile)) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle object file " + objectFile + " was not found"); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle object file " + objectFile + " was not found"); return true; } - for (int i = 0; i < Plugin.currentHost.Plugins.Length; i++) + for (int i = 0; i < Plugin.CurrentHost.Plugins.Length; i++) { - if (Plugin.currentHost.Plugins[i].Object != null && Plugin.currentHost.Plugins[i].Object.CanLoadObject(objectFile)) + if (Plugin.CurrentHost.Plugins[i].Object != null && Plugin.CurrentHost.Plugins[i].Object.CanLoadObject(objectFile)) { - Plugin.currentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject carObject); + Plugin.CurrentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject carObject); if (exteriorLoaded) { - car.CarSections[car.CarSections.Length -1].AppendObject(Plugin.currentHost, car, carObject); + CarSection exteriorCarSection = car.CarSections[CarSectionType.Exterior]; + exteriorCarSection.AppendObject(Plugin.CurrentHost, car, carObject); + car.CarSections[CarSectionType.Exterior] = exteriorCarSection; } else { - car.LoadCarSections(carObject, false); + car.CarSections.Add(CarSectionType.Exterior, new CarSection(Plugin.CurrentHost, ObjectType.Dynamic, false, car, carObject)); } break; } @@ -527,19 +530,19 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool car.Width = block.ReadSingle(UnitOfLength.Meter); if (car.Width == 0) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle width is invalid."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle width is invalid."); car.Width = 2; } car.Height = block.ReadSingle(UnitOfLength.Meter); if (car.Height == 0) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle height is invalid."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle height is invalid."); car.Height = 2; } car.Length = block.ReadSingle(UnitOfLength.Meter); if (car.Length == 0) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle length is invalid."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle length is invalid."); car.Length = 25; } break; @@ -564,27 +567,10 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool string cabViewFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "CABVIEW"), block.ReadString()); if (!File.Exists(cabViewFile)) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Cab view file " + cabViewFile + " was not found"); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Cab view file " + cabViewFile + " was not found"); return true; } - - if (car.CarSections.Length == 0) - { - car.CarSections = new CarSection[1]; - } - else if (car.CarSections.Length > 0) - { - // Cab View must always be at CarSection zero, but the order is not guaranteed within an eng / wag - CarSection[] move = new CarSection[car.CarSections.Length + 1]; - for (int i = 0; i < car.CarSections.Length; i++) - { - move[i + 1] = car.CarSections[i]; - } - car.CarSections = move; - } - car.CarSections[0] = new CarSection(Plugin.currentHost, ObjectType.Overlay, true, car); - car.CameraRestrictionMode = CameraRestrictionMode.On; - Plugin.Renderer.Camera.CurrentRestriction = CameraRestrictionMode.On; + CabviewFileParser.ParseCabViewFile(cabViewFile, ref car); car.HasInteriorView = true; break; @@ -612,7 +598,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool // maximum force applied when starting if (!isEngine) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine force is not expected to be present in a wagon block."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine force is not expected to be present in a wagon block."); break; } maxForce = block.ReadSingle(UnitOfForce.Newton); @@ -627,7 +613,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool // Maximum continuous force if (!isEngine) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine force is not expected to be present in a wagon block."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine force is not expected to be present in a wagon block."); break; } maxContinuousForce = block.ReadSingle(UnitOfForce.Newton); @@ -643,7 +629,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool if (numWheels < 2) { // NumWheels *should* be divisible by two (to get axles), but some content uses a single wheel, e.g. stock Class 50 - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Invalid number of wheels."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Invalid number of wheels."); numWheels = 1; } else @@ -665,7 +651,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool string soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), block.ReadString()); if (!File.Exists(soundFile)) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: SMS file " + soundFile + " was not found."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: SMS file " + soundFile + " was not found."); break; } soundFiles.Add(soundFile); @@ -673,7 +659,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.EngineControllers: if (!isEngine) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine controllers are not expected to be present in a wagon block."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine controllers are not expected to be present in a wagon block."); break; } @@ -687,7 +673,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.Throttle: if (currentEngineType == EngineType.Steam) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: A throttle is not valid for a Steam Locomotive."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: A throttle is not valid for a Steam Locomotive."); break; } train.Handles.Power = ParseHandle(block, train); @@ -761,23 +747,25 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool objectFile = OpenBveApi.Path.CombineFile(Path.GetDirectoryName(fileName), block.ReadString()); if (!File.Exists(objectFile)) { - Plugin.currentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle object file " + objectFile + " was not found"); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle object file " + objectFile + " was not found"); break; } - for (int i = 0; i < Plugin.currentHost.Plugins.Length; i++) + for (int i = 0; i < Plugin.CurrentHost.Plugins.Length; i++) { - if (Plugin.currentHost.Plugins[i].Object != null && Plugin.currentHost.Plugins[i].Object.CanLoadObject(objectFile)) + if (Plugin.CurrentHost.Plugins[i].Object != null && Plugin.CurrentHost.Plugins[i].Object.CanLoadObject(objectFile)) { - Plugin.currentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject freightObject); + Plugin.CurrentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject freightObject); if (exteriorLoaded) { - car.CarSections[car.CarSections.Length - 1].AppendObject(Plugin.currentHost, car, freightObject); + CarSection exteriorCarSection = car.CarSections[CarSectionType.Exterior]; + exteriorCarSection.AppendObject(Plugin.CurrentHost, car, freightObject); + car.CarSections[CarSectionType.Exterior] = exteriorCarSection; } else { - car.LoadCarSections(freightObject, false); + car.CarSections.Add(CarSectionType.Exterior, new CarSection(Plugin.CurrentHost, ObjectType.Dynamic, false, car, freightObject)); } break; } diff --git a/source/TrainManager/Sounds/MSTS/SoundStream.cs b/source/TrainManager/Sounds/MSTS/SoundStream.cs index 249d27d2b3..d5a29433d0 100644 --- a/source/TrainManager/Sounds/MSTS/SoundStream.cs +++ b/source/TrainManager/Sounds/MSTS/SoundStream.cs @@ -5,10 +5,11 @@ namespace TrainManager.MsTsSounds { public class SoundStream { + /// The list of sound triggers within the sound stream public List Triggers; - + /// The volume curve for the sound stream public MsTsVolumeCurve VolumeCurve; - + /// The frequency curve for the sound stream public MsTsFrequencyCurve FrequencyCurve; /// The camera modes in which this sound stream is active public CameraViewMode ActivationCameraModes; diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs index 9c1313fb63..0e2db6290f 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs @@ -36,9 +36,7 @@ public class SpeedIncPast : SoundTrigger private readonly double speedValue; private readonly bool soundLoops; - - private double previousSpeed; - + public SpeedIncPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(car, buffers, selectionMethod) { this.speedValue = speedValue; @@ -54,7 +52,7 @@ public SpeedIncPast(CarBase car, SoundBuffer buffer, double speedValue, bool sou public override void Update(double timeElapsed, double pitchValue, double volumeValue) { double speed = Math.Abs(Car.CurrentSpeed); - if (speed >= speedValue && speed > previousSpeed) + if (speed >= speedValue) { if (Buffer != null) { @@ -64,13 +62,16 @@ public override void Update(double timeElapsed, double pitchValue, double volume } } Triggered = true; + Timer = 0; } else { - Stop(); + Timer += timeElapsed; + if (Timer > 1.0) + { + Stop(); + } } - - previousSpeed = speed; } } @@ -80,9 +81,7 @@ public class SpeedDecPast : SoundTrigger private readonly double speedValue; private readonly bool soundLoops; - - private double previousSpeed; - + public SpeedDecPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(car, buffers, selectionMethod) { this.speedValue = speedValue; @@ -98,7 +97,7 @@ public SpeedDecPast(CarBase car, SoundBuffer buffer, double speedValue, bool sou public override void Update(double timeElapsed, double pitchValue, double volumeValue) { double speed = Math.Abs(Car.CurrentSpeed); - if (speed <= speedValue && speed < previousSpeed) + if (speed <= speedValue) { if (Buffer != null) { @@ -108,13 +107,16 @@ public override void Update(double timeElapsed, double pitchValue, double volume } } Triggered = true; + Timer = 0; } else { - Stop(); + Timer += timeElapsed; + if (Timer > 1.0) + { + Stop(); + } } - - previousSpeed = speed; } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs index 6ebc6273b0..afbbc53594 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs @@ -30,13 +30,15 @@ namespace TrainManager.MsTsSounds { public abstract class SoundTrigger { + /// Holds the list of possible sound buffers to be selected from internal readonly SoundBuffer[] Buffers; - + /// The selection method used to determine the buffer to play private readonly KujuTokenID bufferSelectionMethod; - - + /// Timer used in derived updates + internal double Timer; + /// The previously selected buffer index private int bufferIndex; - + /// Gets the actual sound buffer to be played internal SoundBuffer Buffer { get @@ -57,11 +59,11 @@ internal SoundBuffer Buffer return Buffers[bufferIndex]; } } - + /// The sound source for this trigger internal SoundSource Source; - + /// Holds a reference to the parent car internal readonly CarBase Car; - + /// Whether this sound triger has already been triggered internal bool Triggered; internal SoundTrigger(CarBase car, SoundBuffer buffer) @@ -84,8 +86,12 @@ public virtual void Update(double timeElapsed, double pitchValue, double volumeV internal void Stop() { - Source?.Stop(); - Triggered = false; + if (Triggered) + { + Source?.Stop(); + Triggered = false; + } + } } } From 58b3aa91acb18a3bd4b2235b03df608d856f880b Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 21 Jul 2025 14:19:48 +0100 Subject: [PATCH 29/82] Brake system fix --- source/Plugins/Train.MsTs/Train/VehicleParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 8eb8ef926f..789ba6a774 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -363,7 +363,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool { // Combined air brakes and control signals // Assume equivilant to ElectromagneticStraightAirBrake - car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car); + car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); } else if (brakeSystemTypes.Contains(BrakeSystemType.ECP)) { From de36b843173e9ece1b20e4de575d024eb5c2d3a7 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 24 Jul 2025 13:57:26 +0100 Subject: [PATCH 30/82] Improve handling of dummy handles in ENG files --- source/Plugins/Train.MsTs/Handles/Handle.cs | 52 ++++++++++++------- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 2 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 4 +- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/source/Plugins/Train.MsTs/Handles/Handle.cs b/source/Plugins/Train.MsTs/Handles/Handle.cs index 4db9a055a4..9d73c5d036 100644 --- a/source/Plugins/Train.MsTs/Handles/Handle.cs +++ b/source/Plugins/Train.MsTs/Handles/Handle.cs @@ -17,7 +17,7 @@ internal partial class WagonParser internal Tuple[] NotchDescriptions; - internal AbstractHandle ParseHandle(Block block, TrainBase train) + internal AbstractHandle ParseHandle(Block block, TrainBase train, bool isPower) { HandleMinimum = (int)(block.ReadSingle() * 100); HandleMaximum = (int)(block.ReadSingle() * 100); @@ -25,34 +25,50 @@ internal AbstractHandle ParseHandle(Block block, TrainBase train) HandleStartingPosition = (int)(block.ReadSingle() * 100); // some handles seem to have extra numbers here, I believe ignored by the MSTS parser Block notchDescriptions = block.GetSubBlock(KujuTokenID.NumNotches); - ParseNotchDescriptionBlock(notchDescriptions); + ParseNotchDescriptionBlock(notchDescriptions, isPower); return new VariableHandle(train, NotchDescriptions); } - private void ParseNotchDescriptionBlock(Block block) + private void ParseNotchDescriptionBlock(Block block, bool isPower) { int numNotches = block.ReadInt32(); - NotchDescriptions = new Tuple[numNotches]; - for (int i = 0; i < numNotches; i++) + if (numNotches < 2) { - Block descriptionBlock = block.ReadSubBlock(KujuTokenID.Notch); - double notchPower = descriptionBlock.ReadSingle(); - descriptionBlock.ReadSingle(); // ?? - string notchDescription = descriptionBlock.ReadString(); - if (notchDescription.Equals("dummy", StringComparison.InvariantCultureIgnoreCase)) + /* + * Some AI ENG files seem to use a single dummy notch + * Interpret this as a single actual power notch + */ + NotchDescriptions = new[] { - if (i == 0) - { - notchDescription = "N"; - } - else + new Tuple(0, "N"), + new Tuple(1, isPower ? "P1" :"B1"), + }; + } + else + { + NotchDescriptions = new Tuple[numNotches]; + for (int i = 0; i < numNotches; i++) + { + Block descriptionBlock = block.ReadSubBlock(KujuTokenID.Notch); + double notchPower = descriptionBlock.ReadSingle(); + descriptionBlock.ReadSingle(); // ?? + string notchDescription = descriptionBlock.ReadString(); + if (notchDescription.Equals("dummy", StringComparison.InvariantCultureIgnoreCase)) { - notchDescription = "P" + i; + if (i == 0) + { + notchDescription = "N"; + } + else + { + notchDescription = isPower ? "P" + i : "B" + i; + } + } - + NotchDescriptions[i] = new Tuple(notchPower, notchDescription); } - NotchDescriptions[i] = new Tuple(notchPower, notchDescription); } + } } } diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 85559aac26..fb9e2826f3 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -518,7 +518,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Variable2_Inc_Past: case KujuTokenID.Variable2_Dec_Past: currentSoundSet.VariableValue = block.ReadSingle(); // power value - newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.Variable2Controlled: diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 789ba6a774..a4c1213169 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -676,10 +676,10 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: A throttle is not valid for a Steam Locomotive."); break; } - train.Handles.Power = ParseHandle(block, train); + train.Handles.Power = ParseHandle(block, train, true); break; case KujuTokenID.Brake_Train: - train.Handles.Brake = ParseHandle(block, train); + train.Handles.Brake = ParseHandle(block, train, false); break; case KujuTokenID.DieselEngineIdleRPM: dieselIdleRPM = block.ReadSingle(); From 949588d68a89455154ac8d62c6365dce2cd0cb61 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 24 Jul 2025 14:24:21 +0100 Subject: [PATCH 31/82] Update CVF parser units --- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 78897bc8c9..6dbbebd3a1 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -325,7 +325,7 @@ internal static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject Code = "brakecylinder 0.000145038 *"; break; case Units.Inches_Of_Mercury: - Code = "0"; + Code = "brakecylinder 0.0002953 *"; break; } break; @@ -336,7 +336,7 @@ internal static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject Code = "brakepipe 0.000145038 *"; break; case Units.Inches_Of_Mercury: - Code = "0"; + Code = "brakepipe 0.0002953 *"; break; } break; @@ -347,7 +347,7 @@ internal static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject Code = "mainreservoir 0.000145038 *"; break; case Units.Inches_Of_Mercury: - Code = "0"; + Code = "mainreservoir 0.0002953 *"; break; } break; From d3b54908636efad6e18923fbdbf287732617017f Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 31 Oct 2025 15:09:00 +0000 Subject: [PATCH 32/82] Load gearbox related properties Animate CVF gears lever --- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 51 +++---- .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 13 ++ source/Plugins/Train.MsTs/Panel/CvfParser.cs | 126 +++++++++--------- source/Plugins/Train.MsTs/Plugin.cs | 2 +- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 6 +- .../Plugins/Train.MsTs/Train/ConsistParser.cs | 40 +++--- .../Train.MsTs/Train/Enums/EngineTypes.cs | 1 + .../Plugins/Train.MsTs/Train/VehicleParser.cs | 121 +++++++++++++---- 8 files changed, 223 insertions(+), 137 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 8d034ed5a5..32f7b4fcaf 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -85,7 +85,7 @@ internal void Parse() } } - internal void Create(ref CarBase Car, int Layer) + internal void Create(ref CarBase currentCar, int componentLayer) { if (File.Exists(TexturePath) || Type == CabComponentType.Digital) { @@ -108,7 +108,7 @@ internal void Create(ref CarBase Car, int Layer) int wday, hday; int j; string f; - CultureInfo Culture = CultureInfo.InvariantCulture; + CultureInfo culture = CultureInfo.InvariantCulture; switch (Type) { case CabComponentType.Dial: @@ -125,17 +125,17 @@ internal void Create(ref CarBase Car, int Layer) Size.X *= rW; Size.Y *= rH; PivotPoint *= rH; - j = CabviewFileParser.CreateElement(ref Car.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2((0.5 * Size.X) / (tday.Width * rW), PivotPoint / (tday.Height * rH)), Layer * CabviewFileParser.StackDistance, PanelPosition, tday, null, new Color32(255, 255, 255, 255)); - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0); - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateXDirection = DirIncrease ? new Vector3(1.0, 0.0, 0.0) : new Vector3(-1.0, 0.0, 0.0); - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateYDirection = Vector3.Cross(Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZDirection, Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateXDirection); - f = CabviewFileParser.GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); + j = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2((0.5 * Size.X) / (tday.Width * rW), PivotPoint / (tday.Height * rH)), componentLayer * CabviewFileParser.StackDistance, PanelPosition, tday, null, new Color32(255, 255, 255, 255)); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateXDirection = DirIncrease ? new Vector3(1.0, 0.0, 0.0) : new Vector3(-1.0, 0.0, 0.0); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateYDirection = Vector3.Cross(currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZDirection, currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateXDirection); + f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); InitialAngle = InitialAngle.ToRadians(); LastAngle = LastAngle.ToRadians(); double a0 = (InitialAngle * Maximum - LastAngle * Minimum) / (Maximum - Minimum); double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum); - f += " " + a1.ToString(Culture) + " * " + a0.ToString(Culture) + " +"; - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZFunction = new FunctionScript(Plugin.CurrentHost, f, false); + f += " " + a1.ToString(culture) + " * " + a0.ToString(culture) + " +"; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZFunction = new FunctionScript(Plugin.CurrentHost, f, false); break; case CabComponentType.Lever: /* @@ -179,19 +179,20 @@ internal void Create(ref CarBase Car, int Layer) for (int k = 0; k < textures.Length; k++) { - int l = CabviewFileParser.CreateElement(ref Car.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), Layer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); if (k == 0) j = l; } - f = CabviewFileParser.GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); + f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); switch (panelSubject) { case PanelSubject.Throttle: case PanelSubject.Train_Brake: - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); + case PanelSubject.Gears: + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); break; default: - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); break; } @@ -230,20 +231,20 @@ internal void Create(ref CarBase Car, int Layer) j = -1; for (int k = 0; k < textures.Length; k++) { - int l = CabviewFileParser.CreateElement(ref Car.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), Layer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); if (k == 0) j = l; } - f = CabviewFileParser.GetStackLanguageFromSubject(Car.baseTrain, panelSubject, Units); + f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); switch (panelSubject) { case PanelSubject.Direction: case PanelSubject.Direction_Display: case PanelSubject.Overspeed: - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); break; default: - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); break; } @@ -283,30 +284,30 @@ internal void Create(ref CarBase Car, int Layer) { for (int k = 0; k < frameTextures.Length; k++) { - int l = CabviewFileParser.CreateElement(ref Car.CarSections[CarSectionType.Interior].Groups[0], new Vector2(Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y), new Vector2(digitWidth * rW, Size.Y * rH), new Vector2(0.5, 0.5), Layer * CabviewFileParser.StackDistance, PanelPosition, frameTextures[k], null, textColor, k != 0); + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], new Vector2(Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y), new Vector2(digitWidth * rW, Size.Y * rH), new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, frameTextures[k], null, textColor, k != 0); if (k == 0) j = l; } // build color arrays and mappings - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors = new Color24[NegativeColors.Length + PositiveColors.Length]; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors = new Color24[NegativeColors.Length + PositiveColors.Length]; FrameMappings = new FrameMapping[PositiveColors.Length + NegativeColors.Length]; for (int i = 0; i < NegativeColors.Length; i++) { FrameMappings[i].MappingValue = NegativeColors[i].Item1; FrameMappings[i].FrameKey = i; - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors[i] = NegativeColors[i].Item2; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors[i] = NegativeColors[i].Item2; } for (int i = 0; i < PositiveColors.Length; i++) { FrameMappings[i + NegativeColors.Length].MappingValue = PositiveColors[i].Item1; FrameMappings[i + NegativeColors.Length].FrameKey = i + NegativeColors.Length; - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors[i + NegativeColors.Length] = PositiveColors[i].Item2; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors[i + NegativeColors.Length] = PositiveColors[i].Item2; } // create color and digit functions - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, Units, currentDigit); - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].ColorFunction = new CvfAnimation(panelSubject, Units, FrameMappings); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, Units, currentDigit); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].ColorFunction = new CvfAnimation(panelSubject, Units, FrameMappings); } break; @@ -344,11 +345,11 @@ internal void Create(ref CarBase Car, int Layer) j = -1; for (int k = 0; k < textures.Length; k++) { - int l = CabviewFileParser.CreateElement(ref Car.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), Layer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); if (k == 0) j = l; } - Car.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject); } diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index a1b3607709..25f330d11a 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -202,6 +202,19 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, } lastResult = pantographState; break; + case PanelSubject.Gears: + int gearState = 0; + for (int k = 0; k < dynamicTrain.Cars.Length; k++) + { + if (dynamicTrain.Cars[k].TractionModel is DieselEngine dieselEngine && + dieselEngine.Components.TryGetTypedValue(EngineComponent.Gearbox, out Gearbox gearbox)) + { + gearState = gearbox.CurrentGear; + break; + } + } + lastResult = gearState; + break; } return lastResult; diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 6dbbebd3a1..916e93c0b8 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -55,7 +55,7 @@ class CabviewFileParser private static readonly List cabComponents = new List(); // parse panel config - internal static bool ParseCabViewFile(string fileName, ref CarBase Car) + internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) { CurrentFolder = Path.GetDirectoryName(fileName); Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read); @@ -155,42 +155,42 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) worldWidth = worldHeight * Plugin.Renderer.Screen.AspectRatio; } - double x0 = -PanelCenter.X / PanelResolution; - double x1 = (PanelSize.X - PanelCenter.X) / PanelResolution; - double y0 = (PanelCenter.Y - PanelSize.Y) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; - double y1 = (PanelCenter.Y) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; - Car.CameraRestriction.BottomLeft = new Vector3(x0 * worldWidth, y0 * worldHeight, eyeDistance); - Car.CameraRestriction.TopRight = new Vector3(x1 * worldWidth, y1 * worldHeight, eyeDistance); - Car.DriverYaw = Math.Atan((PanelCenter.X - PanelOrigin.X) * worldWidth / PanelResolution); - Car.DriverPitch = Math.Atan((PanelOrigin.Y - PanelCenter.Y) * worldWidth / PanelResolution); + double x0 = -panelCenter.X / panelResolution; + double x1 = (panelSize.X - panelCenter.X) / panelResolution; + double y0 = (panelCenter.Y - panelSize.Y) / panelResolution * Plugin.Renderer.Screen.AspectRatio; + double y1 = (panelCenter.Y) / panelResolution * Plugin.Renderer.Screen.AspectRatio; + currentCar.CameraRestriction.BottomLeft = new Vector3(x0 * worldWidth, y0 * worldHeight, eyeDistance); + currentCar.CameraRestriction.TopRight = new Vector3(x1 * worldWidth, y1 * worldHeight, eyeDistance); + currentCar.DriverYaw = Math.Atan((panelCenter.X - panelOrigin.X) * worldWidth / panelResolution); + currentCar.DriverPitch = Math.Atan((panelOrigin.Y - panelCenter.Y) * worldWidth / panelResolution); - if(CabViews.Count == 0 || !File.Exists(CabViews[0].FileName)) + if(cabViews.Count == 0 || !File.Exists(cabViews[0].FileName)) { return false; } - Car.CameraRestrictionMode = CameraRestrictionMode.On; + currentCar.CameraRestrictionMode = CameraRestrictionMode.On; Plugin.Renderer.Camera.CurrentRestriction = CameraRestrictionMode.On; - Car.Driver = CabViews[0].Position; - for (int i = 0; i < CabViews.Count; i++) + currentCar.Driver = cabViews[0].Position; + for (int i = 0; i < cabViews.Count; i++) { - Plugin.CurrentHost.RegisterTexture(CabViews[i].FileName, new TextureParameters(null, null), out Texture tday, true); + Plugin.CurrentHost.RegisterTexture(cabViews[i].FileName, new TextureParameters(null, null), out Texture tday, true); switch (i) { case 0: - Car.CarSections.Add(CarSectionType.Interior, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, Car)); - CreateElement(ref Car.CarSections[CarSectionType.Interior].Groups[0], Vector2.Null, PanelSize, new Vector2(0.5, 0.5), 0.0, CabViews[0].Position, tday, null, new Color32(255, 255, 255, 255)); - Car.CarSections[CarSectionType.Interior].ViewDirection = new Transformation(CabViews[0].Direction.Y.ToRadians(), -CabViews[0].Direction.X.ToRadians(), -CabViews[0].Direction.Z.ToRadians()); + currentCar.CarSections.Add(CarSectionType.Interior, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, currentCar)); + CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Vector2.Null, panelSize, new Vector2(0.5, 0.5), 0.0, cabViews[0].Position, tday, null, new Color32(255, 255, 255, 255)); + currentCar.CarSections[CarSectionType.Interior].ViewDirection = new Transformation(cabViews[0].Direction.Y.ToRadians(), -cabViews[0].Direction.X.ToRadians(), -cabViews[0].Direction.Z.ToRadians()); break; case 1: - Car.CarSections.Add(CarSectionType.HeadOutLeft, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, Car)); - CreateElement(ref Car.CarSections[CarSectionType.HeadOutLeft].Groups[0], Vector2.Null, PanelSize, new Vector2(0.5, 0.5), 0.0, CabViews[1].Position, tday, null, new Color32(255, 255, 255, 255)); - Car.CarSections[CarSectionType.HeadOutLeft].ViewDirection = new Transformation(CabViews[1].Direction.Y.ToRadians(), -CabViews[1].Direction.X.ToRadians(), -CabViews[1].Direction.Z.ToRadians()); + currentCar.CarSections.Add(CarSectionType.HeadOutLeft, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, currentCar)); + CreateElement(ref currentCar.CarSections[CarSectionType.HeadOutLeft].Groups[0], Vector2.Null, panelSize, new Vector2(0.5, 0.5), 0.0, cabViews[1].Position, tday, null, new Color32(255, 255, 255, 255)); + currentCar.CarSections[CarSectionType.HeadOutLeft].ViewDirection = new Transformation(cabViews[1].Direction.Y.ToRadians(), -cabViews[1].Direction.X.ToRadians(), -cabViews[1].Direction.Z.ToRadians()); break; case 2: - Car.CarSections.Add(CarSectionType.HeadOutRight, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, Car)); - CreateElement(ref Car.CarSections[CarSectionType.HeadOutRight].Groups[0], Vector2.Null, PanelSize, new Vector2(0.5, 0.5), 0.0, CabViews[2].Position, tday, null, new Color32(255, 255, 255, 255)); - Car.CarSections[CarSectionType.HeadOutRight].ViewDirection = new Transformation(CabViews[2].Direction.Y.ToRadians(), -CabViews[2].Direction.X.ToRadians(), -CabViews[2].Direction.Z.ToRadians()); + currentCar.CarSections.Add(CarSectionType.HeadOutRight, new CarSection(Plugin.CurrentHost, ObjectType.Overlay, true, currentCar)); + CreateElement(ref currentCar.CarSections[CarSectionType.HeadOutRight].Groups[0], Vector2.Null, panelSize, new Vector2(0.5, 0.5), 0.0, cabViews[2].Position, tday, null, new Color32(255, 255, 255, 255)); + currentCar.CarSections[CarSectionType.HeadOutRight].ViewDirection = new Transformation(cabViews[2].Direction.Y.ToRadians(), -cabViews[2].Direction.X.ToRadians(), -cabViews[2].Direction.Z.ToRadians()); break; } } @@ -198,7 +198,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) int currentLayer = 1; for (int i = 0; i < cabComponents.Count; i++) { - cabComponents[i].Create(ref Car, currentLayer); + cabComponents[i].Create(ref currentCar, currentLayer); } @@ -207,7 +207,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase Car) private static CabView currentCabView; - private static readonly List CabViews = new List(); + private static readonly List cabViews = new List(); private static void ParseBlock(Block block) { @@ -268,7 +268,7 @@ private static void ParseBlock(Block block) ParseBlock(newBlock); newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT Y, ROT X, ROT Z ParseBlock(newBlock); - CabViews.Add(currentCabView); + cabViews.Add(currentCabView); currentCabView = new CabView(); //View #2, normally L newBlock = block.ReadSubBlock(KujuTokenID.CabViewFile); @@ -281,7 +281,7 @@ private static void ParseBlock(Block block) ParseBlock(newBlock); newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT Y, ROT X, ROT Z ParseBlock(newBlock); - CabViews.Add(currentCabView); + cabViews.Add(currentCabView); currentCabView = new CabView(); //View #3, normally R newBlock = block.ReadSubBlock(KujuTokenID.CabViewFile); @@ -294,7 +294,7 @@ private static void ParseBlock(Block block) ParseBlock(newBlock); newBlock = block.ReadSubBlock(KujuTokenID.Direction); // ?? CAMERA DIRECTION ==> ROT Y, ROT X, ROT Z ParseBlock(newBlock); - CabViews.Add(currentCabView); + cabViews.Add(currentCabView); newBlock = block.ReadSubBlock(KujuTokenID.EngineData); ParseBlock(newBlock); newBlock = block.ReadSubBlock(KujuTokenID.CabViewControls); @@ -303,13 +303,13 @@ private static void ParseBlock(Block block) } } - const double PanelResolution = 1024.0; - private static readonly Vector2 PanelSize = new Vector2(1024, 768); - private static readonly Vector2 PanelCenter = new Vector2(0, 240); - private static readonly Vector2 PanelOrigin = new Vector2(0, 240); + private const double panelResolution = 1024.0; + private static readonly Vector2 panelSize = new Vector2(1024, 768); + private static readonly Vector2 panelCenter = new Vector2(0, 240); + private static readonly Vector2 panelOrigin = new Vector2(0, 240); // get stack language from subject - internal static string GetStackLanguageFromSubject(TrainBase Train, PanelSubject subject, Units subjectUnits, string suffix = "") + internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject subject, Units subjectUnits, string suffix = "") { // transform subject string Code = string.Empty; @@ -415,33 +415,33 @@ internal static int CreateElement(ref ElementsGroup Group, Vector2 TopLeft, Vect Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Attempted to create an invalid size element"); } - double WorldWidth, WorldHeight; + double worldWidth, worldHeight; if (Plugin.Renderer.Screen.Width >= Plugin.Renderer.Screen.Height) { - WorldWidth = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.HorizontalViewingAngle) * eyeDistance; - WorldHeight = WorldWidth / Plugin.Renderer.Screen.AspectRatio; + worldWidth = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.HorizontalViewingAngle) * eyeDistance; + worldHeight = worldWidth / Plugin.Renderer.Screen.AspectRatio; } else { - WorldHeight = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.VerticalViewingAngle) * eyeDistance / Plugin.Renderer.Screen.AspectRatio; - WorldWidth = WorldHeight * Plugin.Renderer.Screen.AspectRatio; + worldHeight = 2.0 * Math.Tan(0.5 * Plugin.Renderer.Camera.VerticalViewingAngle) * eyeDistance / Plugin.Renderer.Screen.AspectRatio; + worldWidth = worldHeight * Plugin.Renderer.Screen.AspectRatio; } - double x0 = TopLeft.X / PanelResolution; - double x1 = (TopLeft.X + Size.X) / PanelResolution; - double y0 = (PanelSize.Y - TopLeft.Y) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; - double y1 = (PanelSize.Y - (TopLeft.Y + Size.Y)) / PanelResolution * Plugin.Renderer.Screen.AspectRatio; - double xd = 0.5 - PanelCenter.X / PanelResolution; + double x0 = TopLeft.X / panelResolution; + double x1 = (TopLeft.X + Size.X) / panelResolution; + double y0 = (panelSize.Y - TopLeft.Y) / panelResolution * Plugin.Renderer.Screen.AspectRatio; + double y1 = (panelSize.Y - (TopLeft.Y + Size.Y)) / panelResolution * Plugin.Renderer.Screen.AspectRatio; + double xd = 0.5 - panelCenter.X / panelResolution; x0 += xd; x1 += xd; - double yt = PanelSize.Y - PanelResolution / Plugin.Renderer.Screen.AspectRatio; - double yd = (PanelCenter.Y - yt) / (PanelSize.Y - yt) - 0.5; + double yt = panelSize.Y - panelResolution / Plugin.Renderer.Screen.AspectRatio; + double yd = (panelCenter.Y - yt) / (panelSize.Y - yt) - 0.5; y0 += yd; y1 += yd; - x0 = (x0 - 0.5) * WorldWidth; - x1 = (x1 - 0.5) * WorldWidth; - y0 = (y0 - 0.5) * WorldHeight; - y1 = (y1 - 0.5) * WorldHeight; + x0 = (x0 - 0.5) * worldWidth; + x1 = (x1 - 0.5) * worldWidth; + y0 = (y0 - 0.5) * worldHeight; + y1 = (y1 - 0.5) * worldHeight; double xm = x0 * (1.0 - RelativeRotationCenter.X) + x1 * RelativeRotationCenter.X; double ym = y0 * (1.0 - RelativeRotationCenter.Y) + y1 * RelativeRotationCenter.Y; Vector3[] v = new Vector3[4]; @@ -453,21 +453,21 @@ internal static int CreateElement(ref ElementsGroup Group, Vector2 TopLeft, Vect Vertex t1 = new Vertex(v[1], new Vector2(0.0f, 0.0f)); Vertex t2 = new Vertex(v[2], new Vector2(1.0f, 0.0f)); Vertex t3 = new Vertex(v[3], new Vector2(1.0f, 1.0f)); - StaticObject Object = new StaticObject(Plugin.CurrentHost); - Object.Mesh.Vertices = new VertexTemplate[] {t0, t1, t2, t3}; - Object.Mesh.Faces = new[] {new MeshFace(new[] {0, 1, 2, 0, 2, 3}, FaceFlags.Triangles)}; //Must create as a single face like this to avoid Z-sort issues with overlapping bits - Object.Mesh.Materials = new MeshMaterial[1]; - Object.Mesh.Materials[0].Flags = new MaterialFlags(); + StaticObject staticObject = new StaticObject(Plugin.CurrentHost); + staticObject.Mesh.Vertices = new VertexTemplate[] {t0, t1, t2, t3}; + staticObject.Mesh.Faces = new[] {new MeshFace(new[] {0, 1, 2, 0, 2, 3}, FaceFlags.Triangles)}; //Must create as a single face like this to avoid Z-sort issues with overlapping bits + staticObject.Mesh.Materials = new MeshMaterial[1]; + staticObject.Mesh.Materials[0].Flags = new MaterialFlags(); if (DaytimeTexture != null) { - Object.Mesh.Materials[0].Flags |= MaterialFlags.TransparentColor; + staticObject.Mesh.Materials[0].Flags |= MaterialFlags.TransparentColor; } - Object.Mesh.Materials[0].Color = Color; - Object.Mesh.Materials[0].TransparentColor = Color24.Blue; - Object.Mesh.Materials[0].DaytimeTexture = DaytimeTexture; - Object.Mesh.Materials[0].NighttimeTexture = NighttimeTexture; - Object.Dynamic = true; + staticObject.Mesh.Materials[0].Color = Color; + staticObject.Mesh.Materials[0].TransparentColor = Color24.Blue; + staticObject.Mesh.Materials[0].DaytimeTexture = DaytimeTexture; + staticObject.Mesh.Materials[0].NighttimeTexture = NighttimeTexture; + staticObject.Dynamic = true; // calculate offset Vector3 o; o.X = xm + Driver.X; @@ -482,7 +482,7 @@ internal static int CreateElement(ref ElementsGroup Group, Vector2 TopLeft, Vect Group.Elements[n].States[j] = new ObjectState { Translation = Matrix4D.CreateTranslation(o.X, o.Y, -o.Z), - Prototype = Object + Prototype = staticObject }; return n; } @@ -493,9 +493,9 @@ internal static int CreateElement(ref ElementsGroup Group, Vector2 TopLeft, Vect Group.Elements[n] = new AnimatedObject(Plugin.CurrentHost); Group.Elements[n].States = new[] {new ObjectState()}; Group.Elements[n].States[0].Translation = Matrix4D.CreateTranslation(o.X, o.Y, -o.Z); - Group.Elements[n].States[0].Prototype = Object; + Group.Elements[n].States[0].Prototype = staticObject; Group.Elements[n].CurrentState = 0; - Group.Elements[n].internalObject = new ObjectState {Prototype = Object}; + Group.Elements[n].internalObject = new ObjectState {Prototype = staticObject}; Plugin.CurrentHost.CreateDynamicObject(ref Group.Elements[n].internalObject); return n; } diff --git a/source/Plugins/Train.MsTs/Plugin.cs b/source/Plugins/Train.MsTs/Plugin.cs index 8866132c80..ad1c6a9e3d 100644 --- a/source/Plugins/Train.MsTs/Plugin.cs +++ b/source/Plugins/Train.MsTs/Plugin.cs @@ -25,7 +25,7 @@ public class Plugin : TrainInterface public Plugin() { ConsistParser = new ConsistParser(this); - WagonParser = new WagonParser(this); + WagonParser = new WagonParser(); } public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions options, object rendererReference) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index fb9e2826f3..0afb8a1308 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -49,7 +49,7 @@ class SoundModelSystemParser private static Tuple[] curvePoints; - internal static bool ParseSoundFile(string fileName, ref CarBase Car) + internal static bool ParseSoundFile(string fileName, ref CarBase currentCar) { currentFile = fileName; currentFolder = System.IO.Path.GetDirectoryName(fileName); @@ -111,7 +111,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase Car) byte[] newBytes = reader.ReadBytes((int)(fb.Length - fb.Position)); string s = unicode ? Encoding.Unicode.GetString(newBytes) : Encoding.ASCII.GetString(newBytes); TextualBlock block = new TextualBlock(s, KujuTokenID.Tr_SMS); - ParseBlock(block, ref soundSet, ref soundStream, ref Car); + ParseBlock(block, ref soundSet, ref soundStream, ref currentCar); } } @@ -133,7 +133,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase Car) uint remainingBytes = reader.ReadUInt32(); byte[] newBytes = reader.ReadBytes((int)remainingBytes); BinaryBlock block = new BinaryBlock(newBytes, KujuTokenID.Tr_SMS); - ParseBlock(block, ref soundSet, ref soundStream, ref Car); + ParseBlock(block, ref soundSet, ref soundStream, ref currentCar); } } diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index 610608495b..a17dcff3ed 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -28,10 +28,14 @@ internal ConsistParser(Plugin plugin) Plugin = plugin; } - internal void ReadConsist(string fileName, ref AbstractTrain Train) + internal void ReadConsist(string fileName, ref AbstractTrain parsedTrain) { currentCarIndex = -1; - TrainBase train = Train as TrainBase; + TrainBase train = parsedTrain as TrainBase; + if (train == null) + { + throw new Exception(); + } train.Handles.Reverser = new ReverserHandle(train); train.Handles.EmergencyBrake = new EmergencyHandle(train); train.Handles.Power = new PowerHandle(8, 8, new double[] { }, new double[] { }, train); @@ -163,21 +167,21 @@ internal void ReadConsist(string fileName, ref AbstractTrain Train) train.Cars[train.Cars.Length - 1].RearAxle.Follower.TriggerType = EventTriggerType.RearCarRearAxle; train.Cars[train.DriverCar].Windscreen = new Windscreen(256, 10.0, train.Cars[train.DriverCar]); - train.Cars[train.DriverCar].Windscreen.Wipers = new WindscreenWiper(train.Cars[Train.DriverCar].Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 0.0, true); // hack: zero hold time so they act as fast with two states + train.Cars[train.DriverCar].Windscreen.Wipers = new WindscreenWiper(train.Cars[parsedTrain.DriverCar].Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 0.0, true); // hack: zero hold time so they act as fast with two states train.PlaceCars(0.0); } private int currentCarIndex = -1; private CarBase currentCar; private bool reverseCurentCar; - private void ParseBlock(Block block, ref TrainBase Train) + private void ParseBlock(Block block, ref TrainBase currentTrain) { Block newBlock; switch (block.Token) { default: newBlock = block.ReadSubBlock(); - ParseBlock(newBlock, ref Train); + ParseBlock(newBlock, ref currentTrain); break; case KujuTokenID.Default: // presumably used internally by MSTS, not useful @@ -193,7 +197,7 @@ private void ParseBlock(Block block, ref TrainBase Train) try { newBlock = block.ReadSubBlock(); - ParseBlock(newBlock, ref Train); + ParseBlock(newBlock, ref currentTrain); } catch { @@ -222,12 +226,12 @@ private void ParseBlock(Block block, ref TrainBase Train) break; case KujuTokenID.Engine: case KujuTokenID.Wagon: - Array.Resize(ref Train.Cars, Train.Cars.Length + 1); + Array.Resize(ref currentTrain.Cars, currentTrain.Cars.Length + 1); currentCarIndex++; while (block.Length() - block.Position() > 2) { newBlock = block.ReadSubBlock(new[] { KujuTokenID.EngineData, KujuTokenID.WagonData, KujuTokenID.UiD, KujuTokenID.Flip, KujuTokenID.EngineVariables }); - ParseBlock(newBlock, ref Train); + ParseBlock(newBlock, ref currentTrain); } currentCar.Doors = new[] { @@ -241,15 +245,15 @@ private void ParseBlock(Block block, ref TrainBase Train) } currentCar.Breaker = new Breaker(currentCar); currentCar.Sounds.Plugin = new Dictionary(); - Train.Cars[currentCarIndex] = currentCar; + currentTrain.Cars[currentCarIndex] = currentCar; /* * FIXME: Needs removing or sorting when the car is created */ - Train.Cars[currentCarIndex].FrontAxle.Follower.TriggerType = currentCarIndex == 0 ? EventTriggerType.FrontCarFrontAxle : EventTriggerType.OtherCarFrontAxle; - Train.Cars[currentCarIndex].BeaconReceiver.TriggerType = currentCarIndex == 0 ? EventTriggerType.TrainFront : EventTriggerType.None; - Train.Cars[currentCarIndex].BeaconReceiverPosition = 0.5 * Train.Cars[currentCarIndex].Length; - Train.Cars[currentCarIndex].FrontAxle.Position = 0.4 * Train.Cars[currentCarIndex].Length; - Train.Cars[currentCarIndex].RearAxle.Position = -0.4 * Train.Cars[currentCarIndex].Length; + currentTrain.Cars[currentCarIndex].FrontAxle.Follower.TriggerType = currentCarIndex == 0 ? EventTriggerType.FrontCarFrontAxle : EventTriggerType.OtherCarFrontAxle; + currentTrain.Cars[currentCarIndex].BeaconReceiver.TriggerType = currentCarIndex == 0 ? EventTriggerType.TrainFront : EventTriggerType.None; + currentTrain.Cars[currentCarIndex].BeaconReceiverPosition = 0.5 * currentTrain.Cars[currentCarIndex].Length; + currentTrain.Cars[currentCarIndex].FrontAxle.Position = 0.4 * currentTrain.Cars[currentCarIndex].Length; + currentTrain.Cars[currentCarIndex].RearAxle.Position = -0.4 * currentTrain.Cars[currentCarIndex].Length; break; // Engine / wagon block case KujuTokenID.UiD: @@ -261,7 +265,7 @@ private void ParseBlock(Block block, ref TrainBase Train) /* * FIXME: All this needs to be pulled from the eng properties, or fixed so it doesn't matter */ - currentCar = new CarBase(Train, currentCarIndex, 0.35, 0.0025, 1.1); + currentCar = new CarBase(currentTrain, currentCarIndex, 0.35, 0.0025, 1.1); currentCar.HoldBrake = new CarHoldBrake(currentCar); //FIXME END @@ -293,14 +297,14 @@ private void ParseBlock(Block block, ref TrainBase Train) break; case 1: //Just a WagonName- This is likely invalid, but let's ignore - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: No WagonFolder supplied, searching entire trainset folder."); break; case 2: - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset\\" + wagonFiles[1]), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset\\" + wagonFiles[1]), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); break; default: - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[1], block.Token == KujuTokenID.EngineData, ref currentCar, ref Train); + Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[1], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: Two parameters were expected- Check for correct escaping of strings."); break; } diff --git a/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs b/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs index aa055975cf..c69227a764 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs @@ -4,6 +4,7 @@ internal enum EngineType { NoEngine = 0, Diesel, + DieselHydraulic, Steam, Electric } diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index a4c1213169..000e0963e5 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -49,8 +49,6 @@ namespace Train.MsTs { internal partial class WagonParser { - private readonly Plugin plugin; - private readonly Dictionary wagonCache; private readonly Dictionary engineCache; private readonly List soundFiles; @@ -58,15 +56,14 @@ internal partial class WagonParser private double wheelRadius; private bool exteriorLoaded = false; - internal WagonParser(Plugin Plugin) + internal WagonParser() { - plugin = Plugin; wagonCache = new Dictionary(); engineCache = new Dictionary(); soundFiles = new List(); } - internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, ref CarBase Car, ref TrainBase train) + internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, ref CarBase currentCar, ref TrainBase train) { exteriorLoaded = false; wagonFiles = Directory.GetFiles(trainSetDirectory, isEngine ? "*.eng" : "*.wag", SearchOption.AllDirectories); @@ -87,13 +84,13 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r { if (engineCache.ContainsKey(wagonName)) { - ReadWagonData(engineCache[wagonName], ref wagonName, true, ref Car, ref train); + ReadWagonData(engineCache[wagonName], ref wagonName, true, ref currentCar, ref train); } else { for (int i = 0; i < wagonFiles.Length; i++) { - if (ReadWagonData(wagonFiles[i], ref wagonName, true, ref Car, ref train)) + if (ReadWagonData(wagonFiles[i], ref wagonName, true, ref currentCar, ref train)) { break; } @@ -102,7 +99,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r } else { - Car.TractionModel = new BVETrailerCar(Car); + currentCar.TractionModel = new BVETrailerCar(currentCar); } /* * We've now found the engine properties- @@ -111,13 +108,13 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r */ if (wagonCache.ContainsKey(wagonName)) { - ReadWagonData(wagonCache[wagonName], ref wagonName, false, ref Car, ref train); + ReadWagonData(wagonCache[wagonName], ref wagonName, false, ref currentCar, ref train); } else { for (int i = 0; i < wagonFiles.Length; i++) { - if (ReadWagonData(wagonFiles[i], ref wagonName, false, ref Car, ref train)) + if (ReadWagonData(wagonFiles[i], ref wagonName, false, ref currentCar, ref train)) { break; } @@ -128,42 +125,54 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r if (isEngine) { // FIXME: Default BVE values - Car.Specs.JerkPowerUp = 10.0; - Car.Specs.JerkPowerDown = 10.0; + currentCar.Specs.JerkPowerUp = 10.0; + currentCar.Specs.JerkPowerDown = 10.0; switch (currentEngineType) { case EngineType.Diesel: - Car.TractionModel = new DieselEngine(Car, new AccelerationCurve[] { new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) }, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); - Car.TractionModel.FuelTank = new FuelTank(dieselCapacity, 0, dieselCapacity); - Car.TractionModel.IsRunning = true; + currentCar.TractionModel = new DieselEngine(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); + currentCar.TractionModel.FuelTank = new FuelTank(dieselCapacity, 0, dieselCapacity); + currentCar.TractionModel.IsRunning = true; if (maxBrakeAmps > 0 && maxEngineAmps > 0) { - Car.TractionModel.Components.Add(EngineComponent.RegenerativeTractionMotor, new RegenerativeTractionMotor(Car.TractionModel, maxEngineAmps, maxBrakeAmps)); + currentCar.TractionModel.Components.Add(EngineComponent.RegenerativeTractionMotor, new RegenerativeTractionMotor(currentCar.TractionModel, maxEngineAmps, maxBrakeAmps)); } else if (maxEngineAmps > 0) { - Car.TractionModel.Components.Add(EngineComponent.TractionMotor, new TractionMotor(Car.TractionModel, maxEngineAmps)); + currentCar.TractionModel.Components.Add(EngineComponent.TractionMotor, new TractionMotor(currentCar.TractionModel, maxEngineAmps)); } break; + case EngineType.DieselHydraulic: + AccelerationCurve[] accelerationCurves = new AccelerationCurve[Gears.Length]; + for (int i = 0; i < Gears.Length; i++) + { + accelerationCurves[i] = new MSTSAccelerationCurve(currentCar, Gears[i].MaxTractiveForce, maxContinuousForce, Gears[i].MaximumSpeed); + } + + currentCar.TractionModel = new DieselEngine(currentCar, accelerationCurves, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); + currentCar.TractionModel.FuelTank = new FuelTank(dieselCapacity, 0, dieselCapacity); + currentCar.TractionModel.IsRunning = true; + currentCar.TractionModel.Components.Add(EngineComponent.Gearbox, new Gearbox(currentCar.TractionModel, Gears)); + break; case EngineType.Electric: - Car.TractionModel = new ElectricEngine(Car, new AccelerationCurve[] { new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) }); - Car.TractionModel.Components.Add(EngineComponent.Pantograph, new Pantograph(Car.TractionModel)); + currentCar.TractionModel = new ElectricEngine(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }); + currentCar.TractionModel.Components.Add(EngineComponent.Pantograph, new Pantograph(currentCar.TractionModel)); break; case EngineType.Steam: // NOT YET IMPLEMENTED - Car.TractionModel = new BVEMotorCar(Car, new AccelerationCurve[] { new MSTSAccelerationCurve(Car, maxForce, maxContinuousForce, maxVelocity) }); + currentCar.TractionModel = new BVEMotorCar(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }); break; } - Car.ReAdhesionDevice = new BveReAdhesionDevice(Car, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); - Car.Windscreen = new Windscreen(0, 0, Car); - Car.Windscreen.Wipers = new WindscreenWiper(Car.Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 1.0); + currentCar.ReAdhesionDevice = new BveReAdhesionDevice(currentCar, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); + currentCar.Windscreen = new Windscreen(0, 0, currentCar); + currentCar.Windscreen.Wipers = new WindscreenWiper(currentCar.Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 1.0); if (Exhaust.Size > 0) { - Exhaust.Offset.Z -= 0.5 * Car.Length; - Car.ParticleSources.Add(new ParticleSource(Plugin.Renderer, Car, Exhaust.Offset, Exhaust.Size, Exhaust.SmokeMaxMagnitude, Exhaust.Direction)); + Exhaust.Offset.Z -= 0.5 * currentCar.Length; + currentCar.ParticleSources.Add(new ParticleSource(Plugin.Renderer, currentCar, Exhaust.Offset, Exhaust.Size, Exhaust.SmokeMaxMagnitude, Exhaust.Direction)); } } @@ -171,7 +180,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r { for (int i = 0; i < soundFiles.Count; i++) { - SoundModelSystemParser.ParseSoundFile(soundFiles[i], ref Car); + SoundModelSystemParser.ParseSoundFile(soundFiles[i], ref currentCar); } soundFiles.Clear(); } @@ -308,6 +317,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private bool hasAntiSlipDevice; private List vigilanceDevices; private Exhaust Exhaust; + private Gear[] Gears; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { @@ -486,9 +496,25 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } catch { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vechicle Parser: Invalid vehicle type specified."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Invalid vehicle type specified."); + } + } + break; + case KujuTokenID.DieselEngineType: + if (currentEngineType == EngineType.Diesel) + { + string type = block.ReadString(); + switch (type.ToLowerInvariant()) + { + case "hydraulic": + currentEngineType = EngineType.DieselHydraulic; + break; } } + else + { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Invalid vehicle type specified."); + } break; case KujuTokenID.WagonShape: if(Plugin.PreviewOnly) @@ -799,6 +825,47 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.DieselSmokeEffectMaxSmokeRate: Exhaust.SmokeMaxRate = block.ReadSingle(); break; + case KujuTokenID.GearBoxNumberOfGears: + int numGears = block.ReadInt16(); + Gears = new Gear[numGears]; + break; + case KujuTokenID.GearBoxMaxSpeedForGears: + if (Gears == null) + { + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Number of gears must be specified."); + break; + } + + for (int i = 0; i < Gears.Length; i++) + { + Gears[i].MaximumSpeed = block.ReadSingle(UnitOfVelocity.MetersPerSecond, UnitOfVelocity.MilesPerHour); + } + break; + case KujuTokenID.GearBoxMaxTractiveForceForGears: + if (Gears == null) + { + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Number of gears must be specified."); + break; + } + + for (int i = 0; i < Gears.Length; i++) + { + Gears[i].MaxTractiveForce = block.ReadSingle(UnitOfForce.Newton); + } + break; + case KujuTokenID.GearBoxOverspeedPercentageForFailure: + if (Gears == null) + { + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Number of gears must be specified."); + break; + } + + double perc = block.ReadSingle() / 100; + for (int i = 0; i < Gears.Length; i++) + { + Gears[i].OverspeedFailure = Gears[i].MaximumSpeed * perc; + } + break; } return true; } From 2f5108e659f53ac09b7ad6245e682127dd20e63c Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 1 Aug 2025 10:35:37 +0100 Subject: [PATCH 33/82] More sound trigger implementation Rework sound triggers --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 32 +++++---- .../Plugins/Train.MsTs/Train/ConsistParser.cs | 24 +++++++ .../TrainManager/Sounds/MSTS/SoundStream.cs | 69 +++++++++++++++++-- .../Sounds/MSTS/SoundTrigger.Initial.cs | 54 +++++++++++++++ .../Sounds/MSTS/SoundTrigger.Speed.cs | 58 ++++------------ .../MSTS/SoundTrigger.VariableControlled.cs | 65 ++++------------- .../TrainManager/Sounds/MSTS/SoundTrigger.cs | 40 ++++------- source/TrainManager/TrainManager.csproj | 1 + 8 files changed, 198 insertions(+), 145 deletions(-) create mode 100644 source/TrainManager/Sounds/MSTS/SoundTrigger.Initial.cs diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 0afb8a1308..c52abbf865 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -102,7 +102,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase currentCar) subHeader = Encoding.ASCII.GetString(buffer, 0, 8); } SoundSet soundSet = new SoundSet(); - SoundStream soundStream = new SoundStream(); + SoundStream soundStream = new SoundStream(currentCar); if (subHeader[7] == 't') { @@ -154,24 +154,27 @@ internal struct SoundSet internal double VariableValue; internal SoundBuffer[] SoundBuffers; internal int CurrentBuffer; + internal KujuTokenID SelectionMethod; - internal void Create(CarBase car, SoundStream currentSoundStream, KujuTokenID selectionMethod) + internal void Create(CarBase car, SoundStream currentSoundStream) { switch (VariableTriggerType) { + case KujuTokenID.Initial_Trigger: + currentSoundStream.Triggers.Add(new InitialTrigger(SoundBuffers, SelectionMethod, CurrentSoundType != KujuTokenID.PlayOneShot)); + break; case KujuTokenID.Speed_Inc_Past: - currentSoundStream.Triggers.Add(new SpeedIncPast(car, SoundBuffers, selectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); + currentSoundStream.Triggers.Add(new SpeedIncPast(SoundBuffers, SelectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; case KujuTokenID.Speed_Dec_Past: - currentSoundStream.Triggers.Add(new SpeedDecPast(car, SoundBuffers, selectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); + currentSoundStream.Triggers.Add(new SpeedDecPast(SoundBuffers, SelectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; case KujuTokenID.Variable2_Inc_Past: - currentSoundStream.Triggers.Add(new Variable2IncPast(car, SoundBuffers, selectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); + currentSoundStream.Triggers.Add(new Variable2IncPast(SoundBuffers, SelectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; case KujuTokenID.Variable2_Dec_Past: - currentSoundStream.Triggers.Add(new Variable2DecPast(car, SoundBuffers, selectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); + currentSoundStream.Triggers.Add(new Variable2DecPast(SoundBuffers, SelectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; - } } } @@ -290,7 +293,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So if (currentSoundStream.Triggers.Count > 0) { car.Sounds.ControlledSounds.Add(currentSoundStream); - currentSoundStream = new SoundStream(); + currentSoundStream = new SoundStream(car); } break; case KujuTokenID.Priority: @@ -337,13 +340,14 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So if (numSounds > 1 && block.Position() < block.Length() - 4) { Block subBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); - selectionMethod = subBlock.ReadEnumValue(default(KujuTokenID)); + currentSoundSet.SelectionMethod = subBlock.ReadEnumValue(default(KujuTokenID)); } - currentSoundSet.Create(car, currentSoundStream, selectionMethod); + currentSoundSet.Create(car, currentSoundStream); break; case KujuTokenID.ReleaseLoopRelease: // empty block expected - // appear to be paired with StartLoopRelease + // paired with StartLoopRelease + currentSoundSet.Create(car, currentSoundStream); break; case KujuTokenID.File: if (block.ReadPath(currentFolder, out string soundFile)) @@ -541,14 +545,14 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } - selectionMethod = KujuTokenID.SequentialSelection; + currentSoundSet.SelectionMethod = KujuTokenID.SequentialSelection; // Attempt to read selection method if at least one sound file, and some data remaining in the block if (numSounds > 1 && block.Position() < block.Length() - 4) { Block subBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); - selectionMethod = subBlock.ReadEnumValue(default(KujuTokenID)); + currentSoundSet.SelectionMethod = subBlock.ReadEnumValue(default(KujuTokenID)); } - currentSoundSet.Create(car, currentSoundStream, selectionMethod); + currentSoundSet.Create(car, currentSoundStream); break; case KujuTokenID.Volume: double volume = block.ReadSingle(); diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index a17dcff3ed..c821f8140c 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -1,3 +1,27 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + using System; using System.Collections.Generic; using System.IO; diff --git a/source/TrainManager/Sounds/MSTS/SoundStream.cs b/source/TrainManager/Sounds/MSTS/SoundStream.cs index d5a29433d0..2e43e1565e 100644 --- a/source/TrainManager/Sounds/MSTS/SoundStream.cs +++ b/source/TrainManager/Sounds/MSTS/SoundStream.cs @@ -1,5 +1,32 @@ -using OpenBveApi.Runtime; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using OpenBveApi.Runtime; using System.Collections.Generic; +using OpenBveApi.Math; +using SoundManager; +using TrainManager.Car; namespace TrainManager.MsTsSounds { @@ -16,11 +43,16 @@ public class SoundStream /// The modes in which this sound stream is not active public CameraViewMode DeactivationCameraModes; - public SoundStream() + private SoundSource soundSource; + + private CarBase car; + + public SoundStream(CarBase baseCar) { Triggers = new List(); ActivationCameraModes = CameraViewMode.NotDefined; DeactivationCameraModes = CameraViewMode.NotDefined; + car = baseCar; } public void Update(double timeElapsed) @@ -50,18 +82,43 @@ public void Update(double timeElapsed) } } - + /* + * To get the playing buffer and loop state, we itinerate through all sound triggers in a stream + * in order. If the conditions for a trigger are met, the buffer ref and loop status are updated + * + * At the end of our loop, if the buffer is not null, either adjust the pitch / gain (if currently + * playing) or replace the playing buffer with it + * Otherwise, stop the playing buffer. + */ + SoundBuffer soundBuffer = null; + bool loops = false; + for (int i = 0; i < Triggers.Count; i++) { if (canActivate) { - Triggers[i].Update(timeElapsed, pitch, volume); + Triggers[i].Update(timeElapsed, car, ref soundBuffer, ref loops); + } + } + + if (soundBuffer != null) + { + if (soundSource != null && soundSource.Buffer == soundBuffer) + { + soundSource.Volume = volume; + soundSource.Pitch = pitch; } else { - Triggers[i].Stop(); + soundSource = (SoundSource)TrainManagerBase.currentHost.PlaySound(soundBuffer, pitch, volume, Vector3.Zero, car, loops); + } + } + else + { + if (soundSource != null && soundSource.IsPlaying()) + { + soundSource.Stop(); } - } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Initial.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Initial.cs new file mode 100644 index 0000000000..b203acd185 --- /dev/null +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Initial.cs @@ -0,0 +1,54 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using OpenBve.Formats.MsTs; +using SoundManager; +using TrainManager.Car; + +namespace TrainManager.MsTsSounds +{ + /// A trigger which is activated when the car is introduced + public class InitialTrigger: SoundTrigger + { + public InitialTrigger(SoundBuffer buffer, bool soundLoops) : base(buffer, soundLoops) + { + } + + public InitialTrigger(SoundBuffer[] buffers, KujuTokenID selectionMethod, bool soundLoops) : base(buffers, selectionMethod, soundLoops) + { + } + + private bool triggered; + + public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) + { + if (triggered == false) + { + soundBuffer = Buffer; + soundLoops = SoundLoops; + triggered = true; + } + } + } +} diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs index 0e2db6290f..9450cf49bd 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs @@ -24,7 +24,6 @@ using System; using OpenBve.Formats.MsTs; -using OpenBveApi.Math; using SoundManager; using TrainManager.Car; @@ -35,42 +34,24 @@ public class SpeedIncPast : SoundTrigger { private readonly double speedValue; - private readonly bool soundLoops; - public SpeedIncPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(car, buffers, selectionMethod) + public SpeedIncPast(SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(buffers, selectionMethod, soundLoops) { this.speedValue = speedValue; - this.soundLoops = soundLoops; } - public SpeedIncPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) + public SpeedIncPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(buffer, soundLoops) { this.speedValue = speedValue; - this.soundLoops = soundLoops; } - public override void Update(double timeElapsed, double pitchValue, double volumeValue) + public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { - double speed = Math.Abs(Car.CurrentSpeed); + double speed = Math.Abs(car.CurrentSpeed); if (speed >= speedValue) { - if (Buffer != null) - { - if (Triggered == false) - { - this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; - } - } - Triggered = true; - Timer = 0; - } - else - { - Timer += timeElapsed; - if (Timer > 1.0) - { - Stop(); - } + soundBuffer = Buffer; + soundLoops = SoundLoops; } } } @@ -82,40 +63,25 @@ public class SpeedDecPast : SoundTrigger private readonly bool soundLoops; - public SpeedDecPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(car, buffers, selectionMethod) + public SpeedDecPast(SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(buffers, selectionMethod, soundLoops) { this.speedValue = speedValue; this.soundLoops = soundLoops; } - public SpeedDecPast(CarBase car, SoundBuffer buffer, double speedValue, bool soundLoops) : base(car, buffer) + public SpeedDecPast(SoundBuffer buffer, double speedValue, bool soundLoops) : base(buffer, soundLoops) { this.speedValue = speedValue; this.soundLoops = soundLoops; } - public override void Update(double timeElapsed, double pitchValue, double volumeValue) + public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { - double speed = Math.Abs(Car.CurrentSpeed); + double speed = Math.Abs(car.CurrentSpeed); if (speed <= speedValue) { - if (Buffer != null) - { - if (Triggered == false) - { - this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; - } - } - Triggered = true; - Timer = 0; - } - else - { - Timer += timeElapsed; - if (Timer > 1.0) - { - Stop(); - } + soundBuffer = Buffer; + soundLoops = SoundLoops; } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs index 48587376bc..3abaef9574 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs @@ -23,7 +23,6 @@ //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using OpenBve.Formats.MsTs; -using OpenBveApi.Math; using SoundManager; using TrainManager.Car; @@ -37,42 +36,23 @@ namespace TrainManager.MsTsSounds public class Variable2IncPast : SoundTrigger { private readonly double variableValue; - - private readonly bool soundLoops; - - public Variable2IncPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double variableValue, bool soundLoops) : base(car, buffers, selectionMethod) + + public Variable2IncPast(SoundBuffer[] buffers, KujuTokenID selectionMethod, double variableValue, bool soundLoops) : base(buffers, selectionMethod, soundLoops) { this.variableValue = variableValue; - this.soundLoops = soundLoops; } - public Variable2IncPast(CarBase car, SoundBuffer buffer, double variableValue, bool soundLoops) : base(car, buffer) + public Variable2IncPast(SoundBuffer buffer, double variableValue, bool soundLoops) : base(buffer, soundLoops) { this.variableValue = variableValue; - this.soundLoops = soundLoops; } - public override void Update(double timeElapsed, double pitchValue, double volumeValue) + public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { - if (Car.TractionModel.CurrentPower >= variableValue) - { - if (Buffer != null) - { - if (Triggered == false) - { - this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; - } - else - { - this.Source.Pitch = pitchValue; - this.Source.Volume = volumeValue; - } - } - Triggered = true; - } - else + if (car.TractionModel.CurrentPower >= variableValue) { - Stop(); + soundBuffer = Buffer; + soundLoops = SoundLoops; } } } @@ -86,40 +66,21 @@ public class Variable2DecPast : SoundTrigger { private readonly double variableValue; - private readonly bool soundLoops; - - public Variable2DecPast(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod, double variableValue, bool soundLoops) : base(car, buffers, selectionMethod) + public Variable2DecPast(SoundBuffer[] buffers, KujuTokenID selectionMethod, double variableValue, bool soundLoops) : base(buffers, selectionMethod, soundLoops) { this.variableValue = variableValue; - this.soundLoops = soundLoops; } - public Variable2DecPast(CarBase car, SoundBuffer buffer, double variableValue, bool soundLoops) : base(car, buffer) + public Variable2DecPast(SoundBuffer buffer, double variableValue, bool soundLoops) : base(buffer, soundLoops) { this.variableValue = variableValue; - this.soundLoops = soundLoops; } - public override void Update(double timeElapsed, double pitchValue, double volumeValue) + public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { - if (Car.TractionModel.CurrentPower <= variableValue) - { - if (Buffer != null) - { - if (Triggered == false) - { - this.Source = TrainManagerBase.currentHost.PlaySound(Buffer, pitchValue, volumeValue, Vector3.Zero, Car, soundLoops) as SoundSource; - } - else - { - this.Source.Pitch = pitchValue; - this.Source.Volume = volumeValue; - } - } - Triggered = true; - } - else + if (car.TractionModel.CurrentPower <= variableValue) { - Stop(); + soundBuffer = Buffer; + soundLoops = SoundLoops; } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs index afbbc53594..5f12aceb90 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs @@ -34,10 +34,12 @@ public abstract class SoundTrigger internal readonly SoundBuffer[] Buffers; /// The selection method used to determine the buffer to play private readonly KujuTokenID bufferSelectionMethod; + /// Whether the sound loops + internal readonly bool SoundLoops; /// Timer used in derived updates internal double Timer; /// The previously selected buffer index - private int bufferIndex; + internal int BufferIndex; /// Gets the actual sound buffer to be played internal SoundBuffer Buffer { @@ -46,52 +48,36 @@ internal SoundBuffer Buffer switch (bufferSelectionMethod) { case KujuTokenID.SequentialSelection: - bufferIndex++; - if (bufferIndex > Buffers.Length - 1) + BufferIndex++; + if (BufferIndex > Buffers.Length - 1) { - bufferIndex = 0; + BufferIndex = 0; } break; case KujuTokenID.RandomSelection: - bufferIndex = TrainManagerBase.RandomNumberGenerator.Next(0, Buffers.Length - 1); + BufferIndex = TrainManagerBase.RandomNumberGenerator.Next(0, Buffers.Length - 1); break; } - return Buffers[bufferIndex]; + return Buffers[BufferIndex]; } } - /// The sound source for this trigger - internal SoundSource Source; - /// Holds a reference to the parent car - internal readonly CarBase Car; - /// Whether this sound triger has already been triggered - internal bool Triggered; - internal SoundTrigger(CarBase car, SoundBuffer buffer) + internal SoundTrigger(SoundBuffer buffer, bool soundLoops) { - Car = car; Buffers = new[] { buffer }; + SoundLoops = soundLoops; } - internal SoundTrigger(CarBase car, SoundBuffer[] buffers, KujuTokenID selectionMethod) + internal SoundTrigger(SoundBuffer[] buffers, KujuTokenID selectionMethod, bool soundLoops) { - Car = car; Buffers = buffers; bufferSelectionMethod = selectionMethod; + SoundLoops = soundLoops; } - public virtual void Update(double timeElapsed, double pitchValue, double volumeValue) + public virtual void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { } - - internal void Stop() - { - if (Triggered) - { - Source?.Stop(); - Triggered = false; - } - - } } } diff --git a/source/TrainManager/TrainManager.csproj b/source/TrainManager/TrainManager.csproj index d9235ee0d1..51b828652c 100644 --- a/source/TrainManager/TrainManager.csproj +++ b/source/TrainManager/TrainManager.csproj @@ -172,6 +172,7 @@ + From 0d3e901157f4466ff32f0185f692ca39a042728e Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 15 Sep 2025 10:06:05 +0100 Subject: [PATCH 34/82] Implement sanders from ENG --- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 1 + .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 14 +++++++++- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 7 +++++ .../Plugins/Train.MsTs/Train/VehicleParser.cs | 26 ++++++++++++++++++- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 32f7b4fcaf..87224f6263 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -241,6 +241,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Direction: case PanelSubject.Direction_Display: case PanelSubject.Overspeed: + case PanelSubject.Sanders: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); break; default: diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 25f330d11a..6a37c6f99f 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -28,6 +28,7 @@ using OpenBveApi.Trains; using System; using OpenBveApi; +using TrainManager.Car.Systems; using TrainManager.Motor; namespace Train.MsTs @@ -215,8 +216,19 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, } lastResult = gearState; break; + case PanelSubject.Sanders: + int sandState = 0; + for (int k = 0; k < dynamicTrain.Cars.Length; k++) + { + if (dynamicTrain.Cars[k].ReAdhesionDevice is Sanders sanders) + { + sandState = sanders.Active ? 1 :0; + break; + } + } + lastResult = sandState; + break; } - return lastResult; } diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index c52abbf865..ac764848fa 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -36,6 +36,7 @@ using System.IO; using System.Text; using TrainManager.Car; +using TrainManager.Car.Systems; using TrainManager.Motor; using TrainManager.MsTsSounds; @@ -464,6 +465,12 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case SoundTrigger.WiperOff: car.Windscreen.Wipers.SwitchSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); break; + case SoundTrigger.SanderOn: + if (car.ReAdhesionDevice is Sanders sanders) + { + sanders.LoopSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + break; } } else diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 000e0963e5..d6e6ee31ab 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -41,6 +41,7 @@ using SharpCompress.Compressors.Deflate; using TrainManager.BrakeSystems; using TrainManager.Car; +using TrainManager.Car.Systems; using TrainManager.Motor; using TrainManager.Power; using TrainManager.Trains; @@ -165,7 +166,12 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r currentCar.TractionModel = new BVEMotorCar(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }); break; } - currentCar.ReAdhesionDevice = new BveReAdhesionDevice(currentCar, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); + + if (currentCar.ReAdhesionDevice == null) + { + currentCar.ReAdhesionDevice = new BveReAdhesionDevice(currentCar, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); + } + currentCar.Windscreen = new Windscreen(0, 0, currentCar); currentCar.Windscreen.Wipers = new WindscreenWiper(currentCar.Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 1.0); @@ -318,6 +324,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private List vigilanceDevices; private Exhaust Exhaust; private Gear[] Gears; + private double maxSandingSpeed; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { @@ -707,6 +714,23 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.Brake_Train: train.Handles.Brake = ParseHandle(block, train, false); break; + case KujuTokenID.Sanding: + switch (block.ParentBlock.Token) + { + case KujuTokenID.Engine: + maxSandingSpeed = block.ReadSingle(UnitOfVelocity.MetersPerSecond, UnitOfVelocity.MilesPerHour); + break; + case KujuTokenID.EngineControllers: + int p1 = block.ReadInt16(); + int p2 = block.ReadInt16(); + int p3 = block.ReadInt16(); + if (p1 == 0 && p2 == 1 && p3 == 0) + { + car.ReAdhesionDevice = new Sanders(car, SandersType.PressAndHold, maxSandingSpeed); + } + break; + } + break; case KujuTokenID.DieselEngineIdleRPM: dieselIdleRPM = block.ReadSingle(); break; From 293779b08748a12cb00da5254da015b76dd770a4 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 15 Sep 2025 12:28:07 +0100 Subject: [PATCH 35/82] Fix: Don't crash with non-existant consist file --- source/Plugins/Train.MsTs/Plugin.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/source/Plugins/Train.MsTs/Plugin.cs b/source/Plugins/Train.MsTs/Plugin.cs index ad1c6a9e3d..1a718b6055 100644 --- a/source/Plugins/Train.MsTs/Plugin.cs +++ b/source/Plugins/Train.MsTs/Plugin.cs @@ -1,4 +1,6 @@ -using System.Text; +using System; +using System.IO; +using System.Text; using LibRender2; using OpenBveApi; using OpenBveApi.FileSystem; @@ -37,7 +39,7 @@ public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions public override bool CanLoadTrain(string path) { - if (path.ToLowerInvariant().EndsWith(".con")) + if (File.Exists(path) && path.ToLowerInvariant().EndsWith(".con")) { return true; } @@ -63,7 +65,15 @@ public override string GetDescription(string trainPath, Encoding userSelectedEnc { PreviewOnly = true; AbstractTrain train = new TrainBase(TrainState.Pending, TrainType.LocalPlayerTrain); - ConsistParser.ReadConsist(trainPath, ref train); + try + { + ConsistParser.ReadConsist(trainPath, ref train); + } + catch + { + return string.Empty; + } + if(train is TrainBase trainBase && trainBase.Cars.Length != 0) { return trainBase.Cars[train.DriverCar].Description; From d408272623898f71557b84fb74c833daf029c9a7 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 17 Sep 2025 11:55:52 +0100 Subject: [PATCH 36/82] Basic implementation of combined handle, loco brakes --- source/Plugins/Train.MsTs/Handles/Handle.cs | 50 ++++++++----------- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 5 +- .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 15 ++++++ .../Panel/Enums/CabComponentType.cs | 4 +- source/Plugins/Train.MsTs/Train.MsTs.csproj | 1 + .../Train.MsTs/Train/Enums/ControlType.cs | 12 +++++ .../Plugins/Train.MsTs/Train/VehicleParser.cs | 19 +++++++ 7 files changed, 74 insertions(+), 32 deletions(-) create mode 100644 source/Plugins/Train.MsTs/Train/Enums/ControlType.cs diff --git a/source/Plugins/Train.MsTs/Handles/Handle.cs b/source/Plugins/Train.MsTs/Handles/Handle.cs index 9d73c5d036..e9483be52a 100644 --- a/source/Plugins/Train.MsTs/Handles/Handle.cs +++ b/source/Plugins/Train.MsTs/Handles/Handle.cs @@ -26,7 +26,7 @@ internal AbstractHandle ParseHandle(Block block, TrainBase train, bool isPower) // some handles seem to have extra numbers here, I believe ignored by the MSTS parser Block notchDescriptions = block.GetSubBlock(KujuTokenID.NumNotches); ParseNotchDescriptionBlock(notchDescriptions, isPower); - return new VariableHandle(train, NotchDescriptions); + return new VariableHandle(train, isPower, NotchDescriptions); } private void ParseNotchDescriptionBlock(Block block, bool isPower) @@ -34,41 +34,33 @@ private void ParseNotchDescriptionBlock(Block block, bool isPower) int numNotches = block.ReadInt32(); if (numNotches < 2) { - /* - * Some AI ENG files seem to use a single dummy notch - * Interpret this as a single actual power notch - */ - NotchDescriptions = new[] - { - new Tuple(0, "N"), - new Tuple(1, isPower ? "P1" :"B1"), - }; + // percentage based notch + NotchDescriptions = null; + return; } - else + + NotchDescriptions = new Tuple[numNotches]; + for (int i = 0; i < numNotches; i++) { - NotchDescriptions = new Tuple[numNotches]; - for (int i = 0; i < numNotches; i++) + Block descriptionBlock = block.ReadSubBlock(KujuTokenID.Notch); + double notchPower = descriptionBlock.ReadSingle(); + descriptionBlock.ReadSingle(); // ?? + string notchDescription = descriptionBlock.ReadString(); + if (notchDescription.Equals("dummy", StringComparison.InvariantCultureIgnoreCase)) { - Block descriptionBlock = block.ReadSubBlock(KujuTokenID.Notch); - double notchPower = descriptionBlock.ReadSingle(); - descriptionBlock.ReadSingle(); // ?? - string notchDescription = descriptionBlock.ReadString(); - if (notchDescription.Equals("dummy", StringComparison.InvariantCultureIgnoreCase)) + if (i == 0) { - if (i == 0) - { - notchDescription = "N"; - } - else - { - notchDescription = isPower ? "P" + i : "B" + i; - } - + notchDescription = "N"; } - NotchDescriptions[i] = new Tuple(notchPower, notchDescription); + else + { + notchDescription = isPower ? "P" + i : "B" + i; + } + } + NotchDescriptions[i] = new Tuple(notchPower, notchDescription); } - + } } } diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 87224f6263..ecc5198d36 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -1,4 +1,4 @@ -//Simplified BSD License (BSD-2-Clause) +//Simplified BSD License (BSD-2-Clause) // //Copyright (c) 2025, Christopher Lees, The OpenBVE Project // @@ -89,7 +89,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) { if (File.Exists(TexturePath) || Type == CabComponentType.Digital) { - if (FrameMappings.Length == 0 && TotalFrames > 1) + if (FrameMappings.Length < 2 && TotalFrames > 1) { // e.g. Acela power handle has 25 frames for total power value of 100% but no mappings specified FrameMappings = new FrameMapping[TotalFrames]; @@ -186,6 +186,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); switch (panelSubject) { + case PanelSubject.Engine_Brake: case PanelSubject.Throttle: case PanelSubject.Train_Brake: case PanelSubject.Gears: diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 6a37c6f99f..828c6b88a8 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -228,6 +228,21 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, } lastResult = sandState; break; + case PanelSubject.Engine_Brake: + if (!dynamicTrain.Handles.HasLocoBrake) + { + lastResult = 0; + break; + } + for (int i = 0; i < FrameMapping.Length; i++) + { + if (FrameMapping[i].MappingValue >= (double)dynamicTrain.Handles.LocoBrake.Actual / dynamicTrain.Handles.LocoBrake.MaximumNotch) + { + lastResult = FrameMapping[i].FrameKey; + break; + } + } + break; } return lastResult; } diff --git a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs index d78ce03256..2f23f7b88f 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentType.cs @@ -24,6 +24,8 @@ internal enum CabComponentType /// A digital display Digital = 9, /// A digital clock - DigitalClock = 10 + DigitalClock = 10, + /// A combined power + brake controller + CombinedControl = 11 } } diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index af5b120bb1..b365aac66f 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -73,6 +73,7 @@ + diff --git a/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs b/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs new file mode 100644 index 0000000000..c82b9bbf57 --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs @@ -0,0 +1,12 @@ +namespace Train.MsTs +{ + internal enum CombinedControlType + { + /// Unknown control type + Unknown = 0, + /// Throttle handle + Throttle = 1, + /// Dynamic brake handle + Dynamic = 2 + } +} diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index d6e6ee31ab..cd1fd9afe0 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -42,6 +42,7 @@ using TrainManager.BrakeSystems; using TrainManager.Car; using TrainManager.Car.Systems; +using TrainManager.Handles; using TrainManager.Motor; using TrainManager.Power; using TrainManager.Trains; @@ -714,6 +715,24 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.Brake_Train: train.Handles.Brake = ParseHandle(block, train, false); break; + case KujuTokenID.Brake_Engine: + train.Handles.HasLocoBrake = true; + train.Handles.LocoBrake = ParseHandle(block, train, false); + break; + case KujuTokenID.Combined_Control: + block.ReadSingle(); + block.ReadSingle(); + block.ReadSingle(); + block.ReadSingle(); + + CombinedControlType firstCombinedControl = block.ReadEnumValue(default(CombinedControlType)); + CombinedControlType secondCombinedControl = block.ReadEnumValue(default(CombinedControlType)); + + if (firstCombinedControl == CombinedControlType.Throttle && secondCombinedControl == CombinedControlType.Dynamic) + { + train.Handles.HandleType = HandleType.SingleHandle; + } + break; case KujuTokenID.Sanding: switch (block.ParentBlock.Token) { From 4372fbc68129a275185548018ddbfa4dde94613d Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 17 Sep 2025 12:25:38 +0100 Subject: [PATCH 37/82] Use speed for CVF speedometer Use standard overlay colors Build + use multi-state display mappings --- source/Plugins/Train.MsTs/Panel/CabComponent.cs | 17 +++++++++++++++++ source/Plugins/Train.MsTs/Panel/CvfAnimation.cs | 1 + source/Plugins/Train.MsTs/Panel/CvfParser.cs | 5 +++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index ecc5198d36..37c9cbf772 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -383,6 +383,23 @@ private void ReadSubBlock(Block block) TotalFrames = block.ReadInt16(); HorizontalFrames = block.ReadInt16(); VerticalFrames = block.ReadInt16(); + FrameMappings = new FrameMapping[TotalFrames]; + for (int i = 0; i < TotalFrames; i++) + { + FrameMappings[i].FrameKey = i; + var stateBlock = block.ReadSubBlock(KujuTokenID.State); + while (stateBlock.Length() - stateBlock.Position() > 3) + { + Block subBlock = stateBlock.ReadSubBlock(new[] { KujuTokenID.Style, KujuTokenID.SwitchVal }); + if (subBlock.Token == KujuTokenID.SwitchVal) + { + FrameMappings[i].MappingValue = subBlock.ReadSingle(); + break; + } + } + + + } break; case KujuTokenID.DirIncrease: // rotates Clockwise (0) or AntiClockwise (1) diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 828c6b88a8..443589de6b 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -129,6 +129,7 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, } } break; + case PanelSubject.Direction_Display: case PanelSubject.Direction: lastResult = (int)dynamicTrain.Handles.Reverser.Actual + 1; break; diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 916e93c0b8..0fdf48aec3 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -364,13 +364,14 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject Code = "horn"; break; case PanelSubject.Speedometer: + // use speed not speedometer at the minute as wheelslip isn't right switch (subjectUnits) { case Units.Miles_Per_Hour: - Code = "speedometer abs 2.2369362920544 *"; + Code = "speed abs 2.2369362920544 *"; break; case Units.Kilometers_Per_Hour: - Code = "speedometer abs 3.6 *"; + Code = "speed abs 3.6 *"; break; } break; From 929cd085f77daf02874e05675f1e49c6c46e4fe8 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 17 Sep 2025 14:10:29 +0100 Subject: [PATCH 38/82] Add some missing values --- source/Plugins/Train.MsTs/Misc/Units.cs | 17 ++++++++++------- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 9 +++++++++ .../Train.MsTs/Panel/Enums/PanelSubject.cs | 6 ++++++ source/Plugins/Train.MsTs/Sound/SmsParser.cs | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/source/Plugins/Train.MsTs/Misc/Units.cs b/source/Plugins/Train.MsTs/Misc/Units.cs index c039099b3b..d29f4adcb2 100644 --- a/source/Plugins/Train.MsTs/Misc/Units.cs +++ b/source/Plugins/Train.MsTs/Misc/Units.cs @@ -6,12 +6,15 @@ namespace Train.MsTs internal enum Units { Unknown = 0, - Amps, - Gallons, - Inches_Of_Mercury, - Kilometers_Per_Hour, - Miles_Per_Hour, - PSI, - Kilo_Lbs + Amps =1, + Gallons = 2, + Inches_Of_Mercury = 3, + Kilometers_Per_Hour = 4, + Km_Per_Hour = 4, + Miles_Per_Hour = 5, + Meters_Sec = 6, + PSI = 7, + Kilo_Lbs = 8, + Kilopascals = 9 } } diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 0fdf48aec3..f4c0428917 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -327,6 +327,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Inches_Of_Mercury: Code = "brakecylinder 0.0002953 *"; break; + case Units.Kilopascals: + Code = "brakecylinder 0.001 *"; + break; } break; case PanelSubject.Brake_Pipe: @@ -338,6 +341,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Inches_Of_Mercury: Code = "brakepipe 0.0002953 *"; break; + case Units.Kilopascals: + Code = "brakepipe 0.001 *"; + break; } break; case PanelSubject.Main_Res: @@ -349,6 +355,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Inches_Of_Mercury: Code = "mainreservoir 0.0002953 *"; break; + case Units.Kilopascals: + Code = "mainreservoir 0.001 *"; + break; } break; case PanelSubject.Direction: diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs index 1dea4f6f2a..94be992d5d 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -4,6 +4,7 @@ namespace Train.MsTs { internal enum PanelSubject { + Accelerometer, Alerter_Display, Ammeter, Aspect_Display, @@ -17,10 +18,14 @@ internal enum PanelSubject CPH_Display, Cutoff, Cyl_Cocks, + Dampers_Back, Dampers_Front, Direction, Direction_Display, + Dynamic_Brake, + Dynamic_Brake_Display, Engine_Brake, + Engine_Braking_Button, Emergency_Brake, Eq_Res, Firebox, @@ -51,6 +56,7 @@ internal enum PanelSubject Tender_Water, Boiler_Water, Throttle, + Throttle_Display, Traction_Braking, Train_Brake, Vacuum_Reservoir_Pressure, diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index ac764848fa..c4c1fa25b1 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -512,7 +512,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Speed_Inc_Past: case KujuTokenID.Speed_Dec_Past: currentSoundSet.VariableValue = block.ReadSingle(UnitOfVelocity.KilometersPerHour, UnitOfVelocity.MetersPerSecond); // speed in m/s - newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.PlayOneShot, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.PlayOneShot, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger, KujuTokenID.SetStreamVolume }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.SpeedControlled: From cd1ad05d6f6cebc846382c093314dfa5a219c005 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 18 Sep 2025 12:18:20 +0100 Subject: [PATCH 39/82] Improve handling for some more bits --- source/Plugins/Train.MsTs/Misc/Units.cs | 20 +++++++++------- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 9 +++++++ .../Train.MsTs/Panel/Enums/PanelSubject.cs | 1 + source/Plugins/Train.MsTs/Sound/SmsParser.cs | 2 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 24 ++++++++++++++----- .../Vigilance/AbstractVigilanceDevice.cs | 21 ++++++++++++++-- 6 files changed, 59 insertions(+), 18 deletions(-) diff --git a/source/Plugins/Train.MsTs/Misc/Units.cs b/source/Plugins/Train.MsTs/Misc/Units.cs index d29f4adcb2..44fa49fce0 100644 --- a/source/Plugins/Train.MsTs/Misc/Units.cs +++ b/source/Plugins/Train.MsTs/Misc/Units.cs @@ -7,14 +7,16 @@ internal enum Units { Unknown = 0, Amps =1, - Gallons = 2, - Inches_Of_Mercury = 3, - Kilometers_Per_Hour = 4, - Km_Per_Hour = 4, - Miles_Per_Hour = 5, - Meters_Sec = 6, - PSI = 7, - Kilo_Lbs = 8, - Kilopascals = 9 + Kilovolts = 2, + Gallons = 3, + Inches_Of_Mercury = 4, + Kilometers_Per_Hour = 5, + Km_Per_Hour = 5, + Miles_Per_Hour = 6, + Meters_Sec = 7, + PSI = 8, + Kilo_Lbs = 9, + Kilopascals = 10, + Bar = 11 } } diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index f4c0428917..5d059701ed 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -330,6 +330,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Kilopascals: Code = "brakecylinder 0.001 *"; break; + case Units.Bar: + Code = "brakecylinder 0.00001 *"; + break; } break; case PanelSubject.Brake_Pipe: @@ -344,6 +347,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Kilopascals: Code = "brakepipe 0.001 *"; break; + case Units.Bar: + Code = "brakepipe 0.00001 *"; + break; } break; case PanelSubject.Main_Res: @@ -358,6 +364,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Kilopascals: Code = "mainreservoir 0.001 *"; break; + case Units.Bar: + Code = "mainreservoir 0.00001 *"; + break; } break; case PanelSubject.Direction: diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs index 94be992d5d..330ead9760 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -35,6 +35,7 @@ internal enum PanelSubject Gears, Horn, Load_Meter, + Line_Voltage, Main_Res, Overspeed, Pantograph, diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index c4c1fa25b1..d08e9aaeb2 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -316,7 +316,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So break; case KujuTokenID.Initial_Trigger: // when initially appears, hence nothing other than StartLoop should be valid - newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.DisableTrigger }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.DisableTrigger }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.StartLoopRelease: diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index cd1fd9afe0..df43520736 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -253,19 +253,31 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine * Engine files contain two blocks, not in an enclosing block * Assume that these can be of arbritrary order, so read using a dictionary */ - Dictionary blocks = TextualBlock.ReadBlocks(s); - if (!blocks.ContainsKey(KujuTokenID.Wagon)) + List blocks = TextualBlock.ReadBlocks(s); + + List wagonBlocks = blocks.Where(b => b.Token == KujuTokenID.Wagon).ToList(); + List engineBlocks = blocks.Where(b => b.Token == KujuTokenID.Engine).ToList(); + + if (wagonBlocks.Count == 0) { //Not found any wagon data in this file return false; } - if (isEngine && blocks.ContainsKey(KujuTokenID.Engine)) + if (isEngine && engineBlocks.Count > 0) { - return ParseBlock(blocks[KujuTokenID.Engine], fileName, ref wagonName, true, ref car, ref train); + if (engineBlocks.Count > 1) + { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Multiple engine blocks encounted in MSTS ENG file "+ fileName); + } + return ParseBlock(engineBlocks[0], fileName, ref wagonName, true, ref car, ref train); } - if (!isEngine && blocks.ContainsKey(KujuTokenID.Wagon)) + if (!isEngine && wagonBlocks.Count > 0) { - return ParseBlock(blocks[KujuTokenID.Wagon], fileName, ref wagonName, false, ref car, ref train); + if (wagonBlocks.Count > 1) + { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Multiple wagon blocks encounted in MSTS ENG file " + fileName); + } + return ParseBlock(wagonBlocks[0], fileName, ref wagonName, false, ref car, ref train); } return false; } diff --git a/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs b/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs index 9c39e2903d..e0644f9301 100644 --- a/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs +++ b/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs @@ -1,5 +1,6 @@ using System.IO; using OpenBve.Formats.MsTs; +using OpenBveApi.World; namespace Train.MsTs { @@ -58,10 +59,26 @@ internal void ParseBlock(Block block) PenaltyTimeLimit = block.ReadSingle(); break; case KujuTokenID.MonitoringDeviceCriticalLevel: - CriticalLevel = block.ReadSingle(); + if (block.ParentBlock.Token == KujuTokenID.EmergencyStopMonitor) + { + // Check exact behaviour + CriticalLevel = block.ReadSingle(UnitOfVelocity.MetersPerSecond); + } + else + { + CriticalLevel = block.ReadSingle(); + } break; case KujuTokenID.MonitoringDeviceResetLevel: - ResetLevel = block.ReadSingle(); + if (block.ParentBlock.Token == KujuTokenID.EmergencyStopMonitor) + { + // Check exact behaviour + CriticalLevel = block.ReadSingle(UnitOfVelocity.MetersPerSecond); + } + else + { + CriticalLevel = block.ReadSingle(); + } break; case KujuTokenID.MonitoringDeviceAppliesFullBrake: AppliesFullBrake = block.ReadInt32() == 1; From 719914c9f20d1f6270f6d58c648771b98256e06f Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 18 Sep 2025 13:45:59 +0100 Subject: [PATCH 40/82] More missing values --- source/Plugins/Train.MsTs/Misc/Units.cs | 26 ++++++++++--------- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 9 +++++++ .../Train.MsTs/Panel/Enums/PanelSubject.cs | 1 + source/Plugins/Train.MsTs/Sound/SmsParser.cs | 2 +- .../Train/Enums/BrakeEquipmentType.cs | 4 ++- .../Train.MsTs/Train/Enums/ControlType.cs | 8 +++++- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 6 ++--- 7 files changed, 38 insertions(+), 18 deletions(-) diff --git a/source/Plugins/Train.MsTs/Misc/Units.cs b/source/Plugins/Train.MsTs/Misc/Units.cs index 44fa49fce0..ac41344cb2 100644 --- a/source/Plugins/Train.MsTs/Misc/Units.cs +++ b/source/Plugins/Train.MsTs/Misc/Units.cs @@ -6,17 +6,19 @@ namespace Train.MsTs internal enum Units { Unknown = 0, - Amps =1, - Kilovolts = 2, - Gallons = 3, - Inches_Of_Mercury = 4, - Kilometers_Per_Hour = 5, - Km_Per_Hour = 5, - Miles_Per_Hour = 6, - Meters_Sec = 7, - PSI = 8, - Kilo_Lbs = 9, - Kilopascals = 10, - Bar = 11 + Amps = 1, + Volts = 2, + Kilovolts = 3, + Gallons = 4, + Inches_Of_Mercury = 5, + Kilometers_Per_Hour = 6, + Km_Per_Hour = 6, + Miles_Per_Hour = 7, + Meters_Sec = 8, + PSI = 9, + Kilo_Lbs = 10, + Kilopascals = 11, + Bar = 12, + Kgs_Per_Square_Cm = 13 } } diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 5d059701ed..87e1323cf6 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -333,6 +333,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Bar: Code = "brakecylinder 0.00001 *"; break; + case Units.Kgs_Per_Square_Cm: + Code = "brakecylinder 98066.5 *"; + break; } break; case PanelSubject.Brake_Pipe: @@ -350,6 +353,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Bar: Code = "brakepipe 0.00001 *"; break; + case Units.Kgs_Per_Square_Cm: + Code = "brakepipe 98066.5 *"; + break; } break; case PanelSubject.Main_Res: @@ -367,6 +373,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case Units.Bar: Code = "mainreservoir 0.00001 *"; break; + case Units.Kgs_Per_Square_Cm: + Code = "mainreservoir 98066.5 *"; + break; } break; case PanelSubject.Direction: diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs index 330ead9760..21ae693187 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -13,6 +13,7 @@ internal enum PanelSubject Blower, Brake_Cyl, Brake_Pipe, + Cab_Radio, Clock, CP_Handle, CPH_Display, diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index d08e9aaeb2..97d4acc451 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -316,7 +316,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So break; case KujuTokenID.Initial_Trigger: // when initially appears, hence nothing other than StartLoop should be valid - newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.DisableTrigger }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.StartLoopRelease: diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index 3ba86b2bb3..9950cfe7db 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -13,8 +13,10 @@ enum BrakeEquipmentType /// 4 position retaining valve is fitted /// This is meant for freight wagons only Retainer_4_Position, - /// Vacuum brake is fitted. + /// Twin-pipe vacuum brake is fitted. Vacuum_Brake, + /// Single-pipe vacuum brake is fitted. + Vacuum_Single_Pipe, /// Standard triple valve is fitted. Triple_Valve, /// Triple valve that permits partial releasing of the brakes. diff --git a/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs b/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs index c82b9bbf57..f555d0399d 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs @@ -7,6 +7,12 @@ internal enum CombinedControlType /// Throttle handle Throttle = 1, /// Dynamic brake handle - Dynamic = 2 + Dynamic = 2, + /// Independant brake handle + Independent = 3, + /// Independant brake handle + Independant = 3, + /// Train brake handle + Train = 4 } } diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index df43520736..afb3837fd7 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -379,7 +379,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; } // Add brakes last, as we need the acceleration values - if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_Piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_Piped)) + if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_Piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_Piped) || (brakeSystemTypes.Length == 1 && brakeSystemTypes[0] == BrakeSystemType.Handbrake)) { /* * FIXME: Need to implement vac braked / air piped and vice-versa, but for the minute, we'll assume that if one or the other is present @@ -406,7 +406,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool // The car contains no control gear, but is air / vac braked // Assume equivilant to AutomaticAirBrake // NOTE: This must be last in the else-if chain to enure that a vehicle with EP / ECP and these declared is setup correctly - car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0,0,0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); } car.CarBrake.mainReservoir = new MainReservoir(690000.0, 780000.0, 0.01, 0.075 / train.Cars.Length); @@ -442,7 +442,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool { try { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); } catch From 9dcb86215ef9ef075b4f1c305cd79bfee346e0a0 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 19 Sep 2025 14:05:25 +0100 Subject: [PATCH 41/82] Allow loading of SMS files from MSTS common sound directory --- source/Plugins/Train.MsTs/Plugin.cs | 20 +++++++++++++++++-- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 13 ++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/source/Plugins/Train.MsTs/Plugin.cs b/source/Plugins/Train.MsTs/Plugin.cs index 1a718b6055..da1f3e93df 100644 --- a/source/Plugins/Train.MsTs/Plugin.cs +++ b/source/Plugins/Train.MsTs/Plugin.cs @@ -1,7 +1,7 @@ -using System; -using System.IO; +using System.IO; using System.Text; using LibRender2; +using Microsoft.Win32; using OpenBveApi; using OpenBveApi.FileSystem; using OpenBveApi.Hosts; @@ -23,11 +23,27 @@ public class Plugin : TrainInterface internal static FileSystem FileSystem; + internal static string MsTsPath; + internal static bool PreviewOnly; public Plugin() { ConsistParser = new ConsistParser(this); WagonParser = new WagonParser(); + + try + { + MsTsPath = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft Games\\Train Simulator\\1.0", "Path", string.Empty); + string OrTsPath = (string)Registry.GetValue("HKEY_CURRENT_USER\\Software\\OpenRails\\ORTS\\Folders", "Train Simulator", string.Empty); + if (!string.IsNullOrEmpty(OrTsPath)) + { + MsTsPath = OrTsPath; + } + } + catch + { + // ignored + } } public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions options, object rendererReference) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index afb3837fd7..98ebecaf53 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -694,10 +694,19 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; case KujuTokenID.Sound: // parse the sounds *after* we've loaded the traction model though - string soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), block.ReadString()); + string sF = block.ReadString(); + string soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), sF); if (!File.Exists(soundFile)) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: SMS file " + soundFile + " was not found."); + if (Directory.Exists(Plugin.MsTsPath)) + { + // If sound file is not relative to the ENG / WAG, try in the MSTS common sound directory (most generic wagons + coaches) + soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.MsTsPath, "SOUND"), sF); + } + if (!File.Exists(soundFile)) + { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: SMS file " + soundFile + " was not found."); + } break; } soundFiles.Add(soundFile); From 78cae862d5e0e04fc5235fad031c485f0b7ebd45 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 19 Sep 2025 15:30:08 +0100 Subject: [PATCH 42/82] More implementation / fixes --- source/Plugins/Train.MsTs/Panel/CabComponent.cs | 1 + source/Plugins/Train.MsTs/Panel/CvfAnimation.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 37c9cbf772..269cdf0a5b 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -243,6 +243,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Direction_Display: case PanelSubject.Overspeed: case PanelSubject.Sanders: + case PanelSubject.Wheelslip: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); break; default: diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 443589de6b..119722aac8 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -244,6 +244,18 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, } } break; + case PanelSubject.Wheelslip: + int wheelSlip = 0; + for (int k = 0; k < dynamicTrain.Cars.Length; k++) + { + if (dynamicTrain.Cars[k].FrontAxle.CurrentWheelSlip || dynamicTrain.Cars[k].RearAxle.CurrentWheelSlip) + { + wheelSlip = 1; + break; + } + } + lastResult = wheelSlip; + break; } return lastResult; } From 27c1b1d0c08912d6bcb4c8136f821566a7b3a938 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 23 Oct 2025 11:25:16 +0100 Subject: [PATCH 43/82] Work on friction a bit, refactor axles --- source/Plugins/Train.MsTs/Train.MsTs.csproj | 2 + .../Plugins/Train.MsTs/Train/ConsistParser.cs | 2 +- source/Plugins/Train.MsTs/Train/Friction.cs | 41 +++++++++++++++++++ source/Plugins/Train.MsTs/Train/MSTSAxle.cs | 36 ++++++++++++++++ .../Plugins/Train.MsTs/Train/VehicleParser.cs | 9 ++++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 source/Plugins/Train.MsTs/Train/Friction.cs create mode 100644 source/Plugins/Train.MsTs/Train/MSTSAxle.cs diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index b365aac66f..ee540e38cb 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -75,6 +75,8 @@ + + diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index c821f8140c..5bb615f2f1 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -289,7 +289,7 @@ private void ParseBlock(Block block, ref TrainBase currentTrain) /* * FIXME: All this needs to be pulled from the eng properties, or fixed so it doesn't matter */ - currentCar = new CarBase(currentTrain, currentCarIndex, 0.35, 0.0025, 1.1); + currentCar = new CarBase(currentTrain, currentCarIndex); currentCar.HoldBrake = new CarHoldBrake(currentCar); //FIXME END diff --git a/source/Plugins/Train.MsTs/Train/Friction.cs b/source/Plugins/Train.MsTs/Train/Friction.cs new file mode 100644 index 0000000000..7370d566b9 --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/Friction.cs @@ -0,0 +1,41 @@ +using System; +using OpenBve.Formats.MsTs; +using OpenBveApi.World; + +namespace Train.MsTs +{ + internal class Friction + { + /// The friction constant + internal double C1; + /// The friction exponent + internal double E1; + /// The second velocity segment start value + internal double V2; + /// The second friction constant + internal double C2; + /// The second friction exponent + internal double E2; + + internal Friction(Block block) + { + C1 = block.ReadSingle(UnitOfTorque.NewtonMetersPerSecond); + E1 = block.ReadSingle(); + V2 = block.ReadSingle(UnitOfVelocity.MetersPerSecond); + C2 = block.ReadSingle(UnitOfTorque.NewtonMetersPerSecond); + E2 = block.ReadSingle(); + } + + internal double GetResistanceValue(double speed) + { + // see Eng_and_wag_file_reference_guideV2.doc + // Gives the result in newton-meters per second + if (V2 < 0 || speed <= V2) + { + return C1 * Math.Pow(speed, E1); + } + + return C1 + Math.Pow(V2, E1) + C2 * (V2 + Math.Pow(speed - V2, E2)); + } + } +} diff --git a/source/Plugins/Train.MsTs/Train/MSTSAxle.cs b/source/Plugins/Train.MsTs/Train/MSTSAxle.cs new file mode 100644 index 0000000000..a41297213e --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/MSTSAxle.cs @@ -0,0 +1,36 @@ +using OpenBveApi.Hosts; +using OpenBveApi.Trains; + +namespace Train.MsTs +{ + /// Derived class for axles on MSTS cars + public class MSTSAxle : AbstractAxle + { + /// The friction properties + private readonly Friction FrictionProperties; + + internal MSTSAxle(HostInterface currentHost, AbstractTrain train, AbstractCar car, Friction friction) : base(currentHost, train, car) + { + FrictionProperties = friction; + } + + public override double GetResistance(double Speed, double FrontalArea, double AirDensity, double AccelerationDueToGravity) + { + return FrictionProperties.GetResistanceValue(Speed) / baseCar.CurrentMass; + } + + public override double CriticalWheelSlipAccelerationForElectricMotor(double AccelerationDueToGravity) + { + // TODO: This is the BVE formula + double NormalForceAcceleration = Follower.WorldUp.Y * AccelerationDueToGravity; + return 0.35 * Follower.AdhesionMultiplier * NormalForceAcceleration; + } + + public override double CriticalWheelSlipAccelerationForFrictionBrake(double AccelerationDueToGravity) + { + // TODO: This is the BVE formula + double NormalForceAcceleration = Follower.WorldUp.Y * AccelerationDueToGravity; + return 0.35 * Follower.AdhesionMultiplier * NormalForceAcceleration; + } + } +} diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 98ebecaf53..fbc8455c83 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -325,6 +325,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private double dieselIdleUse; private double dieselMaxUse; private double dieselCapacity; + private double dieselMaxTractiveEffortSpeed; private double maxEngineAmps; private double maxBrakeAmps; private double mainReservoirMinimumPressure; @@ -771,6 +772,9 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; } break; + case KujuTokenID.DieselEngineSpeedOfMaxTractiveEffort: + dieselMaxTractiveEffortSpeed = block.ReadSingle(UnitOfVelocity.MetersPerSecond); + break; case KujuTokenID.DieselEngineIdleRPM: dieselIdleRPM = block.ReadSingle(); break; @@ -930,6 +934,11 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool Gears[i].OverspeedFailure = Gears[i].MaximumSpeed * perc; } break; + case KujuTokenID.Friction: + Friction friction = new Friction(block); + car.FrontAxle = new MSTSAxle(Plugin.CurrentHost, train, car, friction); + car.RearAxle = new MSTSAxle(Plugin.CurrentHost, train, car, friction); + break; } return true; } From 5bda7382fc441b7792c713377da290e9613ca150 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 24 Sep 2025 12:49:33 +0100 Subject: [PATCH 44/82] Coupling types and distances --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 2 +- source/Plugins/Train.MsTs/Train.MsTs.csproj | 1 + .../Train/Enums/BrakeEquipmentType.cs | 34 +++++----- .../Train.MsTs/Train/Enums/CouplingType.cs | 10 +++ .../Plugins/Train.MsTs/Train/VehicleParser.cs | 68 +++++++++++++++---- 5 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 source/Plugins/Train.MsTs/Train/Enums/CouplingType.cs diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 97d4acc451..d92641066c 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -287,7 +287,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Stream: while (block.Position() < block.Length() - 3) { - newBlock = block.ReadSubBlock(new[] { KujuTokenID.Priority, KujuTokenID.Triggers, KujuTokenID.Volume, KujuTokenID.VolumeCurve, KujuTokenID.FrequencyCurve, KujuTokenID.Granularity }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.Priority, KujuTokenID.Triggers, KujuTokenID.Variable_Trigger, KujuTokenID.Volume, KujuTokenID.VolumeCurve, KujuTokenID.FrequencyCurve, KujuTokenID.Granularity }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index ee540e38cb..45d0a956ab 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -74,6 +74,7 @@ + diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index 9950cfe7db..3264adb08c 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -5,37 +5,39 @@ namespace Train.MsTs enum BrakeEquipmentType { /// Handbrake is fitted - Handbrake, + Handbrake = 1, /// Manual brake fitted. - Manual_Brake, + Manual_Brake = 2, /// 3 position retaining valve is fitted. - Retainer_3_Position, + Retainer_3_Position = 3, /// 4 position retaining valve is fitted /// This is meant for freight wagons only - Retainer_4_Position, + Retainer_4_Position = 4, /// Twin-pipe vacuum brake is fitted. - Vacuum_Brake, + Vacuum_Brake = 5, /// Single-pipe vacuum brake is fitted. - Vacuum_Single_Pipe, + Vacuum_Single_Pipe = 6, /// Standard triple valve is fitted. - Triple_Valve, + Triple_Valve = 7, /// Triple valve that permits partial releasing of the brakes. - Graduated_Release_Triple_Valve, + Graduated_Release_Triple_Valve = 8, /// Electrically controlled brake system is fitted. Release and application of the brakes are independently controlled. - EP_Brake, + EP_Brake = 9, + EP_Brakes = 9, /// Same functionality as ep_brake - ECP_Brake, + ECP_Brake = 10, /// Air tank used for normal service brake applications. This is required for all brake systems. - Auxiliary_Reservoir = 9, - Auxilary_Reservoir = 9, + Auxiliary_Reservoir = 11, + Auxilary_Reservoir = 11, /// Air tank used for emergency applications. /// This is optional. - Emergency_Brake_Reservoir, + Emergency_Brake_Reservoir = 12, /// Electronic or computer controller on the vehicle that can be set to independently control any parameter of the braking system. - Distributor, + Distributor = 13, /// One pipe controls and supplies the air brakes. - Air_Single_Pipe, + Air_Single_Pipe = 14, /// One pipe for control air, one pipe for supply air - Air_Twin_Pipe + Air_Twin_Pipe = 15, + Air_Brake = 15 } } diff --git a/source/Plugins/Train.MsTs/Train/Enums/CouplingType.cs b/source/Plugins/Train.MsTs/Train/Enums/CouplingType.cs new file mode 100644 index 0000000000..85baeec774 --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/Enums/CouplingType.cs @@ -0,0 +1,10 @@ +namespace Train.MsTs +{ + internal enum CouplingType + { + Unknown = 0, + Automatic = 1, + Bar = 2, + Chain = 3 + } +} diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index fbc8455c83..8bb39cc347 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -339,6 +339,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private Exhaust Exhaust; private Gear[] Gears; private double maxSandingSpeed; + private CouplingType couplingType; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { @@ -505,20 +506,29 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool car.CarBrake.JerkDown = 10; break; case KujuTokenID.Type: - if (isEngine) - { - currentEngineType = block.ReadEnumValue(default(EngineType)); - } - else + switch (block.ParentBlock.Token) { - try - { - WagonType type = block.ReadEnumValue(default(WagonType)); - } - catch - { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Invalid vehicle type specified."); - } + case KujuTokenID.Engine: + case KujuTokenID.Wagon: + if (isEngine) + { + currentEngineType = block.ReadEnumValue(default(EngineType)); + } + else + { + try + { + WagonType type = block.ReadEnumValue(default(WagonType)); + } + catch + { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Invalid vehicle type specified."); + } + } + break; + case KujuTokenID.Coupling: + couplingType = block.ReadEnumValue(default(CouplingType)); + break; } break; case KujuTokenID.DieselEngineType: @@ -939,6 +949,38 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool car.FrontAxle = new MSTSAxle(Plugin.CurrentHost, train, car, friction); car.RearAxle = new MSTSAxle(Plugin.CurrentHost, train, car, friction); break; + case KujuTokenID.Coupling: + while (block.Position() < block.Length() - 2) + { + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); + } + break; + case KujuTokenID.Spring: + while (block.Position() < block.Length() - 2) + { + newBlock = block.ReadSubBlock(); + ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); + } + break; + case KujuTokenID.r0: + try + { + car.Coupler.MinimumDistanceBetweenCars = block.ReadSingle(UnitOfLength.Meter); + car.Coupler.MaximumDistanceBetweenCars = couplingType != CouplingType.Bar ? block.ReadSingle(UnitOfLength.Meter) : car.Coupler.MinimumDistanceBetweenCars; + } + catch + { + // ignored + } + + if (car.Coupler.MaximumDistanceBetweenCars > 2) + { + // some automatic / bar couplers seem to have absurd maximum distances + // so let's assume they're no good + car.Coupler.MaximumDistanceBetweenCars = car.Coupler.MinimumDistanceBetweenCars; + } + break; } return true; } From a74b5db8644b49f717d162847d12107ddaab9fc4 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 24 Sep 2025 12:49:44 +0100 Subject: [PATCH 45/82] New: Use MSTS adhesion values NOTE: Realised that BVE is thinking in per-car, not the per-axle of MSTS, solves massive wheelslip issues --- source/Plugins/Train.MsTs/Train.MsTs.csproj | 1 + source/Plugins/Train.MsTs/Train/Adhesion.cs | 87 +++++++++++++++++++ source/Plugins/Train.MsTs/Train/MSTSAxle.cs | 9 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 12 ++- 4 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 source/Plugins/Train.MsTs/Train/Adhesion.cs diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index 45d0a956ab..74627f0346 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -68,6 +68,7 @@ + diff --git a/source/Plugins/Train.MsTs/Train/Adhesion.cs b/source/Plugins/Train.MsTs/Train/Adhesion.cs new file mode 100644 index 0000000000..832671e0dd --- /dev/null +++ b/source/Plugins/Train.MsTs/Train/Adhesion.cs @@ -0,0 +1,87 @@ +using OpenBve.Formats.MsTs; +using TrainManager.Car; +using TrainManager.Car.Systems; + +namespace Train.MsTs +{ + internal class Adhesion + { + /// Holds a reference to the base car + private readonly CarBase baseCar; + /// The value used in wheelslip conditions + private readonly double WheelSlip; + /// The value used in normal conditions + private readonly double Normal; + /// The value used in sanding conditions + private readonly double Sanding; + + internal Adhesion(Block block, CarBase car, bool isSteamEngine) + { + baseCar = car; + try + { + WheelSlip = block.ReadSingle(); + Normal = block.ReadSingle(); + Sanding = block.ReadSingle(); + + } + catch + { + // Kuju suggested default values + // see Eng_and_wag_file_reference_guideV2.doc + if (isSteamEngine) + { + WheelSlip = 0.15; + Normal = 0.3; + Sanding = 2.0; + } + else + { + WheelSlip = 0.2; + Normal = 0.4; + Sanding = 2.0; + } + } + } + + internal double GetWheelslipValue() + { + double multiplier; + // https://www.trainsim.com/forums/forum/general-discussion/traction/68459-msts-diesel-sanding-effective + // MSTS uses a per-axle drive force calculation, so our traction model's force is divided by the number of axles + // whereas BVE thinks in terms of the whole car, so for a *hacky* version, we don't need the NumWheels divisor or + // the mass, as we're not worrying about mass per axle yet... + // Unlikely to be perfect, but doing it this way resolves massive wheelslip issues + + if (baseCar.ReAdhesionDevice is Sanders sanders && sanders.Active) + { + // Per-axle: + // multiplier = 0.95 * WheelSlip * Sanding * baseCar.CurrentMass / baseCar.DrivingWheels[0].TotalNumber / baseCar.CurrentMass; + multiplier = 0.95 * WheelSlip * Sanding; + } + else + { + if (!baseCar.FrontAxle.CurrentWheelSlip) + { + // Per-axle: + // multiplier = Normal * Sanding * baseCar.CurrentMass / baseCar.TrailingWheels[0].TotalNumber / baseCar.CurrentMass; + multiplier = Normal * Sanding; + } + else + { + // Per-axle: + // multiplier = Normal * Sanding * baseCar.CurrentMass / baseCar.DrivingWheels[0].TotalNumber / baseCar.CurrentMass; + multiplier = WheelSlip * Sanding; + } + } + + if (baseCar.TractionModel.MaximumPossibleAcceleration == 0) + { + // no possible acceleration, so can't wheelslip! + return double.MaxValue; + } + + return baseCar.TractionModel.MaximumPossibleAcceleration * multiplier; + } + } +} diff --git a/source/Plugins/Train.MsTs/Train/MSTSAxle.cs b/source/Plugins/Train.MsTs/Train/MSTSAxle.cs index a41297213e..e91cfef471 100644 --- a/source/Plugins/Train.MsTs/Train/MSTSAxle.cs +++ b/source/Plugins/Train.MsTs/Train/MSTSAxle.cs @@ -8,10 +8,13 @@ public class MSTSAxle : AbstractAxle { /// The friction properties private readonly Friction FrictionProperties; + /// The adhesion properties + private readonly Adhesion AdhesionProperties; - internal MSTSAxle(HostInterface currentHost, AbstractTrain train, AbstractCar car, Friction friction) : base(currentHost, train, car) + internal MSTSAxle(HostInterface currentHost, AbstractTrain train, AbstractCar car, Friction friction, Adhesion adhesion) : base(currentHost, train, car) { FrictionProperties = friction; + AdhesionProperties = adhesion; } public override double GetResistance(double Speed, double FrontalArea, double AirDensity, double AccelerationDueToGravity) @@ -21,9 +24,7 @@ public override double GetResistance(double Speed, double FrontalArea, double Ai public override double CriticalWheelSlipAccelerationForElectricMotor(double AccelerationDueToGravity) { - // TODO: This is the BVE formula - double NormalForceAcceleration = Follower.WorldUp.Y * AccelerationDueToGravity; - return 0.35 * Follower.AdhesionMultiplier * NormalForceAcceleration; + return AdhesionProperties.GetWheelslipValue(); } public override double CriticalWheelSlipAccelerationForFrictionBrake(double AccelerationDueToGravity) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 8bb39cc347..834ae26ecb 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -183,6 +183,9 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r } } + currentCar.FrontAxle = new MSTSAxle(Plugin.CurrentHost, train, currentCar, friction, adhesion); + currentCar.RearAxle = new MSTSAxle(Plugin.CurrentHost, train, currentCar, friction, adhesion); + if (soundFiles.Count > 0) { for (int i = 0; i < soundFiles.Count; i++) @@ -340,6 +343,8 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private Gear[] Gears; private double maxSandingSpeed; private CouplingType couplingType; + private Friction friction; + private Adhesion adhesion; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { @@ -945,9 +950,10 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } break; case KujuTokenID.Friction: - Friction friction = new Friction(block); - car.FrontAxle = new MSTSAxle(Plugin.CurrentHost, train, car, friction); - car.RearAxle = new MSTSAxle(Plugin.CurrentHost, train, car, friction); + friction = new Friction(block); + break; + case KujuTokenID.Adheasion: + adhesion = new Adhesion(block, car, currentEngineType == EngineType.Steam); break; case KujuTokenID.Coupling: while (block.Position() < block.Length() - 2) From b1aba6b298095ef22b2a0da61ab8ff44958a2511 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 6 Oct 2025 10:47:23 +0100 Subject: [PATCH 46/82] Add EQ_Res and Bell to CVF animations --- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 87e1323cf6..b894518986 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -378,6 +378,26 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject break; } break; + case PanelSubject.Eq_Res: + switch (subjectUnits) + { + case Units.PSI: + Code = "equalizingreservoir 0.000145038 *"; + break; + case Units.Inches_Of_Mercury: + Code = "equalizingreservoir 0.0002953 *"; + break; + case Units.Kilopascals: + Code = "equalizingreservoir 0.001 *"; + break; + case Units.Bar: + Code = "equalizingreservoir 0.00001 *"; + break; + case Units.Kgs_Per_Square_Cm: + Code = "equalizingreservoir 98066.5 *"; + break; + } + break; case PanelSubject.Direction: Code = "reverserNotch ++"; break; @@ -387,6 +407,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case PanelSubject.Front_Hlight: Code = "headlights"; break; + case PanelSubject.Bell: + Code = "musichorn"; + break; case PanelSubject.Horn: Code = "horn"; break; From fc1f3207f0be9000dfe05f82dab43ae3e4aa4b87 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 6 Oct 2025 17:52:43 +0100 Subject: [PATCH 47/82] Better handling for missing wagon --- source/Plugins/Train.MsTs/Train/ConsistParser.cs | 10 +++++++++- .../Train.MsTs/Train/Enums/BrakeEquipmentType.cs | 2 ++ source/TrainManager/Brake/AirBrake/ThroughPiped.cs | 6 +++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index 5bb615f2f1..a77826a1c1 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -34,6 +34,7 @@ using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; using SoundManager; +using TrainManager.BrakeSystems; using TrainManager.Car; using TrainManager.Handles; using TrainManager.Power; @@ -325,7 +326,14 @@ private void ParseBlock(Block block, ref TrainBase currentTrain) Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: No WagonFolder supplied, searching entire trainset folder."); break; case 2: - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset\\" + wagonFiles[1]), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); + string wagonDirectory = OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset\\" + wagonFiles[1]); + if (!Directory.Exists(wagonDirectory)) + { + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: WagonFolder " + wagonDirectory + " was not found."); + currentCar.CarBrake = new ThroughPiped(currentCar); // dummy + break; + } + Plugin.WagonParser.Parse(wagonDirectory, wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); break; default: Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[1], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index 3264adb08c..73e01a766a 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -15,8 +15,10 @@ enum BrakeEquipmentType Retainer_4_Position = 4, /// Twin-pipe vacuum brake is fitted. Vacuum_Brake = 5, + Vaccum_Brake = 5, /// Single-pipe vacuum brake is fitted. Vacuum_Single_Pipe = 6, + Vaccum_single_pipe = 6, /// Standard triple valve is fitted. Triple_Valve = 7, /// Triple valve that permits partial releasing of the brakes. diff --git a/source/TrainManager/Brake/AirBrake/ThroughPiped.cs b/source/TrainManager/Brake/AirBrake/ThroughPiped.cs index e728eae1b7..6f2cdbcd30 100644 --- a/source/TrainManager/Brake/AirBrake/ThroughPiped.cs +++ b/source/TrainManager/Brake/AirBrake/ThroughPiped.cs @@ -33,9 +33,9 @@ public class ThroughPiped : CarBrake { public ThroughPiped(CarBase car) : base(car, new AccelerationCurve[] {}) { - DecelerationCurves = new AccelerationCurve[] { }; - BrakeType = BrakeType.None; - BrakePipe = new BrakePipe(0); + decelerationCurves = new AccelerationCurve[] { }; + brakeType = BrakeType.None; + brakePipe = new BrakePipe(0); } public override void Update(double timeElapsed, double currentSpeed, AbstractHandle brakeHandle, out double Deceleration) From 0d8bcced449e9b33aeb9a55dfe6e7fff605fe924 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 19 Sep 2025 10:11:54 +0100 Subject: [PATCH 48/82] More digging / fixing on FreightAnim This is a completely undocumented mess... --- source/Plugins/Train.MsTs/Handles/Handle.cs | 30 ++++++---- source/Plugins/Train.MsTs/Misc/Units.cs | 13 ++-- .../Train.MsTs/Panel/Enums/PanelSubject.cs | 1 + source/Plugins/Train.MsTs/Sound/SmsParser.cs | 4 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 60 +++++++++++++++++-- 5 files changed, 83 insertions(+), 25 deletions(-) diff --git a/source/Plugins/Train.MsTs/Handles/Handle.cs b/source/Plugins/Train.MsTs/Handles/Handle.cs index e9483be52a..e8a8bc1d4e 100644 --- a/source/Plugins/Train.MsTs/Handles/Handle.cs +++ b/source/Plugins/Train.MsTs/Handles/Handle.cs @@ -44,23 +44,29 @@ private void ParseNotchDescriptionBlock(Block block, bool isPower) { Block descriptionBlock = block.ReadSubBlock(KujuTokenID.Notch); double notchPower = descriptionBlock.ReadSingle(); - descriptionBlock.ReadSingle(); // ?? - string notchDescription = descriptionBlock.ReadString(); - if (notchDescription.Equals("dummy", StringComparison.InvariantCultureIgnoreCase)) + try { - if (i == 0) + descriptionBlock.ReadSingle(); // ?? + string notchDescription = descriptionBlock.ReadString(); + if (notchDescription.Equals("dummy", StringComparison.InvariantCultureIgnoreCase)) { - notchDescription = "N"; - } - else - { - notchDescription = isPower ? "P" + i : "B" + i; - } + if (i == 0) + { + notchDescription = "N"; + } + else + { + notchDescription = isPower ? "P" + i : "B" + i; + } + } + NotchDescriptions[i] = new Tuple(notchPower, notchDescription); + } + catch + { + // ignore } - NotchDescriptions[i] = new Tuple(notchPower, notchDescription); } - } } } diff --git a/source/Plugins/Train.MsTs/Misc/Units.cs b/source/Plugins/Train.MsTs/Misc/Units.cs index ac41344cb2..cee8e54025 100644 --- a/source/Plugins/Train.MsTs/Misc/Units.cs +++ b/source/Plugins/Train.MsTs/Misc/Units.cs @@ -14,11 +14,12 @@ internal enum Units Kilometers_Per_Hour = 6, Km_Per_Hour = 6, Miles_Per_Hour = 7, - Meters_Sec = 8, - PSI = 9, - Kilo_Lbs = 10, - Kilopascals = 11, - Bar = 12, - Kgs_Per_Square_Cm = 13 + Miles_Hour_Min = 8, + Meters_Sec = 9, + PSI = 10, + Kilo_Lbs = 11, + Kilopascals = 12, + Bar = 13, + Kgs_Per_Square_Cm = 14 } } diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs index 21ae693187..31184090af 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -33,6 +33,7 @@ internal enum PanelSubject Firehole, Friction_Braking, Front_Hlight, + Fuel_Gauge, Gears, Horn, Load_Meter, diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index d92641066c..b63055f36c 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -316,7 +316,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So break; case KujuTokenID.Initial_Trigger: // when initially appears, hence nothing other than StartLoop should be valid - newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger, KujuTokenID.PlayOneShot, KujuTokenID.SetStreamVolume }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.StartLoopRelease: @@ -529,7 +529,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Variable2_Inc_Past: case KujuTokenID.Variable2_Dec_Past: currentSoundSet.VariableValue = block.ReadSingle(); // power value - newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger, KujuTokenID.PlayOneShot, KujuTokenID.SetStreamVolume }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.Variable2Controlled: diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 834ae26ecb..68499260fa 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -322,6 +322,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private double maxBrakeForce = 0; private BrakeSystemType[] brakeSystemTypes; private EngineType currentEngineType; + private WagonType currentWagonType; private double dieselIdleRPM; private double dieselMaxRPM; private double dieselRPMChangeRate; @@ -373,7 +374,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool { try { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); } catch @@ -523,7 +524,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool { try { - WagonType type = block.ReadEnumValue(default(WagonType)); + currentWagonType = block.ReadEnumValue(default(WagonType)); } catch { @@ -574,7 +575,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool if (exteriorLoaded) { CarSection exteriorCarSection = car.CarSections[CarSectionType.Exterior]; - exteriorCarSection.AppendObject(Plugin.CurrentHost, car, carObject); + exteriorCarSection.AppendObject(Plugin.CurrentHost, Vector3.Zero, car, carObject); car.CarSections[CarSectionType.Exterior] = exteriorCarSection; } else @@ -854,12 +855,60 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool break; } objectFile = OpenBveApi.Path.CombineFile(Path.GetDirectoryName(fileName), block.ReadString()); + if (!File.Exists(objectFile)) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle object file " + objectFile + " was not found"); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: FreightAnim object file " + objectFile + " was not found"); break; } + /* + * + * https://tsforum.forumotion.net/t655-freight-animation + * FreightAnim are a total mess... + * Whilst it appears they were intended to work with all cars, the positioning logic only works correctly + * with tenders. + * + * TENDERS: + * -------- + * First value: Starting Y position (full) + * Second value: Ending Y position (empty) + * Third value: Must be positive or omitted for animation to work. If negative, animation stays at full. + * + * OTHER CARS: + * ---------- + * First value: Ignored + * Second value: Must be any positive number. + * + * However, this is actually done by directly replacing the Y translation component of Matrix[0] within the shape + * This means that if our shape actually has a value here, things can get really messy. + * + * The UKTS RCH wagon loads contain a Y value of approx 2.53, which when loaded in this way actually gets discarded + * + */ + + double loadPosition = 0; + double emptyPosition = 0; + try + { + loadPosition = block.ReadSingle(); + emptyPosition = block.ReadSingle(); + // may also be one more number, but this appears unused + } + catch + { + // ignore + } + + if (isEngine == false && currentWagonType != WagonType.Tender) + { + if (emptyPosition == 0) + { + break; + } + loadPosition = 0; + } + for (int i = 0; i < Plugin.CurrentHost.Plugins.Length; i++) { @@ -868,8 +917,9 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool Plugin.CurrentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject freightObject); if (exteriorLoaded) { + CarSection exteriorCarSection = car.CarSections[CarSectionType.Exterior]; - exteriorCarSection.AppendObject(Plugin.CurrentHost, car, freightObject); + exteriorCarSection.AppendObject(Plugin.CurrentHost, new Vector3(0, loadPosition, 0), car, freightObject); car.CarSections[CarSectionType.Exterior] = exteriorCarSection; } else From c4f42ef1b9652d903692d908c3c0964b4cd641c9 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 6 Oct 2025 15:45:43 +0100 Subject: [PATCH 49/82] Missing tokens, non-standard variants --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 19 +++++++++++++++++-- .../Train/Enums/BrakeEquipmentType.cs | 4 +++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index b63055f36c..80360db27e 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -275,7 +275,22 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So int numStreams = block.ReadInt32(); for (int i = 0; i < numStreams; i++) { - newBlock = block.ReadSubBlock(new[] { KujuTokenID.Stream}); + newBlock = block.ReadSubBlock(); + if (newBlock.Token != KujuTokenID.Stream) + { + i--; + if (newBlock.Token != KujuTokenID.Skip) + { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Unexpected additional block " + newBlock.Token + " encounted within Stream block in SMS file " + currentFile); + } + if (block.Length() - block.Position() <= 3) + { + // WARN: incorrect number of streams supplied + break; + } + continue; + } + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); if (block.Length() - block.Position() <= 3) { @@ -500,7 +515,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So break; case KujuTokenID.Discrete_Trigger: currentSoundSet.CurrentTrigger = (SoundTrigger)block.ReadInt32(); // stored as integer - newBlock = block.ReadSubBlock(new[] { KujuTokenID.PlayOneShot, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.SetStreamVolume }); + newBlock = block.ReadSubBlock(new[] { KujuTokenID.PlayOneShot, KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.SetStreamVolume, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; case KujuTokenID.Variable_Trigger: diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index 73e01a766a..ad44143de0 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -40,6 +40,8 @@ enum BrakeEquipmentType Air_Single_Pipe = 14, /// One pipe for control air, one pipe for supply air Air_Twin_Pipe = 15, - Air_Brake = 15 + Air_Brake = 15, + /// Graduated triple-release valve + Graduated_Triple_Release_Valve = 16 } } From 3c98b659e117762a4f9d1ca60ee0ee9016b2424d Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 6 Oct 2025 19:30:10 +0100 Subject: [PATCH 50/82] Actual error messages for incorrect number of streams / triggers --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 21 +++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 80360db27e..c10a1b027a 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -191,8 +191,15 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So // file root while (block.Position() < block.Length() - 3) { - newBlock = block.ReadSubBlock(true); - ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); + try + { + newBlock = block.ReadSubBlock(true); + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); + } + catch + { + break; + } } break; case KujuTokenID.ScalabiltyGroup: @@ -273,7 +280,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Streams: // each stream represents a unique sound int numStreams = block.ReadInt32(); - for (int i = 0; i < numStreams; i++) + for (int i = 0; i < numStreams - 1; i++) { newBlock = block.ReadSubBlock(); if (newBlock.Token != KujuTokenID.Stream) @@ -285,7 +292,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So } if (block.Length() - block.Position() <= 3) { - // WARN: incorrect number of streams supplied + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numStreams + ", but only found " + (i + 1) + " in Stream block in SMS file " + currentFile); break; } continue; @@ -294,7 +301,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); if (block.Length() - block.Position() <= 3) { - // WARN: incorrect number of streams supplied + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numStreams + ", but only found " + (i + 1) + " in Stream block in SMS file " + currentFile); break; } } @@ -317,14 +324,14 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So break; case KujuTokenID.Triggers: int numTriggers = block.ReadInt32(); - for (int i = 0; i < numTriggers; i++) + for (int i = 0; i < numTriggers - 1; i++) { // two triggers per sound set (start + stop) newBlock = block.ReadSubBlock(new [] {KujuTokenID.Variable_Trigger, KujuTokenID.Initial_Trigger, KujuTokenID.Discrete_Trigger, KujuTokenID.Random_Trigger, KujuTokenID.Dist_Travelled_Trigger}); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); if (block.Length() - block.Position() <= 3) { - // WARN: incorrect number of triggers supplied + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numTriggers + ", but only found " + (i + 1) + " in Triggers block in SMS file " + currentFile); break; } } From 6e6601be95004383f0500edb4bc771765b45d9d4 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 31 Oct 2025 14:54:14 +0000 Subject: [PATCH 51/82] Missing tokens, reformat units Tune friction, add default if we find something off --- source/Plugins/Train.MsTs/Misc/Units.cs | 31 +++++++++++---------- source/Plugins/Train.MsTs/Train/Friction.cs | 22 +++++++++++---- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/source/Plugins/Train.MsTs/Misc/Units.cs b/source/Plugins/Train.MsTs/Misc/Units.cs index cee8e54025..5900a81e8c 100644 --- a/source/Plugins/Train.MsTs/Misc/Units.cs +++ b/source/Plugins/Train.MsTs/Misc/Units.cs @@ -6,20 +6,21 @@ namespace Train.MsTs internal enum Units { Unknown = 0, - Amps = 1, - Volts = 2, - Kilovolts = 3, - Gallons = 4, - Inches_Of_Mercury = 5, - Kilometers_Per_Hour = 6, - Km_Per_Hour = 6, - Miles_Per_Hour = 7, - Miles_Hour_Min = 8, - Meters_Sec = 9, - PSI = 10, - Kilo_Lbs = 11, - Kilopascals = 12, - Bar = 13, - Kgs_Per_Square_Cm = 14 + Milliamps, + Amps, + Volts, + Kilovolts, + Gallons, + Inches_Of_Mercury, + Kilometers_Per_Hour, + Km_Per_Hour = Kilometers_Per_Hour, + Miles_Per_Hour, + Miles_Hour_Min, + Meters_Sec, + PSI, + Kilo_Lbs, + Kilopascals, + Bar, + Kgs_Per_Square_Cm } } diff --git a/source/Plugins/Train.MsTs/Train/Friction.cs b/source/Plugins/Train.MsTs/Train/Friction.cs index 7370d566b9..26317ec1ba 100644 --- a/source/Plugins/Train.MsTs/Train/Friction.cs +++ b/source/Plugins/Train.MsTs/Train/Friction.cs @@ -19,11 +19,23 @@ internal class Friction internal Friction(Block block) { - C1 = block.ReadSingle(UnitOfTorque.NewtonMetersPerSecond); - E1 = block.ReadSingle(); - V2 = block.ReadSingle(UnitOfVelocity.MetersPerSecond); - C2 = block.ReadSingle(UnitOfTorque.NewtonMetersPerSecond); - E2 = block.ReadSingle(); + try + { + C1 = block.ReadSingle(UnitOfTorque.NewtonMetersPerSecond); + E1 = block.ReadSingle(); + V2 = block.ReadSingle(UnitOfVelocity.MetersPerSecond); + C2 = block.ReadSingle(UnitOfTorque.NewtonMetersPerSecond); + E2 = block.ReadSingle(); + } + catch + { + C1 = 100; + E1 = 1; + V2 = -1; + C2 = 0; + E2 = 1; + } + } internal double GetResistanceValue(double speed) From 5f97ebea60d60c4eefa821462bdf8e68a3a13461 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 23 Oct 2025 12:51:39 +0100 Subject: [PATCH 52/82] Fixes Assorted fixes --- .../OpenBVE/UserInterface/formMain.Start.cs | 7 + .../Plugins/Train.MsTs/Panel/CabComponent.cs | 21 ++- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 6 +- .../Plugins/Train.MsTs/Train/ConsistParser.cs | 2 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 178 +++++++++++------- .../Brake/AirBrake/ThroughPiped.cs | 5 +- source/TrainManager/Brake/CarBrake.cs | 2 +- .../Power/MSTS/MSTSAccelerationCurve.cs | 26 ++- 8 files changed, 170 insertions(+), 77 deletions(-) diff --git a/source/OpenBVE/UserInterface/formMain.Start.cs b/source/OpenBVE/UserInterface/formMain.Start.cs index 54f4622e78..fa3074a3e2 100644 --- a/source/OpenBVE/UserInterface/formMain.Start.cs +++ b/source/OpenBVE/UserInterface/formMain.Start.cs @@ -757,6 +757,13 @@ private void PopulateTrainList(string selectedFolder, ListView listView, bool pa { for (int j = 0; j < Program.CurrentHost.Plugins.Length; j++) { + string fileName = Path.GetFileName(Files[i]); + if (fileName[0] == '#' && fileName.EndsWith(".con", StringComparison.InvariantCultureIgnoreCase)) + { + // MSTS / ORTS use a hash at the start of the filename to deliminate AI consists + // These generally have missing cabviews etc- Hide from visibility in the main menu + continue; + } if (Program.CurrentHost.Plugins[j].Train != null && Program.CurrentHost.Plugins[j].Train.CanLoadTrain(Files[i])) { ListViewItem Item = listviewTrainFolders.Items.Add(System.IO.Path.GetFileName(Files[i])); diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 269cdf0a5b..eed84b5af6 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -444,7 +444,26 @@ private void ReadSubBlock(Block block) break; case KujuTokenID.Graphic: string s = block.ReadString(); - TexturePath = OpenBveApi.Path.CombineFile(CabviewFileParser.CurrentFolder, s); + if (!string.IsNullOrEmpty(s)) + { + try + { + TexturePath = OpenBveApi.Path.CombineFile(CabviewFileParser.CurrentFolder, s); + } + catch + { + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "The texture path contains invalid characters in CabComponent " + Type + " in CVF"); + } + + if (!File.Exists(TexturePath)) + { + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "The texture file " + s + " was not found in CabComponent " + Type + " in CVF"); + } + } + else + { + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "A texture file was not specified in CabComponent " + Type + " in CVF"); + } break; case KujuTokenID.Position: Position.X = block.ReadSingle(); diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index b894518986..c0ffee6efd 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -199,7 +199,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) for (int i = 0; i < cabComponents.Count; i++) { cabComponents[i].Create(ref currentCar, currentLayer); - + currentLayer++; // component layering stacks downwards directly through the cabview } return true; @@ -219,7 +219,7 @@ private static void ParseBlock(Block block) while (controlCount > 0) { newBlock = block.ReadSubBlock(); - CabComponent currentComponent = new CabComponent(newBlock, currentCabView.Position); + CabComponent currentComponent = new CabComponent(newBlock, cabViews[0].Position); // cab components can only be applied to CabView #0, others are static views currentComponent.Parse(); cabComponents.Add(currentComponent); controlCount--; @@ -359,6 +359,7 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject } break; case PanelSubject.Main_Res: + case PanelSubject.Vacuum_Reservoir_Pressure: switch (subjectUnits) { case Units.PSI: @@ -410,6 +411,7 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case PanelSubject.Bell: Code = "musichorn"; break; + case PanelSubject.Whistle: case PanelSubject.Horn: Code = "horn"; break; diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index a77826a1c1..71fa149dca 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -190,9 +190,9 @@ internal void ReadConsist(string fileName, ref AbstractTrain parsedTrain) } train.Cars[train.Cars.Length - 1].RearAxle.Follower.TriggerType = EventTriggerType.RearCarRearAxle; - train.Cars[train.DriverCar].Windscreen = new Windscreen(256, 10.0, train.Cars[train.DriverCar]); train.Cars[train.DriverCar].Windscreen.Wipers = new WindscreenWiper(train.Cars[parsedTrain.DriverCar].Windscreen, WiperPosition.Left, WiperPosition.Left, 1.0, 0.0, true); // hack: zero hold time so they act as fast with two states + train.Specs.AveragesPressureDistribution = false; train.PlaceCars(0.0); } diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 68499260fa..05f156a7a7 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -332,11 +332,12 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private double dieselMaxTractiveEffortSpeed; private double maxEngineAmps; private double maxBrakeAmps; - private double mainReservoirMinimumPressure; - private double mainReservoirMaximumPressure; - private double brakeCylinderMaximumPressure; + private double mainReservoirMinimumPressure = 690000.0; + private double mainReservoirMaximumPressure = 780000.0; + private double brakeCylinderMaximumPressure = 440000.0; private double emergencyRate; private double releaseRate; + private double compressionRate = 3500; private double maxVelocity; private bool hasAntiSlipDevice; private List vigilanceDevices; @@ -397,42 +398,59 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } else { - if (brakeSystemTypes.Contains(BrakeSystemType.EP)) + if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) { - // Combined air brakes and control signals - // Assume equivilant to ElectromagneticStraightAirBrake - car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); - } - else if (brakeSystemTypes.Contains(BrakeSystemType.ECP)) - { - // Complex computer control - // Assume equivialant to ElectricCommandBrake at the minute - car.CarBrake = new ElectricCommandBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + AirBrake airBrake; + // FIXME: MR values needs to be (close) in proportion to the BC else the physics bug out + double bcVal = brakeCylinderMaximumPressure / 440000; + mainReservoirMinimumPressure = 690000.0 * bcVal; + mainReservoirMaximumPressure = 780000.0 * bcVal; + double operatingPressure = brakeCylinderMaximumPressure + 0.75 * (mainReservoirMinimumPressure - brakeCylinderMaximumPressure); + + if (brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) + { + // Combined air brakes and control signals + // Assume equivilant to ElectromagneticStraightAirBrake + airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, true); + } + else + { + // Assume equivilant to ElectromagneticStraightAirBrake + airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, false); + } + + airBrake.MainReservoir = new MainReservoir(mainReservoirMinimumPressure, mainReservoirMaximumPressure, 0.01, 0.075 / train.Cars.Length); + airBrake.Compressor = new Compressor(5000.0, airBrake.MainReservoir, car); + airBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); + double r = 200000.0 / airBrake.BrakeCylinder.EmergencyMaximumPressure - 1.0; + if (r < 0.1) r = 0.1; + if (r > 1.0) r = 1.0; + airBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * operatingPressure, 200000.0, 0.5, r); + airBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); + airBrake.EqualizingReservoir.NormalPressure = 1.005 * operatingPressure; + airBrake.StraightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); + + car.CarBrake = airBrake; } - else if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) + + if (brakeSystemTypes.Contains(BrakeSystemType.Vaccum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) { - // The car contains no control gear, but is air / vac braked - // Assume equivilant to AutomaticAirBrake - // NOTE: This must be last in the else-if chain to enure that a vehicle with EP / ECP and these declared is setup correctly - car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + VaccumBrake vaccumBrake = new VaccumBrake(car, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + vaccumBrake.MainReservoir = new MainReservoir(71110, 84660, 0.01, 0.075 / train.Cars.Length); // ~21in/hg - ~25in/hg + vaccumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); + vaccumBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, 1.0); + vaccumBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); + vaccumBrake.EqualizingReservoir.NormalPressure = 1.005 * (vaccumBrake.BrakeCylinder.EmergencyMaximumPressure + 0.75 * (vaccumBrake.MainReservoir.MinimumPressure - vaccumBrake.BrakeCylinder.EmergencyMaximumPressure)); + vaccumBrake.BrakePipe = new BrakePipe(brakeCylinderMaximumPressure, 10000000.0, 1500000.0, 5000000.0, false); + car.CarBrake = vaccumBrake; } - - car.CarBrake.mainReservoir = new MainReservoir(690000.0, 780000.0, 0.01, 0.075 / train.Cars.Length); - car.CarBrake.airCompressor = new Compressor(5000.0, car.CarBrake.mainReservoir, car); - car.CarBrake.equalizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - car.CarBrake.equalizingReservoir.NormalPressure = 1.005 * 490000.0; - double r = 200000.0 / 440000.0 - 1.0; - if (r < 0.1) r = 0.1; - if (r > 1.0) r = 1.0; - car.CarBrake.auxiliaryReservoir = new AuxiliaryReservoir(0.975 * 490000.0, 200000.0, 0.5, r); - car.CarBrake.brakeCylinder = new BrakeCylinder(440000.0, 440000.0, 0.3 * 300000.0, 300000.0, 200000.0); - car.CarBrake.straightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); - + car.CarBrake.BrakeType = car.Index == train.DriverCar ? BrakeType.Main : BrakeType.Auxiliary; + car.CarBrake.JerkUp = 10; + car.CarBrake.JerkDown = 10; } - - car.CarBrake.brakePipe = new BrakePipe(490000.0, 10000000.0, 1500000.0, 5000000.0, true); - car.CarBrake.JerkUp = 10; - car.CarBrake.JerkDown = 10; + car.CarBrake.Initialize(TrainStartMode.ServiceBrakesAts); } break; case KujuTokenID.Engine: @@ -474,42 +492,59 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } else { - if (brakeSystemTypes.Contains(BrakeSystemType.EP)) + if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) { - // Combined air brakes and control signals - // Assume equivilant to ElectromagneticStraightAirBrake - car.CarBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); - } - else if (brakeSystemTypes.Contains(BrakeSystemType.ECP)) - { - // Complex computer control - // Assume equivialant to ElectricCommandBrake at the minute - car.CarBrake = new ElectricCommandBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + AirBrake airBrake; + // FIXME: MR values needs to be (close) in proportion to the BC else the physics bug out + double bcVal = brakeCylinderMaximumPressure / 440000; + mainReservoirMinimumPressure = 690000.0 * bcVal; + mainReservoirMaximumPressure = 780000.0 * bcVal; + double operatingPressure = brakeCylinderMaximumPressure + 0.75 * (mainReservoirMinimumPressure - brakeCylinderMaximumPressure); + + if (brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) + { + // Combined air brakes and control signals + // Assume equivilant to ElectromagneticStraightAirBrake + airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, true); + } + else + { + // Assume equivilant to ElectromagneticStraightAirBrake + airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, false); + } + + airBrake.MainReservoir = new MainReservoir(mainReservoirMinimumPressure, mainReservoirMaximumPressure, 0.01, 0.075 / train.Cars.Length); + airBrake.Compressor = new Compressor(compressionRate, airBrake.MainReservoir, car); + airBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); + double r = 200000.0 / airBrake.BrakeCylinder.EmergencyMaximumPressure - 1.0; + if (r < 0.1) r = 0.1; + if (r > 1.0) r = 1.0; + airBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * operatingPressure, 200000.0, 0.5, r); + airBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); + airBrake.EqualizingReservoir.NormalPressure = 1.005 * operatingPressure; + airBrake.StraightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); + + car.CarBrake = airBrake; } - else if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) + + if (brakeSystemTypes.Contains(BrakeSystemType.Vaccum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) { - // The car contains no control gear, but is air / vac braked - // Assume equivilant to AutomaticAirBrake - // NOTE: This must be last in the else-if chain to enure that a vehicle with EP / ECP and these declared is setup correctly - car.CarBrake = new AutomaticAirBrake(EletropneumaticBrakeType.DelayFillingControl, car, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + VaccumBrake vaccumBrake = new VaccumBrake(car, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + vaccumBrake.MainReservoir = new MainReservoir(71110, 84660, 0.01, 0.075 / train.Cars.Length); // ~21in/hg - ~25in/hg + vaccumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); + vaccumBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, 1.0); + vaccumBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); + vaccumBrake.EqualizingReservoir.NormalPressure = 1.005 * (vaccumBrake.BrakeCylinder.EmergencyMaximumPressure + 0.75 * (vaccumBrake.MainReservoir.MinimumPressure - vaccumBrake.BrakeCylinder.EmergencyMaximumPressure)); + vaccumBrake.BrakePipe = new BrakePipe(brakeCylinderMaximumPressure, 10000000.0, 1500000.0, 5000000.0, false); + car.CarBrake = vaccumBrake; } - - car.CarBrake.mainReservoir = new MainReservoir(mainReservoirMinimumPressure, mainReservoirMaximumPressure, 0.01, 0.075 / train.Cars.Length); - car.CarBrake.airCompressor = new Compressor(5000.0, car.CarBrake.mainReservoir, car); - car.CarBrake.equalizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - car.CarBrake.equalizingReservoir.NormalPressure = 1.005 * brakeCylinderMaximumPressure; - double r = 200000.0 / 440000.0 - 1.0; - if (r < 0.1) r = 0.1; - if (r > 1.0) r = 1.0; - car.CarBrake.auxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, r); - car.CarBrake.brakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure, 0.3 * 300000.0, 300000.0, 200000.0); - car.CarBrake.straightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); - + car.CarBrake.BrakeType = car.Index == car.baseTrain.DriverCar || isEngine ? BrakeType.Main : BrakeType.Auxiliary; + car.CarBrake.JerkUp = 10; + car.CarBrake.JerkDown = 10; } - - car.CarBrake.brakePipe = new BrakePipe(brakeCylinderMaximumPressure, 10000000.0, 1500000.0, 5000000.0, true); - car.CarBrake.JerkUp = 10; - car.CarBrake.JerkDown = 10; + car.CarBrake.Initialize(TrainStartMode.ServiceBrakesAts); break; case KujuTokenID.Type: switch (block.ParentBlock.Token) @@ -591,19 +626,19 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.Size: // Physical size of the car car.Width = block.ReadSingle(UnitOfLength.Meter); - if (car.Width == 0) + if (car.Width <= 0.1) // see for example LU1938TS - typo makes the car 2.26mm high { Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle width is invalid."); car.Width = 2; } car.Height = block.ReadSingle(UnitOfLength.Meter); - if (car.Height == 0) + if (car.Height <= 0.1) { Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle height is invalid."); car.Height = 2; } car.Length = block.ReadSingle(UnitOfLength.Meter); - if (car.Length == 0) + if (car.Length <= 0.5) { Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Vehicle length is invalid."); car.Length = 25; @@ -829,6 +864,13 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.TrainBrakesControllerEmergencyApplicationRate: emergencyRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); break; + case KujuTokenID.AirBrakesAirCompressorPowerRating: + compressionRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + if (compressionRate < 3500 || compressionRate > 34475) // assume valid range to be 0.5psi/s to 5psi/s + { + compressionRate = 3500; + } + break; case KujuTokenID.TrainBrakesControllerMaxReleaseRate: releaseRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); break; diff --git a/source/TrainManager/Brake/AirBrake/ThroughPiped.cs b/source/TrainManager/Brake/AirBrake/ThroughPiped.cs index 6f2cdbcd30..ecbf67d7ad 100644 --- a/source/TrainManager/Brake/AirBrake/ThroughPiped.cs +++ b/source/TrainManager/Brake/AirBrake/ThroughPiped.cs @@ -33,9 +33,8 @@ public class ThroughPiped : CarBrake { public ThroughPiped(CarBase car) : base(car, new AccelerationCurve[] {}) { - decelerationCurves = new AccelerationCurve[] { }; - brakeType = BrakeType.None; - brakePipe = new BrakePipe(0); + BrakeType = BrakeType.None; + BrakePipe = new BrakePipe(0); } public override void Update(double timeElapsed, double currentSpeed, AbstractHandle brakeHandle, out double Deceleration) diff --git a/source/TrainManager/Brake/CarBrake.cs b/source/TrainManager/Brake/CarBrake.cs index eacf627852..78409ebbd5 100644 --- a/source/TrainManager/Brake/CarBrake.cs +++ b/source/TrainManager/Brake/CarBrake.cs @@ -102,7 +102,7 @@ public double DecelerationAtServiceMaximumPressure(int Notch, double currentSpee return 0; } - if (decelerationCurves[0] is MSTSDecelerationCurve dec) + if (DecelerationCurves[0] is MSTSDecelerationCurve dec) { return dec.MaximumAcceleration; } diff --git a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs index 3bb7af082a..4f776bf61e 100644 --- a/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs +++ b/source/TrainManager/Power/MSTS/MSTSAccelerationCurve.cs @@ -1,4 +1,28 @@ -// ReSharper disable InconsistentNaming +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ReSharper disable InconsistentNaming using TrainManager.Car; using TrainManager.Handles; From 0890b6f1a8bd89dd8d73f26f88a7b3f180bf405a Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 23 Oct 2025 18:37:03 +0100 Subject: [PATCH 53/82] Assorted fixes --- source/OpenBveApi/Objects/ObjectInterface.cs | 2 +- .../OpenBveApi/Objects/ObjectTypes/UnifiedObject.cs | 1 + .../Train.MsTs/Train/Enums/BrakeEquipmentType.cs | 12 +++++++++--- source/Plugins/Train.MsTs/Train/VehicleParser.cs | 3 +++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/source/OpenBveApi/Objects/ObjectInterface.cs b/source/OpenBveApi/Objects/ObjectInterface.cs index e3e6d96bb9..7d27418252 100644 --- a/source/OpenBveApi/Objects/ObjectInterface.cs +++ b/source/OpenBveApi/Objects/ObjectInterface.cs @@ -47,7 +47,7 @@ public virtual void SetObjectParser(object parserType) /// Receives the object. /// The encoding for the object /// Whether loading the object was successful. - public abstract bool LoadObject(string path, System.Text.Encoding Encoding, out UnifiedObject unifiedObject); + public abstract bool LoadObject(string path, System.Text.Encoding textEncoding, out UnifiedObject unifiedObject); /// Loads the specified object. /// The path to the file or folder that contains the object. diff --git a/source/OpenBveApi/Objects/ObjectTypes/UnifiedObject.cs b/source/OpenBveApi/Objects/ObjectTypes/UnifiedObject.cs index 08cfe798d2..510b3c1af3 100644 --- a/source/OpenBveApi/Objects/ObjectTypes/UnifiedObject.cs +++ b/source/OpenBveApi/Objects/ObjectTypes/UnifiedObject.cs @@ -87,6 +87,7 @@ public void CreateObject(Vector3 Position, Transformation WorldTransformation, T /// The X value /// The Y value /// The Z value + /// Controls whether the translation is added to the root matrix, or replaces it public abstract void ApplyTranslation(double x, double y, double z, bool absoluteTranslation = false); } diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index ad44143de0..ae7dafc40a 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -16,13 +16,15 @@ enum BrakeEquipmentType /// Twin-pipe vacuum brake is fitted. Vacuum_Brake = 5, Vaccum_Brake = 5, + Vacumn_Brake = 5, // typo /// Single-pipe vacuum brake is fitted. Vacuum_Single_Pipe = 6, - Vaccum_single_pipe = 6, + Vaccum_Single_Pipe = 6, /// Standard triple valve is fitted. Triple_Valve = 7, /// Triple valve that permits partial releasing of the brakes. Graduated_Release_Triple_Valve = 8, + Graduate_Release_Triple_valve = 8, // typo /// Electrically controlled brake system is fitted. Release and application of the brakes are independently controlled. EP_Brake = 9, EP_Brakes = 9, @@ -30,7 +32,7 @@ enum BrakeEquipmentType ECP_Brake = 10, /// Air tank used for normal service brake applications. This is required for all brake systems. Auxiliary_Reservoir = 11, - Auxilary_Reservoir = 11, + Auxilary_Reservoir = 11, // typo /// Air tank used for emergency applications. /// This is optional. Emergency_Brake_Reservoir = 12, @@ -42,6 +44,10 @@ enum BrakeEquipmentType Air_Twin_Pipe = 15, Air_Brake = 15, /// Graduated triple-release valve - Graduated_Triple_Release_Valve = 16 + Graduated_Triple_Release_Valve = 16, + /// Through piped for vacuum + Vacuum_Piped = 17, + /// Through piped for air + Air_Piped = 18 } } diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 05f156a7a7..9c2c154527 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -166,6 +166,9 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r // NOT YET IMPLEMENTED currentCar.TractionModel = new BVEMotorCar(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }); break; + case EngineType.NoEngine: + currentCar.TractionModel = new BVETrailerCar(currentCar); + break; } if (currentCar.ReAdhesionDevice == null) From 132b5d01dd64216c8b1c9672dc3a471fffa014bc Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 23 Oct 2025 19:02:55 +0100 Subject: [PATCH 54/82] Some missing variants, allow wagons outside TrainCfg consist block --- source/OpenBveApi/World/UnitConversions/Torque.cs | 11 +++++++---- source/Plugins/Train.MsTs/Train/ConsistParser.cs | 12 +++++++++--- source/Plugins/Train.MsTs/Train/VehicleParser.cs | 12 ++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/source/OpenBveApi/World/UnitConversions/Torque.cs b/source/OpenBveApi/World/UnitConversions/Torque.cs index 913622a983..0d8223f99b 100644 --- a/source/OpenBveApi/World/UnitConversions/Torque.cs +++ b/source/OpenBveApi/World/UnitConversions/Torque.cs @@ -1,4 +1,4 @@ -//Simplified BSD License (BSD-2-Clause) +//Simplified BSD License (BSD-2-Clause) // //Copyright (c) 2025, Christopher Lees, The OpenBVE Project // @@ -31,6 +31,8 @@ public enum UnitOfTorque { /// Newton-meters per second NewtonMetersPerSecond, + /// Kilo-newton-meters per second + KiloNewtonMetersPerSecond, /// Foot pounds FootPound @@ -42,11 +44,12 @@ public class TorqueConverter : UnitConverter static TorqueConverter() { BaseUnit = UnitOfTorque.NewtonMetersPerSecond; - RegisterConversion(UnitOfTorque.FootPound, v => v * 0.7375621493, v => v / 1.3558179483); - KnownUnits = new Dictionary + RegisterConversion(UnitOfTorque.KiloNewtonMetersPerSecond, v => v / 1000, v => v * 1000); + RegisterConversion(UnitOfTorque.FootPound, v => v * 0.7375621493, v => v / 1.3558179483); + KnownUnits = new Dictionary { // n.b. assume that torque in plain newtons is actually newton meters - { "n/m/s", UnitOfTorque.NewtonMetersPerSecond }, { "nms", UnitOfTorque.NewtonMetersPerSecond }, { "newtonmeterspersecond", UnitOfTorque.NewtonMetersPerSecond }, { "n", UnitOfTorque.NewtonMetersPerSecond }, { "lb/ft", UnitOfTorque.FootPound } + {"n/m/s", UnitOfTorque.NewtonMetersPerSecond}, {"nms", UnitOfTorque.NewtonMetersPerSecond}, {"newtonmeterspersecond", UnitOfTorque.NewtonMetersPerSecond}, {"n", UnitOfTorque.NewtonMetersPerSecond}, {"knms", UnitOfTorque.KiloNewtonMetersPerSecond}, {"lb/ft", UnitOfTorque.FootPound} }; } diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index 71fa149dca..e91bc54c8c 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -205,9 +205,15 @@ private void ParseBlock(Block block, ref TrainBase currentTrain) switch (block.Token) { default: - newBlock = block.ReadSubBlock(); - ParseBlock(newBlock, ref currentTrain); + while (block.Length() - block.Position() > 2) + { + // YUCK: If valid wagon blocks are outside the TrainCfg block (incorrect terminator) + // MSTS actually adds them to the end of the train, e.g. see default oenoloco.con + newBlock = block.ReadSubBlock(true); + ParseBlock(newBlock, ref currentTrain); + } break; + case KujuTokenID.Comment: case KujuTokenID.Default: // presumably used internally by MSTS, not useful block.Skip((int)block.Length()); @@ -221,7 +227,7 @@ private void ParseBlock(Block block, ref TrainBase currentTrain) { try { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, ref currentTrain); } catch diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 9c2c154527..65a42cea0a 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -776,7 +776,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool while (block.Position() < block.Length() - 2) { // large number of potential controls when including diesel + steam, so allow *any* block here - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, fileName, ref wagonName, true, ref car, ref train); } break; @@ -889,7 +889,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool VigilanceDevice device = VigilanceDevice.CreateVigilanceDevice(block.Token); while (block.Position() < block.Length() - 2) { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); device.ParseBlock(newBlock); } vigilanceDevices.Add(device); @@ -978,14 +978,14 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.Effects: while (block.Position() < block.Length() - 2) { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); } break; case KujuTokenID.DieselSpecialEffects: while (block.Position() < block.Length() - 2) { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); } break; @@ -1053,14 +1053,14 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.Coupling: while (block.Position() < block.Length() - 2) { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); } break; case KujuTokenID.Spring: while (block.Position() < block.Length() - 2) { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); } break; From 9732ac864992ada918529ec32aad7e79391a7b84 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 24 Oct 2025 16:23:15 +0100 Subject: [PATCH 55/82] Add MSTS directory to options --- assets/Languages/ca-ES.xlf | 4 + assets/Languages/cs-CZ.xlf | 3 + assets/Languages/de-CH.xlf | 3 + assets/Languages/de-DE.xlf | 4 + assets/Languages/en-GB.xlf | 3 + assets/Languages/en-US.xlf | 3 + assets/Languages/es-ES.xlf | 4 + assets/Languages/fi-FI.xlf | 3 + assets/Languages/fr-FR.xlf | 3 + assets/Languages/hu-HU.xlf | 4 + assets/Languages/id-ID.xlf | 4 + assets/Languages/it-IT.xlf | 3 + assets/Languages/ja-JP.xlf | 3 + assets/Languages/ko-KR.xlf | 3 + assets/Languages/ms_MY.xlf | 4 + assets/Languages/nb_NO.xlf | 4 + assets/Languages/nl-NL.xlf | 3 + assets/Languages/pl-PL.xlf | 4 + assets/Languages/pt-BR.xlf | 3 + assets/Languages/pt-PT.xlf | 4 + assets/Languages/ro-RO.xlf | 3 + assets/Languages/ru-RU.xlf | 3 + assets/Languages/sk-SK.xlf | 3 + assets/Languages/uk-UA.xlf | 3 + assets/Languages/zh-CN.xlf | 3 + assets/Languages/zh-HK.xlf | 3 + assets/Languages/zh-TW.xlf | 3 + source/OpenBVE/System/Options.cs | 1 + .../UserInterface/formMain.Designer.cs | 2724 +++++++++-------- source/OpenBVE/UserInterface/formMain.cs | 14 + source/OpenBVE/UserInterface/formMain.resx | 4 +- source/OpenBveApi/System/FileSystem.cs | 19 +- source/Plugins/Train.MsTs/Handles/Handle.cs | 1 + source/Plugins/Train.MsTs/Plugin.cs | 27 +- .../Plugins/Train.MsTs/Train/ConsistParser.cs | 42 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 4 +- 36 files changed, 1552 insertions(+), 1374 deletions(-) diff --git a/assets/Languages/ca-ES.xlf b/assets/Languages/ca-ES.xlf index 6d0714b94e..d194a26158 100644 --- a/assets/Languages/ca-ES.xlf +++ b/assets/Languages/ca-ES.xlf @@ -1620,6 +1620,10 @@ Other items installation directory: Carpeta d'instal·lació d'altres paquets: + + MSTS installation directory: + Carpeta d'instal·lació de MSTS: + Package compression format: Format de compressió dels paquets: diff --git a/assets/Languages/cs-CZ.xlf b/assets/Languages/cs-CZ.xlf index 0af0804201..5e3b2d4f53 100644 --- a/assets/Languages/cs-CZ.xlf +++ b/assets/Languages/cs-CZ.xlf @@ -1635,6 +1635,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/de-CH.xlf b/assets/Languages/de-CH.xlf index b1d6e59e66..569b331c7c 100644 --- a/assets/Languages/de-CH.xlf +++ b/assets/Languages/de-CH.xlf @@ -1636,6 +1636,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/de-DE.xlf b/assets/Languages/de-DE.xlf index 88b3b1535c..1426c2dfb1 100644 --- a/assets/Languages/de-DE.xlf +++ b/assets/Languages/de-DE.xlf @@ -1635,6 +1635,10 @@ Other items installation directory: Installationsordner für sonstiges: + + MSTS installation directory: + MSTS installationsordner: + Package compression format: Paketkomprimierungsformat: diff --git a/assets/Languages/en-GB.xlf b/assets/Languages/en-GB.xlf index b0da83cb1e..a8e7eed9c6 100644 --- a/assets/Languages/en-GB.xlf +++ b/assets/Languages/en-GB.xlf @@ -1636,6 +1636,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/en-US.xlf b/assets/Languages/en-US.xlf index d57dd5f404..b21ded3cda 100755 --- a/assets/Languages/en-US.xlf +++ b/assets/Languages/en-US.xlf @@ -1243,6 +1243,9 @@ Other items installation directory: + + MSTS installation directory: + Package compression format: diff --git a/assets/Languages/es-ES.xlf b/assets/Languages/es-ES.xlf index 3eef65cc87..07a4a32f6b 100644 --- a/assets/Languages/es-ES.xlf +++ b/assets/Languages/es-ES.xlf @@ -1636,6 +1636,10 @@ Other items installation directory: Carpeta de instalación de otros paquetes: + + MSTS installation directory: + Carpeta de instalación de MSTS: + Package compression format: Formato de compresión de los paquetes: diff --git a/assets/Languages/fi-FI.xlf b/assets/Languages/fi-FI.xlf index fc825ef21b..1695d37af9 100644 --- a/assets/Languages/fi-FI.xlf +++ b/assets/Languages/fi-FI.xlf @@ -1635,6 +1635,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/fr-FR.xlf b/assets/Languages/fr-FR.xlf index 49341868d7..e078483d69 100644 --- a/assets/Languages/fr-FR.xlf +++ b/assets/Languages/fr-FR.xlf @@ -1635,6 +1635,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/hu-HU.xlf b/assets/Languages/hu-HU.xlf index a6a3da38a9..ba880ecd2e 100644 --- a/assets/Languages/hu-HU.xlf +++ b/assets/Languages/hu-HU.xlf @@ -1638,6 +1638,10 @@ Other items installation directory: Egyebek telepítési könyvtára: + + MSTS installation directory: + MSTS telepítési könyvtára: + Package compression format: Csomag tömörítésének formátuma: diff --git a/assets/Languages/id-ID.xlf b/assets/Languages/id-ID.xlf index 4556dc1ee4..e45183b8bb 100644 --- a/assets/Languages/id-ID.xlf +++ b/assets/Languages/id-ID.xlf @@ -1609,6 +1609,10 @@ Other items installation directory: Folder konten lainnya: + + MSTS installation directory: + Folder MSTS: + Package compression format: Format file kompresi: diff --git a/assets/Languages/it-IT.xlf b/assets/Languages/it-IT.xlf index 246cd2d9b6..47830c6570 100644 --- a/assets/Languages/it-IT.xlf +++ b/assets/Languages/it-IT.xlf @@ -1636,6 +1636,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/ja-JP.xlf b/assets/Languages/ja-JP.xlf index 79774feb22..361ff5c7cc 100644 --- a/assets/Languages/ja-JP.xlf +++ b/assets/Languages/ja-JP.xlf @@ -1636,6 +1636,9 @@ Other items installation directory: その他のアイテムのインストール場所: + + MSTS installation directory: + Package compression format: パッケージの圧縮形式: diff --git a/assets/Languages/ko-KR.xlf b/assets/Languages/ko-KR.xlf index 5e3da0f70a..4860e9d191 100644 --- a/assets/Languages/ko-KR.xlf +++ b/assets/Languages/ko-KR.xlf @@ -1636,6 +1636,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/ms_MY.xlf b/assets/Languages/ms_MY.xlf index 63ea3d4c18..ed1e57cf7d 100644 --- a/assets/Languages/ms_MY.xlf +++ b/assets/Languages/ms_MY.xlf @@ -1604,6 +1604,10 @@ Other items installation directory: Direktori pemasangan iitem lain-lain: + + MSTS installation directory: + Direktori pemasangan MSTS: + Package compression format: Format mampatan pakej: diff --git a/assets/Languages/nb_NO.xlf b/assets/Languages/nb_NO.xlf index 3187d30fb5..80bf88be40 100644 --- a/assets/Languages/nb_NO.xlf +++ b/assets/Languages/nb_NO.xlf @@ -1616,6 +1616,10 @@ Other items installation directory: Mappe for installasjon av andre ting: + + MSTS installation directory: + Mappe for installasjon MSTS: + Package compression format: Pakke kompresjonsformat: diff --git a/assets/Languages/nl-NL.xlf b/assets/Languages/nl-NL.xlf index de0b3dc26a..ddd03e622f 100644 --- a/assets/Languages/nl-NL.xlf +++ b/assets/Languages/nl-NL.xlf @@ -1635,6 +1635,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/pl-PL.xlf b/assets/Languages/pl-PL.xlf index 95e3b9858b..37237a0dff 100644 --- a/assets/Languages/pl-PL.xlf +++ b/assets/Languages/pl-PL.xlf @@ -1607,6 +1607,10 @@ Other items installation directory: Folder instalacji innych: + + MSTS installation directory: + Folder instalacji MSTS: + Package compression format: Format kompresji pakietów: diff --git a/assets/Languages/pt-BR.xlf b/assets/Languages/pt-BR.xlf index 3794a91ab4..81228d66b5 100644 --- a/assets/Languages/pt-BR.xlf +++ b/assets/Languages/pt-BR.xlf @@ -1636,6 +1636,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/pt-PT.xlf b/assets/Languages/pt-PT.xlf index f8bca89a18..2430d50123 100644 --- a/assets/Languages/pt-PT.xlf +++ b/assets/Languages/pt-PT.xlf @@ -1652,6 +1652,10 @@ Other items installation directory: Directório de instalação de outros itens: + + MSTS installation directory: + Directório de instalação da MSTS: + Package compression format: Formato de compressão do pacote: diff --git a/assets/Languages/ro-RO.xlf b/assets/Languages/ro-RO.xlf index 26d530c8b6..018615c026 100644 --- a/assets/Languages/ro-RO.xlf +++ b/assets/Languages/ro-RO.xlf @@ -1636,6 +1636,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/ru-RU.xlf b/assets/Languages/ru-RU.xlf index 9aac65118b..938bcafd7c 100644 --- a/assets/Languages/ru-RU.xlf +++ b/assets/Languages/ru-RU.xlf @@ -1635,6 +1635,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/sk-SK.xlf b/assets/Languages/sk-SK.xlf index bdb3c99061..98f36c2820 100644 --- a/assets/Languages/sk-SK.xlf +++ b/assets/Languages/sk-SK.xlf @@ -1554,6 +1554,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/uk-UA.xlf b/assets/Languages/uk-UA.xlf index ecd76db99d..99984897a7 100644 --- a/assets/Languages/uk-UA.xlf +++ b/assets/Languages/uk-UA.xlf @@ -1637,6 +1637,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/assets/Languages/zh-CN.xlf b/assets/Languages/zh-CN.xlf index f811002066..ab94d8fec6 100644 --- a/assets/Languages/zh-CN.xlf +++ b/assets/Languages/zh-CN.xlf @@ -1634,6 +1634,9 @@ Other items installation directory: 其他安装目录: + + MSTS installation directory: + Package compression format: 扩展包压缩格式: diff --git a/assets/Languages/zh-HK.xlf b/assets/Languages/zh-HK.xlf index d6343459f3..fb70d5d5ee 100644 --- a/assets/Languages/zh-HK.xlf +++ b/assets/Languages/zh-HK.xlf @@ -1639,6 +1639,9 @@ Other items installation directory: 其他項目安裝資料夾: + + MSTS installation directory: + Package compression format: 擴展包壓縮格式: diff --git a/assets/Languages/zh-TW.xlf b/assets/Languages/zh-TW.xlf index f0c2e34cca..a75ff3dd87 100644 --- a/assets/Languages/zh-TW.xlf +++ b/assets/Languages/zh-TW.xlf @@ -1636,6 +1636,9 @@ Other items installation directory: Other items installation directory: + + MSTS installation directory: + Package compression format: Package compression format: diff --git a/source/OpenBVE/System/Options.cs b/source/OpenBVE/System/Options.cs index ff62461e1f..d166b7f47b 100644 --- a/source/OpenBVE/System/Options.cs +++ b/source/OpenBVE/System/Options.cs @@ -345,6 +345,7 @@ public override void Save(string fileName) Builder.AppendLine("[folders]"); Builder.AppendLine("route = " + RouteFolder); Builder.AppendLine("train = " + TrainFolder); + Builder.AppendLine("MSTSTrainset = " + Program.FileSystem.MSTSDirectory); Builder.AppendLine(); Builder.AppendLine("[recentlyUsedRoutes]"); for (int i = 0; i < RecentlyUsedRoutes.Length; i++) diff --git a/source/OpenBVE/UserInterface/formMain.Designer.cs b/source/OpenBVE/UserInterface/formMain.Designer.cs index 2b1ba07f03..1c762ab5c0 100644 --- a/source/OpenBVE/UserInterface/formMain.Designer.cs +++ b/source/OpenBVE/UserInterface/formMain.Designer.cs @@ -101,6 +101,53 @@ private void InitializeComponent() { this.panelOptions = new System.Windows.Forms.Panel(); this.buttonOptionsPrevious = new System.Windows.Forms.Button(); this.buttonOptionsNext = new System.Windows.Forms.Button(); + this.panelOptionsPage2 = new System.Windows.Forms.Panel(); + this.groupBoxInputDevice = new System.Windows.Forms.GroupBox(); + this.labelInputDevice = new System.Windows.Forms.Label(); + this.listviewInputDevice = new System.Windows.Forms.ListView(); + this.columnheaderInputDeviceName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnheaderInputDeviceStatus = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnheaderInputDeviceVersion = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnheaderInputDeviceProvider = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnheaderInputDeviceFileName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.checkBoxInputDeviceEnable = new System.Windows.Forms.CheckBox(); + this.buttonInputDeviceConfig = new System.Windows.Forms.Button(); + this.groupBoxObjectParser = new System.Windows.Forms.GroupBox(); + this.labelObjparser = new System.Windows.Forms.Label(); + this.comboBoxObjparser = new System.Windows.Forms.ComboBox(); + this.labelXparser = new System.Windows.Forms.Label(); + this.comboBoxXparser = new System.Windows.Forms.ComboBox(); + this.groupBoxKioskMode = new System.Windows.Forms.GroupBox(); + this.labelKioskTimeout = new System.Windows.Forms.Label(); + this.numericUpDownKioskTimeout = new System.Windows.Forms.NumericUpDown(); + this.checkBoxEnableKiosk = new System.Windows.Forms.CheckBox(); + this.groupBoxAdvancedOptions = new System.Windows.Forms.GroupBox(); + this.checkBoxPanel2Extended = new System.Windows.Forms.CheckBox(); + this.pictureboxCursor = new System.Windows.Forms.PictureBox(); + this.labelCursor = new System.Windows.Forms.Label(); + this.comboboxCursor = new System.Windows.Forms.ComboBox(); + this.checkBoxHacks = new System.Windows.Forms.CheckBox(); + this.checkBoxTransparencyFix = new System.Windows.Forms.CheckBox(); + this.checkBoxUnloadTextures = new System.Windows.Forms.CheckBox(); + this.labelTimeAcceleration = new System.Windows.Forms.Label(); + this.updownTimeAccelerationFactor = new System.Windows.Forms.NumericUpDown(); + this.checkBoxIsUseNewRenderer = new System.Windows.Forms.CheckBox(); + this.checkBoxLoadInAdvance = new System.Windows.Forms.CheckBox(); + this.groupBoxPackageOptions = new System.Windows.Forms.GroupBox(); + this.buttonMSTSTrainsetDirectory = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.textBoxMSTSTrainsetDirectory = new System.Windows.Forms.TextBox(); + this.comboBoxCompressionFormat = new System.Windows.Forms.ComboBox(); + this.labelPackageCompression = new System.Windows.Forms.Label(); + this.buttonOtherDirectory = new System.Windows.Forms.Button(); + this.labelOtherInstallDirectory = new System.Windows.Forms.Label(); + this.textBoxOtherDirectory = new System.Windows.Forms.TextBox(); + this.buttonTrainInstallationDirectory = new System.Windows.Forms.Button(); + this.labelTrainInstallDirectory = new System.Windows.Forms.Label(); + this.textBoxTrainDirectory = new System.Windows.Forms.TextBox(); + this.buttonSetRouteDirectory = new System.Windows.Forms.Button(); + this.labelRouteInstallDirectory = new System.Windows.Forms.Label(); + this.textBoxRouteDirectory = new System.Windows.Forms.TextBox(); this.panelOptionsLeft = new System.Windows.Forms.Panel(); this.groupboxDisplayMode = new System.Windows.Forms.GroupBox(); this.comboBoxFont = new System.Windows.Forms.ComboBox(); @@ -170,50 +217,6 @@ private void InitializeComponent() { this.groupboxSound = new System.Windows.Forms.GroupBox(); this.updownSoundNumber = new System.Windows.Forms.NumericUpDown(); this.labelSoundNumber = new System.Windows.Forms.Label(); - this.panelOptionsPage2 = new System.Windows.Forms.Panel(); - this.groupBoxInputDevice = new System.Windows.Forms.GroupBox(); - this.labelInputDevice = new System.Windows.Forms.Label(); - this.listviewInputDevice = new System.Windows.Forms.ListView(); - this.columnheaderInputDeviceName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnheaderInputDeviceStatus = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnheaderInputDeviceVersion = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnheaderInputDeviceProvider = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnheaderInputDeviceFileName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.checkBoxInputDeviceEnable = new System.Windows.Forms.CheckBox(); - this.buttonInputDeviceConfig = new System.Windows.Forms.Button(); - this.groupBoxObjectParser = new System.Windows.Forms.GroupBox(); - this.labelObjparser = new System.Windows.Forms.Label(); - this.comboBoxObjparser = new System.Windows.Forms.ComboBox(); - this.labelXparser = new System.Windows.Forms.Label(); - this.comboBoxXparser = new System.Windows.Forms.ComboBox(); - this.groupBoxKioskMode = new System.Windows.Forms.GroupBox(); - this.labelKioskTimeout = new System.Windows.Forms.Label(); - this.numericUpDownKioskTimeout = new System.Windows.Forms.NumericUpDown(); - this.checkBoxEnableKiosk = new System.Windows.Forms.CheckBox(); - this.groupBoxAdvancedOptions = new System.Windows.Forms.GroupBox(); - this.checkBoxPanel2Extended = new System.Windows.Forms.CheckBox(); - this.pictureboxCursor = new System.Windows.Forms.PictureBox(); - this.labelCursor = new System.Windows.Forms.Label(); - this.comboboxCursor = new System.Windows.Forms.ComboBox(); - this.checkBoxHacks = new System.Windows.Forms.CheckBox(); - this.checkBoxTransparencyFix = new System.Windows.Forms.CheckBox(); - this.checkBoxUnloadTextures = new System.Windows.Forms.CheckBox(); - this.labelTimeAcceleration = new System.Windows.Forms.Label(); - this.updownTimeAccelerationFactor = new System.Windows.Forms.NumericUpDown(); - this.checkBoxIsUseNewRenderer = new System.Windows.Forms.CheckBox(); - this.checkBoxLoadInAdvance = new System.Windows.Forms.CheckBox(); - this.groupBoxPackageOptions = new System.Windows.Forms.GroupBox(); - this.comboBoxCompressionFormat = new System.Windows.Forms.ComboBox(); - this.labelPackageCompression = new System.Windows.Forms.Label(); - this.buttonOtherDirectory = new System.Windows.Forms.Button(); - this.labelOtherInstallDirectory = new System.Windows.Forms.Label(); - this.textBoxOtherDirectory = new System.Windows.Forms.TextBox(); - this.buttonTrainInstallationDirectory = new System.Windows.Forms.Button(); - this.labelTrainInstallDirectory = new System.Windows.Forms.Label(); - this.textBoxTrainDirectory = new System.Windows.Forms.TextBox(); - this.buttonSetRouteDirectory = new System.Windows.Forms.Button(); - this.labelRouteInstallDirectory = new System.Windows.Forms.Label(); - this.textBoxRouteDirectory = new System.Windows.Forms.TextBox(); this.pictureboxLanguage = new System.Windows.Forms.PictureBox(); this.comboboxLanguages = new System.Windows.Forms.ComboBox(); this.labelOptionsTitleSeparator = new System.Windows.Forms.Label(); @@ -490,6 +493,15 @@ private void InitializeComponent() { this.tabpageRouteSettings.SuspendLayout(); this.panelRouteEncoding.SuspendLayout(); this.panelOptions.SuspendLayout(); + this.panelOptionsPage2.SuspendLayout(); + this.groupBoxInputDevice.SuspendLayout(); + this.groupBoxObjectParser.SuspendLayout(); + this.groupBoxKioskMode.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownKioskTimeout)).BeginInit(); + this.groupBoxAdvancedOptions.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureboxCursor)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.updownTimeAccelerationFactor)).BeginInit(); + this.groupBoxPackageOptions.SuspendLayout(); this.panelOptionsLeft.SuspendLayout(); this.groupboxDisplayMode.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.trackBarHUDSize)).BeginInit(); @@ -514,15 +526,6 @@ private void InitializeComponent() { this.groupboxSimulation.SuspendLayout(); this.groupboxSound.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.updownSoundNumber)).BeginInit(); - this.panelOptionsPage2.SuspendLayout(); - this.groupBoxInputDevice.SuspendLayout(); - this.groupBoxObjectParser.SuspendLayout(); - this.groupBoxKioskMode.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDownKioskTimeout)).BeginInit(); - this.groupBoxAdvancedOptions.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureboxCursor)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.updownTimeAccelerationFactor)).BeginInit(); - this.groupBoxPackageOptions.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pictureboxLanguage)).BeginInit(); this.panelPanels.SuspendLayout(); this.panelReview.SuspendLayout(); @@ -593,7 +596,7 @@ private void InitializeComponent() { this.labelVerticalSeparator.BackColor = System.Drawing.Color.White; this.labelVerticalSeparator.Location = new System.Drawing.Point(158, 0); this.labelVerticalSeparator.Name = "labelVerticalSeparator"; - this.labelVerticalSeparator.Size = new System.Drawing.Size(2, 651); + this.labelVerticalSeparator.Size = new System.Drawing.Size(2, 681); this.labelVerticalSeparator.TabIndex = 9; // // buttonClose @@ -601,7 +604,7 @@ private void InitializeComponent() { this.buttonClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.buttonClose.AutoEllipsis = true; this.buttonClose.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonClose.Location = new System.Drawing.Point(8, 599); + this.buttonClose.Location = new System.Drawing.Point(8, 629); this.buttonClose.Name = "buttonClose"; this.buttonClose.Size = new System.Drawing.Size(144, 26); this.buttonClose.TabIndex = 5; @@ -631,7 +634,7 @@ private void InitializeComponent() { this.panelStart.Controls.Add(this.labelStartTitleBackground); this.panelStart.Location = new System.Drawing.Point(160, 0); this.panelStart.Name = "panelStart"; - this.panelStart.Size = new System.Drawing.Size(699, 631); + this.panelStart.Size = new System.Drawing.Size(699, 661); this.panelStart.TabIndex = 7; // // comboboxMode @@ -639,7 +642,7 @@ private void InitializeComponent() { this.comboboxMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.comboboxMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboboxMode.FormattingEnabled = true; - this.comboboxMode.Location = new System.Drawing.Point(144, 599); + this.comboboxMode.Location = new System.Drawing.Point(144, 629); this.comboboxMode.Name = "comboboxMode"; this.comboboxMode.Size = new System.Drawing.Size(144, 21); this.comboboxMode.TabIndex = 11; @@ -649,7 +652,7 @@ private void InitializeComponent() { this.labelMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelMode.AutoEllipsis = true; this.labelMode.ForeColor = System.Drawing.Color.Black; - this.labelMode.Location = new System.Drawing.Point(16, 601); + this.labelMode.Location = new System.Drawing.Point(16, 631); this.labelMode.Name = "labelMode"; this.labelMode.Size = new System.Drawing.Size(128, 18); this.labelMode.TabIndex = 10; @@ -661,7 +664,7 @@ private void InitializeComponent() { this.groupboxTrainSelection.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.groupboxTrainSelection.Controls.Add(this.tabcontrolTrainSelection); this.groupboxTrainSelection.ForeColor = System.Drawing.Color.Black; - this.groupboxTrainSelection.Location = new System.Drawing.Point(8, 359); + this.groupboxTrainSelection.Location = new System.Drawing.Point(8, 389); this.groupboxTrainSelection.Name = "groupboxTrainSelection"; this.groupboxTrainSelection.Size = new System.Drawing.Size(365, 200); this.groupboxTrainSelection.TabIndex = 7; @@ -816,7 +819,7 @@ private void InitializeComponent() { this.groupboxTrainDetails.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.groupboxTrainDetails.Controls.Add(this.tabcontrolTrainDetails); this.groupboxTrainDetails.ForeColor = System.Drawing.Color.Black; - this.groupboxTrainDetails.Location = new System.Drawing.Point(375, 359); + this.groupboxTrainDetails.Location = new System.Drawing.Point(375, 389); this.groupboxTrainDetails.Name = "groupboxTrainDetails"; this.groupboxTrainDetails.Size = new System.Drawing.Size(319, 200); this.groupboxTrainDetails.TabIndex = 8; @@ -1016,7 +1019,7 @@ private void InitializeComponent() { this.buttonStart.AutoEllipsis = true; this.buttonStart.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonStart.Enabled = false; - this.buttonStart.Location = new System.Drawing.Point(571, 599); + this.buttonStart.Location = new System.Drawing.Point(571, 629); this.buttonStart.Name = "buttonStart"; this.buttonStart.Size = new System.Drawing.Size(120, 26); this.buttonStart.TabIndex = 12; @@ -1032,7 +1035,7 @@ private void InitializeComponent() { this.labelStart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(107)))), ((int)(((byte)(130)))), ((int)(((byte)(153))))); this.labelStart.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.labelStart.ForeColor = System.Drawing.Color.White; - this.labelStart.Location = new System.Drawing.Point(7, 567); + this.labelStart.Location = new System.Drawing.Point(7, 597); this.labelStart.Name = "labelStart"; this.labelStart.Size = new System.Drawing.Size(684, 26); this.labelStart.TabIndex = 9; @@ -1047,7 +1050,7 @@ private void InitializeComponent() { this.labelTrain.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(107)))), ((int)(((byte)(130)))), ((int)(((byte)(153))))); this.labelTrain.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.labelTrain.ForeColor = System.Drawing.Color.White; - this.labelTrain.Location = new System.Drawing.Point(8, 327); + this.labelTrain.Location = new System.Drawing.Point(8, 357); this.labelTrain.Name = "labelTrain"; this.labelTrain.Size = new System.Drawing.Size(683, 24); this.labelTrain.TabIndex = 6; @@ -1062,7 +1065,7 @@ private void InitializeComponent() { this.groupboxRouteSelection.ForeColor = System.Drawing.Color.Black; this.groupboxRouteSelection.Location = new System.Drawing.Point(8, 72); this.groupboxRouteSelection.Name = "groupboxRouteSelection"; - this.groupboxRouteSelection.Size = new System.Drawing.Size(365, 247); + this.groupboxRouteSelection.Size = new System.Drawing.Size(365, 277); this.groupboxRouteSelection.TabIndex = 4; this.groupboxRouteSelection.TabStop = false; this.groupboxRouteSelection.Text = "Selection"; @@ -1078,7 +1081,7 @@ private void InitializeComponent() { this.tabcontrolRouteSelection.Location = new System.Drawing.Point(8, 16); this.tabcontrolRouteSelection.Name = "tabcontrolRouteSelection"; this.tabcontrolRouteSelection.SelectedIndex = 0; - this.tabcontrolRouteSelection.Size = new System.Drawing.Size(349, 223); + this.tabcontrolRouteSelection.Size = new System.Drawing.Size(349, 253); this.tabcontrolRouteSelection.TabIndex = 0; this.tabcontrolRouteSelection.SelectedIndexChanged += new System.EventHandler(this.tabcontrolRouteSelection_SelectedIndexChanged); // @@ -1089,7 +1092,7 @@ private void InitializeComponent() { this.tabpageRouteBrowse.Location = new System.Drawing.Point(4, 22); this.tabpageRouteBrowse.Name = "tabpageRouteBrowse"; this.tabpageRouteBrowse.Padding = new System.Windows.Forms.Padding(3); - this.tabpageRouteBrowse.Size = new System.Drawing.Size(341, 197); + this.tabpageRouteBrowse.Size = new System.Drawing.Size(341, 227); this.tabpageRouteBrowse.TabIndex = 0; this.tabpageRouteBrowse.Text = "File browser"; this.tabpageRouteBrowse.UseVisualStyleBackColor = true; @@ -1106,7 +1109,7 @@ private void InitializeComponent() { this.listviewRouteFiles.MultiSelect = false; this.listviewRouteFiles.Name = "listviewRouteFiles"; this.listviewRouteFiles.ShowGroups = false; - this.listviewRouteFiles.Size = new System.Drawing.Size(325, 159); + this.listviewRouteFiles.Size = new System.Drawing.Size(325, 189); this.listviewRouteFiles.TabIndex = 1; this.listviewRouteFiles.UseCompatibleStateImageBehavior = false; this.listviewRouteFiles.View = System.Windows.Forms.View.Details; @@ -1130,7 +1133,7 @@ private void InitializeComponent() { this.tabpageRouteRecently.Location = new System.Drawing.Point(4, 22); this.tabpageRouteRecently.Name = "tabpageRouteRecently"; this.tabpageRouteRecently.Padding = new System.Windows.Forms.Padding(3); - this.tabpageRouteRecently.Size = new System.Drawing.Size(341, 197); + this.tabpageRouteRecently.Size = new System.Drawing.Size(341, 227); this.tabpageRouteRecently.TabIndex = 1; this.tabpageRouteRecently.Text = "Recently used"; this.tabpageRouteRecently.UseVisualStyleBackColor = true; @@ -1158,7 +1161,7 @@ private void InitializeComponent() { this.tabPageRoutePackages.Controls.Add(this.listViewRoutePackages); this.tabPageRoutePackages.Location = new System.Drawing.Point(4, 22); this.tabPageRoutePackages.Name = "tabPageRoutePackages"; - this.tabPageRoutePackages.Size = new System.Drawing.Size(341, 197); + this.tabPageRoutePackages.Size = new System.Drawing.Size(341, 227); this.tabPageRoutePackages.TabIndex = 2; this.tabPageRoutePackages.Text = "Installed Packages"; this.tabPageRoutePackages.UseVisualStyleBackColor = true; @@ -1190,7 +1193,7 @@ private void InitializeComponent() { this.groupboxRouteDetails.ForeColor = System.Drawing.Color.Black; this.groupboxRouteDetails.Location = new System.Drawing.Point(375, 72); this.groupboxRouteDetails.Name = "groupboxRouteDetails"; - this.groupboxRouteDetails.Size = new System.Drawing.Size(319, 247); + this.groupboxRouteDetails.Size = new System.Drawing.Size(319, 277); this.groupboxRouteDetails.TabIndex = 5; this.groupboxRouteDetails.TabStop = false; this.groupboxRouteDetails.Text = "Details"; @@ -1208,7 +1211,7 @@ private void InitializeComponent() { this.tabcontrolRouteDetails.Location = new System.Drawing.Point(8, 16); this.tabcontrolRouteDetails.Name = "tabcontrolRouteDetails"; this.tabcontrolRouteDetails.SelectedIndex = 0; - this.tabcontrolRouteDetails.Size = new System.Drawing.Size(303, 223); + this.tabcontrolRouteDetails.Size = new System.Drawing.Size(303, 253); this.tabcontrolRouteDetails.TabIndex = 19; this.tabcontrolRouteDetails.SelectedIndexChanged += new System.EventHandler(this.tabcontrolRouteDetails_SelectedIndexChanged); // @@ -1219,7 +1222,7 @@ private void InitializeComponent() { this.tabpageRouteDescription.Location = new System.Drawing.Point(4, 22); this.tabpageRouteDescription.Name = "tabpageRouteDescription"; this.tabpageRouteDescription.Padding = new System.Windows.Forms.Padding(3); - this.tabpageRouteDescription.Size = new System.Drawing.Size(295, 197); + this.tabpageRouteDescription.Size = new System.Drawing.Size(295, 227); this.tabpageRouteDescription.TabIndex = 0; this.tabpageRouteDescription.Text = "Description"; this.tabpageRouteDescription.UseVisualStyleBackColor = true; @@ -1231,11 +1234,11 @@ private void InitializeComponent() { | System.Windows.Forms.AnchorStyles.Right))); this.textboxRouteDescription.BackColor = System.Drawing.SystemColors.Window; this.textboxRouteDescription.Location = new System.Drawing.Point(168, 8); - this.textboxRouteDescription.Multiline = true; this.textboxRouteDescription.Name = "textboxRouteDescription"; this.textboxRouteDescription.ReadOnly = true; - this.textboxRouteDescription.Size = new System.Drawing.Size(119, 183); + this.textboxRouteDescription.Size = new System.Drawing.Size(119, 213); this.textboxRouteDescription.TabIndex = 0; + this.textboxRouteDescription.Text = ""; // // pictureboxRouteImage // @@ -1244,7 +1247,7 @@ private void InitializeComponent() { this.pictureboxRouteImage.Cursor = System.Windows.Forms.Cursors.Hand; this.pictureboxRouteImage.Location = new System.Drawing.Point(8, 8); this.pictureboxRouteImage.Name = "pictureboxRouteImage"; - this.pictureboxRouteImage.Size = new System.Drawing.Size(152, 183); + this.pictureboxRouteImage.Size = new System.Drawing.Size(152, 213); this.pictureboxRouteImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; this.pictureboxRouteImage.TabIndex = 0; this.pictureboxRouteImage.TabStop = false; @@ -1256,7 +1259,7 @@ private void InitializeComponent() { this.tabpageRouteMap.Location = new System.Drawing.Point(4, 22); this.tabpageRouteMap.Name = "tabpageRouteMap"; this.tabpageRouteMap.Padding = new System.Windows.Forms.Padding(3); - this.tabpageRouteMap.Size = new System.Drawing.Size(295, 197); + this.tabpageRouteMap.Size = new System.Drawing.Size(295, 227); this.tabpageRouteMap.TabIndex = 1; this.tabpageRouteMap.Text = "Map"; this.tabpageRouteMap.UseVisualStyleBackColor = true; @@ -1279,12 +1282,12 @@ private void InitializeComponent() { this.pictureBoxContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.toolStripExport}); this.pictureBoxContextMenu.Name = "pictureBoxRouteMapContextMenu"; - this.pictureBoxContextMenu.Size = new System.Drawing.Size(109, 26); + this.pictureBoxContextMenu.Size = new System.Drawing.Size(108, 26); // // toolStripExport // this.toolStripExport.Name = "toolStripExport"; - this.toolStripExport.Size = new System.Drawing.Size(108, 22); + this.toolStripExport.Size = new System.Drawing.Size(107, 22); this.toolStripExport.Text = "Export"; this.toolStripExport.Click += new System.EventHandler(this.toolStripExport_Click); // @@ -1294,7 +1297,7 @@ private void InitializeComponent() { this.tabpageRouteGradient.Location = new System.Drawing.Point(4, 22); this.tabpageRouteGradient.Name = "tabpageRouteGradient"; this.tabpageRouteGradient.Padding = new System.Windows.Forms.Padding(3); - this.tabpageRouteGradient.Size = new System.Drawing.Size(295, 197); + this.tabpageRouteGradient.Size = new System.Drawing.Size(295, 227); this.tabpageRouteGradient.TabIndex = 2; this.tabpageRouteGradient.Text = "Gradient profile"; this.tabpageRouteGradient.UseVisualStyleBackColor = true; @@ -1322,7 +1325,7 @@ private void InitializeComponent() { this.tabpageRouteSettings.Location = new System.Drawing.Point(4, 22); this.tabpageRouteSettings.Name = "tabpageRouteSettings"; this.tabpageRouteSettings.Padding = new System.Windows.Forms.Padding(3); - this.tabpageRouteSettings.Size = new System.Drawing.Size(295, 197); + this.tabpageRouteSettings.Size = new System.Drawing.Size(295, 227); this.tabpageRouteSettings.TabIndex = 3; this.tabpageRouteSettings.Text = "Settings"; this.tabpageRouteSettings.UseVisualStyleBackColor = true; @@ -1519,7 +1522,7 @@ private void InitializeComponent() { this.labelFillerTwo.BackColor = System.Drawing.Color.Silver; this.labelFillerTwo.Location = new System.Drawing.Point(0, 330); this.labelFillerTwo.Name = "labelFillerTwo"; - this.labelFillerTwo.Size = new System.Drawing.Size(160, 151); + this.labelFillerTwo.Size = new System.Drawing.Size(160, 181); this.labelFillerTwo.TabIndex = 2; // // panelOptions @@ -1530,9 +1533,9 @@ private void InitializeComponent() { this.panelOptions.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(243)))), ((int)(((byte)(255)))), ((int)(((byte)(243))))); this.panelOptions.Controls.Add(this.buttonOptionsPrevious); this.panelOptions.Controls.Add(this.buttonOptionsNext); + this.panelOptions.Controls.Add(this.panelOptionsPage2); this.panelOptions.Controls.Add(this.panelOptionsLeft); this.panelOptions.Controls.Add(this.panelOptionsRight); - this.panelOptions.Controls.Add(this.panelOptionsPage2); this.panelOptions.Controls.Add(this.pictureboxLanguage); this.panelOptions.Controls.Add(this.comboboxLanguages); this.panelOptions.Controls.Add(this.labelOptionsTitleSeparator); @@ -1540,7 +1543,7 @@ private void InitializeComponent() { this.panelOptions.Controls.Add(this.labelOptionsTitleBackground); this.panelOptions.Location = new System.Drawing.Point(160, 0); this.panelOptions.Name = "panelOptions"; - this.panelOptions.Size = new System.Drawing.Size(699, 631); + this.panelOptions.Size = new System.Drawing.Size(699, 661); this.panelOptions.TabIndex = 0; // // buttonOptionsPrevious @@ -1567,1412 +1570,1449 @@ private void InitializeComponent() { this.buttonOptionsNext.UseVisualStyleBackColor = true; this.buttonOptionsNext.Click += new System.EventHandler(this.buttonOptionsPrevious_Click); // - // panelOptionsLeft + // panelOptionsPage2 // - this.panelOptionsLeft.Controls.Add(this.groupboxDisplayMode); - this.panelOptionsLeft.Controls.Add(this.groupboxWindow); - this.panelOptionsLeft.Controls.Add(this.groupboxFullscreen); - this.panelOptionsLeft.Controls.Add(this.groupboxInterpolation); - this.panelOptionsLeft.Location = new System.Drawing.Point(8, 72); - this.panelOptionsLeft.Name = "panelOptionsLeft"; - this.panelOptionsLeft.Size = new System.Drawing.Size(316, 576); - this.panelOptionsLeft.TabIndex = 16; + this.panelOptionsPage2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.panelOptionsPage2.Controls.Add(this.groupBoxInputDevice); + this.panelOptionsPage2.Controls.Add(this.groupBoxObjectParser); + this.panelOptionsPage2.Controls.Add(this.groupBoxKioskMode); + this.panelOptionsPage2.Controls.Add(this.groupBoxAdvancedOptions); + this.panelOptionsPage2.Controls.Add(this.groupBoxPackageOptions); + this.panelOptionsPage2.Location = new System.Drawing.Point(0, 72); + this.panelOptionsPage2.Name = "panelOptionsPage2"; + this.panelOptionsPage2.Size = new System.Drawing.Size(683, 583); + this.panelOptionsPage2.TabIndex = 20; // - // groupboxDisplayMode + // groupBoxInputDevice // - this.groupboxDisplayMode.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.groupBoxInputDevice.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxDisplayMode.Controls.Add(this.comboBoxFont); - this.groupboxDisplayMode.Controls.Add(this.labelFontName); - this.groupboxDisplayMode.Controls.Add(this.labelHUDLarge); - this.groupboxDisplayMode.Controls.Add(this.labelHUDNormal); - this.groupboxDisplayMode.Controls.Add(this.labelHUDSmall); - this.groupboxDisplayMode.Controls.Add(this.trackBarHUDSize); - this.groupboxDisplayMode.Controls.Add(this.labelHUDScale); - this.groupboxDisplayMode.Controls.Add(this.comboboxVSync); - this.groupboxDisplayMode.Controls.Add(this.labelVSync); - this.groupboxDisplayMode.Controls.Add(this.radiobuttonFullscreen); - this.groupboxDisplayMode.Controls.Add(this.radiobuttonWindow); - this.groupboxDisplayMode.ForeColor = System.Drawing.Color.Black; - this.groupboxDisplayMode.Location = new System.Drawing.Point(0, 0); - this.groupboxDisplayMode.Name = "groupboxDisplayMode"; - this.groupboxDisplayMode.Size = new System.Drawing.Size(316, 189); - this.groupboxDisplayMode.TabIndex = 4; - this.groupboxDisplayMode.TabStop = false; - this.groupboxDisplayMode.Text = "Display mode"; + this.groupBoxInputDevice.Controls.Add(this.labelInputDevice); + this.groupBoxInputDevice.Controls.Add(this.listviewInputDevice); + this.groupBoxInputDevice.Controls.Add(this.checkBoxInputDeviceEnable); + this.groupBoxInputDevice.Controls.Add(this.buttonInputDeviceConfig); + this.groupBoxInputDevice.ForeColor = System.Drawing.Color.Black; + this.groupBoxInputDevice.Location = new System.Drawing.Point(6, 396); + this.groupBoxInputDevice.Name = "groupBoxInputDevice"; + this.groupBoxInputDevice.Size = new System.Drawing.Size(674, 181); + this.groupBoxInputDevice.TabIndex = 24; + this.groupBoxInputDevice.TabStop = false; + this.groupBoxInputDevice.Text = "Input Device Plugin"; // - // comboBoxFont + // labelInputDevice // - this.comboBoxFont.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.comboBoxFont.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxFont.FormattingEnabled = true; - this.comboBoxFont.Location = new System.Drawing.Point(45, 155); - this.comboBoxFont.Name = "comboBoxFont"; - this.comboBoxFont.Size = new System.Drawing.Size(264, 21); - this.comboBoxFont.TabIndex = 14; - this.comboBoxFont.SelectionChangeCommitted += new System.EventHandler(this.comboBoxFont_SelectedIndexChanged); + this.labelInputDevice.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelInputDevice.Location = new System.Drawing.Point(8, 17); + this.labelInputDevice.Name = "labelInputDevice"; + this.labelInputDevice.Size = new System.Drawing.Size(658, 17); + this.labelInputDevice.TabIndex = 0; + this.labelInputDevice.Text = "WARNING: If you are turn on the Input Device Plugin(s), it may be happen the conf" + + "lict of input setting(s)."; // - // labelFontName + // listviewInputDevice // - this.labelFontName.Location = new System.Drawing.Point(8, 148); - this.labelFontName.Name = "labelFontName"; - this.labelFontName.Size = new System.Drawing.Size(50, 36); - this.labelFontName.TabIndex = 13; - this.labelFontName.Text = "Font:"; - this.labelFontName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.listviewInputDevice.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.listviewInputDevice.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnheaderInputDeviceName, + this.columnheaderInputDeviceStatus, + this.columnheaderInputDeviceVersion, + this.columnheaderInputDeviceProvider, + this.columnheaderInputDeviceFileName}); + this.listviewInputDevice.FullRowSelect = true; + this.listviewInputDevice.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.listviewInputDevice.HideSelection = false; + this.listviewInputDevice.LabelWrap = false; + this.listviewInputDevice.Location = new System.Drawing.Point(8, 38); + this.listviewInputDevice.MultiSelect = false; + this.listviewInputDevice.Name = "listviewInputDevice"; + this.listviewInputDevice.ShowGroups = false; + this.listviewInputDevice.Size = new System.Drawing.Size(658, 103); + this.listviewInputDevice.TabIndex = 1; + this.listviewInputDevice.UseCompatibleStateImageBehavior = false; + this.listviewInputDevice.View = System.Windows.Forms.View.Details; + this.listviewInputDevice.SelectedIndexChanged += new System.EventHandler(this.listviewInputDevice_SelectedIndexChanged); + this.listviewInputDevice.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.listviewInputDevice_MouseDoubleClick); // - // labelHUDLarge + // columnheaderInputDeviceName // - this.labelHUDLarge.Location = new System.Drawing.Point(258, 125); - this.labelHUDLarge.Name = "labelHUDLarge"; - this.labelHUDLarge.Size = new System.Drawing.Size(72, 48); - this.labelHUDLarge.TabIndex = 12; - this.labelHUDLarge.Text = "Large"; - this.labelHUDLarge.TextAlign = System.Drawing.ContentAlignment.TopCenter; + this.columnheaderInputDeviceName.Text = "Name"; // - // labelHUDNormal + // columnheaderInputDeviceStatus // - this.labelHUDNormal.Location = new System.Drawing.Point(162, 125); - this.labelHUDNormal.Name = "labelHUDNormal"; - this.labelHUDNormal.Size = new System.Drawing.Size(70, 48); - this.labelHUDNormal.TabIndex = 11; - this.labelHUDNormal.Text = "Normal"; - this.labelHUDNormal.TextAlign = System.Drawing.ContentAlignment.TopCenter; + this.columnheaderInputDeviceStatus.Text = "Status"; // - // labelHUDSmall + // columnheaderInputDeviceVersion // - this.labelHUDSmall.Location = new System.Drawing.Point(66, 125); - this.labelHUDSmall.Name = "labelHUDSmall"; - this.labelHUDSmall.Size = new System.Drawing.Size(70, 48); - this.labelHUDSmall.TabIndex = 10; - this.labelHUDSmall.Text = "Small"; - this.labelHUDSmall.TextAlign = System.Drawing.ContentAlignment.TopCenter; + this.columnheaderInputDeviceVersion.Text = "Version"; // - // trackBarHUDSize + // columnheaderInputDeviceProvider // - this.trackBarHUDSize.LargeChange = 1; - this.trackBarHUDSize.Location = new System.Drawing.Point(88, 100); - this.trackBarHUDSize.Maximum = 2; - this.trackBarHUDSize.Name = "trackBarHUDSize"; - this.trackBarHUDSize.Size = new System.Drawing.Size(220, 45); - this.trackBarHUDSize.TabIndex = 9; - this.trackBarHUDSize.Value = 1; + this.columnheaderInputDeviceProvider.Text = "Provider"; // - // labelHUDScale + // columnheaderInputDeviceFileName // - this.labelHUDScale.AutoSize = true; - this.labelHUDScale.Location = new System.Drawing.Point(8, 106); - this.labelHUDScale.Name = "labelHUDScale"; - this.labelHUDScale.Size = new System.Drawing.Size(54, 13); - this.labelHUDScale.TabIndex = 8; - this.labelHUDScale.Text = "HUD Size"; + this.columnheaderInputDeviceFileName.Text = "File Name"; + this.columnheaderInputDeviceFileName.Width = 200; // - // comboboxVSync - // - this.comboboxVSync.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.comboboxVSync.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboboxVSync.FormattingEnabled = true; - this.comboboxVSync.Location = new System.Drawing.Point(156, 72); - this.comboboxVSync.Name = "comboboxVSync"; - this.comboboxVSync.Size = new System.Drawing.Size(152, 21); - this.comboboxVSync.TabIndex = 7; - // - // labelVSync - // - this.labelVSync.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelVSync.AutoEllipsis = true; - this.labelVSync.Location = new System.Drawing.Point(8, 72); - this.labelVSync.Name = "labelVSync"; - this.labelVSync.Size = new System.Drawing.Size(148, 18); - this.labelVSync.TabIndex = 2; - this.labelVSync.Text = "Vertical syncronization:"; - this.labelVSync.TextAlign = System.Drawing.ContentAlignment.TopRight; - // - // radiobuttonFullscreen - // - this.radiobuttonFullscreen.AutoSize = true; - this.radiobuttonFullscreen.Location = new System.Drawing.Point(8, 48); - this.radiobuttonFullscreen.Name = "radiobuttonFullscreen"; - this.radiobuttonFullscreen.Size = new System.Drawing.Size(102, 17); - this.radiobuttonFullscreen.TabIndex = 1; - this.radiobuttonFullscreen.TabStop = true; - this.radiobuttonFullscreen.Text = "Fullscreen mode"; - this.radiobuttonFullscreen.UseVisualStyleBackColor = true; - // - // radiobuttonWindow + // checkBoxInputDeviceEnable // - this.radiobuttonWindow.AutoSize = true; - this.radiobuttonWindow.Checked = true; - this.radiobuttonWindow.Location = new System.Drawing.Point(8, 24); - this.radiobuttonWindow.Name = "radiobuttonWindow"; - this.radiobuttonWindow.Size = new System.Drawing.Size(93, 17); - this.radiobuttonWindow.TabIndex = 0; - this.radiobuttonWindow.TabStop = true; - this.radiobuttonWindow.Text = "Window mode"; - this.radiobuttonWindow.UseVisualStyleBackColor = true; + this.checkBoxInputDeviceEnable.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.checkBoxInputDeviceEnable.Enabled = false; + this.checkBoxInputDeviceEnable.Location = new System.Drawing.Point(8, 144); + this.checkBoxInputDeviceEnable.Name = "checkBoxInputDeviceEnable"; + this.checkBoxInputDeviceEnable.Size = new System.Drawing.Size(230, 34); + this.checkBoxInputDeviceEnable.TabIndex = 2; + this.checkBoxInputDeviceEnable.Text = "Enable this Input Device Plugin"; + this.checkBoxInputDeviceEnable.UseVisualStyleBackColor = true; + this.checkBoxInputDeviceEnable.CheckedChanged += new System.EventHandler(this.checkBoxInputDeviceEnable_CheckedChanged); // - // groupboxWindow + // buttonInputDeviceConfig // - this.groupboxWindow.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxWindow.Controls.Add(this.updownWindowHeight); - this.groupboxWindow.Controls.Add(this.labelWindowHeight); - this.groupboxWindow.Controls.Add(this.updownWindowWidth); - this.groupboxWindow.Controls.Add(this.labelWindowWidth); - this.groupboxWindow.ForeColor = System.Drawing.Color.Black; - this.groupboxWindow.Location = new System.Drawing.Point(0, 192); - this.groupboxWindow.Name = "groupboxWindow"; - this.groupboxWindow.Size = new System.Drawing.Size(316, 80); - this.groupboxWindow.TabIndex = 5; - this.groupboxWindow.TabStop = false; - this.groupboxWindow.Text = "Window mode"; + this.buttonInputDeviceConfig.Anchor = System.Windows.Forms.AnchorStyles.Bottom; + this.buttonInputDeviceConfig.BackColor = System.Drawing.SystemColors.ButtonFace; + this.buttonInputDeviceConfig.Enabled = false; + this.buttonInputDeviceConfig.ForeColor = System.Drawing.SystemColors.ControlText; + this.buttonInputDeviceConfig.Location = new System.Drawing.Point(270, 148); + this.buttonInputDeviceConfig.MaximumSize = new System.Drawing.Size(106, 25); + this.buttonInputDeviceConfig.Name = "buttonInputDeviceConfig"; + this.buttonInputDeviceConfig.Size = new System.Drawing.Size(106, 25); + this.buttonInputDeviceConfig.TabIndex = 3; + this.buttonInputDeviceConfig.Text = "Config"; + this.buttonInputDeviceConfig.UseVisualStyleBackColor = true; + this.buttonInputDeviceConfig.Click += new System.EventHandler(this.buttonInputDeviceConfig_Click); // - // updownWindowHeight + // groupBoxObjectParser // - this.updownWindowHeight.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updownWindowHeight.Location = new System.Drawing.Point(156, 48); - this.updownWindowHeight.Maximum = new decimal(new int[] { - 1048575, - 0, - 0, - 0}); - this.updownWindowHeight.Minimum = new decimal(new int[] { - 16, - 0, - 0, - 0}); - this.updownWindowHeight.Name = "updownWindowHeight"; - this.updownWindowHeight.Size = new System.Drawing.Size(152, 20); - this.updownWindowHeight.TabIndex = 3; - this.updownWindowHeight.Value = new decimal(new int[] { - 600, - 0, - 0, - 0}); + this.groupBoxObjectParser.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.groupBoxObjectParser.Controls.Add(this.labelObjparser); + this.groupBoxObjectParser.Controls.Add(this.comboBoxObjparser); + this.groupBoxObjectParser.Controls.Add(this.labelXparser); + this.groupBoxObjectParser.Controls.Add(this.comboBoxXparser); + this.groupBoxObjectParser.ForeColor = System.Drawing.Color.Black; + this.groupBoxObjectParser.Location = new System.Drawing.Point(377, 288); + this.groupBoxObjectParser.Name = "groupBoxObjectParser"; + this.groupBoxObjectParser.Size = new System.Drawing.Size(305, 110); + this.groupBoxObjectParser.TabIndex = 23; + this.groupBoxObjectParser.TabStop = false; + this.groupBoxObjectParser.Text = "Object Parser"; // - // labelWindowHeight + // labelObjparser // - this.labelWindowHeight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelWindowHeight.AutoEllipsis = true; - this.labelWindowHeight.Location = new System.Drawing.Point(8, 50); - this.labelWindowHeight.Name = "labelWindowHeight"; - this.labelWindowHeight.Size = new System.Drawing.Size(148, 18); - this.labelWindowHeight.TabIndex = 2; - this.labelWindowHeight.Text = "Height:"; - this.labelWindowHeight.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.labelObjparser.Location = new System.Drawing.Point(7, 44); + this.labelObjparser.Name = "labelObjparser"; + this.labelObjparser.Size = new System.Drawing.Size(113, 26); + this.labelObjparser.TabIndex = 0; + this.labelObjparser.Text = "Obj Object Parser:"; + this.labelObjparser.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // - // updownWindowWidth + // comboBoxObjparser // - this.updownWindowWidth.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updownWindowWidth.Location = new System.Drawing.Point(156, 24); - this.updownWindowWidth.Maximum = new decimal(new int[] { - 1048575, - 0, - 0, - 0}); - this.updownWindowWidth.Minimum = new decimal(new int[] { - 16, - 0, - 0, - 0}); - this.updownWindowWidth.Name = "updownWindowWidth"; - this.updownWindowWidth.Size = new System.Drawing.Size(152, 20); - this.updownWindowWidth.TabIndex = 1; - this.updownWindowWidth.Value = new decimal(new int[] { - 960, - 0, - 0, - 0}); + this.comboBoxObjparser.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxObjparser.FormattingEnabled = true; + this.comboBoxObjparser.Items.AddRange(new object[] { + "Original", + "Assimp"}); + this.comboBoxObjparser.Location = new System.Drawing.Point(127, 44); + this.comboBoxObjparser.Name = "comboBoxObjparser"; + this.comboBoxObjparser.Size = new System.Drawing.Size(170, 21); + this.comboBoxObjparser.TabIndex = 1; // - // labelWindowWidth + // labelXparser // - this.labelWindowWidth.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelWindowWidth.AutoEllipsis = true; - this.labelWindowWidth.Location = new System.Drawing.Point(8, 26); - this.labelWindowWidth.Name = "labelWindowWidth"; - this.labelWindowWidth.Size = new System.Drawing.Size(148, 18); - this.labelWindowWidth.TabIndex = 0; - this.labelWindowWidth.Text = "Width:"; - this.labelWindowWidth.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.labelXparser.Location = new System.Drawing.Point(7, 17); + this.labelXparser.Name = "labelXparser"; + this.labelXparser.Size = new System.Drawing.Size(113, 26); + this.labelXparser.TabIndex = 0; + this.labelXparser.Text = "X Object Parser:"; + this.labelXparser.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // - // groupboxFullscreen + // comboBoxXparser // - this.groupboxFullscreen.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxFullscreen.Controls.Add(this.comboboxFullscreenBits); - this.groupboxFullscreen.Controls.Add(this.labelFullscreenBits); - this.groupboxFullscreen.Controls.Add(this.updownFullscreenHeight); - this.groupboxFullscreen.Controls.Add(this.labelFullscreenHeight); - this.groupboxFullscreen.Controls.Add(this.updownFullscreenWidth); - this.groupboxFullscreen.Controls.Add(this.labelFullscreenWidth); - this.groupboxFullscreen.ForeColor = System.Drawing.Color.Black; - this.groupboxFullscreen.Location = new System.Drawing.Point(0, 275); - this.groupboxFullscreen.Name = "groupboxFullscreen"; - this.groupboxFullscreen.Size = new System.Drawing.Size(316, 104); - this.groupboxFullscreen.TabIndex = 6; - this.groupboxFullscreen.TabStop = false; - this.groupboxFullscreen.Text = "Fullscreen mode"; + this.comboBoxXparser.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxXparser.FormattingEnabled = true; + this.comboBoxXparser.Items.AddRange(new object[] { + "Original", + "NewXParser", + "Assimp"}); + this.comboBoxXparser.Location = new System.Drawing.Point(127, 21); + this.comboBoxXparser.Name = "comboBoxXparser"; + this.comboBoxXparser.Size = new System.Drawing.Size(170, 21); + this.comboBoxXparser.TabIndex = 1; // - // comboboxFullscreenBits + // groupBoxKioskMode // - this.comboboxFullscreenBits.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.comboboxFullscreenBits.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboboxFullscreenBits.FormattingEnabled = true; - this.comboboxFullscreenBits.Location = new System.Drawing.Point(156, 72); - this.comboboxFullscreenBits.Name = "comboboxFullscreenBits"; - this.comboboxFullscreenBits.Size = new System.Drawing.Size(152, 21); - this.comboboxFullscreenBits.TabIndex = 5; + this.groupBoxKioskMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.groupBoxKioskMode.Controls.Add(this.labelKioskTimeout); + this.groupBoxKioskMode.Controls.Add(this.numericUpDownKioskTimeout); + this.groupBoxKioskMode.Controls.Add(this.checkBoxEnableKiosk); + this.groupBoxKioskMode.ForeColor = System.Drawing.Color.Black; + this.groupBoxKioskMode.Location = new System.Drawing.Point(377, 190); + this.groupBoxKioskMode.Name = "groupBoxKioskMode"; + this.groupBoxKioskMode.Size = new System.Drawing.Size(305, 92); + this.groupBoxKioskMode.TabIndex = 22; + this.groupBoxKioskMode.TabStop = false; + this.groupBoxKioskMode.Text = "Kiosk Mode"; // - // labelFullscreenBits + // labelKioskTimeout // - this.labelFullscreenBits.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelFullscreenBits.AutoEllipsis = true; - this.labelFullscreenBits.Location = new System.Drawing.Point(8, 74); - this.labelFullscreenBits.Name = "labelFullscreenBits"; - this.labelFullscreenBits.Size = new System.Drawing.Size(148, 18); - this.labelFullscreenBits.TabIndex = 4; - this.labelFullscreenBits.Text = "Bits per pixel:"; - this.labelFullscreenBits.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.labelKioskTimeout.Location = new System.Drawing.Point(8, 37); + this.labelKioskTimeout.Name = "labelKioskTimeout"; + this.labelKioskTimeout.Size = new System.Drawing.Size(155, 30); + this.labelKioskTimeout.TabIndex = 2; + this.labelKioskTimeout.Text = "Control timeout (s)"; + this.labelKioskTimeout.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // - // updownFullscreenHeight + // numericUpDownKioskTimeout // - this.updownFullscreenHeight.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updownFullscreenHeight.Location = new System.Drawing.Point(156, 48); - this.updownFullscreenHeight.Maximum = new decimal(new int[] { - 1048575, - 0, - 0, - 0}); - this.updownFullscreenHeight.Minimum = new decimal(new int[] { - 16, - 0, - 0, - 0}); - this.updownFullscreenHeight.Name = "updownFullscreenHeight"; - this.updownFullscreenHeight.Size = new System.Drawing.Size(152, 20); - this.updownFullscreenHeight.TabIndex = 3; - this.updownFullscreenHeight.Value = new decimal(new int[] { - 768, + this.numericUpDownKioskTimeout.DecimalPlaces = 2; + this.numericUpDownKioskTimeout.Location = new System.Drawing.Point(166, 41); + this.numericUpDownKioskTimeout.Maximum = new decimal(new int[] { + 10000, 0, 0, 0}); + this.numericUpDownKioskTimeout.Name = "numericUpDownKioskTimeout"; + this.numericUpDownKioskTimeout.Size = new System.Drawing.Size(131, 20); + this.numericUpDownKioskTimeout.TabIndex = 1; // - // labelFullscreenHeight + // checkBoxEnableKiosk // - this.labelFullscreenHeight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelFullscreenHeight.AutoEllipsis = true; - this.labelFullscreenHeight.Location = new System.Drawing.Point(8, 50); - this.labelFullscreenHeight.Name = "labelFullscreenHeight"; - this.labelFullscreenHeight.Size = new System.Drawing.Size(148, 18); - this.labelFullscreenHeight.TabIndex = 2; - this.labelFullscreenHeight.Text = "Height:"; - this.labelFullscreenHeight.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.checkBoxEnableKiosk.AutoSize = true; + this.checkBoxEnableKiosk.Location = new System.Drawing.Point(9, 20); + this.checkBoxEnableKiosk.Name = "checkBoxEnableKiosk"; + this.checkBoxEnableKiosk.Size = new System.Drawing.Size(118, 17); + this.checkBoxEnableKiosk.TabIndex = 0; + this.checkBoxEnableKiosk.Text = "Enable Kiosk Mode"; + this.checkBoxEnableKiosk.UseVisualStyleBackColor = true; // - // updownFullscreenWidth + // groupBoxAdvancedOptions // - this.updownFullscreenWidth.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updownFullscreenWidth.Location = new System.Drawing.Point(156, 24); - this.updownFullscreenWidth.Maximum = new decimal(new int[] { - 1048575, - 0, - 0, - 0}); - this.updownFullscreenWidth.Minimum = new decimal(new int[] { - 16, - 0, - 0, - 0}); - this.updownFullscreenWidth.Name = "updownFullscreenWidth"; - this.updownFullscreenWidth.Size = new System.Drawing.Size(152, 20); - this.updownFullscreenWidth.TabIndex = 1; - this.updownFullscreenWidth.Value = new decimal(new int[] { - 1024, - 0, - 0, - 0}); + this.groupBoxAdvancedOptions.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxPanel2Extended); + this.groupBoxAdvancedOptions.Controls.Add(this.pictureboxCursor); + this.groupBoxAdvancedOptions.Controls.Add(this.labelCursor); + this.groupBoxAdvancedOptions.Controls.Add(this.comboboxCursor); + this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxHacks); + this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxTransparencyFix); + this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxUnloadTextures); + this.groupBoxAdvancedOptions.Controls.Add(this.labelTimeAcceleration); + this.groupBoxAdvancedOptions.Controls.Add(this.updownTimeAccelerationFactor); + this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxIsUseNewRenderer); + this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxLoadInAdvance); + this.groupBoxAdvancedOptions.ForeColor = System.Drawing.Color.Black; + this.groupBoxAdvancedOptions.Location = new System.Drawing.Point(8, 190); + this.groupBoxAdvancedOptions.Name = "groupBoxAdvancedOptions"; + this.groupBoxAdvancedOptions.Size = new System.Drawing.Size(358, 208); + this.groupBoxAdvancedOptions.TabIndex = 21; + this.groupBoxAdvancedOptions.TabStop = false; + this.groupBoxAdvancedOptions.Text = "Advanced Options"; // - // labelFullscreenWidth + // checkBoxPanel2Extended // - this.labelFullscreenWidth.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelFullscreenWidth.AutoEllipsis = true; - this.labelFullscreenWidth.Location = new System.Drawing.Point(8, 26); - this.labelFullscreenWidth.Name = "labelFullscreenWidth"; - this.labelFullscreenWidth.Size = new System.Drawing.Size(148, 18); - this.labelFullscreenWidth.TabIndex = 0; - this.labelFullscreenWidth.Text = "Width:"; - this.labelFullscreenWidth.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.checkBoxPanel2Extended.AutoSize = true; + this.checkBoxPanel2Extended.Location = new System.Drawing.Point(8, 183); + this.checkBoxPanel2Extended.Name = "checkBoxPanel2Extended"; + this.checkBoxPanel2Extended.Size = new System.Drawing.Size(159, 17); + this.checkBoxPanel2Extended.TabIndex = 20; + this.checkBoxPanel2Extended.Text = "Enable Panel2 extend mode"; + this.checkBoxPanel2Extended.UseVisualStyleBackColor = true; // - // groupboxInterpolation + // pictureboxCursor // - this.groupboxInterpolation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxInterpolation.Controls.Add(this.updownAntiAliasing); - this.groupboxInterpolation.Controls.Add(this.labelAntiAliasing); - this.groupboxInterpolation.Controls.Add(this.labelTransparencyQuality); - this.groupboxInterpolation.Controls.Add(this.labelTransparencyPerformance); - this.groupboxInterpolation.Controls.Add(this.labelTransparency); - this.groupboxInterpolation.Controls.Add(this.updownAnisotropic); - this.groupboxInterpolation.Controls.Add(this.labelAnisotropic); - this.groupboxInterpolation.Controls.Add(this.comboboxInterpolation); - this.groupboxInterpolation.Controls.Add(this.labelInterpolation); - this.groupboxInterpolation.Controls.Add(this.trackbarTransparency); - this.groupboxInterpolation.ForeColor = System.Drawing.Color.Black; - this.groupboxInterpolation.Location = new System.Drawing.Point(0, 381); - this.groupboxInterpolation.Name = "groupboxInterpolation"; - this.groupboxInterpolation.Size = new System.Drawing.Size(316, 160); - this.groupboxInterpolation.TabIndex = 7; - this.groupboxInterpolation.TabStop = false; - this.groupboxInterpolation.Text = "Interpolation"; + this.pictureboxCursor.Location = new System.Drawing.Point(8, 145); + this.pictureboxCursor.Name = "pictureboxCursor"; + this.pictureboxCursor.Size = new System.Drawing.Size(32, 32); + this.pictureboxCursor.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureboxCursor.TabIndex = 18; + this.pictureboxCursor.TabStop = false; // - // updownAntiAliasing + // labelCursor // - this.updownAntiAliasing.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updownAntiAliasing.Location = new System.Drawing.Point(156, 64); - this.updownAntiAliasing.Maximum = new decimal(new int[] { - 16, - 0, - 0, - 0}); - this.updownAntiAliasing.Name = "updownAntiAliasing"; - this.updownAntiAliasing.Size = new System.Drawing.Size(152, 20); - this.updownAntiAliasing.TabIndex = 5; + this.labelCursor.AutoSize = true; + this.labelCursor.Location = new System.Drawing.Point(48, 140); + this.labelCursor.Name = "labelCursor"; + this.labelCursor.Size = new System.Drawing.Size(37, 13); + this.labelCursor.TabIndex = 17; + this.labelCursor.Text = "Cursor"; // - // labelAntiAliasing + // comboboxCursor // - this.labelAntiAliasing.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelAntiAliasing.AutoEllipsis = true; - this.labelAntiAliasing.Location = new System.Drawing.Point(8, 66); - this.labelAntiAliasing.Name = "labelAntiAliasing"; - this.labelAntiAliasing.Size = new System.Drawing.Size(148, 18); - this.labelAntiAliasing.TabIndex = 4; - this.labelAntiAliasing.Text = "Level of anti-aliasing:"; - this.labelAntiAliasing.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.comboboxCursor.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboboxCursor.FormattingEnabled = true; + this.comboboxCursor.Location = new System.Drawing.Point(48, 158); + this.comboboxCursor.Name = "comboboxCursor"; + this.comboboxCursor.Size = new System.Drawing.Size(108, 21); + this.comboboxCursor.TabIndex = 19; + this.comboboxCursor.SelectedIndexChanged += new System.EventHandler(this.comboboxCursor_SelectedIndexChanged); // - // labelTransparencyQuality + // checkBoxHacks // - this.labelTransparencyQuality.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.labelTransparencyQuality.AutoEllipsis = true; - this.labelTransparencyQuality.Location = new System.Drawing.Point(230, 136); - this.labelTransparencyQuality.Name = "labelTransparencyQuality"; - this.labelTransparencyQuality.Size = new System.Drawing.Size(76, 16); - this.labelTransparencyQuality.TabIndex = 9; - this.labelTransparencyQuality.Text = "Smooth"; - this.labelTransparencyQuality.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.checkBoxHacks.AutoSize = true; + this.checkBoxHacks.Location = new System.Drawing.Point(8, 100); + this.checkBoxHacks.Name = "checkBoxHacks"; + this.checkBoxHacks.Size = new System.Drawing.Size(203, 17); + this.checkBoxHacks.TabIndex = 15; + this.checkBoxHacks.Text = "Enable hacks for buggy older content"; + this.checkBoxHacks.UseVisualStyleBackColor = true; // - // labelTransparencyPerformance + // checkBoxTransparencyFix // - this.labelTransparencyPerformance.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.labelTransparencyPerformance.AutoEllipsis = true; - this.labelTransparencyPerformance.Location = new System.Drawing.Point(130, 136); - this.labelTransparencyPerformance.Name = "labelTransparencyPerformance"; - this.labelTransparencyPerformance.Size = new System.Drawing.Size(76, 16); - this.labelTransparencyPerformance.TabIndex = 8; - this.labelTransparencyPerformance.Text = "Sharp"; - this.labelTransparencyPerformance.TextAlign = System.Drawing.ContentAlignment.TopCenter; + this.checkBoxTransparencyFix.AutoSize = true; + this.checkBoxTransparencyFix.Location = new System.Drawing.Point(8, 81); + this.checkBoxTransparencyFix.Name = "checkBoxTransparencyFix"; + this.checkBoxTransparencyFix.Size = new System.Drawing.Size(259, 17); + this.checkBoxTransparencyFix.TabIndex = 14; + this.checkBoxTransparencyFix.Text = "Attempt to fix transparency issues in older content"; + this.checkBoxTransparencyFix.UseVisualStyleBackColor = true; // - // labelTransparency + // checkBoxUnloadTextures // - this.labelTransparency.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelTransparency.AutoEllipsis = true; - this.labelTransparency.Location = new System.Drawing.Point(8, 100); - this.labelTransparency.Name = "labelTransparency"; - this.labelTransparency.Size = new System.Drawing.Size(148, 18); - this.labelTransparency.TabIndex = 6; - this.labelTransparency.Text = "Transparency:"; - this.labelTransparency.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.checkBoxUnloadTextures.AutoSize = true; + this.checkBoxUnloadTextures.Location = new System.Drawing.Point(8, 62); + this.checkBoxUnloadTextures.Name = "checkBoxUnloadTextures"; + this.checkBoxUnloadTextures.Size = new System.Drawing.Size(138, 17); + this.checkBoxUnloadTextures.TabIndex = 13; + this.checkBoxUnloadTextures.Text = "Unload unused textures"; + this.checkBoxUnloadTextures.UseVisualStyleBackColor = true; + this.checkBoxUnloadTextures.CheckedChanged += new System.EventHandler(this.checkBoxUnloadTextures_CheckedChanged); // - // updownAnisotropic + // labelTimeAcceleration // - this.updownAnisotropic.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updownAnisotropic.Enabled = false; - this.updownAnisotropic.Location = new System.Drawing.Point(156, 40); - this.updownAnisotropic.Maximum = new decimal(new int[] { - 16, + this.labelTimeAcceleration.AutoSize = true; + this.labelTimeAcceleration.Location = new System.Drawing.Point(8, 123); + this.labelTimeAcceleration.Name = "labelTimeAcceleration"; + this.labelTimeAcceleration.Size = new System.Drawing.Size(126, 13); + this.labelTimeAcceleration.TabIndex = 10; + this.labelTimeAcceleration.Text = "Accelerated Time Factor:"; + // + // updownTimeAccelerationFactor + // + this.updownTimeAccelerationFactor.Location = new System.Drawing.Point(200, 122); + this.updownTimeAccelerationFactor.Maximum = new decimal(new int[] { + 5, 0, 0, 0}); - this.updownAnisotropic.Name = "updownAnisotropic"; - this.updownAnisotropic.Size = new System.Drawing.Size(152, 20); - this.updownAnisotropic.TabIndex = 3; + this.updownTimeAccelerationFactor.Name = "updownTimeAccelerationFactor"; + this.updownTimeAccelerationFactor.Size = new System.Drawing.Size(52, 20); + this.updownTimeAccelerationFactor.TabIndex = 16; + this.updownTimeAccelerationFactor.ValueChanged += new System.EventHandler(this.updownTimeAccelerationFactor_ValueChanged); // - // labelAnisotropic + // checkBoxIsUseNewRenderer // - this.labelAnisotropic.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelAnisotropic.AutoEllipsis = true; - this.labelAnisotropic.Enabled = false; - this.labelAnisotropic.Location = new System.Drawing.Point(8, 42); - this.labelAnisotropic.Name = "labelAnisotropic"; - this.labelAnisotropic.Size = new System.Drawing.Size(148, 18); - this.labelAnisotropic.TabIndex = 2; - this.labelAnisotropic.Text = "Level of anisotropic filtering:"; - this.labelAnisotropic.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.checkBoxIsUseNewRenderer.AutoSize = true; + this.checkBoxIsUseNewRenderer.Location = new System.Drawing.Point(8, 43); + this.checkBoxIsUseNewRenderer.Name = "checkBoxIsUseNewRenderer"; + this.checkBoxIsUseNewRenderer.Size = new System.Drawing.Size(159, 17); + this.checkBoxIsUseNewRenderer.TabIndex = 2; + this.checkBoxIsUseNewRenderer.Text = "Disable OpenGL display lists"; + this.checkBoxIsUseNewRenderer.UseVisualStyleBackColor = true; // - // comboboxInterpolation + // checkBoxLoadInAdvance // - this.comboboxInterpolation.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.comboboxInterpolation.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboboxInterpolation.FormattingEnabled = true; - this.comboboxInterpolation.Location = new System.Drawing.Point(156, 16); - this.comboboxInterpolation.Name = "comboboxInterpolation"; - this.comboboxInterpolation.Size = new System.Drawing.Size(152, 21); - this.comboboxInterpolation.TabIndex = 1; - this.comboboxInterpolation.SelectedIndexChanged += new System.EventHandler(this.comboboxInterpolation_SelectedIndexChanged); + this.checkBoxLoadInAdvance.AutoSize = true; + this.checkBoxLoadInAdvance.Location = new System.Drawing.Point(8, 24); + this.checkBoxLoadInAdvance.Name = "checkBoxLoadInAdvance"; + this.checkBoxLoadInAdvance.Size = new System.Drawing.Size(106, 17); + this.checkBoxLoadInAdvance.TabIndex = 1; + this.checkBoxLoadInAdvance.Text = "Load in advance"; + this.checkBoxLoadInAdvance.UseVisualStyleBackColor = true; + this.checkBoxLoadInAdvance.CheckedChanged += new System.EventHandler(this.checkBoxLoadInAdvance_CheckedChanged); // - // labelInterpolation + // groupBoxPackageOptions // - this.labelInterpolation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.groupBoxPackageOptions.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.labelInterpolation.AutoEllipsis = true; - this.labelInterpolation.Location = new System.Drawing.Point(8, 18); - this.labelInterpolation.Name = "labelInterpolation"; - this.labelInterpolation.Size = new System.Drawing.Size(148, 18); - this.labelInterpolation.TabIndex = 0; - this.labelInterpolation.Text = "Mode:"; - this.labelInterpolation.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.groupBoxPackageOptions.Controls.Add(this.buttonMSTSTrainsetDirectory); + this.groupBoxPackageOptions.Controls.Add(this.label1); + this.groupBoxPackageOptions.Controls.Add(this.textBoxMSTSTrainsetDirectory); + this.groupBoxPackageOptions.Controls.Add(this.comboBoxCompressionFormat); + this.groupBoxPackageOptions.Controls.Add(this.labelPackageCompression); + this.groupBoxPackageOptions.Controls.Add(this.buttonOtherDirectory); + this.groupBoxPackageOptions.Controls.Add(this.labelOtherInstallDirectory); + this.groupBoxPackageOptions.Controls.Add(this.textBoxOtherDirectory); + this.groupBoxPackageOptions.Controls.Add(this.buttonTrainInstallationDirectory); + this.groupBoxPackageOptions.Controls.Add(this.labelTrainInstallDirectory); + this.groupBoxPackageOptions.Controls.Add(this.textBoxTrainDirectory); + this.groupBoxPackageOptions.Controls.Add(this.buttonSetRouteDirectory); + this.groupBoxPackageOptions.Controls.Add(this.labelRouteInstallDirectory); + this.groupBoxPackageOptions.Controls.Add(this.textBoxRouteDirectory); + this.groupBoxPackageOptions.ForeColor = System.Drawing.Color.Black; + this.groupBoxPackageOptions.Location = new System.Drawing.Point(6, 0); + this.groupBoxPackageOptions.Name = "groupBoxPackageOptions"; + this.groupBoxPackageOptions.Size = new System.Drawing.Size(674, 183); + this.groupBoxPackageOptions.TabIndex = 19; + this.groupBoxPackageOptions.TabStop = false; + this.groupBoxPackageOptions.Text = "Package Management"; // - // trackbarTransparency + // buttonMSTSTrainsetDirectory // - this.trackbarTransparency.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.trackbarTransparency.Location = new System.Drawing.Point(156, 88); - this.trackbarTransparency.Maximum = 2; - this.trackbarTransparency.Name = "trackbarTransparency"; - this.trackbarTransparency.Size = new System.Drawing.Size(152, 45); - this.trackbarTransparency.TabIndex = 7; - this.trackbarTransparency.TickStyle = System.Windows.Forms.TickStyle.Both; + this.buttonMSTSTrainsetDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonMSTSTrainsetDirectory.BackColor = System.Drawing.SystemColors.Control; + this.buttonMSTSTrainsetDirectory.ForeColor = System.Drawing.SystemColors.ControlText; + this.buttonMSTSTrainsetDirectory.Location = new System.Drawing.Point(593, 114); + this.buttonMSTSTrainsetDirectory.Name = "buttonMSTSTrainsetDirectory"; + this.buttonMSTSTrainsetDirectory.Size = new System.Drawing.Size(75, 26); + this.buttonMSTSTrainsetDirectory.TabIndex = 13; + this.buttonMSTSTrainsetDirectory.Text = "Choose..."; + this.buttonMSTSTrainsetDirectory.UseVisualStyleBackColor = true; + this.buttonMSTSTrainsetDirectory.Click += new System.EventHandler(this.buttonMSTSTrainsetDirectory_Click); // - // panelOptionsRight + // label1 // - this.panelOptionsRight.Controls.Add(this.groupBoxOther); - this.panelOptionsRight.Controls.Add(this.groupBoxRailDriver); - this.panelOptionsRight.Controls.Add(this.groupboxDistance); - this.panelOptionsRight.Controls.Add(this.groupboxControls); - this.panelOptionsRight.Controls.Add(this.groupboxVerbosity); - this.panelOptionsRight.Controls.Add(this.groupboxSimulation); - this.panelOptionsRight.Controls.Add(this.groupboxSound); - this.panelOptionsRight.Location = new System.Drawing.Point(332, 72); - this.panelOptionsRight.Name = "panelOptionsRight"; - this.panelOptionsRight.Size = new System.Drawing.Size(316, 579); - this.panelOptionsRight.TabIndex = 17; + this.label1.Location = new System.Drawing.Point(5, 113); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(175, 30); + this.label1.TabIndex = 12; + this.label1.Text = "MSTS Directory:"; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // - // groupBoxOther + // textBoxMSTSTrainsetDirectory // - this.groupBoxOther.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.textBoxMSTSTrainsetDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxOther.Controls.Add(this.comboBoxTimeTableDisplayMode); - this.groupBoxOther.Controls.Add(this.labelTimeTableDisplayMode); - this.groupBoxOther.ForeColor = System.Drawing.Color.Black; - this.groupBoxOther.Location = new System.Drawing.Point(0, 468); - this.groupBoxOther.Name = "groupBoxOther"; - this.groupBoxOther.Size = new System.Drawing.Size(316, 48); - this.groupBoxOther.TabIndex = 19; - this.groupBoxOther.TabStop = false; - this.groupBoxOther.Text = "Other"; + this.textBoxMSTSTrainsetDirectory.BackColor = System.Drawing.SystemColors.Control; + this.textBoxMSTSTrainsetDirectory.ForeColor = System.Drawing.SystemColors.ControlText; + this.textBoxMSTSTrainsetDirectory.Location = new System.Drawing.Point(199, 117); + this.textBoxMSTSTrainsetDirectory.Name = "textBoxMSTSTrainsetDirectory"; + this.textBoxMSTSTrainsetDirectory.ReadOnly = true; + this.textBoxMSTSTrainsetDirectory.Size = new System.Drawing.Size(387, 20); + this.textBoxMSTSTrainsetDirectory.TabIndex = 11; // - // comboBoxTimeTableDisplayMode + // comboBoxCompressionFormat // - this.comboBoxTimeTableDisplayMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.comboBoxTimeTableDisplayMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxTimeTableDisplayMode.FormattingEnabled = true; - this.comboBoxTimeTableDisplayMode.Location = new System.Drawing.Point(156, 16); - this.comboBoxTimeTableDisplayMode.Name = "comboBoxTimeTableDisplayMode"; - this.comboBoxTimeTableDisplayMode.Size = new System.Drawing.Size(152, 21); - this.comboBoxTimeTableDisplayMode.TabIndex = 1; - this.comboBoxTimeTableDisplayMode.SelectedIndexChanged += new System.EventHandler(this.comboBoxTimeTableDisplayMode_SelectedIndexChanged); + this.comboBoxCompressionFormat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxCompressionFormat.Items.AddRange(new object[] { + "LZMA ZIP ( .zip )", + "GZip ( .tgz )", + "BZip2 ( .bz2 )"}); + this.comboBoxCompressionFormat.Location = new System.Drawing.Point(202, 146); + this.comboBoxCompressionFormat.Name = "comboBoxCompressionFormat"; + this.comboBoxCompressionFormat.Size = new System.Drawing.Size(188, 21); + this.comboBoxCompressionFormat.TabIndex = 10; + this.comboBoxCompressionFormat.SelectedIndexChanged += new System.EventHandler(this.comboBoxCompressionFormat_SelectedIndexChanged); // - // labelTimeTableDisplayMode + // labelPackageCompression // - this.labelTimeTableDisplayMode.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.labelPackageCompression.AutoSize = true; + this.labelPackageCompression.Location = new System.Drawing.Point(8, 150); + this.labelPackageCompression.Name = "labelPackageCompression"; + this.labelPackageCompression.Size = new System.Drawing.Size(147, 13); + this.labelPackageCompression.TabIndex = 9; + this.labelPackageCompression.Text = "Package compression format:"; + // + // buttonOtherDirectory + // + this.buttonOtherDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonOtherDirectory.BackColor = System.Drawing.SystemColors.Control; + this.buttonOtherDirectory.ForeColor = System.Drawing.SystemColors.ControlText; + this.buttonOtherDirectory.Location = new System.Drawing.Point(594, 81); + this.buttonOtherDirectory.Name = "buttonOtherDirectory"; + this.buttonOtherDirectory.Size = new System.Drawing.Size(75, 26); + this.buttonOtherDirectory.TabIndex = 8; + this.buttonOtherDirectory.Text = "Choose..."; + this.buttonOtherDirectory.UseVisualStyleBackColor = true; + this.buttonOtherDirectory.Click += new System.EventHandler(this.buttonOtherDirectory_Click); + // + // labelOtherInstallDirectory + // + this.labelOtherInstallDirectory.Location = new System.Drawing.Point(6, 80); + this.labelOtherInstallDirectory.Name = "labelOtherInstallDirectory"; + this.labelOtherInstallDirectory.Size = new System.Drawing.Size(175, 30); + this.labelOtherInstallDirectory.TabIndex = 7; + this.labelOtherInstallDirectory.Text = "Other items installation directory:"; + this.labelOtherInstallDirectory.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // textBoxOtherDirectory + // + this.textBoxOtherDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.labelTimeTableDisplayMode.AutoEllipsis = true; - this.labelTimeTableDisplayMode.Location = new System.Drawing.Point(3, 17); - this.labelTimeTableDisplayMode.Name = "labelTimeTableDisplayMode"; - this.labelTimeTableDisplayMode.Size = new System.Drawing.Size(153, 18); - this.labelTimeTableDisplayMode.TabIndex = 0; - this.labelTimeTableDisplayMode.Text = "Timetable Display Mode:"; - this.labelTimeTableDisplayMode.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.textBoxOtherDirectory.BackColor = System.Drawing.SystemColors.Control; + this.textBoxOtherDirectory.ForeColor = System.Drawing.SystemColors.ControlText; + this.textBoxOtherDirectory.Location = new System.Drawing.Point(200, 84); + this.textBoxOtherDirectory.Name = "textBoxOtherDirectory"; + this.textBoxOtherDirectory.ReadOnly = true; + this.textBoxOtherDirectory.Size = new System.Drawing.Size(387, 20); + this.textBoxOtherDirectory.TabIndex = 6; // - // groupBoxRailDriver + // buttonTrainInstallationDirectory // - this.groupBoxRailDriver.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxRailDriver.Controls.Add(this.labelRailDriverCalibration); - this.groupBoxRailDriver.Controls.Add(this.buttonRailDriverCalibration); - this.groupBoxRailDriver.Controls.Add(this.comboBoxRailDriverUnits); - this.groupBoxRailDriver.Controls.Add(this.labelRailDriverSpeedUnits); - this.groupBoxRailDriver.ForeColor = System.Drawing.Color.Black; - this.groupBoxRailDriver.Location = new System.Drawing.Point(0, 230); - this.groupBoxRailDriver.Name = "groupBoxRailDriver"; - this.groupBoxRailDriver.Size = new System.Drawing.Size(316, 75); - this.groupBoxRailDriver.TabIndex = 21; - this.groupBoxRailDriver.TabStop = false; - this.groupBoxRailDriver.Text = "RailDriver"; + this.buttonTrainInstallationDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonTrainInstallationDirectory.BackColor = System.Drawing.SystemColors.ButtonFace; + this.buttonTrainInstallationDirectory.ForeColor = System.Drawing.SystemColors.ControlText; + this.buttonTrainInstallationDirectory.Location = new System.Drawing.Point(594, 49); + this.buttonTrainInstallationDirectory.Name = "buttonTrainInstallationDirectory"; + this.buttonTrainInstallationDirectory.Size = new System.Drawing.Size(75, 26); + this.buttonTrainInstallationDirectory.TabIndex = 5; + this.buttonTrainInstallationDirectory.Text = "Choose..."; + this.buttonTrainInstallationDirectory.UseVisualStyleBackColor = true; + this.buttonTrainInstallationDirectory.Click += new System.EventHandler(this.buttonTrainInstallationDirectory_Click); // - // labelRailDriverCalibration + // labelTrainInstallDirectory // - this.labelRailDriverCalibration.AutoSize = true; - this.labelRailDriverCalibration.Location = new System.Drawing.Point(7, 46); - this.labelRailDriverCalibration.Name = "labelRailDriverCalibration"; - this.labelRailDriverCalibration.Size = new System.Drawing.Size(78, 13); - this.labelRailDriverCalibration.TabIndex = 5; - this.labelRailDriverCalibration.Text = "Set Calibration:"; + this.labelTrainInstallDirectory.AutoSize = true; + this.labelTrainInstallDirectory.Location = new System.Drawing.Point(6, 52); + this.labelTrainInstallDirectory.Name = "labelTrainInstallDirectory"; + this.labelTrainInstallDirectory.Size = new System.Drawing.Size(129, 13); + this.labelTrainInstallDirectory.TabIndex = 4; + this.labelTrainInstallDirectory.Text = "Train installation directory:"; // - // buttonRailDriverCalibration + // textBoxTrainDirectory // - this.buttonRailDriverCalibration.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonRailDriverCalibration.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonRailDriverCalibration.Location = new System.Drawing.Point(230, 42); - this.buttonRailDriverCalibration.Name = "buttonRailDriverCalibration"; - this.buttonRailDriverCalibration.Size = new System.Drawing.Size(75, 26); - this.buttonRailDriverCalibration.TabIndex = 4; - this.buttonRailDriverCalibration.Text = "Launch..."; - this.buttonRailDriverCalibration.UseVisualStyleBackColor = true; - this.buttonRailDriverCalibration.Click += new System.EventHandler(this.buttonRailDriverCalibration_Click); + this.textBoxTrainDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBoxTrainDirectory.BackColor = System.Drawing.SystemColors.Control; + this.textBoxTrainDirectory.ForeColor = System.Drawing.SystemColors.ControlText; + this.textBoxTrainDirectory.Location = new System.Drawing.Point(200, 51); + this.textBoxTrainDirectory.Name = "textBoxTrainDirectory"; + this.textBoxTrainDirectory.ReadOnly = true; + this.textBoxTrainDirectory.Size = new System.Drawing.Size(387, 20); + this.textBoxTrainDirectory.TabIndex = 3; // - // comboBoxRailDriverUnits + // buttonSetRouteDirectory // - this.comboBoxRailDriverUnits.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxRailDriverUnits.FormattingEnabled = true; - this.comboBoxRailDriverUnits.Items.AddRange(new object[] { - "Miles per Hour (MPH)", - "Kilometers per Hour (KPH)"}); - this.comboBoxRailDriverUnits.Location = new System.Drawing.Point(140, 16); - this.comboBoxRailDriverUnits.Name = "comboBoxRailDriverUnits"; - this.comboBoxRailDriverUnits.Size = new System.Drawing.Size(165, 21); - this.comboBoxRailDriverUnits.TabIndex = 3; - this.comboBoxRailDriverUnits.SelectedIndexChanged += new System.EventHandler(this.comboBoxRailDriverUnits_SelectedIndexChanged); + this.buttonSetRouteDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonSetRouteDirectory.BackColor = System.Drawing.SystemColors.ButtonFace; + this.buttonSetRouteDirectory.ForeColor = System.Drawing.SystemColors.ControlText; + this.buttonSetRouteDirectory.Location = new System.Drawing.Point(594, 18); + this.buttonSetRouteDirectory.Name = "buttonSetRouteDirectory"; + this.buttonSetRouteDirectory.Size = new System.Drawing.Size(75, 26); + this.buttonSetRouteDirectory.TabIndex = 2; + this.buttonSetRouteDirectory.Text = "Choose..."; + this.buttonSetRouteDirectory.UseVisualStyleBackColor = true; + this.buttonSetRouteDirectory.Click += new System.EventHandler(this.buttonSetRouteDirectory_Click); // - // labelRailDriverSpeedUnits + // labelRouteInstallDirectory // - this.labelRailDriverSpeedUnits.Location = new System.Drawing.Point(7, 14); - this.labelRailDriverSpeedUnits.Name = "labelRailDriverSpeedUnits"; - this.labelRailDriverSpeedUnits.Size = new System.Drawing.Size(130, 30); - this.labelRailDriverSpeedUnits.TabIndex = 2; - this.labelRailDriverSpeedUnits.Text = "LED Display speed units:"; - this.labelRailDriverSpeedUnits.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.labelRouteInstallDirectory.AutoSize = true; + this.labelRouteInstallDirectory.Location = new System.Drawing.Point(6, 21); + this.labelRouteInstallDirectory.Name = "labelRouteInstallDirectory"; + this.labelRouteInstallDirectory.Size = new System.Drawing.Size(134, 13); + this.labelRouteInstallDirectory.TabIndex = 1; + this.labelRouteInstallDirectory.Text = "Route installation directory:"; // - // groupboxDistance + // textBoxRouteDirectory // - this.groupboxDistance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.textBoxRouteDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxDistance.Controls.Add(this.comboboxMotionBlur); - this.groupboxDistance.Controls.Add(this.labelMotionBlur); - this.groupboxDistance.Controls.Add(this.labelDistanceUnit); - this.groupboxDistance.Controls.Add(this.updownDistance); - this.groupboxDistance.Controls.Add(this.labelDistance); - this.groupboxDistance.ForeColor = System.Drawing.Color.Black; - this.groupboxDistance.Location = new System.Drawing.Point(0, 0); - this.groupboxDistance.Name = "groupboxDistance"; - this.groupboxDistance.Size = new System.Drawing.Size(316, 80); - this.groupboxDistance.TabIndex = 8; - this.groupboxDistance.TabStop = false; - this.groupboxDistance.Text = "Distance effects"; + this.textBoxRouteDirectory.BackColor = System.Drawing.SystemColors.Control; + this.textBoxRouteDirectory.ForeColor = System.Drawing.SystemColors.ControlText; + this.textBoxRouteDirectory.Location = new System.Drawing.Point(200, 20); + this.textBoxRouteDirectory.Name = "textBoxRouteDirectory"; + this.textBoxRouteDirectory.ReadOnly = true; + this.textBoxRouteDirectory.Size = new System.Drawing.Size(387, 20); + this.textBoxRouteDirectory.TabIndex = 0; // - // comboboxMotionBlur + // panelOptionsLeft // - this.comboboxMotionBlur.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.comboboxMotionBlur.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboboxMotionBlur.FormattingEnabled = true; - this.comboboxMotionBlur.Location = new System.Drawing.Point(144, 48); - this.comboboxMotionBlur.Name = "comboboxMotionBlur"; - this.comboboxMotionBlur.Size = new System.Drawing.Size(152, 21); - this.comboboxMotionBlur.TabIndex = 4; + this.panelOptionsLeft.Controls.Add(this.groupboxDisplayMode); + this.panelOptionsLeft.Controls.Add(this.groupboxWindow); + this.panelOptionsLeft.Controls.Add(this.groupboxFullscreen); + this.panelOptionsLeft.Controls.Add(this.groupboxInterpolation); + this.panelOptionsLeft.Location = new System.Drawing.Point(8, 72); + this.panelOptionsLeft.Name = "panelOptionsLeft"; + this.panelOptionsLeft.Size = new System.Drawing.Size(316, 576); + this.panelOptionsLeft.TabIndex = 16; // - // labelMotionBlur + // groupboxDisplayMode // - this.labelMotionBlur.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.groupboxDisplayMode.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.labelMotionBlur.AutoEllipsis = true; - this.labelMotionBlur.Location = new System.Drawing.Point(5, 51); - this.labelMotionBlur.Name = "labelMotionBlur"; - this.labelMotionBlur.Size = new System.Drawing.Size(140, 18); - this.labelMotionBlur.TabIndex = 3; - this.labelMotionBlur.Text = "Motion blur:"; - this.labelMotionBlur.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // labelDistanceUnit - // - this.labelDistanceUnit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.labelDistanceUnit.AutoEllipsis = true; - this.labelDistanceUnit.Location = new System.Drawing.Point(272, 24); - this.labelDistanceUnit.Name = "labelDistanceUnit"; - this.labelDistanceUnit.Size = new System.Drawing.Size(24, 18); - this.labelDistanceUnit.TabIndex = 2; - this.labelDistanceUnit.Text = "m"; + this.groupboxDisplayMode.Controls.Add(this.comboBoxFont); + this.groupboxDisplayMode.Controls.Add(this.labelFontName); + this.groupboxDisplayMode.Controls.Add(this.labelHUDLarge); + this.groupboxDisplayMode.Controls.Add(this.labelHUDNormal); + this.groupboxDisplayMode.Controls.Add(this.labelHUDSmall); + this.groupboxDisplayMode.Controls.Add(this.trackBarHUDSize); + this.groupboxDisplayMode.Controls.Add(this.labelHUDScale); + this.groupboxDisplayMode.Controls.Add(this.comboboxVSync); + this.groupboxDisplayMode.Controls.Add(this.labelVSync); + this.groupboxDisplayMode.Controls.Add(this.radiobuttonFullscreen); + this.groupboxDisplayMode.Controls.Add(this.radiobuttonWindow); + this.groupboxDisplayMode.ForeColor = System.Drawing.Color.Black; + this.groupboxDisplayMode.Location = new System.Drawing.Point(0, 0); + this.groupboxDisplayMode.Name = "groupboxDisplayMode"; + this.groupboxDisplayMode.Size = new System.Drawing.Size(316, 189); + this.groupboxDisplayMode.TabIndex = 4; + this.groupboxDisplayMode.TabStop = false; + this.groupboxDisplayMode.Text = "Display mode"; // - // updownDistance + // comboBoxFont // - this.updownDistance.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updownDistance.Location = new System.Drawing.Point(144, 24); - this.updownDistance.Maximum = new decimal(new int[] { - 100000, - 0, - 0, - 0}); - this.updownDistance.Minimum = new decimal(new int[] { - 100, - 0, - 0, - 0}); - this.updownDistance.Name = "updownDistance"; - this.updownDistance.Size = new System.Drawing.Size(128, 20); - this.updownDistance.TabIndex = 1; - this.updownDistance.Value = new decimal(new int[] { - 600, - 0, - 0, - 0}); + this.comboBoxFont.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.comboBoxFont.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxFont.FormattingEnabled = true; + this.comboBoxFont.Location = new System.Drawing.Point(45, 155); + this.comboBoxFont.Name = "comboBoxFont"; + this.comboBoxFont.Size = new System.Drawing.Size(264, 21); + this.comboBoxFont.TabIndex = 14; + this.comboBoxFont.SelectionChangeCommitted += new System.EventHandler(this.comboBoxFont_SelectedIndexChanged); // - // labelDistance + // labelFontName // - this.labelDistance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.labelDistance.AutoEllipsis = true; - this.labelDistance.Location = new System.Drawing.Point(9, 26); - this.labelDistance.Name = "labelDistance"; - this.labelDistance.Size = new System.Drawing.Size(136, 18); - this.labelDistance.TabIndex = 0; - this.labelDistance.Text = "Viewing distance:"; - this.labelDistance.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.labelFontName.Location = new System.Drawing.Point(8, 148); + this.labelFontName.Name = "labelFontName"; + this.labelFontName.Size = new System.Drawing.Size(50, 36); + this.labelFontName.TabIndex = 13; + this.labelFontName.Text = "Font:"; + this.labelFontName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // - // groupboxControls + // labelHUDLarge // - this.groupboxControls.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxControls.Controls.Add(this.checkBoxEBAxis); - this.groupboxControls.Controls.Add(this.trackbarJoystickAxisThreshold); - this.groupboxControls.Controls.Add(this.checkboxJoysticksUsed); - this.groupboxControls.Controls.Add(this.labelJoystickAxisThreshold); - this.groupboxControls.ForeColor = System.Drawing.Color.Black; - this.groupboxControls.Location = new System.Drawing.Point(0, 144); - this.groupboxControls.Name = "groupboxControls"; - this.groupboxControls.Size = new System.Drawing.Size(316, 80); - this.groupboxControls.TabIndex = 10; - this.groupboxControls.TabStop = false; - this.groupboxControls.Text = "Controls"; + this.labelHUDLarge.Location = new System.Drawing.Point(258, 125); + this.labelHUDLarge.Name = "labelHUDLarge"; + this.labelHUDLarge.Size = new System.Drawing.Size(72, 48); + this.labelHUDLarge.TabIndex = 12; + this.labelHUDLarge.Text = "Large"; + this.labelHUDLarge.TextAlign = System.Drawing.ContentAlignment.TopCenter; // - // checkBoxEBAxis + // labelHUDNormal // - this.checkBoxEBAxis.Checked = true; - this.checkBoxEBAxis.CheckState = System.Windows.Forms.CheckState.Checked; - this.checkBoxEBAxis.Location = new System.Drawing.Point(8, 41); - this.checkBoxEBAxis.Name = "checkBoxEBAxis"; - this.checkBoxEBAxis.Size = new System.Drawing.Size(190, 36); - this.checkBoxEBAxis.TabIndex = 18; - this.checkBoxEBAxis.Text = "Allow EB on brake axis"; - this.checkBoxEBAxis.UseVisualStyleBackColor = true; + this.labelHUDNormal.Location = new System.Drawing.Point(162, 125); + this.labelHUDNormal.Name = "labelHUDNormal"; + this.labelHUDNormal.Size = new System.Drawing.Size(70, 48); + this.labelHUDNormal.TabIndex = 11; + this.labelHUDNormal.Text = "Normal"; + this.labelHUDNormal.TextAlign = System.Drawing.ContentAlignment.TopCenter; // - // trackbarJoystickAxisThreshold + // labelHUDSmall // - this.trackbarJoystickAxisThreshold.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.trackbarJoystickAxisThreshold.LargeChange = 10; - this.trackbarJoystickAxisThreshold.Location = new System.Drawing.Point(200, 32); - this.trackbarJoystickAxisThreshold.Maximum = 100; - this.trackbarJoystickAxisThreshold.Name = "trackbarJoystickAxisThreshold"; - this.trackbarJoystickAxisThreshold.Size = new System.Drawing.Size(96, 45); - this.trackbarJoystickAxisThreshold.TabIndex = 2; - this.trackbarJoystickAxisThreshold.TickFrequency = 10; - this.trackbarJoystickAxisThreshold.TickStyle = System.Windows.Forms.TickStyle.Both; + this.labelHUDSmall.Location = new System.Drawing.Point(66, 125); + this.labelHUDSmall.Name = "labelHUDSmall"; + this.labelHUDSmall.Size = new System.Drawing.Size(70, 48); + this.labelHUDSmall.TabIndex = 10; + this.labelHUDSmall.Text = "Small"; + this.labelHUDSmall.TextAlign = System.Drawing.ContentAlignment.TopCenter; // - // checkboxJoysticksUsed + // trackBarHUDSize // - this.checkboxJoysticksUsed.AutoSize = true; - this.checkboxJoysticksUsed.Location = new System.Drawing.Point(8, 24); - this.checkboxJoysticksUsed.Name = "checkboxJoysticksUsed"; - this.checkboxJoysticksUsed.Size = new System.Drawing.Size(110, 17); - this.checkboxJoysticksUsed.TabIndex = 0; - this.checkboxJoysticksUsed.Text = "Joysticks enabled"; - this.checkboxJoysticksUsed.UseVisualStyleBackColor = true; - this.checkboxJoysticksUsed.CheckedChanged += new System.EventHandler(this.checkboxJoysticksUsed_CheckedChanged); + this.trackBarHUDSize.LargeChange = 1; + this.trackBarHUDSize.Location = new System.Drawing.Point(88, 100); + this.trackBarHUDSize.Maximum = 2; + this.trackBarHUDSize.Name = "trackBarHUDSize"; + this.trackBarHUDSize.Size = new System.Drawing.Size(220, 45); + this.trackBarHUDSize.TabIndex = 9; + this.trackBarHUDSize.Value = 1; // - // labelJoystickAxisThreshold + // labelHUDScale // - this.labelJoystickAxisThreshold.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.labelJoystickAxisThreshold.AutoEllipsis = true; - this.labelJoystickAxisThreshold.Location = new System.Drawing.Point(110, 10); - this.labelJoystickAxisThreshold.Name = "labelJoystickAxisThreshold"; - this.labelJoystickAxisThreshold.Size = new System.Drawing.Size(180, 18); - this.labelJoystickAxisThreshold.TabIndex = 1; - this.labelJoystickAxisThreshold.Text = "Joystick threshold:"; - this.labelJoystickAxisThreshold.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.labelHUDScale.AutoSize = true; + this.labelHUDScale.Location = new System.Drawing.Point(8, 106); + this.labelHUDScale.Name = "labelHUDScale"; + this.labelHUDScale.Size = new System.Drawing.Size(54, 13); + this.labelHUDScale.TabIndex = 8; + this.labelHUDScale.Text = "HUD Size"; // - // groupboxVerbosity + // comboboxVSync // - this.groupboxVerbosity.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxVerbosity.Controls.Add(this.checkBoxAccessibility); - this.groupboxVerbosity.Controls.Add(this.checkboxErrorMessages); - this.groupboxVerbosity.Controls.Add(this.checkboxWarningMessages); - this.groupboxVerbosity.ForeColor = System.Drawing.Color.Black; - this.groupboxVerbosity.Location = new System.Drawing.Point(0, 396); - this.groupboxVerbosity.Name = "groupboxVerbosity"; - this.groupboxVerbosity.Size = new System.Drawing.Size(316, 64); - this.groupboxVerbosity.TabIndex = 12; - this.groupboxVerbosity.TabStop = false; - this.groupboxVerbosity.Text = "Verbosity"; + this.comboboxVSync.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.comboboxVSync.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboboxVSync.FormattingEnabled = true; + this.comboboxVSync.Location = new System.Drawing.Point(156, 72); + this.comboboxVSync.Name = "comboboxVSync"; + this.comboboxVSync.Size = new System.Drawing.Size(152, 21); + this.comboboxVSync.TabIndex = 7; // - // checkBoxAccessibility + // labelVSync // - this.checkBoxAccessibility.AutoSize = true; - this.checkBoxAccessibility.Location = new System.Drawing.Point(176, 21); - this.checkBoxAccessibility.Name = "checkBoxAccessibility"; - this.checkBoxAccessibility.Size = new System.Drawing.Size(106, 17); - this.checkBoxAccessibility.TabIndex = 2; - this.checkBoxAccessibility.Text = "Accessibility Aids"; - this.checkBoxAccessibility.UseVisualStyleBackColor = true; + this.labelVSync.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelVSync.AutoEllipsis = true; + this.labelVSync.Location = new System.Drawing.Point(8, 72); + this.labelVSync.Name = "labelVSync"; + this.labelVSync.Size = new System.Drawing.Size(148, 18); + this.labelVSync.TabIndex = 2; + this.labelVSync.Text = "Vertical syncronization:"; + this.labelVSync.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // checkboxErrorMessages + // radiobuttonFullscreen // - this.checkboxErrorMessages.AutoSize = true; - this.checkboxErrorMessages.Location = new System.Drawing.Point(8, 38); - this.checkboxErrorMessages.Name = "checkboxErrorMessages"; - this.checkboxErrorMessages.Size = new System.Drawing.Size(127, 17); - this.checkboxErrorMessages.TabIndex = 1; - this.checkboxErrorMessages.Text = "Show error messages"; - this.checkboxErrorMessages.UseVisualStyleBackColor = true; + this.radiobuttonFullscreen.AutoSize = true; + this.radiobuttonFullscreen.Location = new System.Drawing.Point(8, 48); + this.radiobuttonFullscreen.Name = "radiobuttonFullscreen"; + this.radiobuttonFullscreen.Size = new System.Drawing.Size(102, 17); + this.radiobuttonFullscreen.TabIndex = 1; + this.radiobuttonFullscreen.TabStop = true; + this.radiobuttonFullscreen.Text = "Fullscreen mode"; + this.radiobuttonFullscreen.UseVisualStyleBackColor = true; // - // checkboxWarningMessages + // radiobuttonWindow // - this.checkboxWarningMessages.AutoSize = true; - this.checkboxWarningMessages.Location = new System.Drawing.Point(8, 21); - this.checkboxWarningMessages.Name = "checkboxWarningMessages"; - this.checkboxWarningMessages.Size = new System.Drawing.Size(143, 17); - this.checkboxWarningMessages.TabIndex = 0; - this.checkboxWarningMessages.Text = "Show warning messages"; - this.checkboxWarningMessages.UseVisualStyleBackColor = true; + this.radiobuttonWindow.AutoSize = true; + this.radiobuttonWindow.Checked = true; + this.radiobuttonWindow.Location = new System.Drawing.Point(8, 24); + this.radiobuttonWindow.Name = "radiobuttonWindow"; + this.radiobuttonWindow.Size = new System.Drawing.Size(93, 17); + this.radiobuttonWindow.TabIndex = 0; + this.radiobuttonWindow.TabStop = true; + this.radiobuttonWindow.Text = "Window mode"; + this.radiobuttonWindow.UseVisualStyleBackColor = true; // - // groupboxSimulation + // groupboxWindow // - this.groupboxSimulation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.groupboxWindow.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxSimulation.Controls.Add(this.checkBoxLoadingSway); - this.groupboxSimulation.Controls.Add(this.checkboxBlackBox); - this.groupboxSimulation.Controls.Add(this.checkboxDerailments); - this.groupboxSimulation.Controls.Add(this.checkboxCollisions); - this.groupboxSimulation.Controls.Add(this.checkboxToppling); - this.groupboxSimulation.ForeColor = System.Drawing.Color.Black; - this.groupboxSimulation.Location = new System.Drawing.Point(0, 310); - this.groupboxSimulation.Name = "groupboxSimulation"; - this.groupboxSimulation.Size = new System.Drawing.Size(316, 80); - this.groupboxSimulation.TabIndex = 11; - this.groupboxSimulation.TabStop = false; - this.groupboxSimulation.Text = "Detail of simulation"; + this.groupboxWindow.Controls.Add(this.updownWindowHeight); + this.groupboxWindow.Controls.Add(this.labelWindowHeight); + this.groupboxWindow.Controls.Add(this.updownWindowWidth); + this.groupboxWindow.Controls.Add(this.labelWindowWidth); + this.groupboxWindow.ForeColor = System.Drawing.Color.Black; + this.groupboxWindow.Location = new System.Drawing.Point(0, 192); + this.groupboxWindow.Name = "groupboxWindow"; + this.groupboxWindow.Size = new System.Drawing.Size(316, 80); + this.groupboxWindow.TabIndex = 5; + this.groupboxWindow.TabStop = false; + this.groupboxWindow.Text = "Window mode"; // - // checkBoxLoadingSway + // updownWindowHeight // - this.checkBoxLoadingSway.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.checkBoxLoadingSway.AutoSize = true; - this.checkBoxLoadingSway.Location = new System.Drawing.Point(176, 21); - this.checkBoxLoadingSway.Name = "checkBoxLoadingSway"; - this.checkBoxLoadingSway.Size = new System.Drawing.Size(123, 17); - this.checkBoxLoadingSway.TabIndex = 4; - this.checkBoxLoadingSway.Text = "Enable loading sway"; - this.checkBoxLoadingSway.UseVisualStyleBackColor = true; + this.updownWindowHeight.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updownWindowHeight.Location = new System.Drawing.Point(156, 48); + this.updownWindowHeight.Maximum = new decimal(new int[] { + 1048575, + 0, + 0, + 0}); + this.updownWindowHeight.Minimum = new decimal(new int[] { + 16, + 0, + 0, + 0}); + this.updownWindowHeight.Name = "updownWindowHeight"; + this.updownWindowHeight.Size = new System.Drawing.Size(152, 20); + this.updownWindowHeight.TabIndex = 3; + this.updownWindowHeight.Value = new decimal(new int[] { + 600, + 0, + 0, + 0}); // - // checkboxBlackBox + // labelWindowHeight // - this.checkboxBlackBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) + this.labelWindowHeight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.checkboxBlackBox.AutoSize = true; - this.checkboxBlackBox.Location = new System.Drawing.Point(176, 38); - this.checkboxBlackBox.Name = "checkboxBlackBox"; - this.checkboxBlackBox.Size = new System.Drawing.Size(108, 17); - this.checkboxBlackBox.TabIndex = 3; - this.checkboxBlackBox.Text = "Enable black box"; - this.checkboxBlackBox.UseVisualStyleBackColor = true; + this.labelWindowHeight.AutoEllipsis = true; + this.labelWindowHeight.Location = new System.Drawing.Point(8, 50); + this.labelWindowHeight.Name = "labelWindowHeight"; + this.labelWindowHeight.Size = new System.Drawing.Size(148, 18); + this.labelWindowHeight.TabIndex = 2; + this.labelWindowHeight.Text = "Height:"; + this.labelWindowHeight.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // checkboxDerailments + // updownWindowWidth // - this.checkboxDerailments.AutoSize = true; - this.checkboxDerailments.Location = new System.Drawing.Point(8, 55); - this.checkboxDerailments.Name = "checkboxDerailments"; - this.checkboxDerailments.Size = new System.Drawing.Size(81, 17); - this.checkboxDerailments.TabIndex = 2; - this.checkboxDerailments.Text = "Derailments"; - this.checkboxDerailments.UseVisualStyleBackColor = true; + this.updownWindowWidth.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updownWindowWidth.Location = new System.Drawing.Point(156, 24); + this.updownWindowWidth.Maximum = new decimal(new int[] { + 1048575, + 0, + 0, + 0}); + this.updownWindowWidth.Minimum = new decimal(new int[] { + 16, + 0, + 0, + 0}); + this.updownWindowWidth.Name = "updownWindowWidth"; + this.updownWindowWidth.Size = new System.Drawing.Size(152, 20); + this.updownWindowWidth.TabIndex = 1; + this.updownWindowWidth.Value = new decimal(new int[] { + 960, + 0, + 0, + 0}); // - // checkboxCollisions + // labelWindowWidth // - this.checkboxCollisions.AutoSize = true; - this.checkboxCollisions.Location = new System.Drawing.Point(8, 38); - this.checkboxCollisions.Name = "checkboxCollisions"; - this.checkboxCollisions.Size = new System.Drawing.Size(69, 17); - this.checkboxCollisions.TabIndex = 1; - this.checkboxCollisions.Text = "Collisions"; - this.checkboxCollisions.UseVisualStyleBackColor = true; + this.labelWindowWidth.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelWindowWidth.AutoEllipsis = true; + this.labelWindowWidth.Location = new System.Drawing.Point(8, 26); + this.labelWindowWidth.Name = "labelWindowWidth"; + this.labelWindowWidth.Size = new System.Drawing.Size(148, 18); + this.labelWindowWidth.TabIndex = 0; + this.labelWindowWidth.Text = "Width:"; + this.labelWindowWidth.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // checkboxToppling + // groupboxFullscreen // - this.checkboxToppling.AutoSize = true; - this.checkboxToppling.Location = new System.Drawing.Point(8, 21); - this.checkboxToppling.Name = "checkboxToppling"; - this.checkboxToppling.Size = new System.Drawing.Size(67, 17); - this.checkboxToppling.TabIndex = 0; - this.checkboxToppling.Text = "Toppling"; - this.checkboxToppling.UseVisualStyleBackColor = true; + this.groupboxFullscreen.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupboxFullscreen.Controls.Add(this.comboboxFullscreenBits); + this.groupboxFullscreen.Controls.Add(this.labelFullscreenBits); + this.groupboxFullscreen.Controls.Add(this.updownFullscreenHeight); + this.groupboxFullscreen.Controls.Add(this.labelFullscreenHeight); + this.groupboxFullscreen.Controls.Add(this.updownFullscreenWidth); + this.groupboxFullscreen.Controls.Add(this.labelFullscreenWidth); + this.groupboxFullscreen.ForeColor = System.Drawing.Color.Black; + this.groupboxFullscreen.Location = new System.Drawing.Point(0, 275); + this.groupboxFullscreen.Name = "groupboxFullscreen"; + this.groupboxFullscreen.Size = new System.Drawing.Size(316, 104); + this.groupboxFullscreen.TabIndex = 6; + this.groupboxFullscreen.TabStop = false; + this.groupboxFullscreen.Text = "Fullscreen mode"; // - // groupboxSound + // comboboxFullscreenBits // - this.groupboxSound.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.comboboxFullscreenBits.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.comboboxFullscreenBits.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboboxFullscreenBits.FormattingEnabled = true; + this.comboboxFullscreenBits.Location = new System.Drawing.Point(156, 72); + this.comboboxFullscreenBits.Name = "comboboxFullscreenBits"; + this.comboboxFullscreenBits.Size = new System.Drawing.Size(152, 21); + this.comboboxFullscreenBits.TabIndex = 5; + // + // labelFullscreenBits + // + this.labelFullscreenBits.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.groupboxSound.Controls.Add(this.updownSoundNumber); - this.groupboxSound.Controls.Add(this.labelSoundNumber); - this.groupboxSound.ForeColor = System.Drawing.Color.Black; - this.groupboxSound.Location = new System.Drawing.Point(0, 88); - this.groupboxSound.Name = "groupboxSound"; - this.groupboxSound.Size = new System.Drawing.Size(316, 48); - this.groupboxSound.TabIndex = 9; - this.groupboxSound.TabStop = false; - this.groupboxSound.Text = "Sound"; + this.labelFullscreenBits.AutoEllipsis = true; + this.labelFullscreenBits.Location = new System.Drawing.Point(8, 74); + this.labelFullscreenBits.Name = "labelFullscreenBits"; + this.labelFullscreenBits.Size = new System.Drawing.Size(148, 18); + this.labelFullscreenBits.TabIndex = 4; + this.labelFullscreenBits.Text = "Bits per pixel:"; + this.labelFullscreenBits.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // updownSoundNumber + // updownFullscreenHeight // - this.updownSoundNumber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updownSoundNumber.Location = new System.Drawing.Point(140, 16); - this.updownSoundNumber.Maximum = new decimal(new int[] { - 128, + this.updownFullscreenHeight.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updownFullscreenHeight.Location = new System.Drawing.Point(156, 48); + this.updownFullscreenHeight.Maximum = new decimal(new int[] { + 1048575, 0, 0, 0}); - this.updownSoundNumber.Minimum = new decimal(new int[] { - 8, + this.updownFullscreenHeight.Minimum = new decimal(new int[] { + 16, 0, 0, 0}); - this.updownSoundNumber.Name = "updownSoundNumber"; - this.updownSoundNumber.Size = new System.Drawing.Size(152, 20); - this.updownSoundNumber.TabIndex = 3; - this.updownSoundNumber.Value = new decimal(new int[] { - 16, + this.updownFullscreenHeight.Name = "updownFullscreenHeight"; + this.updownFullscreenHeight.Size = new System.Drawing.Size(152, 20); + this.updownFullscreenHeight.TabIndex = 3; + this.updownFullscreenHeight.Value = new decimal(new int[] { + 768, 0, 0, 0}); // - // labelSoundNumber + // labelFullscreenHeight // - this.labelSoundNumber.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.labelFullscreenHeight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.labelSoundNumber.Location = new System.Drawing.Point(5, 18); - this.labelSoundNumber.Name = "labelSoundNumber"; - this.labelSoundNumber.Size = new System.Drawing.Size(136, 18); - this.labelSoundNumber.TabIndex = 2; - this.labelSoundNumber.Text = "Number of allowed sounds:"; - this.labelSoundNumber.TextAlign = System.Drawing.ContentAlignment.TopRight; + this.labelFullscreenHeight.AutoEllipsis = true; + this.labelFullscreenHeight.Location = new System.Drawing.Point(8, 50); + this.labelFullscreenHeight.Name = "labelFullscreenHeight"; + this.labelFullscreenHeight.Size = new System.Drawing.Size(148, 18); + this.labelFullscreenHeight.TabIndex = 2; + this.labelFullscreenHeight.Text = "Height:"; + this.labelFullscreenHeight.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // panelOptionsPage2 + // updownFullscreenWidth // - this.panelOptionsPage2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.panelOptionsPage2.Controls.Add(this.groupBoxInputDevice); - this.panelOptionsPage2.Controls.Add(this.groupBoxObjectParser); - this.panelOptionsPage2.Controls.Add(this.groupBoxKioskMode); - this.panelOptionsPage2.Controls.Add(this.groupBoxAdvancedOptions); - this.panelOptionsPage2.Controls.Add(this.groupBoxPackageOptions); - this.panelOptionsPage2.Location = new System.Drawing.Point(0, 72); - this.panelOptionsPage2.Name = "panelOptionsPage2"; - this.panelOptionsPage2.Size = new System.Drawing.Size(683, 553); - this.panelOptionsPage2.TabIndex = 20; + this.updownFullscreenWidth.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updownFullscreenWidth.Location = new System.Drawing.Point(156, 24); + this.updownFullscreenWidth.Maximum = new decimal(new int[] { + 1048575, + 0, + 0, + 0}); + this.updownFullscreenWidth.Minimum = new decimal(new int[] { + 16, + 0, + 0, + 0}); + this.updownFullscreenWidth.Name = "updownFullscreenWidth"; + this.updownFullscreenWidth.Size = new System.Drawing.Size(152, 20); + this.updownFullscreenWidth.TabIndex = 1; + this.updownFullscreenWidth.Value = new decimal(new int[] { + 1024, + 0, + 0, + 0}); // - // groupBoxInputDevice + // labelFullscreenWidth // - this.groupBoxInputDevice.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) + this.labelFullscreenWidth.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxInputDevice.Controls.Add(this.labelInputDevice); - this.groupBoxInputDevice.Controls.Add(this.listviewInputDevice); - this.groupBoxInputDevice.Controls.Add(this.checkBoxInputDeviceEnable); - this.groupBoxInputDevice.Controls.Add(this.buttonInputDeviceConfig); - this.groupBoxInputDevice.ForeColor = System.Drawing.Color.Black; - this.groupBoxInputDevice.Location = new System.Drawing.Point(6, 374); - this.groupBoxInputDevice.Name = "groupBoxInputDevice"; - this.groupBoxInputDevice.Size = new System.Drawing.Size(674, 173); - this.groupBoxInputDevice.TabIndex = 24; - this.groupBoxInputDevice.TabStop = false; - this.groupBoxInputDevice.Text = "Input Device Plugin"; + this.labelFullscreenWidth.AutoEllipsis = true; + this.labelFullscreenWidth.Location = new System.Drawing.Point(8, 26); + this.labelFullscreenWidth.Name = "labelFullscreenWidth"; + this.labelFullscreenWidth.Size = new System.Drawing.Size(148, 18); + this.labelFullscreenWidth.TabIndex = 0; + this.labelFullscreenWidth.Text = "Width:"; + this.labelFullscreenWidth.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // labelInputDevice + // groupboxInterpolation // - this.labelInputDevice.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.groupboxInterpolation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.labelInputDevice.Location = new System.Drawing.Point(8, 17); - this.labelInputDevice.Name = "labelInputDevice"; - this.labelInputDevice.Size = new System.Drawing.Size(658, 17); - this.labelInputDevice.TabIndex = 0; - this.labelInputDevice.Text = "WARNING: If you are turn on the Input Device Plugin(s), it may be happen the conf" + - "lict of input setting(s)."; + this.groupboxInterpolation.Controls.Add(this.updownAntiAliasing); + this.groupboxInterpolation.Controls.Add(this.labelAntiAliasing); + this.groupboxInterpolation.Controls.Add(this.labelTransparencyQuality); + this.groupboxInterpolation.Controls.Add(this.labelTransparencyPerformance); + this.groupboxInterpolation.Controls.Add(this.labelTransparency); + this.groupboxInterpolation.Controls.Add(this.updownAnisotropic); + this.groupboxInterpolation.Controls.Add(this.labelAnisotropic); + this.groupboxInterpolation.Controls.Add(this.comboboxInterpolation); + this.groupboxInterpolation.Controls.Add(this.labelInterpolation); + this.groupboxInterpolation.Controls.Add(this.trackbarTransparency); + this.groupboxInterpolation.ForeColor = System.Drawing.Color.Black; + this.groupboxInterpolation.Location = new System.Drawing.Point(0, 381); + this.groupboxInterpolation.Name = "groupboxInterpolation"; + this.groupboxInterpolation.Size = new System.Drawing.Size(316, 160); + this.groupboxInterpolation.TabIndex = 7; + this.groupboxInterpolation.TabStop = false; + this.groupboxInterpolation.Text = "Interpolation"; // - // listviewInputDevice + // updownAntiAliasing // - this.listviewInputDevice.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.listviewInputDevice.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnheaderInputDeviceName, - this.columnheaderInputDeviceStatus, - this.columnheaderInputDeviceVersion, - this.columnheaderInputDeviceProvider, - this.columnheaderInputDeviceFileName}); - this.listviewInputDevice.FullRowSelect = true; - this.listviewInputDevice.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; - this.listviewInputDevice.HideSelection = false; - this.listviewInputDevice.LabelWrap = false; - this.listviewInputDevice.Location = new System.Drawing.Point(8, 38); - this.listviewInputDevice.MultiSelect = false; - this.listviewInputDevice.Name = "listviewInputDevice"; - this.listviewInputDevice.ShowGroups = false; - this.listviewInputDevice.Size = new System.Drawing.Size(658, 95); - this.listviewInputDevice.TabIndex = 1; - this.listviewInputDevice.UseCompatibleStateImageBehavior = false; - this.listviewInputDevice.View = System.Windows.Forms.View.Details; - this.listviewInputDevice.SelectedIndexChanged += new System.EventHandler(this.listviewInputDevice_SelectedIndexChanged); - this.listviewInputDevice.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.listviewInputDevice_MouseDoubleClick); + this.updownAntiAliasing.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updownAntiAliasing.Location = new System.Drawing.Point(156, 64); + this.updownAntiAliasing.Maximum = new decimal(new int[] { + 16, + 0, + 0, + 0}); + this.updownAntiAliasing.Name = "updownAntiAliasing"; + this.updownAntiAliasing.Size = new System.Drawing.Size(152, 20); + this.updownAntiAliasing.TabIndex = 5; // - // columnheaderInputDeviceName + // labelAntiAliasing // - this.columnheaderInputDeviceName.Text = "Name"; + this.labelAntiAliasing.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelAntiAliasing.AutoEllipsis = true; + this.labelAntiAliasing.Location = new System.Drawing.Point(8, 66); + this.labelAntiAliasing.Name = "labelAntiAliasing"; + this.labelAntiAliasing.Size = new System.Drawing.Size(148, 18); + this.labelAntiAliasing.TabIndex = 4; + this.labelAntiAliasing.Text = "Level of anti-aliasing:"; + this.labelAntiAliasing.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // columnheaderInputDeviceStatus + // labelTransparencyQuality // - this.columnheaderInputDeviceStatus.Text = "Status"; + this.labelTransparencyQuality.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.labelTransparencyQuality.AutoEllipsis = true; + this.labelTransparencyQuality.Location = new System.Drawing.Point(230, 136); + this.labelTransparencyQuality.Name = "labelTransparencyQuality"; + this.labelTransparencyQuality.Size = new System.Drawing.Size(76, 16); + this.labelTransparencyQuality.TabIndex = 9; + this.labelTransparencyQuality.Text = "Smooth"; + this.labelTransparencyQuality.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // columnheaderInputDeviceVersion + // labelTransparencyPerformance // - this.columnheaderInputDeviceVersion.Text = "Version"; + this.labelTransparencyPerformance.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.labelTransparencyPerformance.AutoEllipsis = true; + this.labelTransparencyPerformance.Location = new System.Drawing.Point(130, 136); + this.labelTransparencyPerformance.Name = "labelTransparencyPerformance"; + this.labelTransparencyPerformance.Size = new System.Drawing.Size(76, 16); + this.labelTransparencyPerformance.TabIndex = 8; + this.labelTransparencyPerformance.Text = "Sharp"; + this.labelTransparencyPerformance.TextAlign = System.Drawing.ContentAlignment.TopCenter; // - // columnheaderInputDeviceProvider + // labelTransparency // - this.columnheaderInputDeviceProvider.Text = "Provider"; + this.labelTransparency.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelTransparency.AutoEllipsis = true; + this.labelTransparency.Location = new System.Drawing.Point(8, 100); + this.labelTransparency.Name = "labelTransparency"; + this.labelTransparency.Size = new System.Drawing.Size(148, 18); + this.labelTransparency.TabIndex = 6; + this.labelTransparency.Text = "Transparency:"; + this.labelTransparency.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // columnheaderInputDeviceFileName + // updownAnisotropic // - this.columnheaderInputDeviceFileName.Text = "File Name"; - this.columnheaderInputDeviceFileName.Width = 200; + this.updownAnisotropic.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updownAnisotropic.Enabled = false; + this.updownAnisotropic.Location = new System.Drawing.Point(156, 40); + this.updownAnisotropic.Maximum = new decimal(new int[] { + 16, + 0, + 0, + 0}); + this.updownAnisotropic.Name = "updownAnisotropic"; + this.updownAnisotropic.Size = new System.Drawing.Size(152, 20); + this.updownAnisotropic.TabIndex = 3; // - // checkBoxInputDeviceEnable + // labelAnisotropic // - this.checkBoxInputDeviceEnable.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.checkBoxInputDeviceEnable.Enabled = false; - this.checkBoxInputDeviceEnable.Location = new System.Drawing.Point(8, 136); - this.checkBoxInputDeviceEnable.Name = "checkBoxInputDeviceEnable"; - this.checkBoxInputDeviceEnable.Size = new System.Drawing.Size(230, 34); - this.checkBoxInputDeviceEnable.TabIndex = 2; - this.checkBoxInputDeviceEnable.Text = "Enable this Input Device Plugin"; - this.checkBoxInputDeviceEnable.UseVisualStyleBackColor = true; - this.checkBoxInputDeviceEnable.CheckedChanged += new System.EventHandler(this.checkBoxInputDeviceEnable_CheckedChanged); + this.labelAnisotropic.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelAnisotropic.AutoEllipsis = true; + this.labelAnisotropic.Enabled = false; + this.labelAnisotropic.Location = new System.Drawing.Point(8, 42); + this.labelAnisotropic.Name = "labelAnisotropic"; + this.labelAnisotropic.Size = new System.Drawing.Size(148, 18); + this.labelAnisotropic.TabIndex = 2; + this.labelAnisotropic.Text = "Level of anisotropic filtering:"; + this.labelAnisotropic.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // buttonInputDeviceConfig + // comboboxInterpolation // - this.buttonInputDeviceConfig.Anchor = System.Windows.Forms.AnchorStyles.Bottom; - this.buttonInputDeviceConfig.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonInputDeviceConfig.Enabled = false; - this.buttonInputDeviceConfig.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonInputDeviceConfig.Location = new System.Drawing.Point(270, 140); - this.buttonInputDeviceConfig.MaximumSize = new System.Drawing.Size(106, 25); - this.buttonInputDeviceConfig.Name = "buttonInputDeviceConfig"; - this.buttonInputDeviceConfig.Size = new System.Drawing.Size(106, 25); - this.buttonInputDeviceConfig.TabIndex = 3; - this.buttonInputDeviceConfig.Text = "Config"; - this.buttonInputDeviceConfig.UseVisualStyleBackColor = true; - this.buttonInputDeviceConfig.Click += new System.EventHandler(this.buttonInputDeviceConfig_Click); + this.comboboxInterpolation.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.comboboxInterpolation.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboboxInterpolation.FormattingEnabled = true; + this.comboboxInterpolation.Location = new System.Drawing.Point(156, 16); + this.comboboxInterpolation.Name = "comboboxInterpolation"; + this.comboboxInterpolation.Size = new System.Drawing.Size(152, 21); + this.comboboxInterpolation.TabIndex = 1; + this.comboboxInterpolation.SelectedIndexChanged += new System.EventHandler(this.comboboxInterpolation_SelectedIndexChanged); // - // groupBoxObjectParser + // labelInterpolation // - this.groupBoxObjectParser.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxObjectParser.Controls.Add(this.labelObjparser); - this.groupBoxObjectParser.Controls.Add(this.comboBoxObjparser); - this.groupBoxObjectParser.Controls.Add(this.labelXparser); - this.groupBoxObjectParser.Controls.Add(this.comboBoxXparser); - this.groupBoxObjectParser.ForeColor = System.Drawing.Color.Black; - this.groupBoxObjectParser.Location = new System.Drawing.Point(375, 258); - this.groupBoxObjectParser.Name = "groupBoxObjectParser"; - this.groupBoxObjectParser.Size = new System.Drawing.Size(305, 110); - this.groupBoxObjectParser.TabIndex = 23; - this.groupBoxObjectParser.TabStop = false; - this.groupBoxObjectParser.Text = "Object Parser"; + this.labelInterpolation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelInterpolation.AutoEllipsis = true; + this.labelInterpolation.Location = new System.Drawing.Point(8, 18); + this.labelInterpolation.Name = "labelInterpolation"; + this.labelInterpolation.Size = new System.Drawing.Size(148, 18); + this.labelInterpolation.TabIndex = 0; + this.labelInterpolation.Text = "Mode:"; + this.labelInterpolation.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // labelObjparser + // trackbarTransparency // - this.labelObjparser.Location = new System.Drawing.Point(7, 44); - this.labelObjparser.Name = "labelObjparser"; - this.labelObjparser.Size = new System.Drawing.Size(113, 26); - this.labelObjparser.TabIndex = 0; - this.labelObjparser.Text = "Obj Object Parser:"; - this.labelObjparser.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.trackbarTransparency.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.trackbarTransparency.Location = new System.Drawing.Point(156, 88); + this.trackbarTransparency.Maximum = 2; + this.trackbarTransparency.Name = "trackbarTransparency"; + this.trackbarTransparency.Size = new System.Drawing.Size(152, 45); + this.trackbarTransparency.TabIndex = 7; + this.trackbarTransparency.TickStyle = System.Windows.Forms.TickStyle.Both; // - // comboBoxObjparser + // panelOptionsRight // - this.comboBoxObjparser.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxObjparser.FormattingEnabled = true; - this.comboBoxObjparser.Items.AddRange(new object[] { - "Original", - "Assimp"}); - this.comboBoxObjparser.Location = new System.Drawing.Point(127, 44); - this.comboBoxObjparser.Name = "comboBoxObjparser"; - this.comboBoxObjparser.Size = new System.Drawing.Size(170, 21); - this.comboBoxObjparser.TabIndex = 1; + this.panelOptionsRight.Controls.Add(this.groupBoxOther); + this.panelOptionsRight.Controls.Add(this.groupBoxRailDriver); + this.panelOptionsRight.Controls.Add(this.groupboxDistance); + this.panelOptionsRight.Controls.Add(this.groupboxControls); + this.panelOptionsRight.Controls.Add(this.groupboxVerbosity); + this.panelOptionsRight.Controls.Add(this.groupboxSimulation); + this.panelOptionsRight.Controls.Add(this.groupboxSound); + this.panelOptionsRight.Location = new System.Drawing.Point(332, 72); + this.panelOptionsRight.Name = "panelOptionsRight"; + this.panelOptionsRight.Size = new System.Drawing.Size(316, 579); + this.panelOptionsRight.TabIndex = 17; // - // labelXparser + // groupBoxOther // - this.labelXparser.Location = new System.Drawing.Point(7, 17); - this.labelXparser.Name = "labelXparser"; - this.labelXparser.Size = new System.Drawing.Size(113, 26); - this.labelXparser.TabIndex = 0; - this.labelXparser.Text = "X Object Parser:"; - this.labelXparser.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.groupBoxOther.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBoxOther.Controls.Add(this.comboBoxTimeTableDisplayMode); + this.groupBoxOther.Controls.Add(this.labelTimeTableDisplayMode); + this.groupBoxOther.ForeColor = System.Drawing.Color.Black; + this.groupBoxOther.Location = new System.Drawing.Point(0, 468); + this.groupBoxOther.Name = "groupBoxOther"; + this.groupBoxOther.Size = new System.Drawing.Size(316, 48); + this.groupBoxOther.TabIndex = 19; + this.groupBoxOther.TabStop = false; + this.groupBoxOther.Text = "Other"; // - // comboBoxXparser + // comboBoxTimeTableDisplayMode // - this.comboBoxXparser.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxXparser.FormattingEnabled = true; - this.comboBoxXparser.Items.AddRange(new object[] { - "Original", - "NewXParser", - "Assimp"}); - this.comboBoxXparser.Location = new System.Drawing.Point(127, 21); - this.comboBoxXparser.Name = "comboBoxXparser"; - this.comboBoxXparser.Size = new System.Drawing.Size(170, 21); - this.comboBoxXparser.TabIndex = 1; + this.comboBoxTimeTableDisplayMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.comboBoxTimeTableDisplayMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxTimeTableDisplayMode.FormattingEnabled = true; + this.comboBoxTimeTableDisplayMode.Location = new System.Drawing.Point(156, 16); + this.comboBoxTimeTableDisplayMode.Name = "comboBoxTimeTableDisplayMode"; + this.comboBoxTimeTableDisplayMode.Size = new System.Drawing.Size(152, 21); + this.comboBoxTimeTableDisplayMode.TabIndex = 1; + this.comboBoxTimeTableDisplayMode.SelectedIndexChanged += new System.EventHandler(this.comboBoxTimeTableDisplayMode_SelectedIndexChanged); // - // groupBoxKioskMode + // labelTimeTableDisplayMode // - this.groupBoxKioskMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxKioskMode.Controls.Add(this.labelKioskTimeout); - this.groupBoxKioskMode.Controls.Add(this.numericUpDownKioskTimeout); - this.groupBoxKioskMode.Controls.Add(this.checkBoxEnableKiosk); - this.groupBoxKioskMode.ForeColor = System.Drawing.Color.Black; - this.groupBoxKioskMode.Location = new System.Drawing.Point(375, 160); - this.groupBoxKioskMode.Name = "groupBoxKioskMode"; - this.groupBoxKioskMode.Size = new System.Drawing.Size(305, 92); - this.groupBoxKioskMode.TabIndex = 22; - this.groupBoxKioskMode.TabStop = false; - this.groupBoxKioskMode.Text = "Kiosk Mode"; + this.labelTimeTableDisplayMode.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelTimeTableDisplayMode.AutoEllipsis = true; + this.labelTimeTableDisplayMode.Location = new System.Drawing.Point(3, 17); + this.labelTimeTableDisplayMode.Name = "labelTimeTableDisplayMode"; + this.labelTimeTableDisplayMode.Size = new System.Drawing.Size(153, 18); + this.labelTimeTableDisplayMode.TabIndex = 0; + this.labelTimeTableDisplayMode.Text = "Timetable Display Mode:"; + this.labelTimeTableDisplayMode.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // - // labelKioskTimeout + // groupBoxRailDriver // - this.labelKioskTimeout.Location = new System.Drawing.Point(8, 37); - this.labelKioskTimeout.Name = "labelKioskTimeout"; - this.labelKioskTimeout.Size = new System.Drawing.Size(155, 30); - this.labelKioskTimeout.TabIndex = 2; - this.labelKioskTimeout.Text = "Control timeout (s)"; - this.labelKioskTimeout.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.groupBoxRailDriver.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBoxRailDriver.Controls.Add(this.labelRailDriverCalibration); + this.groupBoxRailDriver.Controls.Add(this.buttonRailDriverCalibration); + this.groupBoxRailDriver.Controls.Add(this.comboBoxRailDriverUnits); + this.groupBoxRailDriver.Controls.Add(this.labelRailDriverSpeedUnits); + this.groupBoxRailDriver.ForeColor = System.Drawing.Color.Black; + this.groupBoxRailDriver.Location = new System.Drawing.Point(0, 230); + this.groupBoxRailDriver.Name = "groupBoxRailDriver"; + this.groupBoxRailDriver.Size = new System.Drawing.Size(316, 75); + this.groupBoxRailDriver.TabIndex = 21; + this.groupBoxRailDriver.TabStop = false; + this.groupBoxRailDriver.Text = "RailDriver"; // - // numericUpDownKioskTimeout + // labelRailDriverCalibration // - this.numericUpDownKioskTimeout.DecimalPlaces = 2; - this.numericUpDownKioskTimeout.Location = new System.Drawing.Point(166, 41); - this.numericUpDownKioskTimeout.Maximum = new decimal(new int[] { - 10000, - 0, - 0, - 0}); - this.numericUpDownKioskTimeout.Name = "numericUpDownKioskTimeout"; - this.numericUpDownKioskTimeout.Size = new System.Drawing.Size(131, 20); - this.numericUpDownKioskTimeout.TabIndex = 1; + this.labelRailDriverCalibration.AutoSize = true; + this.labelRailDriverCalibration.Location = new System.Drawing.Point(7, 46); + this.labelRailDriverCalibration.Name = "labelRailDriverCalibration"; + this.labelRailDriverCalibration.Size = new System.Drawing.Size(78, 13); + this.labelRailDriverCalibration.TabIndex = 5; + this.labelRailDriverCalibration.Text = "Set Calibration:"; // - // checkBoxEnableKiosk + // buttonRailDriverCalibration // - this.checkBoxEnableKiosk.AutoSize = true; - this.checkBoxEnableKiosk.Location = new System.Drawing.Point(9, 20); - this.checkBoxEnableKiosk.Name = "checkBoxEnableKiosk"; - this.checkBoxEnableKiosk.Size = new System.Drawing.Size(118, 17); - this.checkBoxEnableKiosk.TabIndex = 0; - this.checkBoxEnableKiosk.Text = "Enable Kiosk Mode"; - this.checkBoxEnableKiosk.UseVisualStyleBackColor = true; + this.buttonRailDriverCalibration.BackColor = System.Drawing.SystemColors.ButtonFace; + this.buttonRailDriverCalibration.ForeColor = System.Drawing.SystemColors.ControlText; + this.buttonRailDriverCalibration.Location = new System.Drawing.Point(230, 42); + this.buttonRailDriverCalibration.Name = "buttonRailDriverCalibration"; + this.buttonRailDriverCalibration.Size = new System.Drawing.Size(75, 26); + this.buttonRailDriverCalibration.TabIndex = 4; + this.buttonRailDriverCalibration.Text = "Launch..."; + this.buttonRailDriverCalibration.UseVisualStyleBackColor = true; + this.buttonRailDriverCalibration.Click += new System.EventHandler(this.buttonRailDriverCalibration_Click); // - // groupBoxAdvancedOptions + // comboBoxRailDriverUnits // - this.groupBoxAdvancedOptions.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxPanel2Extended); - this.groupBoxAdvancedOptions.Controls.Add(this.pictureboxCursor); - this.groupBoxAdvancedOptions.Controls.Add(this.labelCursor); - this.groupBoxAdvancedOptions.Controls.Add(this.comboboxCursor); - this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxHacks); - this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxTransparencyFix); - this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxUnloadTextures); - this.groupBoxAdvancedOptions.Controls.Add(this.labelTimeAcceleration); - this.groupBoxAdvancedOptions.Controls.Add(this.updownTimeAccelerationFactor); - this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxIsUseNewRenderer); - this.groupBoxAdvancedOptions.Controls.Add(this.checkBoxLoadInAdvance); - this.groupBoxAdvancedOptions.ForeColor = System.Drawing.Color.Black; - this.groupBoxAdvancedOptions.Location = new System.Drawing.Point(6, 160); - this.groupBoxAdvancedOptions.Name = "groupBoxAdvancedOptions"; - this.groupBoxAdvancedOptions.Size = new System.Drawing.Size(358, 208); - this.groupBoxAdvancedOptions.TabIndex = 21; - this.groupBoxAdvancedOptions.TabStop = false; - this.groupBoxAdvancedOptions.Text = "Advanced Options"; + this.comboBoxRailDriverUnits.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxRailDriverUnits.FormattingEnabled = true; + this.comboBoxRailDriverUnits.Items.AddRange(new object[] { + "Miles per Hour (MPH)", + "Kilometers per Hour (KPH)"}); + this.comboBoxRailDriverUnits.Location = new System.Drawing.Point(140, 16); + this.comboBoxRailDriverUnits.Name = "comboBoxRailDriverUnits"; + this.comboBoxRailDriverUnits.Size = new System.Drawing.Size(165, 21); + this.comboBoxRailDriverUnits.TabIndex = 3; + this.comboBoxRailDriverUnits.SelectedIndexChanged += new System.EventHandler(this.comboBoxRailDriverUnits_SelectedIndexChanged); // - // checkBoxPanel2Extended + // labelRailDriverSpeedUnits // - this.checkBoxPanel2Extended.AutoSize = true; - this.checkBoxPanel2Extended.Location = new System.Drawing.Point(8, 183); - this.checkBoxPanel2Extended.Name = "checkBoxPanel2Extended"; - this.checkBoxPanel2Extended.Size = new System.Drawing.Size(159, 17); - this.checkBoxPanel2Extended.TabIndex = 20; - this.checkBoxPanel2Extended.Text = "Enable Panel2 extend mode"; - this.checkBoxPanel2Extended.UseVisualStyleBackColor = true; + this.labelRailDriverSpeedUnits.Location = new System.Drawing.Point(7, 14); + this.labelRailDriverSpeedUnits.Name = "labelRailDriverSpeedUnits"; + this.labelRailDriverSpeedUnits.Size = new System.Drawing.Size(130, 30); + this.labelRailDriverSpeedUnits.TabIndex = 2; + this.labelRailDriverSpeedUnits.Text = "LED Display speed units:"; + this.labelRailDriverSpeedUnits.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // - // pictureboxCursor + // groupboxDistance // - this.pictureboxCursor.Location = new System.Drawing.Point(8, 145); - this.pictureboxCursor.Name = "pictureboxCursor"; - this.pictureboxCursor.Size = new System.Drawing.Size(32, 32); - this.pictureboxCursor.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; - this.pictureboxCursor.TabIndex = 18; - this.pictureboxCursor.TabStop = false; + this.groupboxDistance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupboxDistance.Controls.Add(this.comboboxMotionBlur); + this.groupboxDistance.Controls.Add(this.labelMotionBlur); + this.groupboxDistance.Controls.Add(this.labelDistanceUnit); + this.groupboxDistance.Controls.Add(this.updownDistance); + this.groupboxDistance.Controls.Add(this.labelDistance); + this.groupboxDistance.ForeColor = System.Drawing.Color.Black; + this.groupboxDistance.Location = new System.Drawing.Point(0, 0); + this.groupboxDistance.Name = "groupboxDistance"; + this.groupboxDistance.Size = new System.Drawing.Size(316, 80); + this.groupboxDistance.TabIndex = 8; + this.groupboxDistance.TabStop = false; + this.groupboxDistance.Text = "Distance effects"; // - // labelCursor + // comboboxMotionBlur // - this.labelCursor.AutoSize = true; - this.labelCursor.Location = new System.Drawing.Point(48, 140); - this.labelCursor.Name = "labelCursor"; - this.labelCursor.Size = new System.Drawing.Size(37, 13); - this.labelCursor.TabIndex = 17; - this.labelCursor.Text = "Cursor"; + this.comboboxMotionBlur.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.comboboxMotionBlur.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboboxMotionBlur.FormattingEnabled = true; + this.comboboxMotionBlur.Location = new System.Drawing.Point(144, 48); + this.comboboxMotionBlur.Name = "comboboxMotionBlur"; + this.comboboxMotionBlur.Size = new System.Drawing.Size(152, 21); + this.comboboxMotionBlur.TabIndex = 4; // - // comboboxCursor + // labelMotionBlur // - this.comboboxCursor.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboboxCursor.FormattingEnabled = true; - this.comboboxCursor.Location = new System.Drawing.Point(48, 158); - this.comboboxCursor.Name = "comboboxCursor"; - this.comboboxCursor.Size = new System.Drawing.Size(108, 21); - this.comboboxCursor.TabIndex = 19; - this.comboboxCursor.SelectedIndexChanged += new System.EventHandler(this.comboboxCursor_SelectedIndexChanged); + this.labelMotionBlur.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelMotionBlur.AutoEllipsis = true; + this.labelMotionBlur.Location = new System.Drawing.Point(5, 51); + this.labelMotionBlur.Name = "labelMotionBlur"; + this.labelMotionBlur.Size = new System.Drawing.Size(140, 18); + this.labelMotionBlur.TabIndex = 3; + this.labelMotionBlur.Text = "Motion blur:"; + this.labelMotionBlur.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // - // checkBoxHacks + // labelDistanceUnit // - this.checkBoxHacks.AutoSize = true; - this.checkBoxHacks.Location = new System.Drawing.Point(8, 100); - this.checkBoxHacks.Name = "checkBoxHacks"; - this.checkBoxHacks.Size = new System.Drawing.Size(203, 17); - this.checkBoxHacks.TabIndex = 15; - this.checkBoxHacks.Text = "Enable hacks for buggy older content"; - this.checkBoxHacks.UseVisualStyleBackColor = true; + this.labelDistanceUnit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.labelDistanceUnit.AutoEllipsis = true; + this.labelDistanceUnit.Location = new System.Drawing.Point(272, 24); + this.labelDistanceUnit.Name = "labelDistanceUnit"; + this.labelDistanceUnit.Size = new System.Drawing.Size(24, 18); + this.labelDistanceUnit.TabIndex = 2; + this.labelDistanceUnit.Text = "m"; // - // checkBoxTransparencyFix + // updownDistance // - this.checkBoxTransparencyFix.AutoSize = true; - this.checkBoxTransparencyFix.Location = new System.Drawing.Point(8, 81); - this.checkBoxTransparencyFix.Name = "checkBoxTransparencyFix"; - this.checkBoxTransparencyFix.Size = new System.Drawing.Size(259, 17); - this.checkBoxTransparencyFix.TabIndex = 14; - this.checkBoxTransparencyFix.Text = "Attempt to fix transparency issues in older content"; - this.checkBoxTransparencyFix.UseVisualStyleBackColor = true; + this.updownDistance.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updownDistance.Location = new System.Drawing.Point(144, 24); + this.updownDistance.Maximum = new decimal(new int[] { + 100000, + 0, + 0, + 0}); + this.updownDistance.Minimum = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.updownDistance.Name = "updownDistance"; + this.updownDistance.Size = new System.Drawing.Size(128, 20); + this.updownDistance.TabIndex = 1; + this.updownDistance.Value = new decimal(new int[] { + 600, + 0, + 0, + 0}); // - // checkBoxUnloadTextures + // labelDistance // - this.checkBoxUnloadTextures.AutoSize = true; - this.checkBoxUnloadTextures.Location = new System.Drawing.Point(8, 62); - this.checkBoxUnloadTextures.Name = "checkBoxUnloadTextures"; - this.checkBoxUnloadTextures.Size = new System.Drawing.Size(138, 17); - this.checkBoxUnloadTextures.TabIndex = 13; - this.checkBoxUnloadTextures.Text = "Unload unused textures"; - this.checkBoxUnloadTextures.UseVisualStyleBackColor = true; - this.checkBoxUnloadTextures.CheckedChanged += new System.EventHandler(this.checkBoxUnloadTextures_CheckedChanged); + this.labelDistance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelDistance.AutoEllipsis = true; + this.labelDistance.Location = new System.Drawing.Point(9, 26); + this.labelDistance.Name = "labelDistance"; + this.labelDistance.Size = new System.Drawing.Size(136, 18); + this.labelDistance.TabIndex = 0; + this.labelDistance.Text = "Viewing distance:"; + this.labelDistance.TextAlign = System.Drawing.ContentAlignment.TopRight; // - // labelTimeAcceleration + // groupboxControls // - this.labelTimeAcceleration.AutoSize = true; - this.labelTimeAcceleration.Location = new System.Drawing.Point(8, 123); - this.labelTimeAcceleration.Name = "labelTimeAcceleration"; - this.labelTimeAcceleration.Size = new System.Drawing.Size(126, 13); - this.labelTimeAcceleration.TabIndex = 10; - this.labelTimeAcceleration.Text = "Accelerated Time Factor:"; + this.groupboxControls.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupboxControls.Controls.Add(this.checkBoxEBAxis); + this.groupboxControls.Controls.Add(this.trackbarJoystickAxisThreshold); + this.groupboxControls.Controls.Add(this.checkboxJoysticksUsed); + this.groupboxControls.Controls.Add(this.labelJoystickAxisThreshold); + this.groupboxControls.ForeColor = System.Drawing.Color.Black; + this.groupboxControls.Location = new System.Drawing.Point(0, 144); + this.groupboxControls.Name = "groupboxControls"; + this.groupboxControls.Size = new System.Drawing.Size(316, 80); + this.groupboxControls.TabIndex = 10; + this.groupboxControls.TabStop = false; + this.groupboxControls.Text = "Controls"; // - // updownTimeAccelerationFactor + // checkBoxEBAxis // - this.updownTimeAccelerationFactor.Location = new System.Drawing.Point(200, 122); - this.updownTimeAccelerationFactor.Maximum = new decimal(new int[] { - 5, - 0, - 0, - 0}); - this.updownTimeAccelerationFactor.Name = "updownTimeAccelerationFactor"; - this.updownTimeAccelerationFactor.Size = new System.Drawing.Size(52, 20); - this.updownTimeAccelerationFactor.TabIndex = 16; - this.updownTimeAccelerationFactor.ValueChanged += new System.EventHandler(this.updownTimeAccelerationFactor_ValueChanged); + this.checkBoxEBAxis.Checked = true; + this.checkBoxEBAxis.CheckState = System.Windows.Forms.CheckState.Checked; + this.checkBoxEBAxis.Location = new System.Drawing.Point(8, 41); + this.checkBoxEBAxis.Name = "checkBoxEBAxis"; + this.checkBoxEBAxis.Size = new System.Drawing.Size(190, 36); + this.checkBoxEBAxis.TabIndex = 18; + this.checkBoxEBAxis.Text = "Allow EB on brake axis"; + this.checkBoxEBAxis.UseVisualStyleBackColor = true; // - // checkBoxIsUseNewRenderer + // trackbarJoystickAxisThreshold // - this.checkBoxIsUseNewRenderer.AutoSize = true; - this.checkBoxIsUseNewRenderer.Location = new System.Drawing.Point(8, 43); - this.checkBoxIsUseNewRenderer.Name = "checkBoxIsUseNewRenderer"; - this.checkBoxIsUseNewRenderer.Size = new System.Drawing.Size(159, 17); - this.checkBoxIsUseNewRenderer.TabIndex = 2; - this.checkBoxIsUseNewRenderer.Text = "Disable OpenGL display lists"; - this.checkBoxIsUseNewRenderer.UseVisualStyleBackColor = true; + this.trackbarJoystickAxisThreshold.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.trackbarJoystickAxisThreshold.LargeChange = 10; + this.trackbarJoystickAxisThreshold.Location = new System.Drawing.Point(200, 32); + this.trackbarJoystickAxisThreshold.Maximum = 100; + this.trackbarJoystickAxisThreshold.Name = "trackbarJoystickAxisThreshold"; + this.trackbarJoystickAxisThreshold.Size = new System.Drawing.Size(96, 45); + this.trackbarJoystickAxisThreshold.TabIndex = 2; + this.trackbarJoystickAxisThreshold.TickFrequency = 10; + this.trackbarJoystickAxisThreshold.TickStyle = System.Windows.Forms.TickStyle.Both; // - // checkBoxLoadInAdvance + // checkboxJoysticksUsed // - this.checkBoxLoadInAdvance.AutoSize = true; - this.checkBoxLoadInAdvance.Location = new System.Drawing.Point(8, 24); - this.checkBoxLoadInAdvance.Name = "checkBoxLoadInAdvance"; - this.checkBoxLoadInAdvance.Size = new System.Drawing.Size(106, 17); - this.checkBoxLoadInAdvance.TabIndex = 1; - this.checkBoxLoadInAdvance.Text = "Load in advance"; - this.checkBoxLoadInAdvance.UseVisualStyleBackColor = true; - this.checkBoxLoadInAdvance.CheckedChanged += new System.EventHandler(this.checkBoxLoadInAdvance_CheckedChanged); + this.checkboxJoysticksUsed.AutoSize = true; + this.checkboxJoysticksUsed.Location = new System.Drawing.Point(8, 24); + this.checkboxJoysticksUsed.Name = "checkboxJoysticksUsed"; + this.checkboxJoysticksUsed.Size = new System.Drawing.Size(110, 17); + this.checkboxJoysticksUsed.TabIndex = 0; + this.checkboxJoysticksUsed.Text = "Joysticks enabled"; + this.checkboxJoysticksUsed.UseVisualStyleBackColor = true; + this.checkboxJoysticksUsed.CheckedChanged += new System.EventHandler(this.checkboxJoysticksUsed_CheckedChanged); // - // groupBoxPackageOptions + // labelJoystickAxisThreshold // - this.groupBoxPackageOptions.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.labelJoystickAxisThreshold.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.labelJoystickAxisThreshold.AutoEllipsis = true; + this.labelJoystickAxisThreshold.Location = new System.Drawing.Point(110, 10); + this.labelJoystickAxisThreshold.Name = "labelJoystickAxisThreshold"; + this.labelJoystickAxisThreshold.Size = new System.Drawing.Size(180, 18); + this.labelJoystickAxisThreshold.TabIndex = 1; + this.labelJoystickAxisThreshold.Text = "Joystick threshold:"; + this.labelJoystickAxisThreshold.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // groupboxVerbosity + // + this.groupboxVerbosity.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxPackageOptions.Controls.Add(this.comboBoxCompressionFormat); - this.groupBoxPackageOptions.Controls.Add(this.labelPackageCompression); - this.groupBoxPackageOptions.Controls.Add(this.buttonOtherDirectory); - this.groupBoxPackageOptions.Controls.Add(this.labelOtherInstallDirectory); - this.groupBoxPackageOptions.Controls.Add(this.textBoxOtherDirectory); - this.groupBoxPackageOptions.Controls.Add(this.buttonTrainInstallationDirectory); - this.groupBoxPackageOptions.Controls.Add(this.labelTrainInstallDirectory); - this.groupBoxPackageOptions.Controls.Add(this.textBoxTrainDirectory); - this.groupBoxPackageOptions.Controls.Add(this.buttonSetRouteDirectory); - this.groupBoxPackageOptions.Controls.Add(this.labelRouteInstallDirectory); - this.groupBoxPackageOptions.Controls.Add(this.textBoxRouteDirectory); - this.groupBoxPackageOptions.ForeColor = System.Drawing.Color.Black; - this.groupBoxPackageOptions.Location = new System.Drawing.Point(6, 0); - this.groupBoxPackageOptions.Name = "groupBoxPackageOptions"; - this.groupBoxPackageOptions.Size = new System.Drawing.Size(674, 154); - this.groupBoxPackageOptions.TabIndex = 19; - this.groupBoxPackageOptions.TabStop = false; - this.groupBoxPackageOptions.Text = "Package Management"; + this.groupboxVerbosity.Controls.Add(this.checkBoxAccessibility); + this.groupboxVerbosity.Controls.Add(this.checkboxErrorMessages); + this.groupboxVerbosity.Controls.Add(this.checkboxWarningMessages); + this.groupboxVerbosity.ForeColor = System.Drawing.Color.Black; + this.groupboxVerbosity.Location = new System.Drawing.Point(0, 396); + this.groupboxVerbosity.Name = "groupboxVerbosity"; + this.groupboxVerbosity.Size = new System.Drawing.Size(316, 64); + this.groupboxVerbosity.TabIndex = 12; + this.groupboxVerbosity.TabStop = false; + this.groupboxVerbosity.Text = "Verbosity"; // - // comboBoxCompressionFormat + // checkBoxAccessibility // - this.comboBoxCompressionFormat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxCompressionFormat.Items.AddRange(new object[] { - "LZMA ZIP ( .zip )", - "GZip ( .tgz )", - "BZip2 ( .bz2 )"}); - this.comboBoxCompressionFormat.Location = new System.Drawing.Point(200, 117); - this.comboBoxCompressionFormat.Name = "comboBoxCompressionFormat"; - this.comboBoxCompressionFormat.Size = new System.Drawing.Size(188, 21); - this.comboBoxCompressionFormat.TabIndex = 10; - this.comboBoxCompressionFormat.SelectedIndexChanged += new System.EventHandler(this.comboBoxCompressionFormat_SelectedIndexChanged); + this.checkBoxAccessibility.AutoSize = true; + this.checkBoxAccessibility.Location = new System.Drawing.Point(176, 21); + this.checkBoxAccessibility.Name = "checkBoxAccessibility"; + this.checkBoxAccessibility.Size = new System.Drawing.Size(106, 17); + this.checkBoxAccessibility.TabIndex = 2; + this.checkBoxAccessibility.Text = "Accessibility Aids"; + this.checkBoxAccessibility.UseVisualStyleBackColor = true; // - // labelPackageCompression + // checkboxErrorMessages // - this.labelPackageCompression.AutoSize = true; - this.labelPackageCompression.Location = new System.Drawing.Point(6, 121); - this.labelPackageCompression.Name = "labelPackageCompression"; - this.labelPackageCompression.Size = new System.Drawing.Size(147, 13); - this.labelPackageCompression.TabIndex = 9; - this.labelPackageCompression.Text = "Package compression format:"; + this.checkboxErrorMessages.AutoSize = true; + this.checkboxErrorMessages.Location = new System.Drawing.Point(8, 38); + this.checkboxErrorMessages.Name = "checkboxErrorMessages"; + this.checkboxErrorMessages.Size = new System.Drawing.Size(127, 17); + this.checkboxErrorMessages.TabIndex = 1; + this.checkboxErrorMessages.Text = "Show error messages"; + this.checkboxErrorMessages.UseVisualStyleBackColor = true; // - // buttonOtherDirectory + // checkboxWarningMessages // - this.buttonOtherDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonOtherDirectory.BackColor = System.Drawing.SystemColors.Control; - this.buttonOtherDirectory.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonOtherDirectory.Location = new System.Drawing.Point(594, 81); - this.buttonOtherDirectory.Name = "buttonOtherDirectory"; - this.buttonOtherDirectory.Size = new System.Drawing.Size(75, 26); - this.buttonOtherDirectory.TabIndex = 8; - this.buttonOtherDirectory.Text = "Choose..."; - this.buttonOtherDirectory.UseVisualStyleBackColor = true; - this.buttonOtherDirectory.Click += new System.EventHandler(this.buttonOtherDirectory_Click); + this.checkboxWarningMessages.AutoSize = true; + this.checkboxWarningMessages.Location = new System.Drawing.Point(8, 21); + this.checkboxWarningMessages.Name = "checkboxWarningMessages"; + this.checkboxWarningMessages.Size = new System.Drawing.Size(143, 17); + this.checkboxWarningMessages.TabIndex = 0; + this.checkboxWarningMessages.Text = "Show warning messages"; + this.checkboxWarningMessages.UseVisualStyleBackColor = true; // - // labelOtherInstallDirectory + // groupboxSimulation // - this.labelOtherInstallDirectory.Location = new System.Drawing.Point(6, 80); - this.labelOtherInstallDirectory.Name = "labelOtherInstallDirectory"; - this.labelOtherInstallDirectory.Size = new System.Drawing.Size(175, 30); - this.labelOtherInstallDirectory.TabIndex = 7; - this.labelOtherInstallDirectory.Text = "Other items installation directory:"; - this.labelOtherInstallDirectory.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.groupboxSimulation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupboxSimulation.Controls.Add(this.checkBoxLoadingSway); + this.groupboxSimulation.Controls.Add(this.checkboxBlackBox); + this.groupboxSimulation.Controls.Add(this.checkboxDerailments); + this.groupboxSimulation.Controls.Add(this.checkboxCollisions); + this.groupboxSimulation.Controls.Add(this.checkboxToppling); + this.groupboxSimulation.ForeColor = System.Drawing.Color.Black; + this.groupboxSimulation.Location = new System.Drawing.Point(0, 310); + this.groupboxSimulation.Name = "groupboxSimulation"; + this.groupboxSimulation.Size = new System.Drawing.Size(316, 80); + this.groupboxSimulation.TabIndex = 11; + this.groupboxSimulation.TabStop = false; + this.groupboxSimulation.Text = "Detail of simulation"; // - // textBoxOtherDirectory + // checkBoxLoadingSway // - this.textBoxOtherDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.checkBoxLoadingSway.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.textBoxOtherDirectory.BackColor = System.Drawing.SystemColors.Control; - this.textBoxOtherDirectory.ForeColor = System.Drawing.SystemColors.ControlText; - this.textBoxOtherDirectory.Location = new System.Drawing.Point(200, 84); - this.textBoxOtherDirectory.Name = "textBoxOtherDirectory"; - this.textBoxOtherDirectory.ReadOnly = true; - this.textBoxOtherDirectory.Size = new System.Drawing.Size(387, 20); - this.textBoxOtherDirectory.TabIndex = 6; + this.checkBoxLoadingSway.AutoSize = true; + this.checkBoxLoadingSway.Location = new System.Drawing.Point(176, 21); + this.checkBoxLoadingSway.Name = "checkBoxLoadingSway"; + this.checkBoxLoadingSway.Size = new System.Drawing.Size(123, 17); + this.checkBoxLoadingSway.TabIndex = 4; + this.checkBoxLoadingSway.Text = "Enable loading sway"; + this.checkBoxLoadingSway.UseVisualStyleBackColor = true; // - // buttonTrainInstallationDirectory + // checkboxBlackBox // - this.buttonTrainInstallationDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonTrainInstallationDirectory.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonTrainInstallationDirectory.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonTrainInstallationDirectory.Location = new System.Drawing.Point(594, 49); - this.buttonTrainInstallationDirectory.Name = "buttonTrainInstallationDirectory"; - this.buttonTrainInstallationDirectory.Size = new System.Drawing.Size(75, 26); - this.buttonTrainInstallationDirectory.TabIndex = 5; - this.buttonTrainInstallationDirectory.Text = "Choose..."; - this.buttonTrainInstallationDirectory.UseVisualStyleBackColor = true; - this.buttonTrainInstallationDirectory.Click += new System.EventHandler(this.buttonTrainInstallationDirectory_Click); + this.checkboxBlackBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.checkboxBlackBox.AutoSize = true; + this.checkboxBlackBox.Location = new System.Drawing.Point(176, 38); + this.checkboxBlackBox.Name = "checkboxBlackBox"; + this.checkboxBlackBox.Size = new System.Drawing.Size(108, 17); + this.checkboxBlackBox.TabIndex = 3; + this.checkboxBlackBox.Text = "Enable black box"; + this.checkboxBlackBox.UseVisualStyleBackColor = true; // - // labelTrainInstallDirectory + // checkboxDerailments // - this.labelTrainInstallDirectory.AutoSize = true; - this.labelTrainInstallDirectory.Location = new System.Drawing.Point(6, 52); - this.labelTrainInstallDirectory.Name = "labelTrainInstallDirectory"; - this.labelTrainInstallDirectory.Size = new System.Drawing.Size(129, 13); - this.labelTrainInstallDirectory.TabIndex = 4; - this.labelTrainInstallDirectory.Text = "Train installation directory:"; + this.checkboxDerailments.AutoSize = true; + this.checkboxDerailments.Location = new System.Drawing.Point(8, 55); + this.checkboxDerailments.Name = "checkboxDerailments"; + this.checkboxDerailments.Size = new System.Drawing.Size(81, 17); + this.checkboxDerailments.TabIndex = 2; + this.checkboxDerailments.Text = "Derailments"; + this.checkboxDerailments.UseVisualStyleBackColor = true; // - // textBoxTrainDirectory + // checkboxCollisions // - this.textBoxTrainDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.textBoxTrainDirectory.BackColor = System.Drawing.SystemColors.Control; - this.textBoxTrainDirectory.ForeColor = System.Drawing.SystemColors.ControlText; - this.textBoxTrainDirectory.Location = new System.Drawing.Point(200, 51); - this.textBoxTrainDirectory.Name = "textBoxTrainDirectory"; - this.textBoxTrainDirectory.ReadOnly = true; - this.textBoxTrainDirectory.Size = new System.Drawing.Size(387, 20); - this.textBoxTrainDirectory.TabIndex = 3; + this.checkboxCollisions.AutoSize = true; + this.checkboxCollisions.Location = new System.Drawing.Point(8, 38); + this.checkboxCollisions.Name = "checkboxCollisions"; + this.checkboxCollisions.Size = new System.Drawing.Size(69, 17); + this.checkboxCollisions.TabIndex = 1; + this.checkboxCollisions.Text = "Collisions"; + this.checkboxCollisions.UseVisualStyleBackColor = true; // - // buttonSetRouteDirectory + // checkboxToppling // - this.buttonSetRouteDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonSetRouteDirectory.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonSetRouteDirectory.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonSetRouteDirectory.Location = new System.Drawing.Point(594, 18); - this.buttonSetRouteDirectory.Name = "buttonSetRouteDirectory"; - this.buttonSetRouteDirectory.Size = new System.Drawing.Size(75, 26); - this.buttonSetRouteDirectory.TabIndex = 2; - this.buttonSetRouteDirectory.Text = "Choose..."; - this.buttonSetRouteDirectory.UseVisualStyleBackColor = true; - this.buttonSetRouteDirectory.Click += new System.EventHandler(this.buttonSetRouteDirectory_Click); + this.checkboxToppling.AutoSize = true; + this.checkboxToppling.Location = new System.Drawing.Point(8, 21); + this.checkboxToppling.Name = "checkboxToppling"; + this.checkboxToppling.Size = new System.Drawing.Size(67, 17); + this.checkboxToppling.TabIndex = 0; + this.checkboxToppling.Text = "Toppling"; + this.checkboxToppling.UseVisualStyleBackColor = true; // - // labelRouteInstallDirectory + // groupboxSound // - this.labelRouteInstallDirectory.AutoSize = true; - this.labelRouteInstallDirectory.Location = new System.Drawing.Point(6, 21); - this.labelRouteInstallDirectory.Name = "labelRouteInstallDirectory"; - this.labelRouteInstallDirectory.Size = new System.Drawing.Size(134, 13); - this.labelRouteInstallDirectory.TabIndex = 1; - this.labelRouteInstallDirectory.Text = "Route installation directory:"; + this.groupboxSound.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupboxSound.Controls.Add(this.updownSoundNumber); + this.groupboxSound.Controls.Add(this.labelSoundNumber); + this.groupboxSound.ForeColor = System.Drawing.Color.Black; + this.groupboxSound.Location = new System.Drawing.Point(0, 88); + this.groupboxSound.Name = "groupboxSound"; + this.groupboxSound.Size = new System.Drawing.Size(316, 48); + this.groupboxSound.TabIndex = 9; + this.groupboxSound.TabStop = false; + this.groupboxSound.Text = "Sound"; // - // textBoxRouteDirectory + // updownSoundNumber // - this.textBoxRouteDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.updownSoundNumber.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updownSoundNumber.Location = new System.Drawing.Point(140, 16); + this.updownSoundNumber.Maximum = new decimal(new int[] { + 128, + 0, + 0, + 0}); + this.updownSoundNumber.Minimum = new decimal(new int[] { + 8, + 0, + 0, + 0}); + this.updownSoundNumber.Name = "updownSoundNumber"; + this.updownSoundNumber.Size = new System.Drawing.Size(152, 20); + this.updownSoundNumber.TabIndex = 3; + this.updownSoundNumber.Value = new decimal(new int[] { + 16, + 0, + 0, + 0}); + // + // labelSoundNumber + // + this.labelSoundNumber.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.textBoxRouteDirectory.BackColor = System.Drawing.SystemColors.Control; - this.textBoxRouteDirectory.ForeColor = System.Drawing.SystemColors.ControlText; - this.textBoxRouteDirectory.Location = new System.Drawing.Point(200, 20); - this.textBoxRouteDirectory.Name = "textBoxRouteDirectory"; - this.textBoxRouteDirectory.ReadOnly = true; - this.textBoxRouteDirectory.Size = new System.Drawing.Size(387, 20); - this.textBoxRouteDirectory.TabIndex = 0; + this.labelSoundNumber.Location = new System.Drawing.Point(5, 18); + this.labelSoundNumber.Name = "labelSoundNumber"; + this.labelSoundNumber.Size = new System.Drawing.Size(136, 18); + this.labelSoundNumber.TabIndex = 2; + this.labelSoundNumber.Text = "Number of allowed sounds:"; + this.labelSoundNumber.TextAlign = System.Drawing.ContentAlignment.TopRight; // // pictureboxLanguage // @@ -3032,7 +3072,7 @@ private void InitializeComponent() { // this.labelFillerThree.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelFillerThree.BackColor = System.Drawing.Color.Silver; - this.labelFillerThree.Location = new System.Drawing.Point(0, 583); + this.labelFillerThree.Location = new System.Drawing.Point(0, 613); this.labelFillerThree.Name = "labelFillerThree"; this.labelFillerThree.Size = new System.Drawing.Size(160, 48); this.labelFillerThree.TabIndex = 4; @@ -3153,7 +3193,7 @@ private void InitializeComponent() { this.panelReview.Controls.Add(this.labelReviewTitleBackground); this.panelReview.Location = new System.Drawing.Point(160, 0); this.panelReview.Name = "panelReview"; - this.panelReview.Size = new System.Drawing.Size(699, 631); + this.panelReview.Size = new System.Drawing.Size(699, 661); this.panelReview.TabIndex = 10; // // comboboxBlackBoxFormat @@ -3161,7 +3201,7 @@ private void InitializeComponent() { this.comboboxBlackBoxFormat.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.comboboxBlackBoxFormat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboboxBlackBoxFormat.FormattingEnabled = true; - this.comboboxBlackBoxFormat.Location = new System.Drawing.Point(104, 599); + this.comboboxBlackBoxFormat.Location = new System.Drawing.Point(104, 629); this.comboboxBlackBoxFormat.Name = "comboboxBlackBoxFormat"; this.comboboxBlackBoxFormat.Size = new System.Drawing.Size(144, 21); this.comboboxBlackBoxFormat.TabIndex = 12; @@ -3171,7 +3211,7 @@ private void InitializeComponent() { this.labelBlackBoxFormat.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelBlackBoxFormat.AutoEllipsis = true; this.labelBlackBoxFormat.ForeColor = System.Drawing.Color.Black; - this.labelBlackBoxFormat.Location = new System.Drawing.Point(8, 602); + this.labelBlackBoxFormat.Location = new System.Drawing.Point(8, 632); this.labelBlackBoxFormat.Name = "labelBlackBoxFormat"; this.labelBlackBoxFormat.Size = new System.Drawing.Size(96, 18); this.labelBlackBoxFormat.TabIndex = 11; @@ -3238,7 +3278,7 @@ private void InitializeComponent() { // this.buttonBlackBoxExport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.buttonBlackBoxExport.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonBlackBoxExport.Location = new System.Drawing.Point(256, 597); + this.buttonBlackBoxExport.Location = new System.Drawing.Point(256, 627); this.buttonBlackBoxExport.Name = "buttonBlackBoxExport"; this.buttonBlackBoxExport.Size = new System.Drawing.Size(120, 26); this.buttonBlackBoxExport.TabIndex = 13; @@ -3254,7 +3294,7 @@ private void InitializeComponent() { this.labelBlackBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(153)))), ((int)(((byte)(107)))), ((int)(((byte)(107))))); this.labelBlackBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.labelBlackBox.ForeColor = System.Drawing.Color.White; - this.labelBlackBox.Location = new System.Drawing.Point(8, 567); + this.labelBlackBox.Location = new System.Drawing.Point(8, 597); this.labelBlackBox.Name = "labelBlackBox"; this.labelBlackBox.Size = new System.Drawing.Size(683, 24); this.labelBlackBox.TabIndex = 10; @@ -3272,7 +3312,7 @@ private void InitializeComponent() { this.groupboxScore.ForeColor = System.Drawing.Color.Black; this.groupboxScore.Location = new System.Drawing.Point(272, 176); this.groupboxScore.Name = "groupboxScore"; - this.groupboxScore.Size = new System.Drawing.Size(419, 383); + this.groupboxScore.Size = new System.Drawing.Size(419, 413); this.groupboxScore.TabIndex = 0; this.groupboxScore.TabStop = false; this.groupboxScore.Text = "Log"; @@ -3281,7 +3321,7 @@ private void InitializeComponent() { // this.checkboxScorePenalties.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.checkboxScorePenalties.AutoSize = true; - this.checkboxScorePenalties.Location = new System.Drawing.Point(8, 356); + this.checkboxScorePenalties.Location = new System.Drawing.Point(8, 386); this.checkboxScorePenalties.Name = "checkboxScorePenalties"; this.checkboxScorePenalties.Size = new System.Drawing.Size(120, 17); this.checkboxScorePenalties.TabIndex = 1; @@ -3294,7 +3334,7 @@ private void InitializeComponent() { this.buttonScoreExport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonScoreExport.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonScoreExport.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonScoreExport.Location = new System.Drawing.Point(291, 350); + this.buttonScoreExport.Location = new System.Drawing.Point(291, 380); this.buttonScoreExport.Name = "buttonScoreExport"; this.buttonScoreExport.Size = new System.Drawing.Size(120, 26); this.buttonScoreExport.TabIndex = 2; @@ -3321,7 +3361,7 @@ private void InitializeComponent() { this.listviewScore.MultiSelect = false; this.listviewScore.Name = "listviewScore"; this.listviewScore.ShowGroups = false; - this.listviewScore.Size = new System.Drawing.Size(403, 327); + this.listviewScore.Size = new System.Drawing.Size(403, 357); this.listviewScore.TabIndex = 0; this.listviewScore.UseCompatibleStateImageBehavior = false; this.listviewScore.View = System.Windows.Forms.View.Details; @@ -3632,14 +3672,14 @@ private void InitializeComponent() { this.panelControls.Controls.Add(this.groupboxControl); this.panelControls.Location = new System.Drawing.Point(160, 0); this.panelControls.Name = "panelControls"; - this.panelControls.Size = new System.Drawing.Size(699, 631); + this.panelControls.Size = new System.Drawing.Size(699, 661); this.panelControls.TabIndex = 13; // // buttonControlReset // this.buttonControlReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.buttonControlReset.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonControlReset.Location = new System.Drawing.Point(8, 293); + this.buttonControlReset.Location = new System.Drawing.Point(8, 323); this.buttonControlReset.Name = "buttonControlReset"; this.buttonControlReset.Size = new System.Drawing.Size(96, 26); this.buttonControlReset.TabIndex = 12; @@ -3651,7 +3691,7 @@ private void InitializeComponent() { // this.buttonControlsExport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.buttonControlsExport.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonControlsExport.Location = new System.Drawing.Point(320, 262); + this.buttonControlsExport.Location = new System.Drawing.Point(320, 292); this.buttonControlsExport.Name = "buttonControlsExport"; this.buttonControlsExport.Size = new System.Drawing.Size(96, 26); this.buttonControlsExport.TabIndex = 7; @@ -3663,7 +3703,7 @@ private void InitializeComponent() { // this.buttonControlsImport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.buttonControlsImport.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonControlsImport.Location = new System.Drawing.Point(216, 262); + this.buttonControlsImport.Location = new System.Drawing.Point(216, 292); this.buttonControlsImport.Name = "buttonControlsImport"; this.buttonControlsImport.Size = new System.Drawing.Size(96, 26); this.buttonControlsImport.TabIndex = 6; @@ -3675,7 +3715,7 @@ private void InitializeComponent() { // this.buttonControlDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonControlDown.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonControlDown.Location = new System.Drawing.Point(595, 262); + this.buttonControlDown.Location = new System.Drawing.Point(595, 292); this.buttonControlDown.Name = "buttonControlDown"; this.buttonControlDown.Size = new System.Drawing.Size(96, 26); this.buttonControlDown.TabIndex = 9; @@ -3687,7 +3727,7 @@ private void InitializeComponent() { // this.buttonControlUp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonControlUp.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonControlUp.Location = new System.Drawing.Point(491, 262); + this.buttonControlUp.Location = new System.Drawing.Point(491, 292); this.buttonControlUp.Name = "buttonControlUp"; this.buttonControlUp.Size = new System.Drawing.Size(96, 26); this.buttonControlUp.TabIndex = 8; @@ -3699,7 +3739,7 @@ private void InitializeComponent() { // this.buttonControlRemove.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.buttonControlRemove.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonControlRemove.Location = new System.Drawing.Point(112, 262); + this.buttonControlRemove.Location = new System.Drawing.Point(112, 292); this.buttonControlRemove.Name = "buttonControlRemove"; this.buttonControlRemove.Size = new System.Drawing.Size(96, 26); this.buttonControlRemove.TabIndex = 5; @@ -3711,7 +3751,7 @@ private void InitializeComponent() { // this.buttonControlAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.buttonControlAdd.BackColor = System.Drawing.SystemColors.ButtonFace; - this.buttonControlAdd.Location = new System.Drawing.Point(8, 262); + this.buttonControlAdd.Location = new System.Drawing.Point(8, 292); this.buttonControlAdd.Name = "buttonControlAdd"; this.buttonControlAdd.Size = new System.Drawing.Size(96, 26); this.buttonControlAdd.TabIndex = 4; @@ -3725,7 +3765,7 @@ private void InitializeComponent() { | System.Windows.Forms.AnchorStyles.Right))); this.groupboxJoysticks.Controls.Add(this.pictureboxJoysticks); this.groupboxJoysticks.ForeColor = System.Drawing.Color.Black; - this.groupboxJoysticks.Location = new System.Drawing.Point(8, 455); + this.groupboxJoysticks.Location = new System.Drawing.Point(8, 485); this.groupboxJoysticks.Name = "groupboxJoysticks"; this.groupboxJoysticks.Size = new System.Drawing.Size(683, 168); this.groupboxJoysticks.TabIndex = 11; @@ -3764,7 +3804,7 @@ private void InitializeComponent() { this.listviewControls.MultiSelect = false; this.listviewControls.Name = "listviewControls"; this.listviewControls.ShowGroups = false; - this.listviewControls.Size = new System.Drawing.Size(683, 213); + this.listviewControls.Size = new System.Drawing.Size(683, 243); this.listviewControls.TabIndex = 3; this.listviewControls.UseCompatibleStateImageBehavior = false; this.listviewControls.View = System.Windows.Forms.View.Details; @@ -3839,7 +3879,7 @@ private void InitializeComponent() { this.groupboxControl.Controls.Add(this.radiobuttonKeyboard); this.groupboxControl.Enabled = false; this.groupboxControl.ForeColor = System.Drawing.Color.Black; - this.groupboxControl.Location = new System.Drawing.Point(8, 319); + this.groupboxControl.Location = new System.Drawing.Point(8, 349); this.groupboxControl.Name = "groupboxControl"; this.groupboxControl.Size = new System.Drawing.Size(683, 128); this.groupboxControl.TabIndex = 10; @@ -4048,7 +4088,7 @@ private void InitializeComponent() { this.panelInfo.Controls.Add(this.labelVersion); this.panelInfo.Controls.Add(this.labelInfoBottom); this.panelInfo.Controls.Add(this.labelInfoTop); - this.panelInfo.Location = new System.Drawing.Point(0, 487); + this.panelInfo.Location = new System.Drawing.Point(0, 517); this.panelInfo.Name = "panelInfo"; this.panelInfo.Size = new System.Drawing.Size(160, 96); this.panelInfo.TabIndex = 3; @@ -4172,7 +4212,7 @@ private void InitializeComponent() { this.panelPackages.Controls.Add(this.panelCreatePackage); this.panelPackages.Location = new System.Drawing.Point(160, 0); this.panelPackages.Name = "panelPackages"; - this.panelPackages.Size = new System.Drawing.Size(699, 631); + this.panelPackages.Size = new System.Drawing.Size(699, 661); this.panelPackages.TabIndex = 14; // // labelPackagesTitleSeparator @@ -4234,7 +4274,7 @@ private void InitializeComponent() { this.panelPackageInstall.Controls.Add(this.pictureBoxPackageImage); this.panelPackageInstall.Location = new System.Drawing.Point(0, 34); this.panelPackageInstall.Name = "panelPackageInstall"; - this.panelPackageInstall.Size = new System.Drawing.Size(699, 597); + this.panelPackageInstall.Size = new System.Drawing.Size(699, 627); this.panelPackageInstall.TabIndex = 4; this.panelPackageInstall.DragDrop += new System.Windows.Forms.DragEventHandler(this.panelPackageInstall_DragDrop); this.panelPackageInstall.DragEnter += new System.Windows.Forms.DragEventHandler(this.panelPackageInstall_DragEnter); @@ -4245,7 +4285,7 @@ private void InitializeComponent() { this.buttonBack2.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonBack2.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonBack2.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonBack2.Location = new System.Drawing.Point(445, 561); + this.buttonBack2.Location = new System.Drawing.Point(445, 591); this.buttonBack2.Name = "buttonBack2"; this.buttonBack2.Size = new System.Drawing.Size(120, 28); this.buttonBack2.TabIndex = 18; @@ -4260,7 +4300,7 @@ private void InitializeComponent() { this.buttonNext.Enabled = false; this.buttonNext.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonNext.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonNext.Location = new System.Drawing.Point(571, 561); + this.buttonNext.Location = new System.Drawing.Point(571, 591); this.buttonNext.Name = "buttonNext"; this.buttonNext.Size = new System.Drawing.Size(120, 28); this.buttonNext.TabIndex = 17; @@ -4297,7 +4337,7 @@ private void InitializeComponent() { this.buttonSelectPackage.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonSelectPackage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonSelectPackage.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonSelectPackage.Location = new System.Drawing.Point(134, 535); + this.buttonSelectPackage.Location = new System.Drawing.Point(134, 565); this.buttonSelectPackage.Name = "buttonSelectPackage"; this.buttonSelectPackage.Size = new System.Drawing.Size(180, 28); this.buttonSelectPackage.TabIndex = 14; @@ -4311,7 +4351,7 @@ private void InitializeComponent() { | System.Windows.Forms.AnchorStyles.Right))); this.textBoxPackageDescription.BackColor = System.Drawing.SystemColors.Control; this.textBoxPackageDescription.ForeColor = System.Drawing.SystemColors.ControlText; - this.textBoxPackageDescription.Location = new System.Drawing.Point(134, 417); + this.textBoxPackageDescription.Location = new System.Drawing.Point(134, 447); this.textBoxPackageDescription.Multiline = true; this.textBoxPackageDescription.Name = "textBoxPackageDescription"; this.textBoxPackageDescription.ReadOnly = true; @@ -4324,7 +4364,7 @@ private void InitializeComponent() { // this.labelPackageDescription.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelPackageDescription.ForeColor = System.Drawing.Color.Black; - this.labelPackageDescription.Location = new System.Drawing.Point(8, 420); + this.labelPackageDescription.Location = new System.Drawing.Point(8, 450); this.labelPackageDescription.Name = "labelPackageDescription"; this.labelPackageDescription.Size = new System.Drawing.Size(120, 18); this.labelPackageDescription.TabIndex = 12; @@ -4335,7 +4375,7 @@ private void InitializeComponent() { // this.linkLabelPackageWebsite.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.linkLabelPackageWebsite.AutoSize = true; - this.linkLabelPackageWebsite.Location = new System.Drawing.Point(134, 397); + this.linkLabelPackageWebsite.Location = new System.Drawing.Point(134, 427); this.linkLabelPackageWebsite.Name = "linkLabelPackageWebsite"; this.linkLabelPackageWebsite.Size = new System.Drawing.Size(112, 13); this.linkLabelPackageWebsite.TabIndex = 11; @@ -4347,7 +4387,7 @@ private void InitializeComponent() { // this.labelPackageWebsite.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelPackageWebsite.ForeColor = System.Drawing.Color.Black; - this.labelPackageWebsite.Location = new System.Drawing.Point(8, 397); + this.labelPackageWebsite.Location = new System.Drawing.Point(8, 427); this.labelPackageWebsite.Name = "labelPackageWebsite"; this.labelPackageWebsite.Size = new System.Drawing.Size(120, 18); this.labelPackageWebsite.TabIndex = 10; @@ -4360,7 +4400,7 @@ private void InitializeComponent() { | System.Windows.Forms.AnchorStyles.Right))); this.textBoxPackageVersion.BackColor = System.Drawing.SystemColors.Control; this.textBoxPackageVersion.ForeColor = System.Drawing.SystemColors.ControlText; - this.textBoxPackageVersion.Location = new System.Drawing.Point(134, 369); + this.textBoxPackageVersion.Location = new System.Drawing.Point(134, 399); this.textBoxPackageVersion.Name = "textBoxPackageVersion"; this.textBoxPackageVersion.Size = new System.Drawing.Size(423, 20); this.textBoxPackageVersion.TabIndex = 9; @@ -4370,7 +4410,7 @@ private void InitializeComponent() { // this.labelPackageVersion.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelPackageVersion.ForeColor = System.Drawing.Color.Black; - this.labelPackageVersion.Location = new System.Drawing.Point(8, 372); + this.labelPackageVersion.Location = new System.Drawing.Point(8, 402); this.labelPackageVersion.Name = "labelPackageVersion"; this.labelPackageVersion.Size = new System.Drawing.Size(120, 18); this.labelPackageVersion.TabIndex = 8; @@ -4383,7 +4423,7 @@ private void InitializeComponent() { | System.Windows.Forms.AnchorStyles.Right))); this.textBoxPackageAuthor.BackColor = System.Drawing.SystemColors.Control; this.textBoxPackageAuthor.ForeColor = System.Drawing.SystemColors.ControlText; - this.textBoxPackageAuthor.Location = new System.Drawing.Point(134, 345); + this.textBoxPackageAuthor.Location = new System.Drawing.Point(134, 375); this.textBoxPackageAuthor.Name = "textBoxPackageAuthor"; this.textBoxPackageAuthor.Size = new System.Drawing.Size(423, 20); this.textBoxPackageAuthor.TabIndex = 7; @@ -4393,7 +4433,7 @@ private void InitializeComponent() { // this.labelPackageAuthor.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelPackageAuthor.ForeColor = System.Drawing.Color.Black; - this.labelPackageAuthor.Location = new System.Drawing.Point(8, 348); + this.labelPackageAuthor.Location = new System.Drawing.Point(8, 378); this.labelPackageAuthor.Name = "labelPackageAuthor"; this.labelPackageAuthor.Size = new System.Drawing.Size(120, 18); this.labelPackageAuthor.TabIndex = 6; @@ -4406,7 +4446,7 @@ private void InitializeComponent() { | System.Windows.Forms.AnchorStyles.Right))); this.textBoxPackageName.BackColor = System.Drawing.SystemColors.Control; this.textBoxPackageName.ForeColor = System.Drawing.SystemColors.ControlText; - this.textBoxPackageName.Location = new System.Drawing.Point(134, 321); + this.textBoxPackageName.Location = new System.Drawing.Point(134, 351); this.textBoxPackageName.Name = "textBoxPackageName"; this.textBoxPackageName.Size = new System.Drawing.Size(423, 20); this.textBoxPackageName.TabIndex = 5; @@ -4416,7 +4456,7 @@ private void InitializeComponent() { // this.labelPackageName.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelPackageName.ForeColor = System.Drawing.Color.Black; - this.labelPackageName.Location = new System.Drawing.Point(8, 324); + this.labelPackageName.Location = new System.Drawing.Point(8, 354); this.labelPackageName.Name = "labelPackageName"; this.labelPackageName.Size = new System.Drawing.Size(120, 18); this.labelPackageName.TabIndex = 4; @@ -4430,7 +4470,7 @@ private void InitializeComponent() { | System.Windows.Forms.AnchorStyles.Right))); this.pictureBoxPackageImage.Location = new System.Drawing.Point(8, 54); this.pictureBoxPackageImage.Name = "pictureBoxPackageImage"; - this.pictureBoxPackageImage.Size = new System.Drawing.Size(683, 241); + this.pictureBoxPackageImage.Size = new System.Drawing.Size(683, 271); this.pictureBoxPackageImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; this.pictureBoxPackageImage.TabIndex = 3; this.pictureBoxPackageImage.TabStop = false; @@ -4449,7 +4489,7 @@ private void InitializeComponent() { this.panelUninstallResult.Controls.Add(this.buttonUninstallFinish); this.panelUninstallResult.Location = new System.Drawing.Point(0, 34); this.panelUninstallResult.Name = "panelUninstallResult"; - this.panelUninstallResult.Size = new System.Drawing.Size(699, 597); + this.panelUninstallResult.Size = new System.Drawing.Size(699, 627); this.panelUninstallResult.TabIndex = 25; // // textBoxUninstallResult @@ -4464,7 +4504,7 @@ private void InitializeComponent() { this.textBoxUninstallResult.Name = "textBoxUninstallResult"; this.textBoxUninstallResult.ReadOnly = true; this.textBoxUninstallResult.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.textBoxUninstallResult.Size = new System.Drawing.Size(683, 438); + this.textBoxUninstallResult.Size = new System.Drawing.Size(683, 468); this.textBoxUninstallResult.TabIndex = 19; // // labelUninstallLog @@ -4516,7 +4556,7 @@ private void InitializeComponent() { this.buttonUninstallFinish.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonUninstallFinish.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonUninstallFinish.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonUninstallFinish.Location = new System.Drawing.Point(276, 543); + this.buttonUninstallFinish.Location = new System.Drawing.Point(276, 573); this.buttonUninstallFinish.Name = "buttonUninstallFinish"; this.buttonUninstallFinish.Size = new System.Drawing.Size(146, 40); this.buttonUninstallFinish.TabIndex = 14; @@ -4537,7 +4577,7 @@ private void InitializeComponent() { this.panelSuccess.Controls.Add(this.buttonInstallFinish); this.panelSuccess.Location = new System.Drawing.Point(0, 34); this.panelSuccess.Name = "panelSuccess"; - this.panelSuccess.Size = new System.Drawing.Size(699, 597); + this.panelSuccess.Size = new System.Drawing.Size(699, 627); this.panelSuccess.TabIndex = 23; // // textBoxFilesInstalled @@ -4552,7 +4592,7 @@ private void InitializeComponent() { this.textBoxFilesInstalled.Name = "textBoxFilesInstalled"; this.textBoxFilesInstalled.ReadOnly = true; this.textBoxFilesInstalled.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.textBoxFilesInstalled.Size = new System.Drawing.Size(683, 438); + this.textBoxFilesInstalled.Size = new System.Drawing.Size(683, 468); this.textBoxFilesInstalled.TabIndex = 19; // // labelListFilesInstalled @@ -4604,7 +4644,7 @@ private void InitializeComponent() { this.buttonInstallFinish.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonInstallFinish.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonInstallFinish.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonInstallFinish.Location = new System.Drawing.Point(276, 543); + this.buttonInstallFinish.Location = new System.Drawing.Point(276, 573); this.buttonInstallFinish.Name = "buttonInstallFinish"; this.buttonInstallFinish.Size = new System.Drawing.Size(146, 40); this.buttonInstallFinish.TabIndex = 14; @@ -4626,14 +4666,14 @@ private void InitializeComponent() { this.panelDependancyError.Controls.Add(this.buttonProceedAnyway); this.panelDependancyError.Location = new System.Drawing.Point(0, 34); this.panelDependancyError.Name = "panelDependancyError"; - this.panelDependancyError.Size = new System.Drawing.Size(699, 597); + this.panelDependancyError.Size = new System.Drawing.Size(699, 627); this.panelDependancyError.TabIndex = 5; // // buttonAbort // this.buttonAbort.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonAbort.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.buttonAbort.Location = new System.Drawing.Point(445, 565); + this.buttonAbort.Location = new System.Drawing.Point(445, 595); this.buttonAbort.Name = "buttonAbort"; this.buttonAbort.Size = new System.Drawing.Size(120, 24); this.buttonAbort.TabIndex = 21; @@ -4666,7 +4706,7 @@ private void InitializeComponent() { this.dataGridViewDependancies.RowHeadersVisible = false; this.dataGridViewDependancies.RowHeadersWidth = 90; this.dataGridViewDependancies.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dataGridViewDependancies.Size = new System.Drawing.Size(683, 438); + this.dataGridViewDependancies.Size = new System.Drawing.Size(683, 468); this.dataGridViewDependancies.TabIndex = 20; this.dataGridViewDependancies.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridViewDependancies_CellContentClick); // @@ -4750,7 +4790,7 @@ private void InitializeComponent() { // this.buttonProceedAnyway.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonProceedAnyway.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.buttonProceedAnyway.Location = new System.Drawing.Point(571, 565); + this.buttonProceedAnyway.Location = new System.Drawing.Point(571, 595); this.buttonProceedAnyway.Name = "buttonProceedAnyway"; this.buttonProceedAnyway.Size = new System.Drawing.Size(120, 24); this.buttonProceedAnyway.TabIndex = 14; @@ -4769,7 +4809,7 @@ private void InitializeComponent() { this.panelPleaseWait.Controls.Add(this.pictureBoxProcessing); this.panelPleaseWait.Location = new System.Drawing.Point(0, 34); this.panelPleaseWait.Name = "panelPleaseWait"; - this.panelPleaseWait.Size = new System.Drawing.Size(699, 597); + this.panelPleaseWait.Size = new System.Drawing.Size(699, 627); this.panelPleaseWait.TabIndex = 28; // // labelProgressFile @@ -4777,7 +4817,7 @@ private void InitializeComponent() { this.labelProgressFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); this.labelProgressFile.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.labelProgressFile.ForeColor = System.Drawing.Color.Black; - this.labelProgressFile.Location = new System.Drawing.Point(1, 431); + this.labelProgressFile.Location = new System.Drawing.Point(1, 446); this.labelProgressFile.Name = "labelProgressFile"; this.labelProgressFile.Size = new System.Drawing.Size(697, 30); this.labelProgressFile.TabIndex = 7; @@ -4789,7 +4829,7 @@ private void InitializeComponent() { this.labelProgressPercent.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); this.labelProgressPercent.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.labelProgressPercent.ForeColor = System.Drawing.Color.Black; - this.labelProgressPercent.Location = new System.Drawing.Point(290, 401); + this.labelProgressPercent.Location = new System.Drawing.Point(290, 416); this.labelProgressPercent.Name = "labelProgressPercent"; this.labelProgressPercent.Size = new System.Drawing.Size(118, 30); this.labelProgressPercent.TabIndex = 6; @@ -4801,7 +4841,7 @@ private void InitializeComponent() { this.labelPleaseWait.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); this.labelPleaseWait.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.labelPleaseWait.ForeColor = System.Drawing.Color.Black; - this.labelPleaseWait.Location = new System.Drawing.Point(1, 462); + this.labelPleaseWait.Location = new System.Drawing.Point(1, 477); this.labelPleaseWait.Name = "labelPleaseWait"; this.labelPleaseWait.Size = new System.Drawing.Size(697, 30); this.labelPleaseWait.TabIndex = 5; @@ -4811,7 +4851,7 @@ private void InitializeComponent() { // pictureBoxProcessing // this.pictureBoxProcessing.Anchor = System.Windows.Forms.AnchorStyles.None; - this.pictureBoxProcessing.Location = new System.Drawing.Point(174, 46); + this.pictureBoxProcessing.Location = new System.Drawing.Point(174, 61); this.pictureBoxProcessing.Name = "pictureBoxProcessing"; this.pictureBoxProcessing.Size = new System.Drawing.Size(350, 350); this.pictureBoxProcessing.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; @@ -4832,7 +4872,7 @@ private void InitializeComponent() { this.panelPackageDependsAdd.Controls.Add(this.splitContainerDependancies); this.panelPackageDependsAdd.Location = new System.Drawing.Point(0, 34); this.panelPackageDependsAdd.Name = "panelPackageDependsAdd"; - this.panelPackageDependsAdd.Size = new System.Drawing.Size(699, 597); + this.panelPackageDependsAdd.Size = new System.Drawing.Size(699, 627); this.panelPackageDependsAdd.TabIndex = 26; // // buttonBack @@ -4841,7 +4881,7 @@ private void InitializeComponent() { this.buttonBack.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonBack.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonBack.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonBack.Location = new System.Drawing.Point(445, 561); + this.buttonBack.Location = new System.Drawing.Point(445, 591); this.buttonBack.Name = "buttonBack"; this.buttonBack.Size = new System.Drawing.Size(120, 28); this.buttonBack.TabIndex = 28; @@ -4855,7 +4895,7 @@ private void InitializeComponent() { this.buttonRemove.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonRemove.Enabled = false; this.buttonRemove.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonRemove.Location = new System.Drawing.Point(8, 525); + this.buttonRemove.Location = new System.Drawing.Point(8, 555); this.buttonRemove.Name = "buttonRemove"; this.buttonRemove.Size = new System.Drawing.Size(166, 26); this.buttonRemove.TabIndex = 25; @@ -4867,7 +4907,7 @@ private void InitializeComponent() { // this.labelNoDependencyReminder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelNoDependencyReminder.ForeColor = System.Drawing.Color.Black; - this.labelNoDependencyReminder.Location = new System.Drawing.Point(8, 560); + this.labelNoDependencyReminder.Location = new System.Drawing.Point(8, 590); this.labelNoDependencyReminder.Name = "labelNoDependencyReminder"; this.labelNoDependencyReminder.Size = new System.Drawing.Size(400, 48); this.labelNoDependencyReminder.TabIndex = 24; @@ -4903,7 +4943,7 @@ private void InitializeComponent() { this.buttonCreatePackage.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonCreatePackage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonCreatePackage.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonCreatePackage.Location = new System.Drawing.Point(571, 561); + this.buttonCreatePackage.Location = new System.Drawing.Point(571, 591); this.buttonCreatePackage.Name = "buttonCreatePackage"; this.buttonCreatePackage.Size = new System.Drawing.Size(120, 28); this.buttonCreatePackage.TabIndex = 14; @@ -4933,8 +4973,8 @@ private void InitializeComponent() { this.splitContainerDependancies.Panel2.Controls.Add(this.buttonReccomends); this.splitContainerDependancies.Panel2.Controls.Add(this.labelSelectedDependencies); this.splitContainerDependancies.Panel2.Controls.Add(this.buttonDepends); - this.splitContainerDependancies.Size = new System.Drawing.Size(683, 469); - this.splitContainerDependancies.SplitterDistance = 244; + this.splitContainerDependancies.Size = new System.Drawing.Size(683, 499); + this.splitContainerDependancies.SplitterDistance = 259; this.splitContainerDependancies.TabIndex = 27; // // labelDependancyType @@ -4985,7 +5025,7 @@ private void InitializeComponent() { this.dataGridViewPackages2.RowHeadersVisible = false; this.dataGridViewPackages2.RowHeadersWidth = 90; this.dataGridViewPackages2.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dataGridViewPackages2.Size = new System.Drawing.Size(683, 162); + this.dataGridViewPackages2.Size = new System.Drawing.Size(683, 177); this.dataGridViewPackages2.TabIndex = 21; this.dataGridViewPackages2.SelectionChanged += new System.EventHandler(this.dataGridViewPackages2_SelectionChanged); // @@ -5053,7 +5093,7 @@ private void InitializeComponent() { this.dataGridViewPackages3.RowHeadersVisible = false; this.dataGridViewPackages3.RowHeadersWidth = 90; this.dataGridViewPackages3.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dataGridViewPackages3.Size = new System.Drawing.Size(683, 161); + this.dataGridViewPackages3.Size = new System.Drawing.Size(683, 176); this.dataGridViewPackages3.TabIndex = 22; this.dataGridViewPackages3.SelectionChanged += new System.EventHandler(this.dataGridViewPackages3_SelectionChanged); // @@ -5148,7 +5188,7 @@ private void InitializeComponent() { this.panelPackageList.Controls.Add(this.labelInstalledPackages); this.panelPackageList.Location = new System.Drawing.Point(0, 34); this.panelPackageList.Name = "panelPackageList"; - this.panelPackageList.Size = new System.Drawing.Size(699, 597); + this.panelPackageList.Size = new System.Drawing.Size(699, 627); this.panelPackageList.TabIndex = 3; // // comboBoxPackageType @@ -5180,7 +5220,7 @@ private void InitializeComponent() { this.createPackageButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; this.createPackageButton.BackColor = System.Drawing.SystemColors.ButtonFace; this.createPackageButton.ForeColor = System.Drawing.SystemColors.ControlText; - this.createPackageButton.Location = new System.Drawing.Point(281, 549); + this.createPackageButton.Location = new System.Drawing.Point(281, 579); this.createPackageButton.Name = "createPackageButton"; this.createPackageButton.Size = new System.Drawing.Size(136, 26); this.createPackageButton.TabIndex = 23; @@ -5193,7 +5233,7 @@ private void InitializeComponent() { this.buttonUninstallPackage.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonUninstallPackage.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonUninstallPackage.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonUninstallPackage.Location = new System.Drawing.Point(555, 549); + this.buttonUninstallPackage.Location = new System.Drawing.Point(555, 579); this.buttonUninstallPackage.Name = "buttonUninstallPackage"; this.buttonUninstallPackage.Size = new System.Drawing.Size(136, 26); this.buttonUninstallPackage.TabIndex = 21; @@ -5206,7 +5246,7 @@ private void InitializeComponent() { this.buttonInstallPackage.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.buttonInstallPackage.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonInstallPackage.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonInstallPackage.Location = new System.Drawing.Point(8, 549); + this.buttonInstallPackage.Location = new System.Drawing.Point(8, 579); this.buttonInstallPackage.Name = "buttonInstallPackage"; this.buttonInstallPackage.Size = new System.Drawing.Size(136, 26); this.buttonInstallPackage.TabIndex = 20; @@ -5238,7 +5278,7 @@ private void InitializeComponent() { this.dataGridViewPackages.RowHeadersVisible = false; this.dataGridViewPackages.RowHeadersWidth = 90; this.dataGridViewPackages.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dataGridViewPackages.Size = new System.Drawing.Size(683, 454); + this.dataGridViewPackages.Size = new System.Drawing.Size(683, 484); this.dataGridViewPackages.TabIndex = 19; this.dataGridViewPackages.SelectionChanged += new System.EventHandler(this.dataGridViewPackages_SelectionChanged); // @@ -5300,7 +5340,7 @@ private void InitializeComponent() { this.panelVersionError.Controls.Add(this.labelVersionHeaderBackground); this.panelVersionError.Location = new System.Drawing.Point(0, 34); this.panelVersionError.Name = "panelVersionError"; - this.panelVersionError.Size = new System.Drawing.Size(699, 597); + this.panelVersionError.Size = new System.Drawing.Size(699, 627); this.panelVersionError.TabIndex = 24; // // buttonCancel @@ -5309,7 +5349,7 @@ private void InitializeComponent() { this.buttonCancel.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonCancel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonCancel.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonCancel.Location = new System.Drawing.Point(445, 561); + this.buttonCancel.Location = new System.Drawing.Point(445, 591); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(120, 28); this.buttonCancel.TabIndex = 29; @@ -5341,7 +5381,7 @@ private void InitializeComponent() { // this.buttonProceedAnyway1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonProceedAnyway1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.buttonProceedAnyway1.Location = new System.Drawing.Point(571, 565); + this.buttonProceedAnyway1.Location = new System.Drawing.Point(571, 595); this.buttonProceedAnyway1.Name = "buttonProceedAnyway1"; this.buttonProceedAnyway1.Size = new System.Drawing.Size(120, 24); this.buttonProceedAnyway1.TabIndex = 14; @@ -5517,7 +5557,7 @@ private void InitializeComponent() { this.panelCreatePackage.Controls.Add(this.panelNewPackage); this.panelCreatePackage.Location = new System.Drawing.Point(0, 34); this.panelCreatePackage.Name = "panelCreatePackage"; - this.panelCreatePackage.Size = new System.Drawing.Size(699, 597); + this.panelCreatePackage.Size = new System.Drawing.Size(699, 627); this.panelCreatePackage.TabIndex = 27; // // buttonCancel2 @@ -5526,7 +5566,7 @@ private void InitializeComponent() { this.buttonCancel2.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonCancel2.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonCancel2.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonCancel2.Location = new System.Drawing.Point(445, 561); + this.buttonCancel2.Location = new System.Drawing.Point(445, 591); this.buttonCancel2.Name = "buttonCancel2"; this.buttonCancel2.Size = new System.Drawing.Size(120, 28); this.buttonCancel2.TabIndex = 38; @@ -5539,7 +5579,7 @@ private void InitializeComponent() { this.SaveFileNameButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.SaveFileNameButton.BackColor = System.Drawing.SystemColors.ButtonFace; this.SaveFileNameButton.ForeColor = System.Drawing.SystemColors.ControlText; - this.SaveFileNameButton.Location = new System.Drawing.Point(571, 510); + this.SaveFileNameButton.Location = new System.Drawing.Point(571, 540); this.SaveFileNameButton.Name = "SaveFileNameButton"; this.SaveFileNameButton.Size = new System.Drawing.Size(120, 26); this.SaveFileNameButton.TabIndex = 37; @@ -5551,7 +5591,7 @@ private void InitializeComponent() { // this.textBoxPackageFileName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.textBoxPackageFileName.Location = new System.Drawing.Point(8, 511); + this.textBoxPackageFileName.Location = new System.Drawing.Point(8, 541); this.textBoxPackageFileName.Name = "textBoxPackageFileName"; this.textBoxPackageFileName.Size = new System.Drawing.Size(557, 20); this.textBoxPackageFileName.TabIndex = 36; @@ -5561,7 +5601,7 @@ private void InitializeComponent() { this.labelSaveAs.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelSaveAs.AutoSize = true; this.labelSaveAs.ForeColor = System.Drawing.Color.Black; - this.labelSaveAs.Location = new System.Drawing.Point(8, 495); + this.labelSaveAs.Location = new System.Drawing.Point(8, 525); this.labelSaveAs.Name = "labelSaveAs"; this.labelSaveAs.Size = new System.Drawing.Size(94, 13); this.labelSaveAs.TabIndex = 35; @@ -5572,7 +5612,7 @@ private void InitializeComponent() { this.labelDependanciesNextStep.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelDependanciesNextStep.AutoSize = true; this.labelDependanciesNextStep.ForeColor = System.Drawing.Color.Black; - this.labelDependanciesNextStep.Location = new System.Drawing.Point(8, 541); + this.labelDependanciesNextStep.Location = new System.Drawing.Point(8, 571); this.labelDependanciesNextStep.Name = "labelDependanciesNextStep"; this.labelDependanciesNextStep.Size = new System.Drawing.Size(360, 13); this.labelDependanciesNextStep.TabIndex = 34; @@ -5584,7 +5624,7 @@ private void InitializeComponent() { this.buttonCreateProceed.BackColor = System.Drawing.SystemColors.ButtonFace; this.buttonCreateProceed.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.buttonCreateProceed.ForeColor = System.Drawing.SystemColors.ControlText; - this.buttonCreateProceed.Location = new System.Drawing.Point(571, 561); + this.buttonCreateProceed.Location = new System.Drawing.Point(571, 591); this.buttonCreateProceed.Name = "buttonCreateProceed"; this.buttonCreateProceed.Size = new System.Drawing.Size(120, 28); this.buttonCreateProceed.TabIndex = 22; @@ -5734,14 +5774,14 @@ private void InitializeComponent() { this.panelReplacePackage.Controls.Add(this.dataGridViewReplacePackage); this.panelReplacePackage.Location = new System.Drawing.Point(0, 137); this.panelReplacePackage.Name = "panelReplacePackage"; - this.panelReplacePackage.Size = new System.Drawing.Size(699, 352); + this.panelReplacePackage.Size = new System.Drawing.Size(699, 382); this.panelReplacePackage.TabIndex = 20; // // replacePackageButton // this.replacePackageButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; this.replacePackageButton.Enabled = false; - this.replacePackageButton.Location = new System.Drawing.Point(270, 303); + this.replacePackageButton.Location = new System.Drawing.Point(270, 333); this.replacePackageButton.Name = "replacePackageButton"; this.replacePackageButton.Size = new System.Drawing.Size(159, 23); this.replacePackageButton.TabIndex = 25; @@ -5782,7 +5822,7 @@ private void InitializeComponent() { this.dataGridViewReplacePackage.RowHeadersVisible = false; this.dataGridViewReplacePackage.RowHeadersWidth = 90; this.dataGridViewReplacePackage.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dataGridViewReplacePackage.Size = new System.Drawing.Size(683, 274); + this.dataGridViewReplacePackage.Size = new System.Drawing.Size(683, 304); this.dataGridViewReplacePackage.TabIndex = 23; this.dataGridViewReplacePackage.SelectionChanged += new System.EventHandler(this.dataGridViewReplacePackage_SelectionChanged); // @@ -5828,7 +5868,7 @@ private void InitializeComponent() { this.panelNewPackage.Enabled = false; this.panelNewPackage.Location = new System.Drawing.Point(-2, 137); this.panelNewPackage.Name = "panelNewPackage"; - this.panelNewPackage.Size = new System.Drawing.Size(702, 355); + this.panelNewPackage.Size = new System.Drawing.Size(702, 385); this.panelNewPackage.TabIndex = 21; // // newPackageClearSelectionButton @@ -5836,7 +5876,7 @@ private void InitializeComponent() { this.newPackageClearSelectionButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.newPackageClearSelectionButton.BackColor = System.Drawing.SystemColors.ButtonFace; this.newPackageClearSelectionButton.ForeColor = System.Drawing.SystemColors.ControlText; - this.newPackageClearSelectionButton.Location = new System.Drawing.Point(572, 326); + this.newPackageClearSelectionButton.Location = new System.Drawing.Point(572, 356); this.newPackageClearSelectionButton.Name = "newPackageClearSelectionButton"; this.newPackageClearSelectionButton.Size = new System.Drawing.Size(121, 26); this.newPackageClearSelectionButton.TabIndex = 30; @@ -5854,7 +5894,7 @@ private void InitializeComponent() { this.filesToPackageBox.Multiline = true; this.filesToPackageBox.Name = "filesToPackageBox"; this.filesToPackageBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.filesToPackageBox.Size = new System.Drawing.Size(685, 250); + this.filesToPackageBox.Size = new System.Drawing.Size(685, 280); this.filesToPackageBox.TabIndex = 29; // // addPackageItemsButton @@ -5862,7 +5902,7 @@ private void InitializeComponent() { this.addPackageItemsButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.addPackageItemsButton.BackColor = System.Drawing.SystemColors.ButtonFace; this.addPackageItemsButton.ForeColor = System.Drawing.SystemColors.ControlText; - this.addPackageItemsButton.Location = new System.Drawing.Point(8, 326); + this.addPackageItemsButton.Location = new System.Drawing.Point(8, 356); this.addPackageItemsButton.Name = "addPackageItemsButton"; this.addPackageItemsButton.Size = new System.Drawing.Size(121, 26); this.addPackageItemsButton.TabIndex = 28; @@ -5902,10 +5942,10 @@ private void InitializeComponent() { this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.BackColor = System.Drawing.Color.White; - this.ClientSize = new System.Drawing.Size(859, 631); + this.ClientSize = new System.Drawing.Size(859, 661); this.Controls.Add(this.labelVerticalSeparator); - this.Controls.Add(this.panelPackages); this.Controls.Add(this.panelOptions); + this.Controls.Add(this.panelPackages); this.Controls.Add(this.panelStart); this.Controls.Add(this.panelInfo); this.Controls.Add(this.panelPanels); @@ -5948,7 +5988,6 @@ private void InitializeComponent() { this.groupboxRouteDetails.ResumeLayout(false); this.tabcontrolRouteDetails.ResumeLayout(false); this.tabpageRouteDescription.ResumeLayout(false); - this.tabpageRouteDescription.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.pictureboxRouteImage)).EndInit(); this.tabpageRouteMap.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.pictureboxRouteMap)).EndInit(); @@ -5960,6 +5999,18 @@ private void InitializeComponent() { this.panelRouteEncoding.ResumeLayout(false); this.panelOptions.ResumeLayout(false); this.panelOptions.PerformLayout(); + this.panelOptionsPage2.ResumeLayout(false); + this.groupBoxInputDevice.ResumeLayout(false); + this.groupBoxObjectParser.ResumeLayout(false); + this.groupBoxKioskMode.ResumeLayout(false); + this.groupBoxKioskMode.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDownKioskTimeout)).EndInit(); + this.groupBoxAdvancedOptions.ResumeLayout(false); + this.groupBoxAdvancedOptions.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureboxCursor)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.updownTimeAccelerationFactor)).EndInit(); + this.groupBoxPackageOptions.ResumeLayout(false); + this.groupBoxPackageOptions.PerformLayout(); this.panelOptionsLeft.ResumeLayout(false); this.groupboxDisplayMode.ResumeLayout(false); this.groupboxDisplayMode.PerformLayout(); @@ -5990,18 +6041,6 @@ private void InitializeComponent() { this.groupboxSimulation.PerformLayout(); this.groupboxSound.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.updownSoundNumber)).EndInit(); - this.panelOptionsPage2.ResumeLayout(false); - this.groupBoxInputDevice.ResumeLayout(false); - this.groupBoxObjectParser.ResumeLayout(false); - this.groupBoxKioskMode.ResumeLayout(false); - this.groupBoxKioskMode.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDownKioskTimeout)).EndInit(); - this.groupBoxAdvancedOptions.ResumeLayout(false); - this.groupBoxAdvancedOptions.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureboxCursor)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.updownTimeAccelerationFactor)).EndInit(); - this.groupBoxPackageOptions.ResumeLayout(false); - this.groupBoxPackageOptions.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.pictureboxLanguage)).EndInit(); this.panelPanels.ResumeLayout(false); this.panelPanels.PerformLayout(); @@ -6508,5 +6547,8 @@ private void InitializeComponent() { private System.Windows.Forms.ComboBox comboBoxFont; private System.Windows.Forms.Label labelFontName; private System.Windows.Forms.Label labelNoDependencyReminder; + private System.Windows.Forms.Button buttonMSTSTrainsetDirectory; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBoxMSTSTrainsetDirectory; } } diff --git a/source/OpenBVE/UserInterface/formMain.cs b/source/OpenBVE/UserInterface/formMain.cs index deab2b1cc9..cc22b9b614 100644 --- a/source/OpenBVE/UserInterface/formMain.cs +++ b/source/OpenBVE/UserInterface/formMain.cs @@ -739,9 +739,11 @@ private void ApplyLanguage() textBoxRouteDirectory.Text = Program.FileSystem.RouteInstallationDirectory; textBoxTrainDirectory.Text = Program.FileSystem.TrainInstallationDirectory; textBoxOtherDirectory.Text = Program.FileSystem.OtherInstallationDirectory; + textBoxMSTSTrainsetDirectory.Text = Program.FileSystem.MSTSDirectory; labelRouteInstallDirectory.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","package_route_directory"}); labelTrainInstallDirectory.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","package_train_directory"}); labelOtherInstallDirectory.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","package_other_directory"}); + labelOtherInstallDirectory.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] { "options", "package_msts_directory" }); labelPackageCompression.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","package_compression"}); //Kiosk Mode groupBoxKioskMode.Text = Translations.GetInterfaceString(HostApplication.OpenBve, new[] {"options","kiosk_mode"}); @@ -2061,5 +2063,17 @@ private void textboxTrainDescription_LinkClicked(object sender, LinkClickedEvent { Process.Start(e.LinkText); } + + private void buttonMSTSTrainsetDirectory_Click(object sender, EventArgs e) + { + using (var folderSelectDialog = new FolderBrowserDialog()) + { + if (folderSelectDialog.ShowDialog() == DialogResult.OK) + { + Program.FileSystem.MSTSDirectory = folderSelectDialog.SelectedPath; + textBoxMSTSTrainsetDirectory.Text = folderSelectDialog.SelectedPath; + } + } + } } } diff --git a/source/OpenBVE/UserInterface/formMain.resx b/source/OpenBVE/UserInterface/formMain.resx index 77fa901946..2d26dd8152 100644 --- a/source/OpenBVE/UserInterface/formMain.resx +++ b/source/OpenBVE/UserInterface/formMain.resx @@ -120,10 +120,10 @@ 456, 18 - + True - + True diff --git a/source/OpenBveApi/System/FileSystem.cs b/source/OpenBveApi/System/FileSystem.cs index 24f12e2243..04fdefe87f 100644 --- a/source/OpenBveApi/System/FileSystem.cs +++ b/source/OpenBveApi/System/FileSystem.cs @@ -56,6 +56,9 @@ public class FileSystem { /// The Loksim3D data directory public string LoksimDataDirectory; + /// The MSTS trainset directory + public string MSTSDirectory; + /// Any lines loaded from the filesystem.cfg which were not understood internal string[] NotUnderstoodLines; @@ -158,7 +161,7 @@ public void SaveCurrentFileSystemConfiguration() { string file = Path.CombineFile(SettingsFolder, "FileSystem.cfg"); StringBuilder newLines = new StringBuilder(); - newLines.AppendLine("Version=1"); + newLines.AppendLine("Version=2"); try { if (File.Exists(file)) @@ -219,6 +222,11 @@ public void SaveCurrentFileSystemConfiguration() { newLines.AppendLine("LoksimPackageInstall=" + ReplacePath(LoksimPackageInstallationDirectory)); } + if (MSTSDirectory != null && + Directory.Exists(OtherInstallationDirectory)) + { + newLines.AppendLine("MSTSTrainset=" + ReplacePath(MSTSDirectory)); + } if (NotUnderstoodLines != null && NotUnderstoodLines.Length != 0) { for (int i = 0; i < NotUnderstoodLines.Length; i++) @@ -350,14 +358,14 @@ private static FileSystem FromConfigurationFile(string file, HostInterface Host) system.AppendToLogFile("WARNING: Invalid filesystem.cfg version detected."); } - if (v <= 1) + if (v <= 2) { //Silently upgrade to the current config version - system.Version = 1; + system.Version = 2; break; } - system.AppendToLogFile("WARNING: A newer filesystem.cfg version " + v + " was detected. The current version is 1."); + system.AppendToLogFile("WARNING: A newer filesystem.cfg version " + v + " was detected. The current version is 2."); system.Version = v; break; @@ -417,6 +425,9 @@ private static FileSystem FromConfigurationFile(string file, HostInterface Host) case "loksimpackageinstall": system.LoksimPackageInstallationDirectory = GetAbsolutePath(value, true); break; + case "mststrainset": + system.MSTSDirectory = GetAbsolutePath(value, true); + break; default: if (system.NotUnderstoodLines == null) { diff --git a/source/Plugins/Train.MsTs/Handles/Handle.cs b/source/Plugins/Train.MsTs/Handles/Handle.cs index e8a8bc1d4e..8e2de01d3b 100644 --- a/source/Plugins/Train.MsTs/Handles/Handle.cs +++ b/source/Plugins/Train.MsTs/Handles/Handle.cs @@ -65,6 +65,7 @@ private void ParseNotchDescriptionBlock(Block block, bool isPower) catch { // ignore + NotchDescriptions[i] = new Tuple(notchPower, i.ToString()); } } } diff --git a/source/Plugins/Train.MsTs/Plugin.cs b/source/Plugins/Train.MsTs/Plugin.cs index da1f3e93df..db02780cc6 100644 --- a/source/Plugins/Train.MsTs/Plugin.cs +++ b/source/Plugins/Train.MsTs/Plugin.cs @@ -23,21 +23,31 @@ public class Plugin : TrainInterface internal static FileSystem FileSystem; - internal static string MsTsPath; + internal static BaseOptions CurrentOptions; internal static bool PreviewOnly; public Plugin() { ConsistParser = new ConsistParser(this); WagonParser = new WagonParser(); + } + public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions options, object rendererReference) + { + CurrentHost = host; + FileSystem = fileSystem; + CurrentOptions = options; + Renderer = (BaseRenderer) rendererReference; try { - MsTsPath = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft Games\\Train Simulator\\1.0", "Path", string.Empty); - string OrTsPath = (string)Registry.GetValue("HKEY_CURRENT_USER\\Software\\OpenRails\\ORTS\\Folders", "Train Simulator", string.Empty); - if (!string.IsNullOrEmpty(OrTsPath)) + if (string.IsNullOrEmpty(FileSystem.MSTSDirectory)) { - MsTsPath = OrTsPath; + FileSystem.MSTSDirectory = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft Games\\Train Simulator\\1.0", "Path", string.Empty); + string OrTsPath = (string)Registry.GetValue("HKEY_CURRENT_USER\\Software\\OpenRails\\ORTS\\Folders", "Train Simulator", string.Empty); + if (!string.IsNullOrEmpty(OrTsPath)) + { + FileSystem.MSTSDirectory = OrTsPath; + } } } catch @@ -46,13 +56,6 @@ public Plugin() } } - public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions options, object rendererReference) - { - CurrentHost = host; - FileSystem = fileSystem; - Renderer = (BaseRenderer) rendererReference; - } - public override bool CanLoadTrain(string path) { if (File.Exists(path) && path.ToLowerInvariant().EndsWith(".con")) diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index e91bc54c8c..2069f81a64 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -45,8 +45,7 @@ namespace Train.MsTs internal class ConsistParser { internal readonly Plugin Plugin; - - internal string CurrentFolder; + internal string TrainsetDirectory; internal ConsistParser(Plugin plugin) { @@ -71,19 +70,31 @@ internal void ReadConsist(string fileName, ref AbstractTrain parsedTrain) train.Handles.HoldBrake = new HoldBrakeHandle(train); train.Specs.AveragesPressureDistribution = true; train.SafetySystems.Headlights = new LightSource(train, 2); - CurrentFolder = Path.GetDirectoryName(fileName); - if (CurrentFolder == null) + if(Directory.Exists(Plugin.FileSystem.MSTSDirectory)) { - throw new Exception("Unable to determine the MSTS consist working directory."); + TrainsetDirectory = OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.MSTSDirectory, "TRAINS\\trainset"); } - DirectoryInfo d = Directory.GetParent(CurrentFolder); - if(d == null || !d.Name.Equals("TRAINS", StringComparison.InvariantCultureIgnoreCase)) + else { - //FIXME: Better finding of the trainset folder (set in options?) - throw new Exception("Unable to find the MSTS TRAINS folder."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "The MSTS directory has not been set. Attempting to find the trainset directory."); + string currentFolder = Path.GetDirectoryName(fileName); + if (currentFolder == null) + { + throw new Exception("MSTS Consist Parser: Unable to determine the consist working directory."); + } + DirectoryInfo d = Directory.GetParent(currentFolder); + if (d == null || !d.Name.Equals("TRAINS", StringComparison.InvariantCultureIgnoreCase)) + { + //FIXME: Better finding of the trainset folder (set in options?) + throw new Exception("MSTS Consist Parser: Unable to find the MSTS TRAINS folder."); + } + TrainsetDirectory = OpenBveApi.Path.CombineDirectory(currentFolder, "trainset"); } - CurrentFolder = d.FullName; + if (!Directory.Exists(TrainsetDirectory)) + { + throw new Exception("MSTS Consist Parser: Unable to find the MSTS trainset folder."); + } Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); @@ -164,6 +175,11 @@ internal void ReadConsist(string fileName, ref AbstractTrain parsedTrain) } } + if (train.Cars.Length == 0) + { + throw new InvalidDataException("Consist " + fileName + " appears to be invalid or malformed"); + } + bool hasCabview = false; //create couplers & other necessary properties for the thing to load //TODO: Pull out MSTS properties @@ -328,11 +344,11 @@ private void ParseBlock(Block block, ref TrainBase currentTrain) break; case 1: //Just a WagonName- This is likely invalid, but let's ignore - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); + Plugin.WagonParser.Parse(TrainsetDirectory, wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: No WagonFolder supplied, searching entire trainset folder."); break; case 2: - string wagonDirectory = OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset\\" + wagonFiles[1]); + string wagonDirectory = OpenBveApi.Path.CombineDirectory(TrainsetDirectory, wagonFiles[1]); if (!Directory.Exists(wagonDirectory)) { Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: WagonFolder " + wagonDirectory + " was not found."); @@ -342,7 +358,7 @@ private void ParseBlock(Block block, ref TrainBase currentTrain) Plugin.WagonParser.Parse(wagonDirectory, wagonFiles[0], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); break; default: - Plugin.WagonParser.Parse(OpenBveApi.Path.CombineDirectory(CurrentFolder, "trainset"), wagonFiles[1], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); + Plugin.WagonParser.Parse(TrainsetDirectory, wagonFiles[1], block.Token == KujuTokenID.EngineData, ref currentCar, ref currentTrain); Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: Two parameters were expected- Check for correct escaping of strings."); break; } diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 65a42cea0a..a9a3936867 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -753,10 +753,10 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool string soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Path.GetDirectoryName(fileName), "SOUND"), sF); if (!File.Exists(soundFile)) { - if (Directory.Exists(Plugin.MsTsPath)) + if (Directory.Exists(Plugin.FileSystem.MSTSDirectory)) { // If sound file is not relative to the ENG / WAG, try in the MSTS common sound directory (most generic wagons + coaches) - soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.MsTsPath, "SOUND"), sF); + soundFile = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.MSTSDirectory, "SOUND"), sF); } if (!File.Exists(soundFile)) { From eabdd5224fc949bbd94d361cfdf88a696a530498 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 24 Oct 2025 20:32:40 +0100 Subject: [PATCH 56/82] Update error message formatting, more fixes --- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 12 ++- .../Train.MsTs/Panel/Enums/PanelSubject.cs | 11 +++ source/Plugins/Train.MsTs/Train/Adhesion.cs | 13 +++ .../Train/Enums/BrakeEquipmentType.cs | 4 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 83 ++++++++++++------- 5 files changed, 86 insertions(+), 37 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index c0ffee6efd..7ecab59249 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -91,7 +91,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) } else if (!headerString.StartsWith("SIMISA@@")) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Unrecognized cabview file header " + headerString + " in " + fileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Cabview Parser: Unrecognized cabview file header " + headerString + " in " + fileName); return false; } @@ -120,7 +120,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) } else if (subHeader[7] != 'b') { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Unrecognized subHeader " + subHeader + " in " + fileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Cabview Parser: Unrecognized subHeader " + subHeader + " in " + fileName); return false; } else @@ -218,7 +218,11 @@ private static void ParseBlock(Block block) int controlCount = block.ReadInt16(); while (controlCount > 0) { - newBlock = block.ReadSubBlock(); + newBlock = block.ReadSubBlock(true); + if (newBlock.Token == KujuTokenID.Skip) + { + continue; + } CabComponent currentComponent = new CabComponent(newBlock, cabViews[0].Position); // cab components can only be applied to CabView #0, others are static views currentComponent.Parse(); cabComponents.Add(currentComponent); @@ -465,7 +469,7 @@ internal static int CreateElement(ref ElementsGroup Group, Vector2 TopLeft, Vect { if (Size.X == 0 || Size.Y == 0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Attempted to create an invalid size element"); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Cabview Parser: Attempted to create an invalid size element"); } double worldWidth, worldHeight; diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs index 31184090af..737397b198 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -68,5 +68,16 @@ internal enum PanelSubject Wheelslip, Whistle, Wipers, + + // From MSTSBin v1.7 documentation + Ammeter_Abs, + Doors_Display, + Pantograph2, // pantograph2 state [need to dig into this, but I think Pantograph2 was 'broken' until BIN patch, although appears in original GLOBAL folder defines and some default stock] + Pantographs_4c, // 4-state combined controller for pantograoh 1+2 + Pantographs_4, // with end position + Pantographs_5, // 5-state combined controller for pantograph 1+2 + RPM, + Speed_Projected, // projected speed in one minute + SpeedLimit, // signal limit, not track limit } } diff --git a/source/Plugins/Train.MsTs/Train/Adhesion.cs b/source/Plugins/Train.MsTs/Train/Adhesion.cs index 832671e0dd..7c0d2682e0 100644 --- a/source/Plugins/Train.MsTs/Train/Adhesion.cs +++ b/source/Plugins/Train.MsTs/Train/Adhesion.cs @@ -1,4 +1,5 @@ using OpenBve.Formats.MsTs; +using OpenBveApi.Interface; using TrainManager.Car; using TrainManager.Car.Systems; @@ -24,6 +25,18 @@ internal Adhesion(Block block, CarBase car, bool isSteamEngine) Normal = block.ReadSingle(); Sanding = block.ReadSingle(); + if (Sanding < 0.75 || Normal < 0.05 || WheelSlip < 0.05) + { + /* + * e.g. MT Class 47 + * If we don't apply at least 75 percent of rated power when *sanding* let alone normally + * the ENG file is clearly bugged + */ + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Discarding implausible Adheasion values"); + WheelSlip = 0.2; + Normal = 0.4; + Sanding = 2.0; + } } catch { diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index ae7dafc40a..7c1d22f457 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -48,6 +48,8 @@ enum BrakeEquipmentType /// Through piped for vacuum Vacuum_Piped = 17, /// Through piped for air - Air_Piped = 18 + Air_Piped = 18, + /// Brake reservoir for emergency brakes only + Emergency_reservoir = 19 } } diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index a9a3936867..c445a45dd7 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -234,7 +234,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine } else if (!headerString.StartsWith("SIMISA@@")) { - throw new Exception("Unrecognized vehicle file header " + headerString + " in " + fileName); + throw new Exception("MSTS Vehicle Parser: Unrecognized vehicle file header " + headerString + " in " + fileName); } string subHeader; @@ -273,7 +273,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine { if (engineBlocks.Count > 1) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Multiple engine blocks encounted in MSTS ENG file "+ fileName); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Multiple engine blocks encounted in MSTS ENG file "+ fileName); } return ParseBlock(engineBlocks[0], fileName, ref wagonName, true, ref car, ref train); } @@ -281,7 +281,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine { if (wagonBlocks.Count > 1) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Multiple wagon blocks encounted in MSTS ENG file " + fileName); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Multiple wagon blocks encounted in MSTS WAG file " + fileName); } return ParseBlock(wagonBlocks[0], fileName, ref wagonName, false, ref car, ref train); } @@ -293,29 +293,27 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine { throw new Exception("Unrecognized subHeader \"" + subHeader + "\" in " + fileName); } - else + + using (BinaryReader reader = new BinaryReader(fb)) { - using (BinaryReader reader = new BinaryReader(fb)) + KujuTokenID currentToken = (KujuTokenID) reader.ReadUInt16(); + if (currentToken != KujuTokenID.Wagon) { - KujuTokenID currentToken = (KujuTokenID) reader.ReadUInt16(); - if (currentToken != KujuTokenID.Wagon) - { - throw new Exception(); //Shape definition - } - reader.ReadUInt16(); - uint remainingBytes = reader.ReadUInt32(); - byte[] newBytes = reader.ReadBytes((int) remainingBytes); - BinaryBlock block = new BinaryBlock(newBytes, KujuTokenID.Wagon); - try - { - ParseBlock(block, fileName, ref wagonName, isEngine, ref car, ref train); - } - catch (InvalidDataException) - { - return false; - } - + throw new Exception(); //Shape definition + } + reader.ReadUInt16(); + uint remainingBytes = reader.ReadUInt32(); + byte[] newBytes = reader.ReadBytes((int) remainingBytes); + BinaryBlock block = new BinaryBlock(newBytes, KujuTokenID.Wagon); + try + { + ParseBlock(block, fileName, ref wagonName, isEngine, ref car, ref train); + } + catch (InvalidDataException) + { + return false; } + } return true; } @@ -350,6 +348,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private CouplingType couplingType; private Friction friction; private Adhesion adhesion; + private UnitOfPressure brakeSystemDefaultUnits = UnitOfPressure.PoundsPerSquareInch; private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { @@ -658,6 +657,26 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.BrakeSystemType: // Determines the brake system types available brakeSystemTypes = block.ReadEnumArray(default(BrakeSystemType)); + // WARNING: If vehicle only has vac brakes, default parameters for brake system are in/hg + // otherwise, default parameters are psi + bool hasAirBrakes = false; + for (int i = 0; i < brakeSystemTypes.Length; i++) + { + switch (brakeSystemTypes[i]) + { + case BrakeSystemType.Air_single_pipe: + case BrakeSystemType.Air_twin_pipe: + case BrakeSystemType.EP: + case BrakeSystemType.ECP: + hasAirBrakes = true; + break; + } + } + + if (!hasAirBrakes) + { + brakeSystemDefaultUnits = UnitOfPressure.InchesOfMercury; + } break; case KujuTokenID.CabView: // Loads cab view file @@ -699,7 +718,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool // maximum force applied when starting if (!isEngine) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine force is not expected to be present in a wagon block."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: MaxForce is not expected to be present in a wagon block."); break; } maxForce = block.ReadSingle(UnitOfForce.Newton); @@ -714,7 +733,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool // Maximum continuous force if (!isEngine) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine force is not expected to be present in a wagon block."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: MaxContinuousForce is not expected to be present in a wagon block."); break; } maxContinuousForce = block.ReadSingle(UnitOfForce.Newton); @@ -769,7 +788,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.EngineControllers: if (!isEngine) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Engine controllers are not expected to be present in a wagon block."); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: An EngineControllers block is not valid in a wagon block."); break; } @@ -862,10 +881,10 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool mainReservoirMaximumPressure = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); break; case KujuTokenID.BrakeCylinderPressureForMaxBrakeBrakeForce: - brakeCylinderMaximumPressure = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + brakeCylinderMaximumPressure = block.ReadSingle(UnitOfPressure.Pascal, brakeSystemDefaultUnits); break; case KujuTokenID.TrainBrakesControllerEmergencyApplicationRate: - emergencyRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + emergencyRate = block.ReadSingle(UnitOfPressure.Pascal, brakeSystemDefaultUnits); break; case KujuTokenID.AirBrakesAirCompressorPowerRating: compressionRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); @@ -875,7 +894,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } break; case KujuTokenID.TrainBrakesControllerMaxReleaseRate: - releaseRate = block.ReadSingle(UnitOfPressure.Pascal, UnitOfPressure.PoundsPerSquareInch); + releaseRate = block.ReadSingle(UnitOfPressure.Pascal, brakeSystemDefaultUnits); break; case KujuTokenID.AntiSlip: // if any value in this block, car has wheelslip detection control @@ -1010,7 +1029,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.GearBoxMaxSpeedForGears: if (Gears == null) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Number of gears must be specified."); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Gears must be specified when using GearBoxMaxSpeedForGears."); break; } @@ -1022,7 +1041,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.GearBoxMaxTractiveForceForGears: if (Gears == null) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Number of gears must be specified."); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Gears must be specified when using GearBoxMaxTractiveForceForGears."); break; } @@ -1034,7 +1053,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool case KujuTokenID.GearBoxOverspeedPercentageForFailure: if (Gears == null) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Number of gears must be specified."); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Vehicle Parser: Gears must be specified when using GearBoxOVerspeedPercentageForFailure."); break; } From 50cf7a9790312cc50edb44a894dc6db53177ee8b Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 27 Oct 2025 14:58:42 +0000 Subject: [PATCH 57/82] SMS work --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 51 ++++++++++++------- .../Plugins/Train.MsTs/Sound/TriggerTypes.cs | 6 ++- .../TrainManager/Sounds/MSTS/SoundStream.cs | 17 ++++--- .../Sounds/MSTS/SoundTrigger.Initial.cs | 5 +- .../Sounds/MSTS/SoundTrigger.Speed.cs | 18 +++++-- .../MSTS/SoundTrigger.VariableControlled.cs | 16 +++++- .../TrainManager/Sounds/MSTS/SoundTrigger.cs | 2 + 7 files changed, 79 insertions(+), 36 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index c10a1b027a..3ee035025e 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -103,7 +103,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase currentCar) subHeader = Encoding.ASCII.GetString(buffer, 0, 8); } SoundSet soundSet = new SoundSet(); - SoundStream soundStream = new SoundStream(currentCar); + SoundStream soundStream = new SoundStream(currentCar, CameraViewMode.NotDefined, CameraViewMode.NotDefined); if (subHeader[7] == 't') { @@ -156,6 +156,8 @@ internal struct SoundSet internal SoundBuffer[] SoundBuffers; internal int CurrentBuffer; internal KujuTokenID SelectionMethod; + internal CameraViewMode ActivationCameraModes; + internal CameraViewMode DeactivationCameraModes; internal void Create(CarBase car, SoundStream currentSoundStream) { @@ -251,27 +253,27 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.ExternalCam: if (currentSoundSet.Activation) { - currentSoundStream.ActivationCameraModes |= CameraViewMode.Exterior; - currentSoundStream.ActivationCameraModes |= CameraViewMode.Track; - currentSoundStream.ActivationCameraModes |= CameraViewMode.FlyBy; - currentSoundStream.ActivationCameraModes |= CameraViewMode.FlyByZooming; + currentSoundSet.ActivationCameraModes |= CameraViewMode.Exterior; + currentSoundSet.ActivationCameraModes |= CameraViewMode.Track; + currentSoundSet.ActivationCameraModes |= CameraViewMode.FlyBy; + currentSoundSet.ActivationCameraModes |= CameraViewMode.FlyByZooming; } else { - currentSoundStream.DeactivationCameraModes |= CameraViewMode.Exterior; - currentSoundStream.DeactivationCameraModes |= CameraViewMode.Track; - currentSoundStream.DeactivationCameraModes |= CameraViewMode.FlyBy; - currentSoundStream.DeactivationCameraModes |= CameraViewMode.FlyByZooming; + currentSoundSet.DeactivationCameraModes |= CameraViewMode.Exterior; + currentSoundSet.DeactivationCameraModes |= CameraViewMode.Track; + currentSoundSet.DeactivationCameraModes |= CameraViewMode.FlyBy; + currentSoundSet.DeactivationCameraModes |= CameraViewMode.FlyByZooming; } break; case KujuTokenID.CabCam: if (currentSoundSet.Activation) { - currentSoundStream.ActivationCameraModes |= CameraViewMode.Interior; + currentSoundSet.ActivationCameraModes |= CameraViewMode.Interior; } else { - currentSoundStream.DeactivationCameraModes |= CameraViewMode.Interior; + currentSoundSet.DeactivationCameraModes |= CameraViewMode.Interior; } break; case KujuTokenID.PassengerCam: @@ -280,7 +282,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Streams: // each stream represents a unique sound int numStreams = block.ReadInt32(); - for (int i = 0; i < numStreams - 1; i++) + for (int i = 0; i < numStreams; i++) { newBlock = block.ReadSubBlock(); if (newBlock.Token != KujuTokenID.Stream) @@ -292,7 +294,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So } if (block.Length() - block.Position() <= 3) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numStreams + ", but only found " + (i + 1) + " in Stream block in SMS file " + currentFile); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numStreams + ", but only found " + i + " in Stream block in SMS file " + currentFile); break; } continue; @@ -301,7 +303,7 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); if (block.Length() - block.Position() <= 3) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numStreams + ", but only found " + (i + 1) + " in Stream block in SMS file " + currentFile); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numStreams + ", but only found " + i + " in Stream block in SMS file " + currentFile); break; } } @@ -309,14 +311,20 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Stream: while (block.Position() < block.Length() - 3) { - newBlock = block.ReadSubBlock(new[] { KujuTokenID.Priority, KujuTokenID.Triggers, KujuTokenID.Variable_Trigger, KujuTokenID.Volume, KujuTokenID.VolumeCurve, KujuTokenID.FrequencyCurve, KujuTokenID.Granularity }); + newBlock = block.ReadSubBlock(); + if (newBlock.Token == KujuTokenID.Stream) + { + // EBPHNWSE121.sms - Completely bugged, copy + paste error (?) + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Unexpected Stream found within a Stream block in SMS file " + currentFile); + continue; + } ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } if (currentSoundStream.Triggers.Count > 0) { car.Sounds.ControlledSounds.Add(currentSoundStream); - currentSoundStream = new SoundStream(car); + currentSoundStream = new SoundStream(car, currentSoundSet.ActivationCameraModes, currentSoundSet.DeactivationCameraModes); } break; case KujuTokenID.Priority: @@ -324,20 +332,21 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So break; case KujuTokenID.Triggers: int numTriggers = block.ReadInt32(); - for (int i = 0; i < numTriggers - 1; i++) + for (int i = 0; i < numTriggers; i++) { // two triggers per sound set (start + stop) newBlock = block.ReadSubBlock(new [] {KujuTokenID.Variable_Trigger, KujuTokenID.Initial_Trigger, KujuTokenID.Discrete_Trigger, KujuTokenID.Random_Trigger, KujuTokenID.Dist_Travelled_Trigger}); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); if (block.Length() - block.Position() <= 3) { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numTriggers + ", but only found " + (i + 1) + " in Triggers block in SMS file " + currentFile); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "Expected " + numTriggers + ", but only found " + i + " in Triggers block in SMS file " + currentFile); break; } } break; case KujuTokenID.Initial_Trigger: // when initially appears, hence nothing other than StartLoop should be valid + currentSoundSet.VariableTriggerType = KujuTokenID.Initial_Trigger; newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger, KujuTokenID.PlayOneShot, KujuTokenID.SetStreamVolume }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; @@ -493,6 +502,12 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So sanders.LoopSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; + case SoundTrigger.TrainBrakePressureDecrease: + if ((currentSoundStream.ActivationCameraModes & CameraViewMode.Interior) != 0) + { + car.CarBrake.AirZero = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + break; } } else diff --git a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs index 2e71f6125f..feef5ece9f 100644 --- a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs +++ b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs @@ -56,9 +56,11 @@ internal enum SoundTrigger VigilanceAlarmReset = 48, // 49 - 53 not listed - // TODO: CHECK 53 / 54: + // // acelaeng.sms has 53 commented as Brake Normal Apply and 54 as BrakeEmergencyApply - + // + // OpenRails forum suggests that trigger 53 never actually worked + // TrainBrakePressureDecrease = 54, //55 not listed VigilanceAlarmOn = 56, diff --git a/source/TrainManager/Sounds/MSTS/SoundStream.cs b/source/TrainManager/Sounds/MSTS/SoundStream.cs index 2e43e1565e..99d5640f4a 100644 --- a/source/TrainManager/Sounds/MSTS/SoundStream.cs +++ b/source/TrainManager/Sounds/MSTS/SoundStream.cs @@ -1,4 +1,4 @@ -//Simplified BSD License (BSD-2-Clause) +//Simplified BSD License (BSD-2-Clause) // //Copyright (c) 2025, Christopher Lees, The OpenBVE Project // @@ -39,19 +39,19 @@ public class SoundStream /// The frequency curve for the sound stream public MsTsFrequencyCurve FrequencyCurve; /// The camera modes in which this sound stream is active - public CameraViewMode ActivationCameraModes; + public readonly CameraViewMode ActivationCameraModes; /// The modes in which this sound stream is not active - public CameraViewMode DeactivationCameraModes; + public readonly CameraViewMode DeactivationCameraModes; private SoundSource soundSource; private CarBase car; - public SoundStream(CarBase baseCar) + public SoundStream(CarBase baseCar, CameraViewMode activationCameraModes, CameraViewMode deactivationCameraModes) { Triggers = new List(); - ActivationCameraModes = CameraViewMode.NotDefined; - DeactivationCameraModes = CameraViewMode.NotDefined; + ActivationCameraModes = activationCameraModes; + DeactivationCameraModes = deactivationCameraModes; car = baseCar; } @@ -91,6 +91,10 @@ public void Update(double timeElapsed) * Otherwise, stop the playing buffer. */ SoundBuffer soundBuffer = null; + if (soundSource != null) + { + soundBuffer = soundSource.Buffer; + } bool loops = false; for (int i = 0; i < Triggers.Count; i++) @@ -110,6 +114,7 @@ public void Update(double timeElapsed) } else { + soundSource?.Stop(); soundSource = (SoundSource)TrainManagerBase.currentHost.PlaySound(soundBuffer, pitch, volume, Vector3.Zero, car, loops); } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Initial.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Initial.cs index b203acd185..bddc012cfc 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.Initial.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Initial.cs @@ -39,15 +39,14 @@ public InitialTrigger(SoundBuffer[] buffers, KujuTokenID selectionMethod, bool s { } - private bool triggered; public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { - if (triggered == false) + if (Triggered == false) { soundBuffer = Buffer; soundLoops = SoundLoops; - triggered = true; + Triggered = true; } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs index 9450cf49bd..28b60addc4 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.Speed.cs @@ -52,6 +52,12 @@ public override void Update(double timeElapsed, CarBase car, ref SoundBuffer sou { soundBuffer = Buffer; soundLoops = SoundLoops; + Triggered = true; + } + + if (speed < speedValue) + { + Triggered = false; } } } @@ -60,28 +66,30 @@ public override void Update(double timeElapsed, CarBase car, ref SoundBuffer sou public class SpeedDecPast : SoundTrigger { private readonly double speedValue; - - private readonly bool soundLoops; public SpeedDecPast(SoundBuffer[] buffers, KujuTokenID selectionMethod, double speedValue, bool soundLoops) : base(buffers, selectionMethod, soundLoops) { this.speedValue = speedValue; - this.soundLoops = soundLoops; } public SpeedDecPast(SoundBuffer buffer, double speedValue, bool soundLoops) : base(buffer, soundLoops) { this.speedValue = speedValue; - this.soundLoops = soundLoops; } public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { double speed = Math.Abs(car.CurrentSpeed); - if (speed <= speedValue) + if (speed <= speedValue && Triggered == false) { soundBuffer = Buffer; soundLoops = SoundLoops; + Triggered = true; + } + + if (speed > speedValue) + { + Triggered = false; } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs index 3abaef9574..bad9b48754 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs @@ -49,10 +49,16 @@ public Variable2IncPast(SoundBuffer buffer, double variableValue, bool soundLoop public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { - if (car.TractionModel.CurrentPower >= variableValue) + if (car.TractionModel.CurrentPower >= variableValue && Triggered == false) { soundBuffer = Buffer; soundLoops = SoundLoops; + Triggered = true; + } + + if (car.TractionModel.CurrentPower < variableValue) + { + Triggered = false; } } } @@ -77,10 +83,16 @@ public Variable2DecPast(SoundBuffer buffer, double variableValue, bool soundLoop public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) { - if (car.TractionModel.CurrentPower <= variableValue) + if (car.TractionModel.CurrentPower <= variableValue && Triggered == false) { soundBuffer = Buffer; soundLoops = SoundLoops; + Triggered = true; + } + + if (car.TractionModel.CurrentPower > variableValue) + { + Triggered = false; } } } diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs index 5f12aceb90..df15f82caf 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.cs @@ -40,6 +40,8 @@ public abstract class SoundTrigger internal double Timer; /// The previously selected buffer index internal int BufferIndex; + /// Stores whether the trigger is currently active + internal bool Triggered; /// Gets the actual sound buffer to be played internal SoundBuffer Buffer { From f744e4c43807e5f86a1df07f722c962885f8c8ba Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 30 Oct 2025 11:38:11 +0000 Subject: [PATCH 58/82] Fix brake systems for other engines, move some stuff around --- source/OpenBveApi/OpenBveApi.csproj | 1 + source/OpenBveApi/Train/Motor/EngineType.cs | 41 ++++ source/Plugins/Train.MsTs/Train.MsTs.csproj | 2 - .../Train.MsTs/Train/Enums/CouplingType.cs | 10 - .../Train.MsTs/Train/Enums/EngineTypes.cs | 11 - .../Plugins/Train.MsTs/Train/VehicleParser.cs | 225 +++++++----------- 6 files changed, 123 insertions(+), 167 deletions(-) create mode 100644 source/OpenBveApi/Train/Motor/EngineType.cs delete mode 100644 source/Plugins/Train.MsTs/Train/Enums/CouplingType.cs delete mode 100644 source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs diff --git a/source/OpenBveApi/OpenBveApi.csproj b/source/OpenBveApi/OpenBveApi.csproj index 75329db2e3..c630fb743d 100644 --- a/source/OpenBveApi/OpenBveApi.csproj +++ b/source/OpenBveApi/OpenBveApi.csproj @@ -265,6 +265,7 @@ + diff --git a/source/OpenBveApi/Train/Motor/EngineType.cs b/source/OpenBveApi/Train/Motor/EngineType.cs new file mode 100644 index 0000000000..10417acae7 --- /dev/null +++ b/source/OpenBveApi/Train/Motor/EngineType.cs @@ -0,0 +1,41 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +namespace OpenBveApi.Motor +{ + /// Describes the different types of engine + public enum EngineType + { + /// No engine + NoEngine = 0, + /// Diesel prime mover, electric transmission + Diesel, + /// Diesel prime mover, hydralic transmission + DieselHydraulic, + /// Steam engine + Steam, + /// Pure electric engine + Electric + } +} diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index 74627f0346..d88a95bae5 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -75,8 +75,6 @@ - - diff --git a/source/Plugins/Train.MsTs/Train/Enums/CouplingType.cs b/source/Plugins/Train.MsTs/Train/Enums/CouplingType.cs deleted file mode 100644 index 85baeec774..0000000000 --- a/source/Plugins/Train.MsTs/Train/Enums/CouplingType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Train.MsTs -{ - internal enum CouplingType - { - Unknown = 0, - Automatic = 1, - Bar = 2, - Chain = 3 - } -} diff --git a/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs b/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs deleted file mode 100644 index c69227a764..0000000000 --- a/source/Plugins/Train.MsTs/Train/Enums/EngineTypes.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Train.MsTs -{ - internal enum EngineType - { - NoEngine = 0, - Diesel, - DieselHydraulic, - Steam, - Electric - } -} diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index c445a45dd7..b8e2da9783 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -22,14 +22,8 @@ //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using LibRender2.Smoke; using LibRender2.Trains; -using OpenBve.Formats.Msts; using OpenBve.Formats.MsTs; using OpenBveApi.Interface; using OpenBveApi.Math; @@ -39,6 +33,11 @@ using OpenBveApi.World; using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; using TrainManager.BrakeSystems; using TrainManager.Car; using TrainManager.Car.Systems; @@ -170,7 +169,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r currentCar.TractionModel = new BVETrailerCar(currentCar); break; } - + if (currentCar.ReAdhesionDevice == null) { currentCar.ReAdhesionDevice = new BveReAdhesionDevice(currentCar, hasAntiSlipDevice ? ReadhesionDeviceType.TypeB : ReadhesionDeviceType.NotFitted); @@ -186,6 +185,81 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r } } + if (brakeSystemTypes != null) + { + + // Add brakes last, as we need the acceleration values + if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_Piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_Piped) || (brakeSystemTypes.Length == 1 && brakeSystemTypes[0] == BrakeSystemType.Handbrake)) + { + /* + * FIXME: Need to implement vac braked / air piped and vice-versa, but for the minute, we'll assume that if one or the other is present + * then the vehicle has no brakes + */ + currentCar.CarBrake = new ThroughPiped(currentCar); + } + else + { + if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) + { + AirBrake airBrake; + // FIXME: MR values needs to be (close) in proportion to the BC else the physics bug out + double bcVal = brakeCylinderMaximumPressure / 440000; + mainReservoirMinimumPressure = 690000.0 * bcVal; + mainReservoirMaximumPressure = 780000.0 * bcVal; + double operatingPressure = brakeCylinderMaximumPressure + 0.75 * (mainReservoirMinimumPressure - brakeCylinderMaximumPressure); + + if (brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) + { + // Combined air brakes and control signals + // Assume equivilant to ElectromagneticStraightAirBrake + airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, currentCar, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, true); + } + else + { + // Assume equivilant to ElectromagneticStraightAirBrake + airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, currentCar, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); + airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, false); + } + + airBrake.MainReservoir = new MainReservoir(mainReservoirMinimumPressure, mainReservoirMaximumPressure, 0.01, 0.075 / train.Cars.Length); + airBrake.Compressor = new Compressor(5000.0, airBrake.MainReservoir, currentCar); + airBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); + double r = 200000.0 / airBrake.BrakeCylinder.EmergencyMaximumPressure - 1.0; + if (r < 0.1) r = 0.1; + if (r > 1.0) r = 1.0; + airBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * operatingPressure, 200000.0, 0.5, r); + airBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); + airBrake.EqualizingReservoir.NormalPressure = 1.005 * operatingPressure; + airBrake.StraightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); + + currentCar.CarBrake = airBrake; + } + + if (brakeSystemTypes.Contains(BrakeSystemType.Vaccum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) + { + VaccumBrake vaccumBrake = new VaccumBrake(currentCar, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + vaccumBrake.MainReservoir = new MainReservoir(71110, 84660, 0.01, 0.075 / train.Cars.Length); // ~21in/hg - ~25in/hg + vaccumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); + vaccumBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, 1.0); + vaccumBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); + vaccumBrake.EqualizingReservoir.NormalPressure = 1.005 * (vaccumBrake.BrakeCylinder.EmergencyMaximumPressure + 0.75 * (vaccumBrake.MainReservoir.MinimumPressure - vaccumBrake.BrakeCylinder.EmergencyMaximumPressure)); + vaccumBrake.BrakePipe = new BrakePipe(brakeCylinderMaximumPressure, 10000000.0, 1500000.0, 5000000.0, false); + currentCar.CarBrake = vaccumBrake; + } + + currentCar.CarBrake.BrakeType = currentCar.Index == train.DriverCar || isEngine ? BrakeType.Main : BrakeType.Auxiliary; + currentCar.CarBrake.JerkUp = 10; + currentCar.CarBrake.JerkDown = 10; + } + + currentCar.CarBrake.Initialize(TrainStartMode.ServiceBrakesAts); + } + else + { + currentCar.CarBrake = new ThroughPiped(currentCar); + } + currentCar.FrontAxle = new MSTSAxle(Plugin.CurrentHost, train, currentCar, friction, adhesion); currentCar.RearAxle = new MSTSAxle(Plugin.CurrentHost, train, currentCar, friction, adhesion); @@ -385,74 +459,6 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool //ignore } } - if (brakeSystemTypes == null) - { - break; - } - // Add brakes last, as we need the acceleration values - if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_Piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_Piped) || (brakeSystemTypes.Length == 1 && brakeSystemTypes[0] == BrakeSystemType.Handbrake)) - { - /* - * FIXME: Need to implement vac braked / air piped and vice-versa, but for the minute, we'll assume that if one or the other is present - * then the vehicle has no brakes - */ - car.CarBrake = new ThroughPiped(car); - } - else - { - if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) - { - AirBrake airBrake; - // FIXME: MR values needs to be (close) in proportion to the BC else the physics bug out - double bcVal = brakeCylinderMaximumPressure / 440000; - mainReservoirMinimumPressure = 690000.0 * bcVal; - mainReservoirMaximumPressure = 780000.0 * bcVal; - double operatingPressure = brakeCylinderMaximumPressure + 0.75 * (mainReservoirMinimumPressure - brakeCylinderMaximumPressure); - - if (brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) - { - // Combined air brakes and control signals - // Assume equivilant to ElectromagneticStraightAirBrake - airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); - airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, true); - } - else - { - // Assume equivilant to ElectromagneticStraightAirBrake - airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); - airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, false); - } - - airBrake.MainReservoir = new MainReservoir(mainReservoirMinimumPressure, mainReservoirMaximumPressure, 0.01, 0.075 / train.Cars.Length); - airBrake.Compressor = new Compressor(5000.0, airBrake.MainReservoir, car); - airBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); - double r = 200000.0 / airBrake.BrakeCylinder.EmergencyMaximumPressure - 1.0; - if (r < 0.1) r = 0.1; - if (r > 1.0) r = 1.0; - airBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * operatingPressure, 200000.0, 0.5, r); - airBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - airBrake.EqualizingReservoir.NormalPressure = 1.005 * operatingPressure; - airBrake.StraightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); - - car.CarBrake = airBrake; - } - - if (brakeSystemTypes.Contains(BrakeSystemType.Vaccum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) - { - VaccumBrake vaccumBrake = new VaccumBrake(car, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); - vaccumBrake.MainReservoir = new MainReservoir(71110, 84660, 0.01, 0.075 / train.Cars.Length); // ~21in/hg - ~25in/hg - vaccumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); - vaccumBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, 1.0); - vaccumBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - vaccumBrake.EqualizingReservoir.NormalPressure = 1.005 * (vaccumBrake.BrakeCylinder.EmergencyMaximumPressure + 0.75 * (vaccumBrake.MainReservoir.MinimumPressure - vaccumBrake.BrakeCylinder.EmergencyMaximumPressure)); - vaccumBrake.BrakePipe = new BrakePipe(brakeCylinderMaximumPressure, 10000000.0, 1500000.0, 5000000.0, false); - car.CarBrake = vaccumBrake; - } - car.CarBrake.BrakeType = car.Index == train.DriverCar ? BrakeType.Main : BrakeType.Auxiliary; - car.CarBrake.JerkUp = 10; - car.CarBrake.JerkDown = 10; - } - car.CarBrake.Initialize(TrainStartMode.ServiceBrakesAts); } break; case KujuTokenID.Engine: @@ -478,75 +484,6 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool //ignore } } - - if (brakeSystemTypes == null) - { - break; - } - // Add brakes last, as we need the acceleration values - if (brakeSystemTypes.Contains(BrakeSystemType.Vacuum_Piped) || brakeSystemTypes.Contains(BrakeSystemType.Air_Piped)) - { - /* - * FIXME: Need to implement vac braked / air piped and vice-versa, but for the minute, we'll assume that if one or the other is present - * then the vehicle has no brakes - */ - car.CarBrake = new ThroughPiped(car); - } - else - { - if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) - { - AirBrake airBrake; - // FIXME: MR values needs to be (close) in proportion to the BC else the physics bug out - double bcVal = brakeCylinderMaximumPressure / 440000; - mainReservoirMinimumPressure = 690000.0 * bcVal; - mainReservoirMaximumPressure = 780000.0 * bcVal; - double operatingPressure = brakeCylinderMaximumPressure + 0.75 * (mainReservoirMinimumPressure - brakeCylinderMaximumPressure); - - if (brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) - { - // Combined air brakes and control signals - // Assume equivilant to ElectromagneticStraightAirBrake - airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); - airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, true); - } - else - { - // Assume equivilant to ElectromagneticStraightAirBrake - airBrake = new ElectromagneticStraightAirBrake(EletropneumaticBrakeType.None, car, 0, 0, 0, 0, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); - airBrake.BrakePipe = new BrakePipe(operatingPressure, 10000000.0, 1500000.0, 5000000.0, false); - } - - airBrake.MainReservoir = new MainReservoir(mainReservoirMinimumPressure, mainReservoirMaximumPressure, 0.01, 0.075 / train.Cars.Length); - airBrake.Compressor = new Compressor(compressionRate, airBrake.MainReservoir, car); - airBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); - double r = 200000.0 / airBrake.BrakeCylinder.EmergencyMaximumPressure - 1.0; - if (r < 0.1) r = 0.1; - if (r > 1.0) r = 1.0; - airBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * operatingPressure, 200000.0, 0.5, r); - airBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - airBrake.EqualizingReservoir.NormalPressure = 1.005 * operatingPressure; - airBrake.StraightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); - - car.CarBrake = airBrake; - } - - if (brakeSystemTypes.Contains(BrakeSystemType.Vaccum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) - { - VaccumBrake vaccumBrake = new VaccumBrake(car, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); - vaccumBrake.MainReservoir = new MainReservoir(71110, 84660, 0.01, 0.075 / train.Cars.Length); // ~21in/hg - ~25in/hg - vaccumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); - vaccumBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, 1.0); - vaccumBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - vaccumBrake.EqualizingReservoir.NormalPressure = 1.005 * (vaccumBrake.BrakeCylinder.EmergencyMaximumPressure + 0.75 * (vaccumBrake.MainReservoir.MinimumPressure - vaccumBrake.BrakeCylinder.EmergencyMaximumPressure)); - vaccumBrake.BrakePipe = new BrakePipe(brakeCylinderMaximumPressure, 10000000.0, 1500000.0, 5000000.0, false); - car.CarBrake = vaccumBrake; - } - car.CarBrake.BrakeType = car.Index == car.baseTrain.DriverCar || isEngine ? BrakeType.Main : BrakeType.Auxiliary; - car.CarBrake.JerkUp = 10; - car.CarBrake.JerkDown = 10; - } - car.CarBrake.Initialize(TrainStartMode.ServiceBrakesAts); break; case KujuTokenID.Type: switch (block.ParentBlock.Token) From c390e4245b639fcbb1a244c1202caf8335c5240a Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 30 Oct 2025 15:40:48 +0000 Subject: [PATCH 59/82] Create DigitalClock for CVF --- assets/Compatibility/numbers.png | Bin 1943 -> 1964 bytes .../AnimatedObject/AnimatedObject.cs | 2 +- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 54 ++++++++++-- .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 83 ++++++++++++++++-- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 18 +++- .../Panel/Enums/CabComponentStyle.cs | 14 +++ source/Plugins/Train.MsTs/Train.MsTs.csproj | 1 + 7 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs diff --git a/assets/Compatibility/numbers.png b/assets/Compatibility/numbers.png index 6b3914036b83b0cfb9a5605add6c8a3b20007f37..1b61e682327bf3eabd3660ef8aebe11d90a8b04b 100644 GIT binary patch delta 1474 zcmaLXe>~H990zbD)OA}a8<_`VKe8frXd{mN7E_sW#Zrl`5>g>$J^aXFh9(qsT(@YJ zwo~Lm5rs~E)J3Vt&(a}3X8Z2@oy~oxbdUSz-jCn!G7yvjfD#DG;r`!LzAj$ir+-PM@+C5<1omZ>!AEHEX)&QM zD+I^^Kn?-WV#*;9?n^*98Up7bi5vzGoRY6N?^Jb}^HS*&V5ux!0AL;lE#{Xlk<2fV zfnpg1e>t3D891k;T%|GyrYsNy1EFsP0zs)%X>p`dK*>`Iz$@@=BoaU@R+^gwL?S>m z`#%&AxUjGgeB3c!MMdQ`r%Ek{lEdssJhst@l-rbTINe6(%*WMF7qBiXu=&o9G~PFD-)CRrusr8+pZZ0nM~ zE;Wm%>ZoFEExt|9T0w1Hy`qrOA2&7mpcL8u(z}VVzXA-Z6g_j;f4!H>WO|N_=M%O@ zxb4ug>o^|aO11PI@H6HZv{C3-KYv>ig|+I)EbEgUg4RwO!x|vkiIkCsike(!6ls(= z^c>e&{G8!)-WfwP^6}RoQTdK(Y2JrpiwZcA~AYI3AG{<(@nn^z9m)FtT%S) z#WoHh?xuI?oI8D4t0j-Atr@yI#llDw+&NMvM;t^26*|;Z?w$#ajwJc=_GZ|gFSP$? z9e>l~g|%7C8G+yN=(Rn1)wguJo5QKedAd#PTh2*QE!9I~W)}@c6Ej(_1^Xv*O^c58 zbTxTdSdDv{ey;Bor>7J=PT;Rl{cAxaG+$D{P9d;n0qkY!Nf|VWA;UTy5S=C;N}&7u8vbB*BMaojrIzlN9Ls{h{6ns>Yc_f$ zhgzJCtqRuIfLpvyH8 zMNPIZT~&6)v-In%U3@paK0E6V4kGEg3My%ye0QJ4LVJv{s=PP>1_87 z(GToF-F1%&$a3a6JQ86f16Y4xP%AY&c5k;=05wFB0168U~4s%P>_r=e9 zE>wjePQNZAA$=frQ}xw-4a70u$rg-uE&@cR(gyV=%&Q_&6pN?iz zCN>Kho2(R(&o<~wYWD1mo$WQOSD4LrhRw$))aze0eb=%z;~f06Q1Nu7xm0WmPW%VD C6LFaU delta 1454 zcmZ3(Kb?Pq3S;X;)tBNwfBu7@U*G?M&<`;AeX=T}O8uXoKmYvr0YX22{spp8Nsug9 z1So}se*FCjMSp&N2OX(H11Jcw8REwu-~R%UGn%bHAv6()O&H|AUq3(E z?loj!V7NAUKhwm?fz0OhZ{NND@bS~~S4&0Wdxkb!~uwWo_?NJZS+ z>92!BN@d#jpB7S(+`glEYQn;$W%`@W#Hxwc1E$~Ra$?wY>{4C^ z0lTM@@*5bQ*B4}!(7hiJoEF0lpeeKShnX67T#D13K+qofA z@9~6Qt)E>iCZ~-v_V@??=V&+f2%p-i#g`qBr4(#APwkn`omZRZEE2fr+I-oTHPA%g zW5x~5G8bN#j%|)DPv>R`FSP#g%u3L2tx7$gPetno_178izwCG`mo)9}4BOq?{XRY` zD9|{+>Q+b9ziT$rO{&v8F4=0A`)%-k(~*{Alo!mI)pNUsXYIx|+30?^$9@@yPg?U> z-(uC4-1un2jg`hbUR=3wILbie+mdNKciz<<_VKyY5R{zRK6l378(t}^j!u?Y^;EZ?!*ZLbd5zDq*|69w=yHG;noEr0OU0#XZ^I4)VI8HjP;Az|5a&MaD%=V@wms_8Q zh%TCRxNG)7t!{4HlSiI}#c=aP31`$L9QE25&XU@(i%0Sv{~Y0mjQ`zt_a3}+Pg?m< z^eN#Kp6=ydCnS87@_7gbcsWll#se`8ZQW2rrbL% z>dte~`$vy+rb6)4$~E;<4ZSg6sxzxb9?}QvD> zVK^Q$b$Vg@i-h%Q*6h0$q^#(AG5^U1&aD$&);|^2m3zqtJMQgvlqT1gN9tm$;)LQL5+1>L? zbB+J*t$uj#H*e0uA{O=#(d5PZl=Caz2bDfg5x2KJ_xd|m{G%0D-?YgkrL~ pyFMzmC#U^faei;#KjVMT^>^&sKKrUpC#ZyA@O1TaS?83{1OS3#c`X0{ diff --git a/source/OpenBveApi/Objects/ObjectTypes/AnimatedObject/AnimatedObject.cs b/source/OpenBveApi/Objects/ObjectTypes/AnimatedObject/AnimatedObject.cs index 760c52192b..c43b468bb6 100644 --- a/source/OpenBveApi/Objects/ObjectTypes/AnimatedObject/AnimatedObject.cs +++ b/source/OpenBveApi/Objects/ObjectTypes/AnimatedObject/AnimatedObject.cs @@ -729,7 +729,7 @@ public void Update(AbstractTrain Train, int CarIndex, double TrackPosition, Vect internalObject.Translation = Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z); } - if (ColorFunction != null) + if (ColorFunction != null && Colors != null) { int color = (int)ColorFunction.LastResult; if (UpdateFunctions) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index eed84b5af6..1999a677d7 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -57,9 +57,11 @@ internal class CabComponent private int LeadingZeros; private FrameMapping[] FrameMappings = new FrameMapping[0]; private readonly Vector3 PanelPosition; - + private CabComponentStyle Style; private Tuple[] PositiveColors; private Tuple[] NegativeColors; + private Color24 ControlColor; + private int Accuracy; internal void Parse() { @@ -87,7 +89,7 @@ internal void Parse() internal void Create(ref CarBase currentCar, int componentLayer) { - if (File.Exists(TexturePath) || Type == CabComponentType.Digital) + if (File.Exists(TexturePath) || Type == CabComponentType.Digital || Type == CabComponentType.DigitalClock) { if (FrameMappings.Length < 2 && TotalFrames > 1) { @@ -190,7 +192,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Throttle: case PanelSubject.Train_Brake: case PanelSubject.Gears: - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); break; default: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); @@ -244,7 +246,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Overspeed: case PanelSubject.Sanders: case PanelSubject.Wheelslip: - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, FrameMappings); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); break; default: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); @@ -309,8 +311,8 @@ internal void Create(ref CarBase currentCar, int componentLayer) } // create color and digit functions - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject, Units, currentDigit); - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].ColorFunction = new CvfAnimation(panelSubject, Units, FrameMappings); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Units, currentDigit); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].ColorFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Units, FrameMappings); } break; @@ -352,10 +354,39 @@ internal void Create(ref CarBase currentCar, int componentLayer) if (k == 0) j = l; } - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(panelSubject); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject); + + } + break; + case CabComponentType.DigitalClock: + Position.X *= rW; + Position.Y *= rH; + totalDigits = Accuracy == 1 ? 8 : 5; // with or without secs + j = -1; + digitWidth = Size.X / totalDigits; + textColor = ControlColor; + frameTextures = new Texture[12]; + TexturePath = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.DataFolder, "Compatibility"), "numbers.png"); // arial 9.5pt + Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + + for (int i = 0; i < 10; i++) + { + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, i * 24, 16, 24), null), out frameTextures[i], true); } + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, 240, 16, 16), null), out frameTextures[11], true); + for (int currentDigit = 0; currentDigit < totalDigits; currentDigit++) + { + for (int k = 0; k < frameTextures.Length; k++) + { + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], new Vector2(Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y), new Vector2(digitWidth * rW, Size.Y * rH), new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, frameTextures[k], null, textColor, k != 0); + if (k == 0) j = l; + } + + // create digit functions + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Style, Accuracy == 1 ? currentDigit : currentDigit + 3); + } break; } } @@ -440,7 +471,7 @@ private void ReadSubBlock(Block block) MouseControl = block.ReadInt16() == 1; break; case KujuTokenID.Style: - block.Skip((int)block.Length()); + Style = block.ReadEnumValue(default(CabComponentStyle)); break; case KujuTokenID.Graphic: string s = block.ReadString(); @@ -541,7 +572,12 @@ private void ReadSubBlock(Block block) } } } - + break; + case KujuTokenID.ControlColour: + ControlColor = new Color24((byte)block.ReadInt16(), (byte)block.ReadInt16(), (byte)block.ReadInt16()); + break; + case KujuTokenID.Accuracy: + Accuracy = block.ReadInt16(); break; } } diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 119722aac8..bfbd05d0b7 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -30,12 +30,15 @@ using OpenBveApi; using TrainManager.Car.Systems; using TrainManager.Motor; +using OpenBveApi.Hosts; namespace Train.MsTs { /// Animation class handling CVF elements with keyframe mappings internal class CvfAnimation : AnimationScript { + internal readonly HostInterface CurrentHost; + internal readonly PanelSubject Subject; internal readonly FrameMapping[] FrameMapping; @@ -46,8 +49,9 @@ internal class CvfAnimation : AnimationScript private int lastResult; - internal CvfAnimation(PanelSubject subject) + internal CvfAnimation(HostInterface host, PanelSubject subject) { + CurrentHost = host; Subject = subject; switch (subject) { @@ -63,8 +67,9 @@ internal CvfAnimation(PanelSubject subject) Digit = -1; } - internal CvfAnimation(PanelSubject subject, FrameMapping[] frameMapping) + internal CvfAnimation(HostInterface host, PanelSubject subject, FrameMapping[] frameMapping) { + CurrentHost = host; Subject = subject; FrameMapping = frameMapping; Minimum = 0; @@ -72,9 +77,10 @@ internal CvfAnimation(PanelSubject subject, FrameMapping[] frameMapping) Digit = -1; } - internal CvfAnimation(PanelSubject subject, Units unit, int digit) + internal CvfAnimation(HostInterface host, PanelSubject subject, Units unit, int digit) { - Subject= subject; + CurrentHost = host; + Subject = subject; switch (unit) { case Units.Miles_Per_Hour: @@ -87,8 +93,9 @@ internal CvfAnimation(PanelSubject subject, Units unit, int digit) Digit = digit; } - internal CvfAnimation(PanelSubject subject, Units unit, FrameMapping[] frameMapping) + internal CvfAnimation(HostInterface host, PanelSubject subject, Units unit, FrameMapping[] frameMapping) { + CurrentHost = host; Subject = subject; switch (unit) { @@ -104,6 +111,22 @@ internal CvfAnimation(PanelSubject subject, Units unit, FrameMapping[] frameMapp FrameMapping = frameMapping; } + internal CvfAnimation(HostInterface host, PanelSubject subject, CabComponentStyle style, int digit) + { + CurrentHost = host; + Subject = subject; + Digit = digit; + switch (style) + { + case CabComponentStyle.TwelveHour: + UnitConversionFactor = 1; + break; + case CabComponentStyle.TwentyFourHour: + UnitConversionFactor = 0; + break; + } + } + public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, double trackPosition, int sectionIndex, bool isPartOfTrain, double timeElapsed, int currentState) { dynamic dynamicTrain = train; @@ -256,13 +279,61 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, } lastResult = wheelSlip; break; + case PanelSubject.Clock: + double hour = Math.Floor(CurrentHost.InGameTime / 3600.0); + hour %= 24; + if (UnitConversionFactor == 1) + { + if (hour > 12) + { + hour -= 12; + } + } + double min = Math.Floor(CurrentHost.InGameTime / 60 % 60); + double sec = CurrentHost.InGameTime % 60; + switch (Digit) + { + case 7: + // H + lastResult = hour >= 10 ? (int)(hour / 10) : 0; + break; + case 6: + // HH + lastResult = (int)(hour % 10); + break; + case 5: + // HH: + lastResult = 11; + break; + case 4: + // HH:M + lastResult = min >= 10 ? (int)(min / 10) : 0; + break; + case 3: + // HH:MM + lastResult = (int)(min % 10); + break; + case 2: + // HH:MM: + lastResult = 11; + break; + case 1: + // HH:MM:S + lastResult = sec >= 10 ? (int)(sec / 10) : 0; + break; + case 0: + // HH:MM:SS + lastResult = (int)(sec % 10); + break; + } + break; } return lastResult; } public AnimationScript Clone() { - return new CvfAnimation(Subject, FrameMapping); + return new CvfAnimation(CurrentHost, Subject, FrameMapping); } public double LastResult diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 7ecab59249..8fa7651605 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -52,11 +52,14 @@ class CabviewFileParser internal static string CurrentFolder; + internal static string FileName; + private static readonly List cabComponents = new List(); // parse panel config internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) { + FileName = fileName; CurrentFolder = Path.GetDirectoryName(fileName); Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read); @@ -91,7 +94,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) } else if (!headerString.StartsWith("SIMISA@@")) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Cabview Parser: Unrecognized cabview file header " + headerString + " in " + fileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS CVF Parser: Unrecognized cabview file header " + headerString + " in " + FileName); return false; } @@ -120,7 +123,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) } else if (subHeader[7] != 'b') { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Cabview Parser: Unrecognized subHeader " + subHeader + " in " + fileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS CVF Parser: Unrecognized subHeader " + subHeader + " in " + FileName); return false; } else @@ -215,8 +218,9 @@ private static void ParseBlock(Block block) switch (block.Token) { case KujuTokenID.CabViewControls: - int controlCount = block.ReadInt16(); - while (controlCount > 0) + int count = block.ReadInt16(); + int controlCount = count; + while (block.Length() - block.Position() > 5) { newBlock = block.ReadSubBlock(true); if (newBlock.Token == KujuTokenID.Skip) @@ -229,6 +233,12 @@ private static void ParseBlock(Block block) controlCount--; } + if (controlCount != 0) + { + // control count was wrong... + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS CVF Parser: Expected " + count + " controls, but found " + (count - controlCount) + " in file " + FileName); + } + break; case KujuTokenID.Direction: currentCabView.Direction.X = block.ReadSingle(); diff --git a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs new file mode 100644 index 0000000000..20c9aab4fd --- /dev/null +++ b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs @@ -0,0 +1,14 @@ +namespace Train.MsTs +{ + internal enum CabComponentStyle + { + None = 0, + Needle, + Sprung, + Not_Sprung, + OnOff, + TwentyFourHour, + TwelveHour, + While_Pressed, + } +} diff --git a/source/Plugins/Train.MsTs/Train.MsTs.csproj b/source/Plugins/Train.MsTs/Train.MsTs.csproj index d88a95bae5..e321a68373 100644 --- a/source/Plugins/Train.MsTs/Train.MsTs.csproj +++ b/source/Plugins/Train.MsTs/Train.MsTs.csproj @@ -63,6 +63,7 @@ + From d12d7bc3b69599f45705bd7314fda866d2713093 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 31 Oct 2025 11:09:05 +0000 Subject: [PATCH 60/82] Add basic tender / tank engine classes, implement IsTenderRequired --- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 12 +-- .../Panel/Enums/CabComponentStyle.cs | 2 + .../Plugins/Train.MsTs/Train/ConsistParser.cs | 12 +++ .../Plugins/Train.MsTs/Train/VehicleParser.cs | 82 ++++++++++++++++++- source/TrainManager/Motor/Fuel/FuelTank.cs | 7 ++ .../Motor/SteamEngine/TankEngine.cs | 61 ++++++++++++++ .../TrainManager/Motor/SteamEngine/Tender.cs | 55 +++++++++++++ .../Motor/SteamEngine/TenderEngine.cs | 49 +++++++++++ source/TrainManager/TrainManager.csproj | 3 + 9 files changed, 273 insertions(+), 10 deletions(-) create mode 100644 source/TrainManager/Motor/SteamEngine/TankEngine.cs create mode 100644 source/TrainManager/Motor/SteamEngine/Tender.cs create mode 100644 source/TrainManager/Motor/SteamEngine/TenderEngine.cs diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 1999a677d7..1476c28e95 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -67,7 +67,7 @@ internal void Parse() { if (!Enum.TryParse(myBlock.Token.ToString(), true, out Type)) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Unrecognised CabViewComponent type."); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS CVF Parser: Unrecognised CabViewComponent type."); return; } @@ -435,7 +435,7 @@ private void ReadSubBlock(Block block) break; case KujuTokenID.DirIncrease: // rotates Clockwise (0) or AntiClockwise (1) - DirIncrease = block.ReadInt16() == 1; + DirIncrease = block.ReadBool(); break; case KujuTokenID.Orientation: //Flip? @@ -468,7 +468,7 @@ private void ReadSubBlock(Block block) VerticalFrames = block.ReadInt16(); break; case KujuTokenID.MouseControl: - MouseControl = block.ReadInt16() == 1; + MouseControl = block.ReadBool(); break; case KujuTokenID.Style: Style = block.ReadEnumValue(default(CabComponentStyle)); @@ -483,17 +483,17 @@ private void ReadSubBlock(Block block) } catch { - Plugin.CurrentHost.AddMessage(MessageType.Error, true, "The texture path contains invalid characters in CabComponent " + Type + " in CVF"); + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS CVF Parser: The texture path contains invalid characters in CabComponent " + Type); } if (!File.Exists(TexturePath)) { - Plugin.CurrentHost.AddMessage(MessageType.Error, true, "The texture file " + s + " was not found in CabComponent " + Type + " in CVF"); + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS CVF Parser: The texture file " + s + " was not found in CabComponent " + Type); } } else { - Plugin.CurrentHost.AddMessage(MessageType.Error, true, "A texture file was not specified in CabComponent " + Type + " in CVF"); + Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS CVF Parser: A texture file was not specified in CabComponent " + Type); } break; case KujuTokenID.Position: diff --git a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs index 20c9aab4fd..9335862e68 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs @@ -4,6 +4,8 @@ internal enum CabComponentStyle { None = 0, Needle, + Pointer, + Solid, Sprung, Not_Sprung, OnOff, diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index 2069f81a64..ba836e2cb6 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -37,6 +37,7 @@ using TrainManager.BrakeSystems; using TrainManager.Car; using TrainManager.Handles; +using TrainManager.Motor; using TrainManager.Power; using TrainManager.Trains; @@ -203,6 +204,17 @@ internal void ReadConsist(string fileName, ref AbstractTrain parsedTrain) } train.Cars[train.Cars.Length - 1].RearAxle.Follower.TriggerType = i == train.Cars.Length - 1 ? EventTriggerType.RearCarRearAxle : EventTriggerType.OtherCarRearAxle; + + if (train.Cars[i].TractionModel is TenderEngine) + { + bool hasTender = i > 0 && train.Cars[i - 1].TractionModel is Tender || i < train.Cars.Length - 2 && train.Cars[i + 1].TractionModel is Tender; + + if (hasTender == false) + { + // this is actually harmless at the minute + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MSTS Consist Parser: Steam Engine in car " + i + " requires a tender, but none is present."); + } + } } train.Cars[train.Cars.Length - 1].RearAxle.Follower.TriggerType = EventTriggerType.RearCarRearAxle; diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index b8e2da9783..665f970f22 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -56,6 +56,7 @@ internal partial class WagonParser private string[] wagonFiles; private double wheelRadius; private bool exteriorLoaded = false; + private bool RequiresTender = false; internal WagonParser() { @@ -133,7 +134,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r { case EngineType.Diesel: currentCar.TractionModel = new DieselEngine(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); - currentCar.TractionModel.FuelTank = new FuelTank(dieselCapacity, 0, dieselCapacity); + currentCar.TractionModel.FuelTank = new FuelTank(GetMaxDieselCapacity(currentCar.Index)); currentCar.TractionModel.IsRunning = true; if (maxBrakeAmps > 0 && maxEngineAmps > 0) @@ -153,7 +154,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r } currentCar.TractionModel = new DieselEngine(currentCar, accelerationCurves, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); - currentCar.TractionModel.FuelTank = new FuelTank(dieselCapacity, 0, dieselCapacity); + currentCar.TractionModel.FuelTank = new FuelTank(GetMaxDieselCapacity(currentCar.Index)); currentCar.TractionModel.IsRunning = true; currentCar.TractionModel.Components.Add(EngineComponent.Gearbox, new Gearbox(currentCar.TractionModel, Gears)); break; @@ -162,8 +163,24 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r currentCar.TractionModel.Components.Add(EngineComponent.Pantograph, new Pantograph(currentCar.TractionModel)); break; case EngineType.Steam: - // NOT YET IMPLEMENTED - currentCar.TractionModel = new BVEMotorCar(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }); + // NOT YET IMPLEMENTED FULLY + if (RequiresTender) + { + currentCar.TractionModel = new TenderEngine(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }); + if (currentCar.Index > 0) + { + CarBase previousCar = currentCar.baseTrain.Cars[currentCar.Index - 1]; + if (previousCar.TractionModel is Tender tender && tender.MaxWaterLevel == -1) + { + // recreate, as values are stored in the ENG + previousCar.TractionModel = new Tender(previousCar, MaxFuelLevel, MaxWaterLevel); + } + } + } + else + { + currentCar.TractionModel = new TankEngine(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }, MaxFuelLevel, MaxWaterLevel); + } break; case EngineType.NoEngine: currentCar.TractionModel = new BVETrailerCar(currentCar); @@ -184,6 +201,13 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r currentCar.ParticleSources.Add(new ParticleSource(Plugin.Renderer, currentCar, Exhaust.Offset, Exhaust.Size, Exhaust.SmokeMaxMagnitude, Exhaust.Direction)); } } + else + { + if (currentWagonType == WagonType.Tender) + { + currentCar.TractionModel = new Tender(currentCar, MaxFuelLevel, MaxWaterLevel); + } + } if (brakeSystemTypes != null) { @@ -423,6 +447,22 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private Friction friction; private Adhesion adhesion; private UnitOfPressure brakeSystemDefaultUnits = UnitOfPressure.PoundsPerSquareInch; + private double MaxWaterLevel = -1; + private double MaxFuelLevel = -1; + + private double GetMaxDieselCapacity(int carIndex) + { + if (dieselCapacity <= 0 && MaxFuelLevel > 0) + { + return MaxFuelLevel; // if zero capacity, try the figure from EngineVariables + } + + if (dieselCapacity == 0) + { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: Diesel locomotive for car " + carIndex + " appears to have zero fuel capacity."); + } + return dieselCapacity; + } private bool ParseBlock(Block block, string fileName, ref string wagonName, bool isEngine, ref CarBase car, ref TrainBase train) { @@ -1038,6 +1078,40 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool car.Coupler.MaximumDistanceBetweenCars = car.Coupler.MinimumDistanceBetweenCars; } break; + case KujuTokenID.IsTenderRequired: + RequiresTender = block.ReadBool(); + break; + case KujuTokenID.EngineVariables: + switch (currentEngineType) + { + case EngineType.DieselHydraulic: + case EngineType.Diesel: + // Fuel capacity (L) + // NOTE: Max fuel is set by MaxDieselLevel + MaxFuelLevel = block.ReadSingle(UnitOfVolume.Litres); + break; + case EngineType.Steam: + // https://tsforum.forumotion.net/t120-msts-helpful-facts-and-links-part-14-enginevariables-for-steam-locomotives-by-slipperman12 + // Fire temp (deg c) + // Fire mass (lbs) + // Water mass in boiler (lbs) + // Boiler pressure (psi) + // Tender water mass (If switched to in ACT mode, so ignore) + // Tender coal mass (If switched to in ACT mode, so ignore) + // Smoke quantity multiplier + // Fire condition + // Coal quality + // NOTE: Max fuel / water levels are set by MaxTenderCoalMass and MaxTenderWaterMass + break; + } + break; + case KujuTokenID.MaxTenderCoalMass: + MaxFuelLevel = block.ReadSingle(UnitOfWeight.Kilograms, UnitOfWeight.Pounds); + break; + case KujuTokenID.MaxTenderWaterMass: + // at atmospheric pressure, 1kg of water = 1L + MaxWaterLevel = block.ReadSingle(UnitOfWeight.Kilograms, UnitOfWeight.Pounds); + break; } return true; } diff --git a/source/TrainManager/Motor/Fuel/FuelTank.cs b/source/TrainManager/Motor/Fuel/FuelTank.cs index 8f08364928..0c4d6e7dce 100644 --- a/source/TrainManager/Motor/Fuel/FuelTank.cs +++ b/source/TrainManager/Motor/Fuel/FuelTank.cs @@ -25,5 +25,12 @@ public FuelTank(double maxLevel, double minLevel, double currentLevel) MinLevel = minLevel; CurrentLevel = currentLevel; } + + public FuelTank(double currentLevel) + { + MaxLevel = currentLevel; + MinLevel = 0; + CurrentLevel = currentLevel; + } } } diff --git a/source/TrainManager/Motor/SteamEngine/TankEngine.cs b/source/TrainManager/Motor/SteamEngine/TankEngine.cs new file mode 100644 index 0000000000..a2d1275c04 --- /dev/null +++ b/source/TrainManager/Motor/SteamEngine/TankEngine.cs @@ -0,0 +1,61 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using TrainManager.Car; +using TrainManager.Power; + +namespace TrainManager.Motor +{ + public class TankEngine : TractionModel + { + /// The maximum water level + public readonly double TankMaxWaterLevel; + /// The current water level + public double TankWaterLevel; + + public TankEngine(CarBase baseCar, AccelerationCurve[] accelerationCurves, double maxFuelLevel, double maxWaterLevel) : base(baseCar, accelerationCurves, true) + { + FuelTank = new FuelTank(maxFuelLevel, 0, maxFuelLevel); + TankMaxWaterLevel = maxWaterLevel; + TankWaterLevel = maxWaterLevel; + } + + public TankEngine(CarBase baseCar, AccelerationCurve[] accelerationCurves, double maxFuelLevel, double fuelLevel, double maxWaterLevel, double waterLevel) : base(baseCar, accelerationCurves, true) + { + FuelTank = new FuelTank(maxFuelLevel, 0, fuelLevel); + TankMaxWaterLevel = maxWaterLevel; + TankWaterLevel = waterLevel; + } + + public override void Update(double timeElapsed) + { + } + + // TODO: PLACEHOLDER VALUES + + public override double CurrentPower => 1.0; + + public override double TargetAcceleration => AccelerationCurves[0].GetAccelerationOutput(BaseCar.CurrentSpeed); + } +} diff --git a/source/TrainManager/Motor/SteamEngine/Tender.cs b/source/TrainManager/Motor/SteamEngine/Tender.cs new file mode 100644 index 0000000000..be2f1a37f1 --- /dev/null +++ b/source/TrainManager/Motor/SteamEngine/Tender.cs @@ -0,0 +1,55 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using TrainManager.Car; + +namespace TrainManager.Motor +{ + public class Tender : TractionModel + { + /// The maximum water level + public readonly double MaxWaterLevel; + /// The current water level + public double WaterLevel; + + public Tender(CarBase car, double maxFuelLevel, double maxWaterLevel) : base(car, null, false) + { + FuelTank = new FuelTank(maxFuelLevel, 0, maxFuelLevel); + MaxWaterLevel = maxWaterLevel; + WaterLevel = maxWaterLevel; + } + + public Tender(CarBase car, double maxFuelLevel, double fuelLevel, double maxWaterLevel, double waterLevel) : base(car, null, false) + { + FuelTank = new FuelTank(maxFuelLevel, 0, fuelLevel); + MaxWaterLevel = maxWaterLevel; + WaterLevel = waterLevel; + } + + public override void Update(double timeElapsed) + { + + } + } +} diff --git a/source/TrainManager/Motor/SteamEngine/TenderEngine.cs b/source/TrainManager/Motor/SteamEngine/TenderEngine.cs new file mode 100644 index 0000000000..ee158b85ae --- /dev/null +++ b/source/TrainManager/Motor/SteamEngine/TenderEngine.cs @@ -0,0 +1,49 @@ +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using TrainManager.Car; +using TrainManager.Power; + +namespace TrainManager.Motor +{ + public class TenderEngine : TractionModel + { + /// Holds a reference to the connected tender, or null if no tender connected + public Tender Tender; + + public TenderEngine(CarBase baseCar, AccelerationCurve[] accelerationCurves) : base(baseCar, accelerationCurves, true) + { + } + + public override void Update(double timeElapsed) + { + } + + // TODO: PLACEHOLDER VALUES + + public override double CurrentPower => 1.0; + + public override double TargetAcceleration => AccelerationCurves[0].GetAccelerationOutput(BaseCar.CurrentSpeed); + } +} diff --git a/source/TrainManager/TrainManager.csproj b/source/TrainManager/TrainManager.csproj index 51b828652c..d23302adb6 100644 --- a/source/TrainManager/TrainManager.csproj +++ b/source/TrainManager/TrainManager.csproj @@ -136,6 +136,9 @@ + + + From a7504608f60824a61a62d0adeec1d5cf2579d339 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 31 Oct 2025 12:32:18 +0000 Subject: [PATCH 61/82] More cleaning and fixes Build fix --- .../Panel/Enums/CabComponentStyle.cs | 5 ++- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 36 ++++++++----------- .../Train.MsTs/Train/Enums/ControlType.cs | 3 +- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs index 9335862e68..ccc5ce07a0 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/CabComponentStyle.cs @@ -1,4 +1,5 @@ -namespace Train.MsTs +// ReSharper disable UnusedMember.Global +namespace Train.MsTs { internal enum CabComponentStyle { @@ -12,5 +13,7 @@ internal enum CabComponentStyle TwentyFourHour, TwelveHour, While_Pressed, + Pressed, + Liquid, } } diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 3ee035025e..1c451b2381 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -359,21 +359,18 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So * map to existing subsystems */ currentSoundSet.CurrentSoundType = block.Token; - - int numSounds = block.ReadInt32(); + int numSounds = block.ReadInt16(); currentSoundSet.SoundBuffers = new SoundBuffer[numSounds]; - for (int i = 0; i < numSounds; i++) + currentSoundSet.SelectionMethod = KujuTokenID.SequentialSelection; + while (numSounds > 0 && block.Position() < block.Length() - 4) { - newBlock = block.ReadSubBlock(KujuTokenID.File); + newBlock = block.ReadSubBlock(); + if (newBlock.Token == KujuTokenID.File) + { + numSounds--; + } ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } - KujuTokenID selectionMethod = KujuTokenID.SequentialSelection; - // Attempt to read selection method if at least one sound file, and some data remaining in the block - if (numSounds > 1 && block.Position() < block.Length() - 4) - { - Block subBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); - currentSoundSet.SelectionMethod = subBlock.ReadEnumValue(default(KujuTokenID)); - } currentSoundSet.Create(car, currentSoundStream); break; case KujuTokenID.ReleaseLoopRelease: @@ -583,18 +580,15 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So currentSoundSet.CurrentSoundType = block.Token; numSounds = block.ReadInt16(); currentSoundSet.SoundBuffers = new SoundBuffer[numSounds]; - for (int i = 0; i < numSounds; i++) - { - newBlock = block.ReadSubBlock(KujuTokenID.File); - ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); - } - currentSoundSet.SelectionMethod = KujuTokenID.SequentialSelection; - // Attempt to read selection method if at least one sound file, and some data remaining in the block - if (numSounds > 1 && block.Position() < block.Length() - 4) + while (numSounds > 0 && block.Position() < block.Length() - 4) { - Block subBlock = block.ReadSubBlock(KujuTokenID.SelectionMethod); - currentSoundSet.SelectionMethod = subBlock.ReadEnumValue(default(KujuTokenID)); + newBlock = block.ReadSubBlock(); + if (newBlock.Token == KujuTokenID.File) + { + numSounds--; + } + ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); } currentSoundSet.Create(car, currentSoundStream); break; diff --git a/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs b/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs index f555d0399d..763996fabd 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/ControlType.cs @@ -1,4 +1,5 @@ -namespace Train.MsTs +// ReSharper disable UnusedMember.Global +namespace Train.MsTs { internal enum CombinedControlType { From f30e2b92e65868107d32613fc654b655aa5aadda Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 3 Nov 2025 11:21:29 +0000 Subject: [PATCH 62/82] Implement VigilanceMonitor --- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 2 + .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 13 +++++ source/Plugins/Train.MsTs/Sound/SmsParser.cs | 7 +++ .../Plugins/Train.MsTs/Train/VehicleParser.cs | 1 + .../Vigilance/AbstractVigilanceDevice.cs | 36 ++++++++++++-- .../Train.MsTs/Vigilance/VigilanceMonitor.cs | 48 ++++++++++++++++++- 6 files changed, 103 insertions(+), 4 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 1476c28e95..622ec6252a 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -246,6 +246,8 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Overspeed: case PanelSubject.Sanders: case PanelSubject.Wheelslip: + case PanelSubject.Alerter_Display: + case PanelSubject.Penalty_App: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); break; default: diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index bfbd05d0b7..15869f0ec6 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -31,6 +31,7 @@ using TrainManager.Car.Systems; using TrainManager.Motor; using OpenBveApi.Hosts; +using TrainManager.SafetySystems; namespace Train.MsTs { @@ -327,6 +328,18 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, break; } break; + case PanelSubject.Alerter_Display: + if (dynamicTrain.Cars[dynamicTrain.DriverCar].DSD != null && dynamicTrain.Cars[dynamicTrain.DriverCar].DSD.CurrentState == DriverSupervisionDeviceState.Alarm) + { + lastResult = 1; + } + break; + case PanelSubject.Penalty_App: + if (dynamicTrain.Cars[dynamicTrain.DriverCar].DSD != null && dynamicTrain.Cars[dynamicTrain.DriverCar].DSD.CurrentState == DriverSupervisionDeviceState.Triggered) + { + lastResult = 1; + } + break; } return lastResult; } diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 1c451b2381..9c9242b312 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -505,6 +505,13 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So car.CarBrake.AirZero = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; + case SoundTrigger.VigilanceAlarmOn: + if (car.DSD != null) + { + car.DSD.AlarmSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + car.DSD.AlertSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + break; } } else diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 665f970f22..3c2068a5bd 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -888,6 +888,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool newBlock = block.ReadSubBlock(true); device.ParseBlock(newBlock); } + device.Create(car); vigilanceDevices.Add(device); break; case KujuTokenID.FreightAnim: diff --git a/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs b/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs index e0644f9301..3389ecf5ce 100644 --- a/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs +++ b/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs @@ -1,6 +1,31 @@ -using System.IO; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System.IO; using OpenBve.Formats.MsTs; using OpenBveApi.World; +using TrainManager.Car; namespace Train.MsTs { @@ -45,6 +70,11 @@ internal static VigilanceDevice CreateVigilanceDevice(KujuTokenID token) } } + internal virtual void Create(CarBase car) + { + + } + internal void ParseBlock(Block block) { switch (block.Token) @@ -73,11 +103,11 @@ internal void ParseBlock(Block block) if (block.ParentBlock.Token == KujuTokenID.EmergencyStopMonitor) { // Check exact behaviour - CriticalLevel = block.ReadSingle(UnitOfVelocity.MetersPerSecond); + ResetLevel = block.ReadSingle(UnitOfVelocity.MetersPerSecond); } else { - CriticalLevel = block.ReadSingle(); + ResetLevel = block.ReadSingle(); } break; case KujuTokenID.MonitoringDeviceAppliesFullBrake: diff --git a/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs index f666e9b427..e69262d3e7 100644 --- a/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs +++ b/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs @@ -1,6 +1,52 @@ -namespace Train.MsTs +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using TrainManager.Car; +using TrainManager.SafetySystems; + +namespace Train.MsTs { internal class VigilanceMonitor : VigilanceDevice { + internal override void Create(CarBase car) + { + DriverSupervisionDeviceTypes deviceType = DriverSupervisionDeviceTypes.None; + + if (CutsPower) + { + deviceType = DriverSupervisionDeviceTypes.CutsPower; + } + if (AppliesFullBrake) + { + deviceType = DriverSupervisionDeviceTypes.ApplyBrake; + } + if (AppliesEmergencyBrake) + { + deviceType = DriverSupervisionDeviceTypes.ApplyEmergencyBrake; + } + + car.DSD = new DriverSupervisionDevice(car, deviceType, DriverSupervisionDeviceMode.AnyHandle, DriverSupervisionDeviceTriggerMode.TrainMoving, AlarmTimeLimit, TimeLimit, PenaltyTimeLimit); + } } } From ab3eedf9b23c0db8e754907750fb0605b1798939 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 3 Nov 2025 14:11:33 +0000 Subject: [PATCH 63/82] Implement OverspeedMonitor, some moving of bits --- .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 16 ++++++- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 8 ++-- .../Vigilance/AbstractVigilanceDevice.cs | 8 ++-- .../Train.MsTs/Vigilance/OverspeedMonitor.cs | 48 ++++++++++++++++++- .../Train.MsTs/Vigilance/VigilanceMonitor.cs | 10 ++-- 5 files changed, 75 insertions(+), 15 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 15869f0ec6..371404572b 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -27,6 +27,7 @@ using OpenBveApi.Motor; using OpenBveApi.Trains; using System; +using System.Collections.Generic; using OpenBveApi; using TrainManager.Car.Systems; using TrainManager.Motor; @@ -329,13 +330,24 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, } break; case PanelSubject.Alerter_Display: - if (dynamicTrain.Cars[dynamicTrain.DriverCar].DSD != null && dynamicTrain.Cars[dynamicTrain.DriverCar].DSD.CurrentState == DriverSupervisionDeviceState.Alarm) + // can't use an extension on a dynamic directly, have to get to known type first + Dictionary safetySystems = dynamicTrain.Cars[dynamicTrain.DriverCar].SafetySystems; + if (safetySystems.TryGetTypedValue(SafetySystem.DriverSupervisionDevice, out DriverSupervisionDevice dsd) && dsd.CurrentState == SafetySystemState.Alarm) + { + lastResult = 1; + } + if (safetySystems.TryGetTypedValue(SafetySystem.OverspeedDevice, out OverspeedDevice osd) && osd.CurrentState == SafetySystemState.Alarm) { lastResult = 1; } break; case PanelSubject.Penalty_App: - if (dynamicTrain.Cars[dynamicTrain.DriverCar].DSD != null && dynamicTrain.Cars[dynamicTrain.DriverCar].DSD.CurrentState == DriverSupervisionDeviceState.Triggered) + safetySystems = dynamicTrain.Cars[dynamicTrain.DriverCar].SafetySystems; + if (safetySystems.TryGetTypedValue(SafetySystem.DriverSupervisionDevice, out dsd) && dsd.CurrentState == SafetySystemState.Triggered) + { + lastResult = 1; + } + if (safetySystems.TryGetTypedValue(SafetySystem.OverspeedDevice, out osd) && osd.CurrentState == SafetySystemState.Triggered) { lastResult = 1; } diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 9c9242b312..ea74d27a45 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -39,6 +39,7 @@ using TrainManager.Car.Systems; using TrainManager.Motor; using TrainManager.MsTsSounds; +using TrainManager.SafetySystems; namespace Train.MsTs { @@ -506,10 +507,11 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So } break; case SoundTrigger.VigilanceAlarmOn: - if (car.DSD != null) + // NOTE: appears to only apply to DSD, not overspeed + if (car.SafetySystems.TryGetTypedValue(SafetySystem.DriverSupervisionDevice, out DriverSupervisionDevice dsd)) { - car.DSD.AlarmSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); - car.DSD.AlertSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + dsd.AlarmSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + dsd.AlertSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; } diff --git a/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs b/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs index 3389ecf5ce..c0231fea81 100644 --- a/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs +++ b/source/Plugins/Train.MsTs/Vigilance/AbstractVigilanceDevice.cs @@ -89,10 +89,10 @@ internal void ParseBlock(Block block) PenaltyTimeLimit = block.ReadSingle(); break; case KujuTokenID.MonitoringDeviceCriticalLevel: - if (block.ParentBlock.Token == KujuTokenID.EmergencyStopMonitor) + if (block.ParentBlock.Token == KujuTokenID.OverspeedMonitor) { // Check exact behaviour - CriticalLevel = block.ReadSingle(UnitOfVelocity.MetersPerSecond); + CriticalLevel = block.ReadSingle(UnitOfVelocity.MetersPerSecond, UnitOfVelocity.MilesPerHour); } else { @@ -100,10 +100,10 @@ internal void ParseBlock(Block block) } break; case KujuTokenID.MonitoringDeviceResetLevel: - if (block.ParentBlock.Token == KujuTokenID.EmergencyStopMonitor) + if (block.ParentBlock.Token == KujuTokenID.OverspeedMonitor) { // Check exact behaviour - ResetLevel = block.ReadSingle(UnitOfVelocity.MetersPerSecond); + ResetLevel = block.ReadSingle(UnitOfVelocity.MetersPerSecond, UnitOfVelocity.MilesPerHour); } else { diff --git a/source/Plugins/Train.MsTs/Vigilance/OverspeedMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/OverspeedMonitor.cs index 5e6d884495..a26391d746 100644 --- a/source/Plugins/Train.MsTs/Vigilance/OverspeedMonitor.cs +++ b/source/Plugins/Train.MsTs/Vigilance/OverspeedMonitor.cs @@ -1,6 +1,52 @@ -namespace Train.MsTs +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using TrainManager.Car; +using TrainManager.SafetySystems; + +namespace Train.MsTs { internal class OverspeedMonitor : VigilanceDevice { + internal override void Create(CarBase car) + { + SafetySystemType deviceType = SafetySystemType.None; + + if (CutsPower) + { + deviceType = SafetySystemType.CutsPower; + } + if (AppliesFullBrake) + { + deviceType = SafetySystemType.ApplyBrake; + } + if (AppliesEmergencyBrake) + { + deviceType = SafetySystemType.ApplyEmergencyBrake; + } + + car.SafetySystems.Add(SafetySystem.OverspeedDevice, new OverspeedDevice(car, deviceType, CriticalLevel, ResetLevel, TimeLimit, PenaltyTimeLimit, 0)); + } } } diff --git a/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs index e69262d3e7..486facdd69 100644 --- a/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs +++ b/source/Plugins/Train.MsTs/Vigilance/VigilanceMonitor.cs @@ -31,22 +31,22 @@ internal class VigilanceMonitor : VigilanceDevice { internal override void Create(CarBase car) { - DriverSupervisionDeviceTypes deviceType = DriverSupervisionDeviceTypes.None; + SafetySystemType deviceType = SafetySystemType.None; if (CutsPower) { - deviceType = DriverSupervisionDeviceTypes.CutsPower; + deviceType = SafetySystemType.CutsPower; } if (AppliesFullBrake) { - deviceType = DriverSupervisionDeviceTypes.ApplyBrake; + deviceType = SafetySystemType.ApplyBrake; } if (AppliesEmergencyBrake) { - deviceType = DriverSupervisionDeviceTypes.ApplyEmergencyBrake; + deviceType = SafetySystemType.ApplyEmergencyBrake; } - car.DSD = new DriverSupervisionDevice(car, deviceType, DriverSupervisionDeviceMode.AnyHandle, DriverSupervisionDeviceTriggerMode.TrainMoving, AlarmTimeLimit, TimeLimit, PenaltyTimeLimit); + car.SafetySystems.Add(SafetySystem.DriverSupervisionDevice, new DriverSupervisionDevice(car, deviceType, DriverSupervisionDeviceMode.AnyHandle, SafetySystemTriggerMode.TrainMoving, AlarmTimeLimit, TimeLimit, PenaltyTimeLimit)); } } } From 0ff537190383fa8562e076c8cd9d06d3a89167b0 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 6 Nov 2025 11:53:18 +0000 Subject: [PATCH 64/82] Use brake cylinder rates --- source/Plugins/Train.MsTs/Train/VehicleParser.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 3c2068a5bd..ba90a21823 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -226,8 +226,10 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r if (brakeSystemTypes.Contains(BrakeSystemType.Air_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Air_twin_pipe) || brakeSystemTypes.Contains(BrakeSystemType.EP) || brakeSystemTypes.Contains(BrakeSystemType.ECP)) { AirBrake airBrake; - // FIXME: MR values needs to be (close) in proportion to the BC else the physics bug out + // FIXME: Relationship of brake system values needs to be (close) to original proportions else the physics bug out double bcVal = brakeCylinderMaximumPressure / 440000; + double emergencyVal = emergencyRate / 300000.0; + double releaseVal = releaseRate / 200000.0; mainReservoirMinimumPressure = 690000.0 * bcVal; mainReservoirMaximumPressure = 780000.0 * bcVal; double operatingPressure = brakeCylinderMaximumPressure + 0.75 * (mainReservoirMinimumPressure - brakeCylinderMaximumPressure); @@ -248,14 +250,14 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r airBrake.MainReservoir = new MainReservoir(mainReservoirMinimumPressure, mainReservoirMaximumPressure, 0.01, 0.075 / train.Cars.Length); airBrake.Compressor = new Compressor(5000.0, airBrake.MainReservoir, currentCar); - airBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); + airBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 90000.0, emergencyRate, releaseRate); double r = 200000.0 / airBrake.BrakeCylinder.EmergencyMaximumPressure - 1.0; if (r < 0.1) r = 0.1; if (r > 1.0) r = 1.0; airBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * operatingPressure, 200000.0, 0.5, r); airBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); airBrake.EqualizingReservoir.NormalPressure = 1.005 * operatingPressure; - airBrake.StraightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0); + airBrake.StraightAirPipe = new StraightAirPipe(300000.0, emergencyRate * emergencyVal, releaseRate * releaseVal); currentCar.CarBrake = airBrake; } @@ -264,7 +266,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r { VaccumBrake vaccumBrake = new VaccumBrake(currentCar, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); vaccumBrake.MainReservoir = new MainReservoir(71110, 84660, 0.01, 0.075 / train.Cars.Length); // ~21in/hg - ~25in/hg - vaccumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 0.3 * 300000.0, 300000.0, 200000.0); + vaccumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 90000.0, 300000.0, 200000.0); vaccumBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, 1.0); vaccumBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); vaccumBrake.EqualizingReservoir.NormalPressure = 1.005 * (vaccumBrake.BrakeCylinder.EmergencyMaximumPressure + 0.75 * (vaccumBrake.MainReservoir.MinimumPressure - vaccumBrake.BrakeCylinder.EmergencyMaximumPressure)); From 73afc9147bc516d92d77e1fc4443ee2a27010521 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 6 Nov 2025 12:18:45 +0000 Subject: [PATCH 65/82] Update CVF animations Use helper function for digit mapping --- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 7 +- .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 125 +++++++++++++++--- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 3 + source/Plugins/Train.MsTs/Train/Adhesion.cs | 2 +- 4 files changed, 112 insertions(+), 25 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 622ec6252a..c7da0be44b 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -245,9 +245,11 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Direction_Display: case PanelSubject.Overspeed: case PanelSubject.Sanders: + case PanelSubject.Sanding: case PanelSubject.Wheelslip: case PanelSubject.Alerter_Display: case PanelSubject.Penalty_App: + case PanelSubject.Throttle_Display: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); break; default: @@ -260,11 +262,6 @@ internal void Create(ref CarBase currentCar, int componentLayer) break; case CabComponentType.Digital: - if (panelSubject != PanelSubject.Speedometer && panelSubject != PanelSubject.Speedlim_Display) - { - break; - } - Position.X *= rW; Position.Y *= rH; diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 371404572b..5b9cc6fda9 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -49,7 +49,7 @@ internal class CvfAnimation : AnimationScript internal readonly double UnitConversionFactor; - private int lastResult; + private double lastResult; internal CvfAnimation(HostInterface host, PanelSubject subject) { @@ -91,6 +91,9 @@ internal CvfAnimation(HostInterface host, PanelSubject subject, Units unit, int case Units.Kilometers_Per_Hour: UnitConversionFactor = 3.6; break; + case Units.PSI: + UnitConversionFactor = 0.000145038; + break; } Digit = digit; } @@ -134,6 +137,7 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, dynamic dynamicTrain = train; switch (Subject) { + case PanelSubject.Throttle_Display: case PanelSubject.Throttle: for (int i = 0; i < FrameMapping.Length; i++) { @@ -187,23 +191,7 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, break; case PanelSubject.Speedometer: double currentSpeed = Math.Abs(train.CurrentSpeed) * UnitConversionFactor; - if (Digit == -1) - { - // color - for (int i = FrameMapping.Length - 1; i > 0; i--) - { - if (FrameMapping[i].MappingValue <= currentSpeed) - { - lastResult = FrameMapping[i].FrameKey; - break; - } - } - } - else - { - // digit - lastResult = (int)(currentSpeed / (int)Math.Pow(10, Digit) % 10); - } + MapDigitalResult(currentSpeed); break; case PanelSubject.Aspect_Display: lastResult = train.CurrentSignalAspect; @@ -243,12 +231,26 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, lastResult = gearState; break; case PanelSubject.Sanders: + // sanders button is pressed int sandState = 0; for (int k = 0; k < dynamicTrain.Cars.Length; k++) { if (dynamicTrain.Cars[k].ReAdhesionDevice is Sanders sanders) { - sandState = sanders.Active ? 1 :0; + sandState = sanders.State >= SandersState.Active ? 1 :0; + break; + } + } + lastResult = sandState; + break; + case PanelSubject.Sanding: + // sand is actually being dispensed / doing something + sandState = 0; + for (int k = 0; k < dynamicTrain.Cars.Length; k++) + { + if (dynamicTrain.Cars[k].ReAdhesionDevice is Sanders sanders && train.CurrentSpeed <= sanders.MaximumSpeed) + { + sandState = sanders.State == SandersState.Active ? 1 : 0; break; } } @@ -352,10 +354,95 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, lastResult = 1; } break; + case PanelSubject.Load_Meter: + case PanelSubject.Ammeter: + case PanelSubject.Ammeter_Abs: + double amps = 0; + if (dynamicTrain != null) + { + int totalMotors = 0; + double ampsTotal = 0; + for (int k = 0; k < dynamicTrain.Cars.Length; k++) + { + if (dynamicTrain.Cars[k].TractionModel is DieselEngine dieselEngine) + { + if (dieselEngine.Components.TryGetTypedValue(EngineComponent.TractionMotor, out TractionMotor t)) + { + totalMotors++; + ampsTotal += t.CurrentAmps; + } + else if (dieselEngine.Components.TryGetTypedValue(EngineComponent.RegenerativeTractionMotor, out RegenerativeTractionMotor rt)) + { + totalMotors++; + ampsTotal += rt.CurrentAmps; + } + } + } + + if (totalMotors == 0) + { + amps = 0; + } + else + { + amps = ampsTotal / totalMotors; + if (Subject == PanelSubject.Ammeter_Abs) + { + amps = Math.Abs(amps); + } + } + } + else + { + amps = 0; + } + MapDigitalResult(amps); + break; + case PanelSubject.Brake_Pipe: + double bp = dynamicTrain.Cars[dynamicTrain.DriverCar].CarBrake.BrakePipe.CurrentPressure; + bp *= UnitConversionFactor; + MapDigitalResult(bp); + break; + case PanelSubject.Eq_Res: + double er = dynamicTrain.Cars[dynamicTrain.DriverCar].CarBrake.EqualizingReservoir.CurrentPressure; + er *= UnitConversionFactor; + MapDigitalResult(er); + break; + case PanelSubject.Brake_Cyl: + double bc = dynamicTrain.Cars[dynamicTrain.DriverCar].CarBrake.BrakeCylinder.CurrentPressure; + bc *= UnitConversionFactor; + MapDigitalResult(bc); + break; + case PanelSubject.Main_Res: + double mr = dynamicTrain.Cars[dynamicTrain.DriverCar].CarBrake.MainReservoir.CurrentPressure; + mr *= UnitConversionFactor; + MapDigitalResult(mr); + break; } return lastResult; } + private void MapDigitalResult(double val) + { + if (Digit == -1) + { + // color + for (int i = 0; i < FrameMapping.Length; i++) + { + if (FrameMapping[i].MappingValue <= val) + { + lastResult = FrameMapping[i].FrameKey; + break; + } + } + } + else + { + // digit + lastResult = (int)(val / (int)Math.Pow(10, Digit) % 10); + } + } + public AnimationScript Clone() { return new CvfAnimation(CurrentHost, Subject, FrameMapping); diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 8fa7651605..954150de67 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -332,6 +332,9 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject case PanelSubject.Ammeter: Code = "amps"; break; + case PanelSubject.Ammeter_Abs: + Code = "amps abs"; + break; case PanelSubject.Brake_Cyl: switch (subjectUnits) { diff --git a/source/Plugins/Train.MsTs/Train/Adhesion.cs b/source/Plugins/Train.MsTs/Train/Adhesion.cs index 7c0d2682e0..b7dc5b600f 100644 --- a/source/Plugins/Train.MsTs/Train/Adhesion.cs +++ b/source/Plugins/Train.MsTs/Train/Adhesion.cs @@ -66,7 +66,7 @@ internal double GetWheelslipValue() // the mass, as we're not worrying about mass per axle yet... // Unlikely to be perfect, but doing it this way resolves massive wheelslip issues - if (baseCar.ReAdhesionDevice is Sanders sanders && sanders.Active) + if (baseCar.ReAdhesionDevice is Sanders sanders && sanders.State == SandersState.Active) { // Per-axle: // multiplier = 0.95 * WheelSlip * Sanding * baseCar.CurrentMass / baseCar.DrivingWheels[0].TotalNumber / baseCar.CurrentMass; From 7e4314cdded07de0fa8c5fadb3f04c56a6415b3c Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 6 Nov 2025 16:32:40 +0000 Subject: [PATCH 66/82] Add combined power handle, more CVF --- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 1 + .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 31 +++++++++++++++++-- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 1 + 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index c7da0be44b..5560228e78 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -250,6 +250,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Alerter_Display: case PanelSubject.Penalty_App: case PanelSubject.Throttle_Display: + case PanelSubject.CPH_Display: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); break; default: diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 5b9cc6fda9..0388ba60ff 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -137,6 +137,28 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, dynamic dynamicTrain = train; switch (Subject) { + case PanelSubject.CP_Handle: + case PanelSubject.CPH_Display: + // NOTE: 0.5 mapping == N + double mapping = 0.5; + if (dynamicTrain.Handles.Brake.Actual > 0) + { + mapping += (double)dynamicTrain.Handles.Brake.Actual / dynamicTrain.Handles.Brake.MaximumNotch * 0.5; + } + else + { + mapping -= (double)dynamicTrain.Handles.Power.Actual / dynamicTrain.Handles.Power.MaximumNotch * 0.5; + } + for (int i = 0; i < FrameMapping.Length; i++) + { + + if (FrameMapping[i].MappingValue >= mapping) + { + lastResult = FrameMapping[i].FrameKey; + break; + } + } + break; case PanelSubject.Throttle_Display: case PanelSubject.Throttle: for (int i = 0; i < FrameMapping.Length; i++) @@ -427,11 +449,15 @@ private void MapDigitalResult(double val) if (Digit == -1) { // color + lastResult = FrameMapping[FrameMapping.Length - 1].FrameKey; for (int i = 0; i < FrameMapping.Length; i++) { if (FrameMapping[i].MappingValue <= val) { - lastResult = FrameMapping[i].FrameKey; + if (i == FrameMapping.Length - 1 || FrameMapping[i + 1].MappingValue > val) + { + lastResult = FrameMapping[i].FrameKey; + } break; } } @@ -439,7 +465,8 @@ private void MapDigitalResult(double val) else { // digit - lastResult = (int)(val / (int)Math.Pow(10, Digit) % 10); + double absVal = Math.Abs(val); + lastResult = (int)(absVal / (int)Math.Pow(10, Digit) % 10); } } diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 954150de67..20fe1a09ca 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -329,6 +329,7 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject string Code = string.Empty; switch (subject) { + case PanelSubject.Load_Meter: case PanelSubject.Ammeter: Code = "amps"; break; From a9d77dd0a7419710c13546dfdac8484389049eec Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 7 Nov 2025 12:29:35 +0000 Subject: [PATCH 67/82] Change: Map ORTS gearup / geardown sounds --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 10 ++++++++++ .../Plugins/Train.MsTs/Sound/TriggerTypes.cs | 18 +++++++++++++++--- .../TrainManager/Motor/DieselEngine/Gearbox.cs | 4 ++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index ea74d27a45..351897cafb 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -514,6 +514,16 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So dsd.AlertSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; + case SoundTrigger.GearUp: + case SoundTrigger.GearDown: + if (car.TractionModel.Components.TryGetTypedValue(EngineComponent.Gearbox, out Gearbox gearbox)) + { + if (currentSoundSet.CurrentTrigger == SoundTrigger.GearUp) + { + gearbox.GearUpSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + } + break; } } else diff --git a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs index feef5ece9f..f4486ebf11 100644 --- a/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs +++ b/source/Plugins/Train.MsTs/Sound/TriggerTypes.cs @@ -73,14 +73,26 @@ internal enum SoundTrigger UncoupleC = 63, // 64 + 65 not listed Pantograph2Up = 66, - Pantograph2Down = 67 + Pantograph2Down = 67, /* * NOTE: * https://open-rails.readthedocs.io/en/latest/sound.html - * Considerably more ORTS specific triggers + * ORTS specific triggers * - * Not looking to handle these at the minute. + * Not looking to handle most of these at the minute. */ + WaterPump1On = 90, + WaterPump1Off = 91, + WaterPump2On = 92, + WaterPump2Off = 93, + GearUp = 101, + GearDown = 102, + ReverserToForwardBackward = 103, + ReverserToNeutral = 104, + DoorOpen = 105, + DoorClose = 106, + MirrorOpen = 107, + MirrorClose = 108 } } diff --git a/source/TrainManager/Motor/DieselEngine/Gearbox.cs b/source/TrainManager/Motor/DieselEngine/Gearbox.cs index 85059c54b6..6fcb3eeb3a 100644 --- a/source/TrainManager/Motor/DieselEngine/Gearbox.cs +++ b/source/TrainManager/Motor/DieselEngine/Gearbox.cs @@ -11,9 +11,9 @@ public class Gearbox : AbstractComponent /// The current gear public int CurrentGear; /// The sound played when the gear is increased - internal CarSound GearUpSound; + public CarSound GearUpSound; /// The sound played when the gear is decreased - internal CarSound GearDownSound; + public CarSound GearDownSound; /// The sound played when the gearbox returns to neutral internal CarSound NeutralSound; /// The maximum speed attainable in the current gear From ee5ab0bb00f1a0858a32680675dc3e8eaebae664 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 7 Nov 2025 13:05:12 +0000 Subject: [PATCH 68/82] Change: Map ORTS reverser directional sounds --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 26 +++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 351897cafb..75876e0ebb 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -391,13 +391,33 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So Plugin.CurrentHost.RegisterSound(soundFile, currentSoundSet.ActivationDistance, out var soundHandle); currentSoundSet.SoundBuffers[currentSoundSet.CurrentBuffer] = soundHandle as SoundBuffer; break; - case SoundTrigger.ReverserChange: + case SoundTrigger.ReverserToForwardBackward: if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot) { car.baseTrain.Handles.Reverser.EngageSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + break; + case SoundTrigger.ReverserToNeutral: + if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot) + { car.baseTrain.Handles.Reverser.ReleaseSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } break; + case SoundTrigger.ReverserChange: + if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot) + { + // as we don't know the order these may be presented in, check the buffer + if (car.baseTrain.Handles.Reverser.EngageSound.Buffer == null) + { + car.baseTrain.Handles.Reverser.EngageSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + + if (car.baseTrain.Handles.Reverser.ReleaseSound.Buffer == null) + { + car.baseTrain.Handles.Reverser.ReleaseSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } + } + break; case SoundTrigger.ThrottleChange: if (currentSoundSet.CurrentSoundType == KujuTokenID.PlayOneShot) { @@ -522,6 +542,10 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So { gearbox.GearUpSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); } + else + { + gearbox.GearDownSound = new CarSound(Plugin.CurrentHost, soundFile, 2.0, car.Driver); + } } break; } From 1530518666cb10b826e1f3cda49765738a66f5fd Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Fri, 7 Nov 2025 14:45:36 +0000 Subject: [PATCH 69/82] New: Add discrete icon to MSTS trains --- assets/Menu/icon_msts.png | Bin 0 -> 2142 bytes source/OpenBVE/UserInterface/formMain.Start.cs | 2 +- source/OpenBVE/UserInterface/formMain.cs | 5 ++++- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 assets/Menu/icon_msts.png diff --git a/assets/Menu/icon_msts.png b/assets/Menu/icon_msts.png new file mode 100644 index 0000000000000000000000000000000000000000..5770a13c6cf791e4b206d4b03630d96dc58ccd4a GIT binary patch literal 2142 zcmV-k2%-0hP)&LqkJHQ(gaR3OO@26ciLMDI_E$F*7eRO~CLjdxfpHbwuH9simc|C|Jsh7*=K1(<#S|F>R; zbY5vjGyl?2|J;X~Kn?%l3;*i?|DqI|dtaN5g#YtoJ$`f81et>|EC?ilQ)Zx2)%DIn1}!-D*_x7C#1A>l!=n@%z0KZ2qY^| z_wnt;tuFu8epzf~GC2T6VR8TI3M&f$0RR6kGXwvLB41KBd{ZaIzs|zO5J*=qNk9N# zJ{ln#F;yHXe?KuB9|MGvq+s(Ac>n+a0d!JMQvg8b*k%9#1&2vQK~#8Nt&n4XWLq9a z&pt-kwjJ8GZQE$uwr%6uw#V~%W>->mJ4q+gZ|-_ozqQWu--Y}IXrArh^ZBU44B_+N zsPn_#-mq+hLED`24ZhwS4IF!&Z@1mP=63(T#9V%)GA>u-bmqR~a}Y|QR<->jXD%ucIA2j|D`BwN}Ost9-(67T^OuWupfT$_T06$ zTZ75;-amO54zz0(vD!*QHk*`Wc64>S29uL%TqF|p-EuuWv|TQZf2}Z%?KAA#VrI{b z2CS9^U-v9H28Sae=h$!BeS4kTYb&s-@ATs z-u8LMZ>0UU<(ci?dzUU=x!FH5^0(g3S3$$EhMA-pT|s@I#^!4rn(%o%9{&(z(8hN4 z9N95fKBB4UjE_r;_}gKiYu;U4FnhViHi`OA{N(Zb{X>4A|L#S$>p~YhFp!W-$7qbv zfv=vVyp$*zKFzo>Zt(b&%1uAJ_d1-mo%8u@G6@86*VChZ*}<%;tGb!p zi-G^U3qS7ie6;lF9rMF?f7m%6`+Z5UG(9yE94bRTR#jciG915J|Mj-J9-mM7gw1d` zK1}zyhj$HohT8=(!Pa8}P5hEyT81EEtXP+A(cdr^n;Te&gS8fW9{PmtJHDFHcVwfc z!&c3?)TLmg3<;(#q<*A3_`!_cTxGsD5C5D=&!pCVKJQDcAp1Nm&1 zqf%zU&w85%+DAw2?HDLSB{@QSwdrijnfukDP$(Ld)l>xr?&QPEr}gm}ard(Zx*fMV zWdbpgVxQ_|n0GBLrtz>J4UA=VRFqiHJz3!+s1XLFkU*(k(UtQwEs;<(N>YHRgA;hd>EQLm6+QyO0erXkl1(trREbi4v2j4S#8Cxr_Dz}HAm;HRkyc+lzvuo!SS znwA7;DG5-35WRA_>Hrqh9$oDtoDnR6qqRJghX8;ygATe*PBTS<2Wo1yk5E)(q6i-! zLr5f%13*N}RWt>#TA2b4tcH^q?EoU1z)@V)oJbKskt?AnArR(zq5pb786#K8WfHkW zUbm-CK^4+82MBI_4Ikb_M+zn Date: Fri, 7 Nov 2025 15:14:35 +0000 Subject: [PATCH 70/82] Change: Update readme and some headers [skip ci] --- Readme.md | 6 ++++- source/Plugins/Train.MsTs/Effects/Exhaust.cs | 26 ++++++++++++++++++- source/Plugins/Train.MsTs/Handles/Handle.cs | 26 ++++++++++++++++++- source/Plugins/Train.MsTs/Train/Adhesion.cs | 26 ++++++++++++++++++- source/Plugins/Train.MsTs/Train/Friction.cs | 26 ++++++++++++++++++- source/Plugins/Train.MsTs/Train/MSTSAxle.cs | 26 ++++++++++++++++++- .../Train.MsTs/Vigilance/AWSMonitor.cs | 11 ++++++++ .../Vigilance/EmergencyStopMonitor.cs | 1 + 8 files changed, 142 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index d0a59c703c..6584db02f7 100644 --- a/Readme.md +++ b/Readme.md @@ -6,11 +6,15 @@ This repository contains the source code for the Train Simulator OpenBVE, a 3D cab based simulator. -The simulator supports the following route formats: +Supported route formats: * Native CSV / RW. * BVE5 TXT format. * Mechanik DAT format. +Supported train formats: +* BVE2 / BVE4, with native OpenBVE extensions. +* Microsoft Train Simulator (MSTS) + OpenBVE is built in OpenGL, using the OpenTK framework for windowing. ### Fixed Errata diff --git a/source/Plugins/Train.MsTs/Effects/Exhaust.cs b/source/Plugins/Train.MsTs/Effects/Exhaust.cs index c577d530e8..eb2b499acc 100644 --- a/source/Plugins/Train.MsTs/Effects/Exhaust.cs +++ b/source/Plugins/Train.MsTs/Effects/Exhaust.cs @@ -1,4 +1,28 @@ -using OpenBveApi.Math; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using OpenBveApi.Math; namespace Train.MsTs { diff --git a/source/Plugins/Train.MsTs/Handles/Handle.cs b/source/Plugins/Train.MsTs/Handles/Handle.cs index 8e2de01d3b..7dc68553a6 100644 --- a/source/Plugins/Train.MsTs/Handles/Handle.cs +++ b/source/Plugins/Train.MsTs/Handles/Handle.cs @@ -1,4 +1,28 @@ -using System; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System; using OpenBve.Formats.MsTs; using TrainManager.Handles; using TrainManager.Trains; diff --git a/source/Plugins/Train.MsTs/Train/Adhesion.cs b/source/Plugins/Train.MsTs/Train/Adhesion.cs index b7dc5b600f..8b68b250ad 100644 --- a/source/Plugins/Train.MsTs/Train/Adhesion.cs +++ b/source/Plugins/Train.MsTs/Train/Adhesion.cs @@ -1,4 +1,28 @@ -using OpenBve.Formats.MsTs; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using OpenBve.Formats.MsTs; using OpenBveApi.Interface; using TrainManager.Car; using TrainManager.Car.Systems; diff --git a/source/Plugins/Train.MsTs/Train/Friction.cs b/source/Plugins/Train.MsTs/Train/Friction.cs index 26317ec1ba..9ce39fdb19 100644 --- a/source/Plugins/Train.MsTs/Train/Friction.cs +++ b/source/Plugins/Train.MsTs/Train/Friction.cs @@ -1,4 +1,28 @@ -using System; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System; using OpenBve.Formats.MsTs; using OpenBveApi.World; diff --git a/source/Plugins/Train.MsTs/Train/MSTSAxle.cs b/source/Plugins/Train.MsTs/Train/MSTSAxle.cs index e91cfef471..ecb7a47fa0 100644 --- a/source/Plugins/Train.MsTs/Train/MSTSAxle.cs +++ b/source/Plugins/Train.MsTs/Train/MSTSAxle.cs @@ -1,4 +1,28 @@ -using OpenBveApi.Hosts; +//Simplified BSD License (BSD-2-Clause) +// +//Copyright (c) 2025, Christopher Lees, The OpenBVE Project +// +//Redistribution and use in source and binary forms, with or without +//modification, are permitted provided that the following conditions are met: +// +//1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +//2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +//ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using OpenBveApi.Hosts; using OpenBveApi.Trains; namespace Train.MsTs diff --git a/source/Plugins/Train.MsTs/Vigilance/AWSMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/AWSMonitor.cs index 2e2ea0bc0a..a8cd6dc6a5 100644 --- a/source/Plugins/Train.MsTs/Vigilance/AWSMonitor.cs +++ b/source/Plugins/Train.MsTs/Vigilance/AWSMonitor.cs @@ -2,5 +2,16 @@ { internal class AWSMonitor : VigilanceDevice { + /* + * NOT IMPLEMENTED + * + * NOTE: OpenRails does not implement the AWSMonitor + * + * Need to look further into whether it ever actually worked, + * or was implemented in any real-world content. + * + * No use of it in a relatively large UK content collection + * (some AWS displays appear to be simulated via CAB_SIGNAL_DISPLAY) + */ } } diff --git a/source/Plugins/Train.MsTs/Vigilance/EmergencyStopMonitor.cs b/source/Plugins/Train.MsTs/Vigilance/EmergencyStopMonitor.cs index 44d7e86c96..81229e7dcc 100644 --- a/source/Plugins/Train.MsTs/Vigilance/EmergencyStopMonitor.cs +++ b/source/Plugins/Train.MsTs/Vigilance/EmergencyStopMonitor.cs @@ -2,5 +2,6 @@ { internal class EmergencyStopMonitor : VigilanceDevice { + // NOT YET IMPLEMENTED } } From 469360e9ad3c95f85e0979ddf7c8fb27d5dc9a47 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 12 Nov 2025 16:30:50 +0000 Subject: [PATCH 71/82] Work on ammeter, multiple disparate engines --- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 33 +++++++++++++++++++ .../Motor/ElectricEngine/ElectricEngine.cs | 4 --- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index ba90a21823..7ac43153d7 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -57,6 +57,7 @@ internal partial class WagonParser private double wheelRadius; private bool exteriorLoaded = false; private bool RequiresTender = false; + private bool EngineAlreadyParsed = false; internal WagonParser() { @@ -126,9 +127,14 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r // as properties may not be in order, set this stuff last if (isEngine) { + EngineAlreadyParsed = true; // FIXME: Default BVE values currentCar.Specs.JerkPowerUp = 10.0; currentCar.Specs.JerkPowerDown = 10.0; + // NOTE: Amps figure appears to need to be divided by the total wheels (to give a per-axle figure) + // see NWC_40138.eng for example- This is an issue where higher amps figures are in play + maxEngineAmps /= currentCar.DrivingWheels[0].TotalNumber; + maxBrakeAmps /= currentCar.DrivingWheels[0].TotalNumber; switch (currentEngineType) { @@ -161,6 +167,15 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r case EngineType.Electric: currentCar.TractionModel = new ElectricEngine(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }); currentCar.TractionModel.Components.Add(EngineComponent.Pantograph, new Pantograph(currentCar.TractionModel)); + + if (maxBrakeAmps > 0 && maxEngineAmps > 0) + { + currentCar.TractionModel.Components.Add(EngineComponent.RegenerativeTractionMotor, new RegenerativeTractionMotor(currentCar.TractionModel, maxEngineAmps, maxBrakeAmps)); + } + else if (maxEngineAmps > 0) + { + currentCar.TractionModel.Components.Add(EngineComponent.TractionMotor, new TractionMotor(currentCar.TractionModel, maxEngineAmps)); + } break; case EngineType.Steam: // NOT YET IMPLEMENTED FULLY @@ -779,6 +794,12 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool } break; case KujuTokenID.Throttle: + if (EngineAlreadyParsed) + { + // NOTE: Per-car handles not yet supported, so take the handles from the first engine in the train + // otherwise, if we've got a VariableHandle (0-100) followed by notched, we'll be stuck at basically zero power + break; + } if (currentEngineType == EngineType.Steam) { Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: A throttle is not valid for a Steam Locomotive."); @@ -787,13 +808,25 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool train.Handles.Power = ParseHandle(block, train, true); break; case KujuTokenID.Brake_Train: + if (EngineAlreadyParsed) + { + break; + } train.Handles.Brake = ParseHandle(block, train, false); break; case KujuTokenID.Brake_Engine: + if (EngineAlreadyParsed) + { + break; + } train.Handles.HasLocoBrake = true; train.Handles.LocoBrake = ParseHandle(block, train, false); break; case KujuTokenID.Combined_Control: + if (EngineAlreadyParsed) + { + break; + } block.ReadSingle(); block.ReadSingle(); block.ReadSingle(); diff --git a/source/TrainManager/Motor/ElectricEngine/ElectricEngine.cs b/source/TrainManager/Motor/ElectricEngine/ElectricEngine.cs index 31d5b377cd..48805a9e55 100644 --- a/source/TrainManager/Motor/ElectricEngine/ElectricEngine.cs +++ b/source/TrainManager/Motor/ElectricEngine/ElectricEngine.cs @@ -36,10 +36,6 @@ public ElectricEngine(CarBase car, AccelerationCurve[] accelerationCurves) : bas { } - public ElectricEngine(CarBase car, AccelerationCurve[] accelerationCurves) : base(car, accelerationCurves, true) - { - } - public override void Update(double timeElapsed) { if (BaseCar.baseTrain.Specs.PantographState != PantographState.Raised) From 1ccf34c745d75b3488297e2eb720091b7c3c5852 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 19 Nov 2025 15:09:22 +0000 Subject: [PATCH 72/82] Change: Set sensible properties for car with missing ENG / WAG --- source/Plugins/Train.MsTs/Train/ConsistParser.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index ba836e2cb6..3948583ecb 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -364,6 +364,11 @@ private void ParseBlock(Block block, ref TrainBase currentTrain) if (!Directory.Exists(wagonDirectory)) { Plugin.CurrentHost.AddMessage(MessageType.Error, true, "MSTS Consist Parser: WagonFolder " + wagonDirectory + " was not found."); + currentCar.Width = 2.6; + currentCar.Height = 3.6; + currentCar.Length = 25; + currentCar.EmptyMass = 1000; + currentCar.TractionModel = new BVETrailerCar(currentCar); currentCar.CarBrake = new ThroughPiped(currentCar); // dummy break; } From beb73bcbd7450f6bd655920a564100b137c78a60 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 19 Nov 2025 16:12:07 +0000 Subject: [PATCH 73/82] Fix: Handle nonsensical friction data Presumably MSTS is silently discarding values which make no sense --- source/Plugins/Train.MsTs/Train/Friction.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/Plugins/Train.MsTs/Train/Friction.cs b/source/Plugins/Train.MsTs/Train/Friction.cs index 9ce39fdb19..191346b4b2 100644 --- a/source/Plugins/Train.MsTs/Train/Friction.cs +++ b/source/Plugins/Train.MsTs/Train/Friction.cs @@ -24,6 +24,7 @@ using System; using OpenBve.Formats.MsTs; +using OpenBveApi.Interface; using OpenBveApi.World; namespace Train.MsTs @@ -50,9 +51,20 @@ internal Friction(Block block) V2 = block.ReadSingle(UnitOfVelocity.MetersPerSecond); C2 = block.ReadSingle(UnitOfTorque.NewtonMetersPerSecond); E2 = block.ReadSingle(); + + if (E1 <= 0 || E2 <= 0 || V2 < 0) + { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: The Friction properties contain nonsensical data."); + C1 = 100; + E1 = 1; + V2 = -1; + C2 = 0; + E2 = 1; + } } catch { + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "MSTS Vehicle Parser: The Friction properties contain invalid data."); C1 = 100; E1 = 1; V2 = -1; From d1e088a1a0f88d061316519b04444446262ced6f Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 19 Nov 2025 16:12:23 +0000 Subject: [PATCH 74/82] CVF fixes --- source/OpenBveApi/Train/Motor/PantographState.cs | 4 ++-- source/Plugins/Train.MsTs/Panel/CabComponent.cs | 3 +++ source/Plugins/Train.MsTs/Panel/CvfParser.cs | 4 ++-- source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs | 3 +++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/source/OpenBveApi/Train/Motor/PantographState.cs b/source/OpenBveApi/Train/Motor/PantographState.cs index f9ae1ac3c4..8df10e411f 100644 --- a/source/OpenBveApi/Train/Motor/PantographState.cs +++ b/source/OpenBveApi/Train/Motor/PantographState.cs @@ -27,10 +27,10 @@ namespace OpenBveApi.Motor /// The possible states of a pantograph public enum PantographState { - /// The pantograph is raised - Raised, /// The pantograph is lowered Lowered, + /// The pantograph is raised + Raised, /// The pantograph is raised, but no wire is present Dewired } diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 5560228e78..ec68ea0b51 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -138,6 +138,9 @@ internal void Create(ref CarBase currentCar, int componentLayer) double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum); f += " " + a1.ToString(culture) + " * " + a0.ToString(culture) + " +"; currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZFunction = new FunctionScript(Plugin.CurrentHost, f, false); + // backstop by default e.g. ammeter when using dynamic brakes + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZFunction.Minimum = InitialAngle; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZFunction.Maximum = LastAngle; break; case CabComponentType.Lever: /* diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index 20fe1a09ca..ea995e4ad6 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -323,7 +323,7 @@ private static void ParseBlock(Block block) private static readonly Vector2 panelOrigin = new Vector2(0, 240); // get stack language from subject - internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject subject, Units subjectUnits, string suffix = "") + internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject subject, Units subjectUnits) { // transform subject string Code = string.Empty; @@ -476,7 +476,7 @@ internal static string GetStackLanguageFromSubject(TrainBase train, PanelSubject Code = "0"; break; } - return Code + suffix; + return Code; } internal static int CreateElement(ref ElementsGroup Group, Vector2 TopLeft, Vector2 Size, Vector2 RelativeRotationCenter, double Distance, Vector3 Driver, Texture DaytimeTexture, Texture NighttimeTexture, Color32 Color, bool AddStateToLastElement = false) diff --git a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs index 737397b198..51f0181bbd 100644 --- a/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs +++ b/source/Plugins/Train.MsTs/Panel/Enums/PanelSubject.cs @@ -79,5 +79,8 @@ internal enum PanelSubject RPM, Speed_Projected, // projected speed in one minute SpeedLimit, // signal limit, not track limit + + // probably MSTSBin + Dynamic_Brake_Force, } } From 19f1b0fb3fe05421440a894909a5ab09866b10b5 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 20 Nov 2025 09:31:05 +0000 Subject: [PATCH 75/82] Add Variable1 controlled sounds --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 12 +++- .../MSTS/SoundTrigger.VariableControlled.cs | 70 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 75876e0ebb..b44ef8ef64 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -173,6 +173,12 @@ internal void Create(CarBase car, SoundStream currentSoundStream) case KujuTokenID.Speed_Dec_Past: currentSoundStream.Triggers.Add(new SpeedDecPast(SoundBuffers, SelectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; + case KujuTokenID.Variable1_Inc_Past: + currentSoundStream.Triggers.Add(new Variable1IncPast(SoundBuffers, SelectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); + break; + case KujuTokenID.Variable1_Dec_Past: + currentSoundStream.Triggers.Add(new Variable1DecPast(SoundBuffers, SelectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); + break; case KujuTokenID.Variable2_Inc_Past: currentSoundStream.Triggers.Add(new Variable2IncPast(SoundBuffers, SelectionMethod, VariableValue, CurrentSoundType != KujuTokenID.PlayOneShot)); break; @@ -599,13 +605,13 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case KujuTokenID.Distance_Inc_Past: case KujuTokenID.Distance_Dec_Past: break; - case KujuTokenID.Variable1_Inc_Past: - case KujuTokenID.Variable1_Dec_Past: case KujuTokenID.Variable1Controlled: break; + case KujuTokenID.Variable1_Inc_Past: + case KujuTokenID.Variable1_Dec_Past: case KujuTokenID.Variable2_Inc_Past: case KujuTokenID.Variable2_Dec_Past: - currentSoundSet.VariableValue = block.ReadSingle(); // power value + currentSoundSet.VariableValue = block.ReadSingle(); // variable value newBlock = block.ReadSubBlock(new[] { KujuTokenID.StartLoop, KujuTokenID.StartLoopRelease, KujuTokenID.ReleaseLoopRelease, KujuTokenID.ReleaseLoopReleaseWithJump, KujuTokenID.EnableTrigger, KujuTokenID.DisableTrigger, KujuTokenID.PlayOneShot, KujuTokenID.SetStreamVolume }); ParseBlock(newBlock, ref currentSoundSet, ref currentSoundStream, ref car); break; diff --git a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs index bad9b48754..c90072a984 100644 --- a/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs +++ b/source/TrainManager/Sounds/MSTS/SoundTrigger.VariableControlled.cs @@ -28,6 +28,76 @@ namespace TrainManager.MsTsSounds { + /// A sound trigger controlled by Variable1 increasing past the setpoint + /// Variable1 represents the current wheel rotation speed + /// Diesel- EngineRPM + /// Electric- TractiveForce + /// Steam- CylinderPressure + public class Variable1IncPast : SoundTrigger + { + private readonly double variableValue; + + public Variable1IncPast(SoundBuffer[] buffers, KujuTokenID selectionMethod, double variableValue, bool soundLoops) : base(buffers, selectionMethod, soundLoops) + { + this.variableValue = variableValue; + } + + public Variable1IncPast(SoundBuffer buffer, double variableValue, bool soundLoops) : base(buffer, soundLoops) + { + this.variableValue = variableValue; + } + + public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) + { + if (car.baseTrain.Handles.Power.Actual > 0 && car.CurrentSpeed / 1000 / car.DrivingWheels[0].Radius / System.Math.PI * 5 > variableValue) + { + soundBuffer = Buffer; + soundLoops = SoundLoops; + Triggered = true; + } + + if (car.baseTrain.Handles.Power.Actual == 0 || car.TractionModel.CurrentPower < variableValue) + { + Triggered = false; + } + } + } + + /// A sound trigger controlled by Variable1 increasing past the setpoint + /// Variable1 represents the current wheel rotation speed + /// Diesel- EngineRPM + /// Electric- TractiveForce + /// Steam- CylinderPressure + public class Variable1DecPast : SoundTrigger + { + private readonly double variableValue; + + public Variable1DecPast(SoundBuffer[] buffers, KujuTokenID selectionMethod, double variableValue, bool soundLoops) : base(buffers, selectionMethod, soundLoops) + { + this.variableValue = variableValue; + } + + public Variable1DecPast(SoundBuffer buffer, double variableValue, bool soundLoops) : base(buffer, soundLoops) + { + this.variableValue = variableValue; + } + + public override void Update(double timeElapsed, CarBase car, ref SoundBuffer soundBuffer, ref bool soundLoops) + { + if (car.baseTrain.Handles.Power.Actual > 0 && car.CurrentSpeed / 1000 / car.DrivingWheels[0].Radius / System.Math.PI * 5 < variableValue) + { + soundBuffer = Buffer; + soundLoops = SoundLoops; + Triggered = true; + } + + if (car.baseTrain.Handles.Power.Actual == 0 || car.TractionModel.CurrentPower > variableValue) + { + Triggered = false; + } + } + } + /// A sound trigger controlled by Variable2 increasing past the setpoint /// Variable2 represents the proportion of power the car's TractionModel is currently generating /// Diesel- EngineRPM From 196e244ecec1b3e0098979bf92105dbf681bf618 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 20 Nov 2025 10:34:42 +0000 Subject: [PATCH 76/82] Implement GearboxOperationMode --- source/Plugins/Train.MsTs/Train/VehicleParser.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 7ac43153d7..b957fc5c4c 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -162,7 +162,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r currentCar.TractionModel = new DieselEngine(currentCar, accelerationCurves, dieselIdleRPM, dieselIdleRPM, dieselMaxRPM, dieselRPMChangeRate, dieselRPMChangeRate, dieselIdleUse, dieselMaxUse); currentCar.TractionModel.FuelTank = new FuelTank(GetMaxDieselCapacity(currentCar.Index)); currentCar.TractionModel.IsRunning = true; - currentCar.TractionModel.Components.Add(EngineComponent.Gearbox, new Gearbox(currentCar.TractionModel, Gears)); + currentCar.TractionModel.Components.Add(EngineComponent.Gearbox, new Gearbox(currentCar.TractionModel, Gears, gearboxOperationMode)); break; case EngineType.Electric: currentCar.TractionModel = new ElectricEngine(currentCar, new AccelerationCurve[] { new MSTSAccelerationCurve(currentCar, maxForce, maxContinuousForce, maxVelocity) }); @@ -459,6 +459,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine private List vigilanceDevices; private Exhaust Exhaust; private Gear[] Gears; + private GearboxOperation gearboxOperationMode = GearboxOperation.Manual; private double maxSandingSpeed; private CouplingType couplingType; private Friction friction; @@ -1076,6 +1077,9 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool Gears[i].OverspeedFailure = Gears[i].MaximumSpeed * perc; } break; + case KujuTokenID.GearBoxOperation: + gearboxOperationMode = block.ReadEnumValue(default(GearboxOperation)); + break; case KujuTokenID.Friction: friction = new Friction(block); break; From c10e08e784fce8107edcee96accdbb95f01f56be Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 20 Nov 2025 15:27:03 +0000 Subject: [PATCH 77/82] Fix: Boost horn radius --- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index b44ef8ef64..1b895d99a9 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -467,14 +467,14 @@ private static void ParseBlock(Block block, ref SoundSet currentSoundSet, ref So case SoundTrigger.HornOn: if (currentSoundSet.CurrentSoundType == KujuTokenID.StartLoopRelease && car.Horns[0] != null) { - Plugin.CurrentHost.RegisterSound(soundFile, 2.0, out soundHandle); + Plugin.CurrentHost.RegisterSound(soundFile, 30.0, out soundHandle); car.Horns[0].LoopSound = soundHandle as SoundBuffer; } break; case SoundTrigger.BellOn: if (currentSoundSet.CurrentSoundType == KujuTokenID.StartLoopRelease && car.Horns[2] != null) { - Plugin.CurrentHost.RegisterSound(soundFile, 2.0, out soundHandle); + Plugin.CurrentHost.RegisterSound(soundFile, 30.0, out soundHandle); car.Horns[0].LoopSound = soundHandle as SoundBuffer; } break; From 2e478db38e151845ed58b5a43830043532d3061c Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 20 Nov 2025 15:58:52 +0000 Subject: [PATCH 78/82] Change: Reset to sensible parameters for each new vehicle Because some stuff omits things.... --- source/Plugins/Train.MsTs/Train/Adhesion.cs | 19 +++++++++++++ source/Plugins/Train.MsTs/Train/Friction.cs | 9 ++++++ source/Plugins/Train.MsTs/Train/MSTSAxle.cs | 3 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 28 +++++++++++++++++-- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train/Adhesion.cs b/source/Plugins/Train.MsTs/Train/Adhesion.cs index 8b68b250ad..481cc1724d 100644 --- a/source/Plugins/Train.MsTs/Train/Adhesion.cs +++ b/source/Plugins/Train.MsTs/Train/Adhesion.cs @@ -40,6 +40,25 @@ internal class Adhesion /// The value used in sanding conditions private readonly double Sanding; + internal Adhesion(CarBase car, bool isSteamEngine) + { + baseCar = car; + // Kuju suggested default values + // see Eng_and_wag_file_reference_guideV2.doc + if (isSteamEngine) + { + WheelSlip = 0.15; + Normal = 0.3; + Sanding = 2.0; + } + else + { + WheelSlip = 0.2; + Normal = 0.4; + Sanding = 2.0; + } + } + internal Adhesion(Block block, CarBase car, bool isSteamEngine) { baseCar = car; diff --git a/source/Plugins/Train.MsTs/Train/Friction.cs b/source/Plugins/Train.MsTs/Train/Friction.cs index 191346b4b2..989d681921 100644 --- a/source/Plugins/Train.MsTs/Train/Friction.cs +++ b/source/Plugins/Train.MsTs/Train/Friction.cs @@ -42,6 +42,15 @@ internal class Friction /// The second friction exponent internal double E2; + internal Friction() + { + C1 = 100; + E1 = 1; + V2 = -1; + C2 = 0; + E2 = 1; + } + internal Friction(Block block) { try diff --git a/source/Plugins/Train.MsTs/Train/MSTSAxle.cs b/source/Plugins/Train.MsTs/Train/MSTSAxle.cs index ecb7a47fa0..b16e619608 100644 --- a/source/Plugins/Train.MsTs/Train/MSTSAxle.cs +++ b/source/Plugins/Train.MsTs/Train/MSTSAxle.cs @@ -22,6 +22,7 @@ //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +using System; using OpenBveApi.Hosts; using OpenBveApi.Trains; @@ -43,7 +44,7 @@ internal MSTSAxle(HostInterface currentHost, AbstractTrain train, AbstractCar ca public override double GetResistance(double Speed, double FrontalArea, double AirDensity, double AccelerationDueToGravity) { - return FrictionProperties.GetResistanceValue(Speed) / baseCar.CurrentMass; + return FrictionProperties.GetResistanceValue(Speed) / Math.Max(1.0, baseCar.CurrentMass); } public override double CriticalWheelSlipAccelerationForElectricMotor(double AccelerationDueToGravity) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index b957fc5c4c..95f0e4d340 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -68,6 +68,19 @@ internal WagonParser() internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, ref CarBase currentCar, ref TrainBase train) { + /* + * Reset to 'sensible' defaults for required parameters + * => International Union of Railways figure for 'standard' wheel size + * => Default BVE brake system parameters (we know these work correctly) + * => Reset gearbox parameters + * + */ + wheelRadius = 0.92; + mainReservoirMinimumPressure = 690000; + mainReservoirMaximumPressure = 780000; + brakeCylinderMaximumPressure = 440000; + compressionRate = 3500; + Gears = null; exteriorLoaded = false; wagonFiles = Directory.GetFiles(trainSetDirectory, isEngine ? "*.eng" : "*.wag", SearchOption.AllDirectories); currentEngineType = EngineType.NoEngine; @@ -131,6 +144,11 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r // FIXME: Default BVE values currentCar.Specs.JerkPowerUp = 10.0; currentCar.Specs.JerkPowerDown = 10.0; + if (currentCar.DrivingWheels.Count == 0) + { + // An engine must implicity have at least one set of driving wheels + currentCar.DrivingWheels.Add(new Wheels(1, wheelRadius)); + } // NOTE: Amps figure appears to need to be divided by the total wheels (to give a per-axle figure) // see NWC_40138.eng for example- This is an issue where higher amps figures are in play maxEngineAmps /= currentCar.DrivingWheels[0].TotalNumber; @@ -222,6 +240,12 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r { currentCar.TractionModel = new Tender(currentCar, MaxFuelLevel, MaxWaterLevel); } + + if (currentCar.TrailingWheels.Count == 0) + { + // non-engine must implicitly have at least one set of trailing wheels + currentCar.TrailingWheels.Add(new Wheels(1, wheelRadius)); + } } if (brakeSystemTypes != null) @@ -301,8 +325,8 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r currentCar.CarBrake = new ThroughPiped(currentCar); } - currentCar.FrontAxle = new MSTSAxle(Plugin.CurrentHost, train, currentCar, friction, adhesion); - currentCar.RearAxle = new MSTSAxle(Plugin.CurrentHost, train, currentCar, friction, adhesion); + currentCar.FrontAxle = new MSTSAxle(Plugin.CurrentHost, train, currentCar, friction ?? new Friction(), adhesion ?? new Adhesion(currentCar, currentEngineType == EngineType.Steam)); + currentCar.RearAxle = new MSTSAxle(Plugin.CurrentHost, train, currentCar, friction ?? new Friction(), adhesion ?? new Adhesion(currentCar, currentEngineType == EngineType.Steam)); if (soundFiles.Count > 0) { From 5fda49bc4737042a26f0db4357e43a1134fbecc9 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Thu, 20 Nov 2025 16:32:14 +0000 Subject: [PATCH 79/82] Fix: Incorrect deceleration figure used in vaccuum brakes --- source/Plugins/Train.MsTs/Train/VehicleParser.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index 95f0e4d340..a96e187afc 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -73,13 +73,15 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r * => International Union of Railways figure for 'standard' wheel size * => Default BVE brake system parameters (we know these work correctly) * => Reset gearbox parameters - * + * => Set max force figures to zero */ wheelRadius = 0.92; mainReservoirMinimumPressure = 690000; mainReservoirMaximumPressure = 780000; brakeCylinderMaximumPressure = 440000; compressionRate = 3500; + maxForce = 0; + maxBrakeForce = 0; Gears = null; exteriorLoaded = false; wagonFiles = Directory.GetFiles(trainSetDirectory, isEngine ? "*.eng" : "*.wag", SearchOption.AllDirectories); @@ -303,7 +305,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r if (brakeSystemTypes.Contains(BrakeSystemType.Vaccum_single_pipe) || brakeSystemTypes.Contains(BrakeSystemType.Vacuum_twin_pipe)) { - VaccumBrake vaccumBrake = new VaccumBrake(currentCar, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxForce) }); + VaccumBrake vaccumBrake = new VaccumBrake(currentCar, new AccelerationCurve[] { new MSTSDecelerationCurve(train, maxBrakeForce == 0 ? maxForce : maxBrakeForce) }); vaccumBrake.MainReservoir = new MainReservoir(71110, 84660, 0.01, 0.075 / train.Cars.Length); // ~21in/hg - ~25in/hg vaccumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 90000.0, 300000.0, 200000.0); vaccumBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, 1.0); From d7cddbd8a274013aa7bf05534da131bf4c69a310 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 24 Nov 2025 10:14:16 +0000 Subject: [PATCH 80/82] Change: Map Friction_Braking --- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 1 + .../Plugins/Train.MsTs/Panel/CvfAnimation.cs | 45 +++++++++---------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index ec68ea0b51..56949cbcc1 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -254,6 +254,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Penalty_App: case PanelSubject.Throttle_Display: case PanelSubject.CPH_Display: + case PanelSubject.Friction_Braking: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); break; default: diff --git a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs index 0388ba60ff..84e80f143e 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfAnimation.cs @@ -149,36 +149,19 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, { mapping -= (double)dynamicTrain.Handles.Power.Actual / dynamicTrain.Handles.Power.MaximumNotch * 0.5; } - for (int i = 0; i < FrameMapping.Length; i++) - { - - if (FrameMapping[i].MappingValue >= mapping) - { - lastResult = FrameMapping[i].FrameKey; - break; - } - } + MapResult(mapping); break; case PanelSubject.Throttle_Display: case PanelSubject.Throttle: - for (int i = 0; i < FrameMapping.Length; i++) - { - if (FrameMapping[i].MappingValue >= (double)dynamicTrain.Handles.Power.Actual / dynamicTrain.Handles.Power.MaximumNotch) - { - lastResult = FrameMapping[i].FrameKey; - break; - } - } + MapResult((double)dynamicTrain.Handles.Power.Actual / dynamicTrain.Handles.Power.MaximumNotch); break; case PanelSubject.Train_Brake: - for (int i = 0; i < FrameMapping.Length; i++) - { - if (FrameMapping[i].MappingValue >= (double)dynamicTrain.Handles.Brake.Actual / dynamicTrain.Handles.Brake.MaximumNotch) - { - lastResult = FrameMapping[i].FrameKey; - break; - } - } + MapResult((double)dynamicTrain.Handles.Brake.Actual / dynamicTrain.Handles.Brake.MaximumNotch); + break; + case PanelSubject.Friction_Braking: + // NOTE: Assumed at the minute this goes out at speed zero + bool isBraking = Math.Abs(train.CurrentSpeed) > 0 && (dynamicTrain.Handles.Brake.Actual > 0 || (dynamicTrain.Handles.HasLocoBrake && dynamicTrain.Handles.LocoBrake.Actual > 0)); + MapResult(isBraking ? 1 : 0); break; case PanelSubject.Direction_Display: case PanelSubject.Direction: @@ -444,6 +427,18 @@ public double ExecuteScript(AbstractTrain train, int carIndex, Vector3 position, return lastResult; } + private void MapResult(double val) + { + for (int i = 0; i < FrameMapping.Length; i++) + { + if (FrameMapping[i].MappingValue >= val) + { + lastResult = FrameMapping[i].FrameKey; + break; + } + } + } + private void MapDigitalResult(double val) { if (Digit == -1) From aff31fb65670728096d8af42a960dccd6f18177a Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 3 Dec 2025 13:16:25 +0000 Subject: [PATCH 81/82] Cleaning --- .../OpenBVE/UserInterface/formMain.Start.cs | 2 +- source/OpenBVE/UserInterface/formMain.cs | 2 +- .../Plugins/Train.MsTs/Panel/CabComponent.cs | 499 +++++++++--------- source/Plugins/Train.MsTs/Panel/CvfParser.cs | 6 +- source/Plugins/Train.MsTs/Plugin.cs | 21 +- source/Plugins/Train.MsTs/Sound/SmsParser.cs | 4 +- source/Plugins/Train.MsTs/Train/Adhesion.cs | 8 +- .../Plugins/Train.MsTs/Train/ConsistParser.cs | 2 +- .../Train/Enums/BrakeEquipmentType.cs | 2 +- .../Train.MsTs/Train/Enums/BrakeSystemType.cs | 2 +- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 32 +- 11 files changed, 289 insertions(+), 291 deletions(-) diff --git a/source/OpenBVE/UserInterface/formMain.Start.cs b/source/OpenBVE/UserInterface/formMain.Start.cs index 73583deae2..5c0dcccfc4 100644 --- a/source/OpenBVE/UserInterface/formMain.Start.cs +++ b/source/OpenBVE/UserInterface/formMain.Start.cs @@ -723,7 +723,7 @@ private void PopulateTrainList(string selectedFolder, ListView listView, bool pa try { string[] Folders = Directory.GetDirectories(selectedFolder); - string[] Files = Directory.GetFiles(Folder); + string[] Files = Directory.GetFiles(selectedFolder); Array.Sort(Folders); Array.Sort(Files); for (int i = 0; i < Folders.Length; i++) diff --git a/source/OpenBVE/UserInterface/formMain.cs b/source/OpenBVE/UserInterface/formMain.cs index 86c95af5e3..336754e1f6 100644 --- a/source/OpenBVE/UserInterface/formMain.cs +++ b/source/OpenBVE/UserInterface/formMain.cs @@ -258,7 +258,7 @@ private void formMain_Load(object sender, EventArgs e) continue; } ListViewItem Item = listviewTrainRecently.Items.Add(trainFileName); - Item.ImageKey = TrainFileName.EndsWith(".con", StringComparison.InvariantCultureIgnoreCase) ? @"msts" : @"train"; + Item.ImageKey = trainFileName.EndsWith(".con", StringComparison.InvariantCultureIgnoreCase) ? @"msts" : @"train"; Item.Tag = Interface.CurrentOptions.RecentlyUsedTrains[i]; if (textboxTrainFolder.Items.Count == 0 || !textboxTrainFolder.Items.Contains(trainPath)) { diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 56949cbcc1..49e922a277 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -89,61 +89,63 @@ internal void Parse() internal void Create(ref CarBase currentCar, int componentLayer) { - if (File.Exists(TexturePath) || Type == CabComponentType.Digital || Type == CabComponentType.DigitalClock) + if (!File.Exists(TexturePath) && Type != CabComponentType.Digital && Type != CabComponentType.DigitalClock) { - if (FrameMappings.Length < 2 && TotalFrames > 1) + return; + } + if (FrameMappings.Length < 2 && TotalFrames > 1) + { + // e.g. Acela power handle has 25 frames for total power value of 100% but no mappings specified + FrameMappings = new FrameMapping[TotalFrames]; + // frame 0 is always mapping value 0 + for (int i = 1; i < TotalFrames; i++) { - // e.g. Acela power handle has 25 frames for total power value of 100% but no mappings specified - FrameMappings = new FrameMapping[TotalFrames]; - // frame 0 is always mapping value 0 - for (int i = 1; i < TotalFrames; i++) - { - FrameMappings[i].MappingValue = (double)i / TotalFrames; - FrameMappings[i].FrameKey = i; - } - + FrameMappings[i].MappingValue = (double)i / TotalFrames; + FrameMappings[i].FrameKey = i; } - //Create element - double rW = 1024.0 / 640.0; - double rH = 768.0 / 480.0; - int wday, hday; - int j; - string f; - CultureInfo culture = CultureInfo.InvariantCulture; - switch (Type) - { - case CabComponentType.Dial: - Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(null, null), out Texture tday, true); - // correct angle position if appropriate - if (!DirIncrease && InitialAngle > LastAngle) - { - InitialAngle = -(365 - InitialAngle); - } + } - //Get final position from the 640px panel (Yuck...) - Position.X *= rW; - Position.Y *= rH; - Size.X *= rW; - Size.Y *= rH; - PivotPoint *= rH; - j = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2((0.5 * Size.X) / (tday.Width * rW), PivotPoint / (tday.Height * rH)), componentLayer * CabviewFileParser.StackDistance, PanelPosition, tday, null, new Color32(255, 255, 255, 255)); - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZDirection = new Vector3(0.0, 0.0, -1.0); - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateXDirection = DirIncrease ? new Vector3(1.0, 0.0, 0.0) : new Vector3(-1.0, 0.0, 0.0); - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateYDirection = Vector3.Cross(currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZDirection, currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateXDirection); - f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); - InitialAngle = InitialAngle.ToRadians(); - LastAngle = LastAngle.ToRadians(); - double a0 = (InitialAngle * Maximum - LastAngle * Minimum) / (Maximum - Minimum); - double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum); - f += " " + a1.ToString(culture) + " * " + a0.ToString(culture) + " +"; - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZFunction = new FunctionScript(Plugin.CurrentHost, f, false); - // backstop by default e.g. ammeter when using dynamic brakes - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZFunction.Minimum = InitialAngle; - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].RotateZFunction.Maximum = LastAngle; - break; - case CabComponentType.Lever: - /* + //Create element + const double rW = 1024.0 / 640.0; + const double rH = 768.0 / 480.0; + int wday, hday; + int elementIndex; + string f; + CultureInfo culture = CultureInfo.InvariantCulture; + switch (Type) + { + case CabComponentType.Dial: + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(null, null), out Texture tday, true); + // correct angle position if appropriate + if (!DirIncrease && InitialAngle > LastAngle) + { + InitialAngle = -(365 - InitialAngle); + } + + //Get final position from the 640px panel (Yuck...) + Position.X *= rW; + Position.Y *= rH; + Size.X *= rW; + Size.Y *= rH; + PivotPoint *= rH; + elementIndex = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2((0.5 * Size.X) / (tday.Width * rW), PivotPoint / (tday.Height * rH)), componentLayer * CabviewFileParser.StackDistance, PanelPosition, tday, null, new Color32(255, 255, 255, 255)); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].RotateZDirection = new Vector3(0.0, 0.0, -1.0); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].RotateXDirection = DirIncrease ? new Vector3(1.0, 0.0, 0.0) : new Vector3(-1.0, 0.0, 0.0); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].RotateYDirection = Vector3.Cross(currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].RotateZDirection, currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].RotateXDirection); + f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); + InitialAngle = InitialAngle.ToRadians(); + LastAngle = LastAngle.ToRadians(); + double a0 = (InitialAngle * Maximum - LastAngle * Minimum) / (Maximum - Minimum); + double a1 = (LastAngle - InitialAngle) / (Maximum - Minimum); + f += " " + a1.ToString(culture) + " * " + a0.ToString(culture) + " +"; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].RotateZFunction = new FunctionScript(Plugin.CurrentHost, f, false); + // backstop by default e.g. ammeter when using dynamic brakes + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].RotateZFunction.Minimum = InitialAngle; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].RotateZFunction.Maximum = LastAngle; + break; + case CabComponentType.Lever: + /* * TODO: * Need to revisit the actual position versus frame with MSTS content. * @@ -154,245 +156,244 @@ internal void Create(ref CarBase currentCar, int componentLayer) * Oddly, all frames appear to be distinct. Need to check OR + MSTS handling * Suspect there's a notch delay or something that should use these. */ - Position.X *= rW; - Position.Y *= rH; - Size.X *= rW; - Size.Y *= rH; - Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - if (wday > 0 & hday > 0) + Position.X *= rW; + Position.Y *= rH; + Size.X *= rW; + Size.Y *= rH; + Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + if (wday > 0 & hday > 0) + { + Texture[] textures = new Texture[TotalFrames]; + int row = 0; + int column = 0; + int frameWidth = wday / HorizontalFrames; + int frameHeight = hday / VerticalFrames; + for (int k = 0; k < TotalFrames; k++) { - Texture[] textures = new Texture[TotalFrames]; - int row = 0; - int column = 0; - int frameWidth = wday / HorizontalFrames; - int frameHeight = hday / VerticalFrames; - for (int k = 0; k < TotalFrames; k++) - { - Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); - if (column < HorizontalFrames - 1) - { - column++; - } - else - { - column = 0; - row++; - } - } - - j = -1; - for (int k = 0; k < textures.Length; k++) + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); + if (column < HorizontalFrames - 1) { - - int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); - if (k == 0) j = l; + column++; } - - f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); - switch (panelSubject) + else { - case PanelSubject.Engine_Brake: - case PanelSubject.Throttle: - case PanelSubject.Train_Brake: - case PanelSubject.Gears: - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); - break; - default: - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); - break; + column = 0; + row++; } + } + elementIndex = -1; + for (int k = 0; k < textures.Length; k++) + { + + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + if (k == 0) elementIndex = l; } - break; - case CabComponentType.TriState: - case CabComponentType.TwoState: - case CabComponentType.MultiStateDisplay: - Position.X *= rW; - Position.Y *= rH; - Size.X *= rW; - Size.Y *= rH; - Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - if (wday > 0 & hday > 0) + f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); + switch (panelSubject) { - Texture[] textures = new Texture[TotalFrames]; - int row = 0; - int column = 0; - int frameWidth = wday / HorizontalFrames; - int frameHeight = hday / VerticalFrames; - for (int k = 0; k < TotalFrames; k++) - { - Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); - if (column < HorizontalFrames - 1) - { - column++; - } - else - { - column = 0; - row++; - } - } + case PanelSubject.Engine_Brake: + case PanelSubject.Throttle: + case PanelSubject.Train_Brake: + case PanelSubject.Gears: + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); + break; + default: + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); + break; + } - j = -1; - for (int k = 0; k < textures.Length; k++) + } + + break; + case CabComponentType.TriState: + case CabComponentType.TwoState: + case CabComponentType.MultiStateDisplay: + Position.X *= rW; + Position.Y *= rH; + Size.X *= rW; + Size.Y *= rH; + Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + if (wday > 0 & hday > 0) + { + Texture[] textures = new Texture[TotalFrames]; + int row = 0; + int column = 0; + int frameWidth = wday / HorizontalFrames; + int frameHeight = hday / VerticalFrames; + for (int k = 0; k < TotalFrames; k++) + { + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); + if (column < HorizontalFrames - 1) { - int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); - if (k == 0) j = l; + column++; } - - f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); - switch (panelSubject) + else { - case PanelSubject.Direction: - case PanelSubject.Direction_Display: - case PanelSubject.Overspeed: - case PanelSubject.Sanders: - case PanelSubject.Sanding: - case PanelSubject.Wheelslip: - case PanelSubject.Alerter_Display: - case PanelSubject.Penalty_App: - case PanelSubject.Throttle_Display: - case PanelSubject.CPH_Display: - case PanelSubject.Friction_Braking: - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); - break; - default: - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); - break; + column = 0; + row++; } + } + elementIndex = -1; + for (int k = 0; k < textures.Length; k++) + { + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + if (k == 0) elementIndex = l; + } + f = CabviewFileParser.GetStackLanguageFromSubject(currentCar.baseTrain, panelSubject, Units); + switch (panelSubject) + { + case PanelSubject.Direction: + case PanelSubject.Direction_Display: + case PanelSubject.Overspeed: + case PanelSubject.Sanders: + case PanelSubject.Sanding: + case PanelSubject.Wheelslip: + case PanelSubject.Alerter_Display: + case PanelSubject.Penalty_App: + case PanelSubject.Throttle_Display: + case PanelSubject.CPH_Display: + case PanelSubject.Friction_Braking: + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); + break; + default: + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].StateFunction = new FunctionScript(Plugin.CurrentHost, f, false); + break; } - break; - case CabComponentType.Digital: - Position.X *= rW; - Position.Y *= rH; - Color24 textColor = PositiveColors[0].Item2; + } - Texture[] frameTextures = new Texture[11]; - TexturePath = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.DataFolder, "Compatibility"), "numbers.png"); // arial 9.5pt - Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + break; + case CabComponentType.Digital: + Position.X *= rW; + Position.Y *= rH; - for (int i = 0; i < 10; i++) - { - Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, i * 24, 16, 24), null), out frameTextures[i], true); - } + Color24 textColor = PositiveColors[0].Item2; - Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, 0, 16, 24), null), out frameTextures[10], true); // repeated zero [check vice MSTS] + Texture[] frameTextures = new Texture[11]; + TexturePath = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.DataFolder, "Compatibility"), "numbers.png"); // arial 9.5pt + Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - int numMaxDigits = (int)Math.Floor(Math.Log10(Maximum) + 1); - int numMinDigits = (int)Math.Floor(Math.Log10(Minimum) + 1); + for (int i = 0; i < 10; i++) + { + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, i * 24, 16, 24), null), out frameTextures[i], true); + } - int totalDigits = Math.Max(numMinDigits, numMaxDigits) + LeadingZeros; - j = -1; - double digitWidth = Size.X / totalDigits; - for (int currentDigit = 0; currentDigit < totalDigits; currentDigit++) - { - for (int k = 0; k < frameTextures.Length; k++) - { - int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], new Vector2(Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y), new Vector2(digitWidth * rW, Size.Y * rH), new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, frameTextures[k], null, textColor, k != 0); - if (k == 0) j = l; - } + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, 0, 16, 24), null), out frameTextures[10], true); // repeated zero [check vice MSTS] - // build color arrays and mappings - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors = new Color24[NegativeColors.Length + PositiveColors.Length]; - FrameMappings = new FrameMapping[PositiveColors.Length + NegativeColors.Length]; - for (int i = 0; i < NegativeColors.Length; i++) - { - FrameMappings[i].MappingValue = NegativeColors[i].Item1; - FrameMappings[i].FrameKey = i; - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors[i] = NegativeColors[i].Item2; - } + int numMaxDigits = (int)Math.Floor(Math.Log10(Maximum) + 1); + int numMinDigits = (int)Math.Floor(Math.Log10(Minimum) + 1); - for (int i = 0; i < PositiveColors.Length; i++) - { - FrameMappings[i + NegativeColors.Length].MappingValue = PositiveColors[i].Item1; - FrameMappings[i + NegativeColors.Length].FrameKey = i + NegativeColors.Length; - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].Colors[i + NegativeColors.Length] = PositiveColors[i].Item2; - } + int totalDigits = Math.Max(numMinDigits, numMaxDigits) + LeadingZeros; + elementIndex = -1; + double digitWidth = Size.X / totalDigits; + for (int currentDigit = 0; currentDigit < totalDigits; currentDigit++) + { + for (int k = 0; k < frameTextures.Length; k++) + { + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], new Vector2(Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y), new Vector2(digitWidth * rW, Size.Y * rH), new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, frameTextures[k], null, textColor, k != 0); + if (k == 0) elementIndex = l; + } - // create color and digit functions - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Units, currentDigit); - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].ColorFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Units, FrameMappings); + // build color arrays and mappings + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].Colors = new Color24[NegativeColors.Length + PositiveColors.Length]; + FrameMappings = new FrameMapping[PositiveColors.Length + NegativeColors.Length]; + for (int i = 0; i < NegativeColors.Length; i++) + { + FrameMappings[i].MappingValue = NegativeColors[i].Item1; + FrameMappings[i].FrameKey = i; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].Colors[i] = NegativeColors[i].Item2; + } + + for (int i = 0; i < PositiveColors.Length; i++) + { + FrameMappings[i + NegativeColors.Length].MappingValue = PositiveColors[i].Item1; + FrameMappings[i + NegativeColors.Length].FrameKey = i + NegativeColors.Length; + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].Colors[i + NegativeColors.Length] = PositiveColors[i].Item2; } - break; - case CabComponentType.CabSignalDisplay: - TotalFrames = 8; - HorizontalFrames = 4; - VerticalFrames = 2; - Position.X *= rW; - Position.Y *= rH; - Size.X *= rW; - Size.Y *= rH; - Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - if (wday > 0 & hday > 0) + // create color and digit functions + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Units, currentDigit); + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].ColorFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Units, FrameMappings); + } + + break; + case CabComponentType.CabSignalDisplay: + TotalFrames = 8; + HorizontalFrames = 4; + VerticalFrames = 2; + Position.X *= rW; + Position.Y *= rH; + Size.X *= rW; + Size.Y *= rH; + Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + if (wday > 0 & hday > 0) + { + Texture[] textures = new Texture[8]; + // 4 h-frames, 2 v-frames + int row = 0; + int column = 0; + int frameWidth = wday / HorizontalFrames; + int frameHeight = hday / VerticalFrames; + for (int k = 0; k < TotalFrames; k++) { - Texture[] textures = new Texture[8]; - // 4 h-frames, 2 v-frames - int row = 0; - int column = 0; - int frameWidth = wday / HorizontalFrames; - int frameHeight = hday / VerticalFrames; - for (int k = 0; k < TotalFrames; k++) + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); + if (column < HorizontalFrames - 1) { - Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(column * frameWidth, row * frameHeight, frameWidth, frameHeight), null), out textures[k]); - if (column < HorizontalFrames - 1) - { - column++; - } - else - { - column = 0; - row++; - } + column++; } - - j = -1; - for (int k = 0; k < textures.Length; k++) + else { - int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); - if (k == 0) j = l; + column = 0; + row++; } - - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject); - } - break; - case CabComponentType.DigitalClock: - Position.X *= rW; - Position.Y *= rH; - totalDigits = Accuracy == 1 ? 8 : 5; // with or without secs - j = -1; - digitWidth = Size.X / totalDigits; - textColor = ControlColor; - - frameTextures = new Texture[12]; - TexturePath = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.DataFolder, "Compatibility"), "numbers.png"); // arial 9.5pt - Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); - - for (int i = 0; i < 10; i++) + + elementIndex = -1; + for (int k = 0; k < textures.Length; k++) { - Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, i * 24, 16, 24), null), out frameTextures[i], true); + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], Position, Size, new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, textures[k], null, new Color32(255, 255, 255, 255), k != 0); + if (k == 0) elementIndex = l; } - Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, 240, 16, 16), null), out frameTextures[11], true); - for (int currentDigit = 0; currentDigit < totalDigits; currentDigit++) - { - for (int k = 0; k < frameTextures.Length; k++) - { - int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], new Vector2(Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y), new Vector2(digitWidth * rW, Size.Y * rH), new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, frameTextures[k], null, textColor, k != 0); - if (k == 0) j = l; - } + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject); - // create digit functions - currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[j].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Style, Accuracy == 1 ? currentDigit : currentDigit + 3); + } + break; + case CabComponentType.DigitalClock: + Position.X *= rW; + Position.Y *= rH; + totalDigits = Accuracy == 1 ? 8 : 5; // with or without secs + elementIndex = -1; + digitWidth = Size.X / totalDigits; + textColor = ControlColor; + + frameTextures = new Texture[12]; + TexturePath = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(Plugin.FileSystem.DataFolder, "Compatibility"), "numbers.png"); // arial 9.5pt + Plugin.CurrentHost.QueryTextureDimensions(TexturePath, out wday, out hday); + + for (int i = 0; i < 10; i++) + { + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, i * 24, 16, 24), null), out frameTextures[i], true); + } + Plugin.CurrentHost.RegisterTexture(TexturePath, new TextureParameters(new TextureClipRegion(0, 240, 16, 16), null), out frameTextures[11], true); + + for (int currentDigit = 0; currentDigit < totalDigits; currentDigit++) + { + for (int k = 0; k < frameTextures.Length; k++) + { + int l = CabviewFileParser.CreateElement(ref currentCar.CarSections[CarSectionType.Interior].Groups[0], new Vector2(Position.X + Size.X - (digitWidth * (currentDigit + 1)), Position.Y), new Vector2(digitWidth * rW, Size.Y * rH), new Vector2(0.5, 0.5), componentLayer * CabviewFileParser.StackDistance, PanelPosition, frameTextures[k], null, textColor, k != 0); + if (k == 0) elementIndex = l; } - break; - } + + // create digit functions + currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, Style, Accuracy == 1 ? currentDigit : currentDigit + 3); + } + break; } } diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index ea995e4ad6..bf1939bdfe 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -42,7 +42,7 @@ namespace Train.MsTs { - class CabviewFileParser + internal class CabviewFileParser { // constants internal const double StackDistance = 0.000001; @@ -66,7 +66,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) byte[] buffer = new byte[34]; fb.Read(buffer, 0, 2); - bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE); + bool unicode = buffer[0] == 0xFF && buffer[1] == 0xFE; string headerString; if (unicode) @@ -161,7 +161,7 @@ internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) double x0 = -panelCenter.X / panelResolution; double x1 = (panelSize.X - panelCenter.X) / panelResolution; double y0 = (panelCenter.Y - panelSize.Y) / panelResolution * Plugin.Renderer.Screen.AspectRatio; - double y1 = (panelCenter.Y) / panelResolution * Plugin.Renderer.Screen.AspectRatio; + double y1 = panelCenter.Y / panelResolution * Plugin.Renderer.Screen.AspectRatio; currentCar.CameraRestriction.BottomLeft = new Vector3(x0 * worldWidth, y0 * worldHeight, eyeDistance); currentCar.CameraRestriction.TopRight = new Vector3(x1 * worldWidth, y1 * worldHeight, eyeDistance); currentCar.DriverYaw = Math.Atan((panelCenter.X - panelOrigin.X) * worldWidth / panelResolution); diff --git a/source/Plugins/Train.MsTs/Plugin.cs b/source/Plugins/Train.MsTs/Plugin.cs index db02780cc6..e9759777e2 100644 --- a/source/Plugins/Train.MsTs/Plugin.cs +++ b/source/Plugins/Train.MsTs/Plugin.cs @@ -40,14 +40,15 @@ public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions Renderer = (BaseRenderer) rendererReference; try { - if (string.IsNullOrEmpty(FileSystem.MSTSDirectory)) + if (!string.IsNullOrEmpty(FileSystem.MSTSDirectory)) { - FileSystem.MSTSDirectory = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft Games\\Train Simulator\\1.0", "Path", string.Empty); - string OrTsPath = (string)Registry.GetValue("HKEY_CURRENT_USER\\Software\\OpenRails\\ORTS\\Folders", "Train Simulator", string.Empty); - if (!string.IsNullOrEmpty(OrTsPath)) - { - FileSystem.MSTSDirectory = OrTsPath; - } + return; + } + FileSystem.MSTSDirectory = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft Games\\Train Simulator\\1.0", "Path", string.Empty); + string OrTsPath = (string)Registry.GetValue("HKEY_CURRENT_USER\\Software\\OpenRails\\ORTS\\Folders", "Train Simulator", string.Empty); + if (!string.IsNullOrEmpty(OrTsPath)) + { + FileSystem.MSTSDirectory = OrTsPath; } } catch @@ -58,11 +59,7 @@ public override void Load(HostInterface host, FileSystem fileSystem, BaseOptions public override bool CanLoadTrain(string path) { - if (File.Exists(path) && path.ToLowerInvariant().EndsWith(".con")) - { - return true; - } - return false; + return File.Exists(path) && path.ToLowerInvariant().EndsWith(".con"); } public override bool LoadTrain(Encoding encoding, string trainPath, ref AbstractTrain train, ref Control[] currentControls) diff --git a/source/Plugins/Train.MsTs/Sound/SmsParser.cs b/source/Plugins/Train.MsTs/Sound/SmsParser.cs index 1b895d99a9..f651092c55 100644 --- a/source/Plugins/Train.MsTs/Sound/SmsParser.cs +++ b/source/Plugins/Train.MsTs/Sound/SmsParser.cs @@ -43,7 +43,7 @@ namespace Train.MsTs { - class SoundModelSystemParser + internal class SoundModelSystemParser { private static string currentFolder; @@ -60,7 +60,7 @@ internal static bool ParseSoundFile(string fileName, ref CarBase currentCar) byte[] buffer = new byte[34]; fb.Read(buffer, 0, 2); - bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE); + bool unicode = buffer[0] == 0xFF && buffer[1] == 0xFE; string headerString; if (unicode) diff --git a/source/Plugins/Train.MsTs/Train/Adhesion.cs b/source/Plugins/Train.MsTs/Train/Adhesion.cs index 481cc1724d..a3ca162941 100644 --- a/source/Plugins/Train.MsTs/Train/Adhesion.cs +++ b/source/Plugins/Train.MsTs/Train/Adhesion.cs @@ -49,14 +49,14 @@ internal Adhesion(CarBase car, bool isSteamEngine) { WheelSlip = 0.15; Normal = 0.3; - Sanding = 2.0; } else { WheelSlip = 0.2; Normal = 0.4; - Sanding = 2.0; } + + Sanding = 2.0; } internal Adhesion(Block block, CarBase car, bool isSteamEngine) @@ -89,14 +89,14 @@ internal Adhesion(Block block, CarBase car, bool isSteamEngine) { WheelSlip = 0.15; Normal = 0.3; - Sanding = 2.0; } else { WheelSlip = 0.2; Normal = 0.4; - Sanding = 2.0; } + + Sanding = 2.0; } } diff --git a/source/Plugins/Train.MsTs/Train/ConsistParser.cs b/source/Plugins/Train.MsTs/Train/ConsistParser.cs index 3948583ecb..53268f2a15 100644 --- a/source/Plugins/Train.MsTs/Train/ConsistParser.cs +++ b/source/Plugins/Train.MsTs/Train/ConsistParser.cs @@ -102,7 +102,7 @@ internal void ReadConsist(string fileName, ref AbstractTrain parsedTrain) byte[] buffer = new byte[34]; fb.Read(buffer, 0, 2); - bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE); + bool unicode = buffer[0] == 0xFF && buffer[1] == 0xFE; string headerString; if (unicode) diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs index 7c1d22f457..eb1603eecb 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeEquipmentType.cs @@ -2,7 +2,7 @@ // ReSharper disable UnusedMember.Global namespace Train.MsTs { - enum BrakeEquipmentType + internal enum BrakeEquipmentType { /// Handbrake is fitted Handbrake = 1, diff --git a/source/Plugins/Train.MsTs/Train/Enums/BrakeSystemType.cs b/source/Plugins/Train.MsTs/Train/Enums/BrakeSystemType.cs index f13c57081b..d6bf893c61 100644 --- a/source/Plugins/Train.MsTs/Train/Enums/BrakeSystemType.cs +++ b/source/Plugins/Train.MsTs/Train/Enums/BrakeSystemType.cs @@ -2,7 +2,7 @@ // ReSharper disable UnusedMember.Global namespace Train.MsTs { - enum BrakeSystemType + internal enum BrakeSystemType { /// One pipe controls and supplies the air brakes. Air_single_pipe, diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index a96e187afc..d977feab33 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -347,7 +347,7 @@ internal bool ReadWagonData(string fileName, ref string wagonName, bool isEngine byte[] buffer = new byte[34]; fb.Read(buffer, 0, 2); - bool unicode = (buffer[0] == 0xFF && buffer[1] == 0xFE); + bool unicode = buffer[0] == 0xFF && buffer[1] == 0xFE; string headerString; if (unicode) @@ -536,7 +536,7 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool try { newBlock = block.ReadSubBlock(true); - ParseBlock(newBlock, fileName, ref wagonName, isEngine, ref car, ref train); + ParseBlock(newBlock, fileName, ref wagonName, false, ref car, ref train); } catch { @@ -1015,23 +1015,23 @@ private bool ParseBlock(Block block, string fileName, ref string wagonName, bool for (int i = 0; i < Plugin.CurrentHost.Plugins.Length; i++) { - - if (Plugin.CurrentHost.Plugins[i].Object != null && Plugin.CurrentHost.Plugins[i].Object.CanLoadObject(objectFile)) + if (Plugin.CurrentHost.Plugins[i].Object == null || !Plugin.CurrentHost.Plugins[i].Object.CanLoadObject(objectFile)) + { + continue; + } + Plugin.CurrentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject freightObject); + if (exteriorLoaded) { - Plugin.CurrentHost.Plugins[i].Object.LoadObject(objectFile, Path.GetDirectoryName(fileName), Encoding.Default, out UnifiedObject freightObject); - if (exteriorLoaded) - { - CarSection exteriorCarSection = car.CarSections[CarSectionType.Exterior]; - exteriorCarSection.AppendObject(Plugin.CurrentHost, new Vector3(0, loadPosition, 0), car, freightObject); - car.CarSections[CarSectionType.Exterior] = exteriorCarSection; - } - else - { - car.CarSections.Add(CarSectionType.Exterior, new CarSection(Plugin.CurrentHost, ObjectType.Dynamic, false, car, freightObject)); - } - break; + CarSection exteriorCarSection = car.CarSections[CarSectionType.Exterior]; + exteriorCarSection.AppendObject(Plugin.CurrentHost, new Vector3(0, loadPosition, 0), car, freightObject); + car.CarSections[CarSectionType.Exterior] = exteriorCarSection; } + else + { + car.CarSections.Add(CarSectionType.Exterior, new CarSection(Plugin.CurrentHost, ObjectType.Dynamic, false, car, freightObject)); + } + break; } break; case KujuTokenID.Effects: From 076f10fa8226cce7cc33a4bc43b658a61b9f7448 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 3 Dec 2025 13:36:55 +0000 Subject: [PATCH 82/82] Change: Map CP_Handle --- source/Plugins/Train.MsTs/Panel/CabComponent.cs | 2 ++ source/Plugins/Train.MsTs/Panel/CvfParser.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/source/Plugins/Train.MsTs/Panel/CabComponent.cs b/source/Plugins/Train.MsTs/Panel/CabComponent.cs index 49e922a277..2555c082af 100644 --- a/source/Plugins/Train.MsTs/Panel/CabComponent.cs +++ b/source/Plugins/Train.MsTs/Panel/CabComponent.cs @@ -210,6 +210,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) case CabComponentType.TriState: case CabComponentType.TwoState: case CabComponentType.MultiStateDisplay: + case CabComponentType.CombinedControl: Position.X *= rW; Position.Y *= rH; Size.X *= rW; @@ -255,6 +256,7 @@ internal void Create(ref CarBase currentCar, int componentLayer) case PanelSubject.Alerter_Display: case PanelSubject.Penalty_App: case PanelSubject.Throttle_Display: + case PanelSubject.CP_Handle: case PanelSubject.CPH_Display: case PanelSubject.Friction_Braking: currentCar.CarSections[CarSectionType.Interior].Groups[0].Elements[elementIndex].StateFunction = new CvfAnimation(Plugin.CurrentHost, panelSubject, FrameMappings); diff --git a/source/Plugins/Train.MsTs/Panel/CvfParser.cs b/source/Plugins/Train.MsTs/Panel/CvfParser.cs index bf1939bdfe..398b52a458 100644 --- a/source/Plugins/Train.MsTs/Panel/CvfParser.cs +++ b/source/Plugins/Train.MsTs/Panel/CvfParser.cs @@ -60,6 +60,7 @@ internal class CabviewFileParser internal static bool ParseCabViewFile(string fileName, ref CarBase currentCar) { FileName = fileName; + cabComponents.Clear(); CurrentFolder = Path.GetDirectoryName(fileName); Stream fb = new FileStream(fileName, FileMode.Open, FileAccess.Read);