internal void Parse(string fileName, TrainBase Train, ref UnifiedObject[] CarObjects, ref UnifiedObject[] BogieObjects, ref UnifiedObject[] CouplerObjects, out bool[] interiorVisible) { //The current XML file to load XmlDocument currentXML = new XmlDocument(); //Load the marker's XML file currentXML.Load(fileName); currentPath = System.IO.Path.GetDirectoryName(fileName); if (System.IO.File.Exists(OpenBveApi.Path.CombineFile(currentPath, "train.dat"))) { for (int i = 0; i < Train.Cars.Length; i++) { if (Train.Cars[i].Specs.IsMotorCar) { AccelerationCurves = new BveAccelerationCurve[Train.Cars[i].Specs.AccelerationCurves.Length]; for (int j = 0; j < Train.Cars[i].Specs.AccelerationCurves.Length; j++) { BveAccelerationCurve c = (BveAccelerationCurve)Train.Cars[i].Specs.AccelerationCurves[j]; AccelerationCurves[j] = c.Clone(c.Multiplier); } } } } CarObjectsReversed = new bool[Train.Cars.Length]; BogieObjectsReversed = new bool[Train.Cars.Length * 2]; interiorVisible = new bool[Train.Cars.Length]; if (currentXML.DocumentElement != null) { XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/openBVE/Train/*[self::Car or self::Coupler]"); if (DocumentNodes == null || DocumentNodes.Count == 0) { Plugin.currentHost.AddMessage(MessageType.Error, false, "No car nodes defined in XML file " + fileName); //If we have no appropriate nodes specified, return false and fallback to loading the legacy Sound.cfg file throw new Exception("Empty train.xml file"); } int carIndex = 0; //Use the index here for easy access to the car count for (int i = 0; i < DocumentNodes.Count; i++) { if (carIndex > Train.Cars.Length) { Plugin.currentHost.AddMessage(MessageType.Warning, false, "WARNING: A total of " + DocumentNodes.Count + " cars were specified in XML file " + fileName + " whilst only " + Train.Cars.Length + " were specified in the train.dat file."); break; } if (DocumentNodes[i].ChildNodes.OfType <XmlElement>().Any()) { if (DocumentNodes[i].Name == "Car") { ParseCarNode(DocumentNodes[i], fileName, carIndex, ref Train, ref CarObjects, ref BogieObjects, ref interiorVisible[carIndex]); } else { if (carIndex - 1 > Train.Cars.Length - 2) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Unexpected extra coupler encountered in XML file " + fileName); continue; } foreach (XmlNode c in DocumentNodes[i].ChildNodes) { switch (c.Name.ToLowerInvariant()) { case "minimum": if (!NumberFormats.TryParseDoubleVb6(c.InnerText, out Train.Cars[carIndex - 1].Coupler.MinimumDistanceBetweenCars)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "MinimumDistanceBetweenCars is invalid for coupler " + carIndex + "in XML file " + fileName); } break; case "maximum": if (!NumberFormats.TryParseDoubleVb6(c.InnerText, out Train.Cars[carIndex - 1].Coupler.MaximumDistanceBetweenCars)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "MaximumDistanceBetweenCars is invalid for coupler " + carIndex + "in XML file " + fileName); } break; case "object": if (string.IsNullOrEmpty(c.InnerText)) { Plugin.currentHost.AddMessage(MessageType.Warning, false, "Invalid object path for Coupler " + (carIndex - 1) + " in XML file " + fileName); break; } string f = OpenBveApi.Path.CombineFile(currentPath, c.InnerText); if (System.IO.File.Exists(f)) { Plugin.currentHost.LoadObject(f, Encoding.Default, out CouplerObjects[carIndex - 1]); } break; } } } } else if (!String.IsNullOrEmpty(DocumentNodes[i].InnerText)) { try { string childFile = OpenBveApi.Path.CombineFile(currentPath, DocumentNodes[i].InnerText); XmlDocument childXML = new XmlDocument(); childXML.Load(childFile); XmlNodeList childNodes = childXML.DocumentElement.SelectNodes("/openBVE/Car"); //We need to save and restore the current path to make relative paths within the child file work correctly string savedPath = currentPath; currentPath = System.IO.Path.GetDirectoryName(childFile); ParseCarNode(childNodes[0], fileName, i, ref Train, ref CarObjects, ref BogieObjects, ref interiorVisible[carIndex]); currentPath = savedPath; } catch { Plugin.currentHost.AddMessage(MessageType.Error, false, "Failed to load the child Car XML file specified in " + DocumentNodes[i].InnerText); } } if (i == DocumentNodes.Count && carIndex < Train.Cars.Length) { //If this is the case, the number of motor cars is the primary thing which may get confused.... //Not a lot to be done about this until a full replacement is built for the train.dat file & we can dump it entirely Plugin.currentHost.AddMessage(MessageType.Warning, false, "WARNING: The number of cars specified in the train.xml file does not match that in the train.dat- Some properties may be invalid."); } if (DocumentNodes[i].Name == "Car") { carIndex++; } } if (Train.Cars[Train.DriverCar].CameraRestrictionMode != CameraRestrictionMode.NotSpecified) { Plugin.Renderer.Camera.CurrentRestriction = Train.Cars[Train.DriverCar].CameraRestrictionMode; } DocumentNodes = currentXML.DocumentElement.SelectNodes("/openBVE/Train/NotchDescriptions"); if (DocumentNodes != null && DocumentNodes.Count > 0) { //Optional section for (int i = 0; i < DocumentNodes.Count; i++) { if (DocumentNodes[i].ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode c in DocumentNodes[i].ChildNodes) { switch (c.Name.ToLowerInvariant()) { case "power": Train.Handles.Power.NotchDescriptions = c.InnerText.Split(';'); for (int j = 0; j < Train.Handles.Power.NotchDescriptions.Length; j++) { Size s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Power.NotchDescriptions[j]); if (s.Width > Train.Handles.Power.MaxWidth) { Train.Handles.Power.MaxWidth = s.Width; } } break; case "brake": Train.Handles.Brake.NotchDescriptions = c.InnerText.Split(';'); for (int j = 0; j < Train.Handles.Brake.NotchDescriptions.Length; j++) { Size s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Brake.NotchDescriptions[j]); if (s.Width > Train.Handles.Brake.MaxWidth) { Train.Handles.Brake.MaxWidth = s.Width; } } break; case "locobrake": if (Train.Handles.LocoBrake == null) { continue; } Train.Handles.LocoBrake.NotchDescriptions = c.InnerText.Split(';'); for (int j = 0; j < Train.Handles.LocoBrake.NotchDescriptions.Length; j++) { Size s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.LocoBrake.NotchDescriptions[j]); if (s.Width > Train.Handles.LocoBrake.MaxWidth) { Train.Handles.LocoBrake.MaxWidth = s.Width; } } break; case "reverser": Train.Handles.Reverser.NotchDescriptions = c.InnerText.Split(';'); for (int j = 0; j < Train.Handles.Reverser.NotchDescriptions.Length; j++) { Size s = Plugin.Renderer.Fonts.NormalFont.MeasureString(Train.Handles.Reverser.NotchDescriptions[j]); if (s.Width > Train.Handles.Reverser.MaxWidth) { Train.Handles.Reverser.MaxWidth = s.Width; } } break; } } } } } for (int i = 0; i < Train.Cars.Length; i++) { if (CarObjects[i] != null) { if (CarObjectsReversed[i]) { { // reverse axle positions double temp = Train.Cars[i].FrontAxle.Position; Train.Cars[i].FrontAxle.Position = -Train.Cars[i].RearAxle.Position; Train.Cars[i].RearAxle.Position = -temp; } if (CarObjects[i] is StaticObject) { StaticObject obj = (StaticObject)CarObjects[i].Clone(); obj.ApplyScale(-1.0, 1.0, -1.0); CarObjects[i] = obj; } else if (CarObjects[i] is AnimatedObjectCollection) { AnimatedObjectCollection obj = (AnimatedObjectCollection)CarObjects[i].Clone(); obj.Reverse(); CarObjects[i] = obj; } else { throw new NotImplementedException(); } } } } //Check for bogie objects and reverse if necessary..... for (int i = 0; i < Train.Cars.Length * 2; i++) { bool IsOdd = (i % 2 != 0); int CarIndex = i / 2; if (BogieObjects[i] != null) { if (BogieObjectsReversed[i]) { { // reverse axle positions if (IsOdd) { double temp = Train.Cars[CarIndex].FrontBogie.FrontAxle.Position; Train.Cars[CarIndex].FrontBogie.FrontAxle.Position = -Train.Cars[CarIndex].FrontBogie.RearAxle.Position; Train.Cars[CarIndex].FrontBogie.RearAxle.Position = -temp; } else { double temp = Train.Cars[CarIndex].RearBogie.FrontAxle.Position; Train.Cars[CarIndex].RearBogie.FrontAxle.Position = -Train.Cars[CarIndex].RearBogie.RearAxle.Position; Train.Cars[CarIndex].RearBogie.RearAxle.Position = -temp; } } if (BogieObjects[i] is StaticObject) { StaticObject obj = (StaticObject)BogieObjects[i].Clone(); obj.ApplyScale(-1.0, 1.0, -1.0); BogieObjects[i] = obj; } else if (BogieObjects[i] is AnimatedObjectCollection) { AnimatedObjectCollection obj = (AnimatedObjectCollection)BogieObjects[i].Clone(); obj.Reverse(); BogieObjects[i] = obj; } else { throw new NotImplementedException(); } } } } } }
// parse extensions config internal void ParseExtensionsConfig(string TrainPath, System.Text.Encoding Encoding, ref UnifiedObject[] CarObjects, ref UnifiedObject[] BogieObjects, ref UnifiedObject[] CouplerObjects, ref bool[] VisibleFromInterior, TrainBase Train) { bool[] CarObjectsReversed = new bool[Train.Cars.Length]; bool[] BogieObjectsReversed = new bool[Train.Cars.Length * 2]; bool[] CarsDefined = new bool[Train.Cars.Length]; bool[] BogiesDefined = new bool[Train.Cars.Length * 2]; System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; string FileName = OpenBveApi.Path.CombineFile(TrainPath, "extensions.cfg"); if (System.IO.File.Exists(FileName)) { Encoding = TextEncoding.GetSystemEncodingFromFile(FileName, Encoding); string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); for (int i = 0; i < Lines.Length; i++) { int j = Lines[i].IndexOf(';'); if (j >= 0) { Lines[i] = Lines[i].Substring(0, j).Trim(new char[] { }); } else { Lines[i] = Lines[i].Trim(new char[] { }); } } for (int i = 0; i < Lines.Length; i++) { if (Lines[i].Length != 0) { switch (Lines[i].ToLowerInvariant()) { case "[exterior]": // exterior i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = Lines[i].Substring(j + 1).TrimStart(new char[] { }); int n; if (int.TryParse(a, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Cars.Length) { if (Path.ContainsInvalidChars(b)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string File = OpenBveApi.Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { Plugin.currentHost.LoadObject(File, Encoding, out CarObjects[n]); } else { Plugin.currentHost.AddMessage(MessageType.Error, true, "The car object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "The car index " + a + " does not reference an existing car at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "The car index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; break; default: if (Lines[i].StartsWith("[car", StringComparison.OrdinalIgnoreCase) & Lines[i].EndsWith("]", StringComparison.Ordinal)) { // car string t = Lines[i].Substring(4, Lines[i].Length - 5); int n; if (int.TryParse(t, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Cars.Length) { if (CarsDefined[n]) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Car " + n.ToString(Culture) + " has already been declared at line " + (i + 1).ToString(Culture) + " in file " + FileName); } CarsDefined[n] = true; bool DefinedLength = false; bool DefinedAxles = false; i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = Lines[i].Substring(j + 1).TrimStart(new char[] { }); switch (a.ToLowerInvariant()) { case "object": if (string.IsNullOrEmpty(b)) { Plugin.currentHost.AddMessage(MessageType.Error, true, "An empty car object was supplied at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } if (Path.ContainsInvalidChars(b)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string File = OpenBveApi.Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { Plugin.currentHost.LoadObject(File, Encoding, out CarObjects[n]); } else { Plugin.currentHost.AddMessage(MessageType.Error, true, "The car object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "length": { double m; if (double.TryParse(b, System.Globalization.NumberStyles.Float, Culture, out m)) { if (m > 0.0) { Train.Cars[n].Length = m; Train.Cars[n].BeaconReceiverPosition = 0.5 * m; DefinedLength = true; } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "Value is expected to be a positive floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "Value is expected to be a positive floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "axles": { int k = b.IndexOf(','); if (k >= 0) { string c = b.Substring(0, k).TrimEnd(new char[] { }); string d = b.Substring(k + 1).TrimStart(new char[] { }); double rear, front; if (!double.TryParse(c, System.Globalization.NumberStyles.Float, Culture, out rear)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Rear is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(d, System.Globalization.NumberStyles.Float, Culture, out front)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Front is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (rear >= front) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Rear is expected to be less than Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Train.Cars[n].RearAxle.Position = rear; Train.Cars[n].FrontAxle.Position = front; DefinedAxles = true; } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "An argument-separating comma is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "reversed": CarObjectsReversed[n] = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; case "loadingsway": Train.Cars[n].EnableLoadingSway = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; case "visiblefrominterior": VisibleFromInterior[n] = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; default: Plugin.currentHost.AddMessage(MessageType.Warning, false, "Unsupported key-value pair " + a + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; if (DefinedLength & !DefinedAxles) { double AxleDistance = 0.4 * Train.Cars[n].Length; Train.Cars[n].RearAxle.Position = -AxleDistance; Train.Cars[n].FrontAxle.Position = AxleDistance; } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "The car index " + t + " does not reference an existing car at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "The car index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else if (Lines[i].StartsWith("[coupler", StringComparison.OrdinalIgnoreCase) & Lines[i].EndsWith("]", StringComparison.Ordinal)) { // coupler string t = Lines[i].Substring(8, Lines[i].Length - 9); int n; if (int.TryParse(t, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Cars.Length - 1) { i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = Lines[i].Substring(j + 1).TrimStart(new char[] { }); switch (a.ToLowerInvariant()) { case "distances": { int k = b.IndexOf(','); if (k >= 0) { string c = b.Substring(0, k).TrimEnd(new char[] { }); string d = b.Substring(k + 1).TrimStart(new char[] { }); double min, max; if (!double.TryParse(c, System.Globalization.NumberStyles.Float, Culture, out min)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Minimum is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(d, System.Globalization.NumberStyles.Float, Culture, out max)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Maximum is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (min > max) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Minimum is expected to be less than Maximum in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Train.Cars[n].Coupler.MinimumDistanceBetweenCars = min; Train.Cars[n].Coupler.MaximumDistanceBetweenCars = max; } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "An argument-separating comma is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "object": if (string.IsNullOrEmpty(b)) { Plugin.currentHost.AddMessage(MessageType.Error, true, "An empty coupler object was supplied at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } if (Path.ContainsInvalidChars(b)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string File = OpenBveApi.Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { Plugin.currentHost.LoadObject(File, Encoding, out CouplerObjects[n]); } else { Plugin.currentHost.AddMessage(MessageType.Error, true, "The coupler object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; default: Plugin.currentHost.AddMessage(MessageType.Warning, false, "Unsupported key-value pair " + a + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "The coupler index " + t + " does not reference an existing coupler at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "The coupler index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else if (Lines[i].StartsWith("[bogie", StringComparison.OrdinalIgnoreCase) & Lines[i].EndsWith("]", StringComparison.Ordinal)) { // car string t = Lines[i].Substring(6, Lines[i].Length - 7); int n; if (int.TryParse(t, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n > BogiesDefined.Length - 1) { continue; } if (BogiesDefined[n]) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Bogie " + n.ToString(Culture) + " has already been declared at line " + (i + 1).ToString(Culture) + " in file " + FileName); } BogiesDefined[n] = true; //Assuming that there are two bogies per car bool IsOdd = (n % 2 != 0); int CarIndex = n / 2; if (n >= 0 & n < Train.Cars.Length * 2) { bool DefinedAxles = false; i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = Lines[i].Substring(j + 1).TrimStart(new char[] { }); switch (a.ToLowerInvariant()) { case "object": if (Path.ContainsInvalidChars(b)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { if (string.IsNullOrEmpty(b)) { Plugin.currentHost.AddMessage(MessageType.Error, true, "An empty bogie object was supplied at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } string File = OpenBveApi.Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { Plugin.currentHost.LoadObject(File, Encoding, out BogieObjects[n]); } else { Plugin.currentHost.AddMessage(MessageType.Error, true, "The bogie object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "length": { Plugin.currentHost.AddMessage(MessageType.Error, false, "A defined length is not supported for bogies at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "axles": { int k = b.IndexOf(','); if (k >= 0) { string c = b.Substring(0, k).TrimEnd(new char[] { }); string d = b.Substring(k + 1).TrimStart(new char[] { }); double rear, front; if (!double.TryParse(c, System.Globalization.NumberStyles.Float, Culture, out rear)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Rear is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(d, System.Globalization.NumberStyles.Float, Culture, out front)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Front is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (rear >= front) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Rear is expected to be less than Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { if (IsOdd) { Train.Cars[CarIndex].FrontBogie.RearAxle.Position = rear; Train.Cars[CarIndex].FrontBogie.FrontAxle.Position = front; } else { Train.Cars[CarIndex].RearBogie.RearAxle.Position = rear; Train.Cars[CarIndex].RearBogie.FrontAxle.Position = front; } DefinedAxles = true; } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "An argument-separating comma is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "reversed": BogieObjectsReversed[n] = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; default: Plugin.currentHost.AddMessage(MessageType.Warning, false, "Unsupported key-value pair " + a + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; if (!DefinedAxles) { if (IsOdd) { double AxleDistance = 0.4 * Train.Cars[CarIndex].FrontBogie.Length; Train.Cars[CarIndex].FrontBogie.RearAxle.Position = -AxleDistance; Train.Cars[CarIndex].FrontBogie.FrontAxle.Position = AxleDistance; } else { double AxleDistance = 0.4 * Train.Cars[CarIndex].RearBogie.Length; Train.Cars[CarIndex].RearBogie.RearAxle.Position = -AxleDistance; Train.Cars[CarIndex].RearBogie.FrontAxle.Position = AxleDistance; } } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "The car index " + t + " does not reference an existing car at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "The car index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { // default if (Lines.Length == 1 && Encoding.Equals(Encoding.Unicode)) { /* * If only one line, there's a good possibility that our file is NOT Unicode at all * and that the misdetection has turned it into garbage * * Try again with ASCII instead */ ParseExtensionsConfig(TrainPath, Encoding.GetEncoding(1252), ref CarObjects, ref BogieObjects, ref CouplerObjects, ref VisibleFromInterior, Train); return; } Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; } } } // check for car objects and reverse if necessary int carObjects = 0; for (int i = 0; i < Train.Cars.Length; i++) { if (CarObjects[i] != null) { carObjects++; if (CarObjectsReversed[i]) { { // reverse axle positions double temp = Train.Cars[i].FrontAxle.Position; Train.Cars[i].FrontAxle.Position = -Train.Cars[i].RearAxle.Position; Train.Cars[i].RearAxle.Position = -temp; } if (CarObjects[i] is StaticObject) { StaticObject obj = (StaticObject)CarObjects[i].Clone(); obj.ApplyScale(-1.0, 1.0, -1.0); CarObjects[i] = obj; } else if (CarObjects[i] is AnimatedObjectCollection) { AnimatedObjectCollection obj = (AnimatedObjectCollection)CarObjects[i].Clone(); obj.Reverse(); CarObjects[i] = obj; } else { throw new NotImplementedException(); } } } } //Check for bogie objects and reverse if necessary..... int bogieObjects = 0; for (int i = 0; i < Train.Cars.Length * 2; i++) { bool IsOdd = (i % 2 != 0); int CarIndex = i / 2; if (BogieObjects[i] != null) { bogieObjects++; if (BogieObjectsReversed[i]) { { // reverse axle positions if (IsOdd) { double temp = Train.Cars[CarIndex].FrontBogie.FrontAxle.Position; Train.Cars[CarIndex].FrontBogie.FrontAxle.Position = -Train.Cars[CarIndex].FrontBogie.RearAxle.Position; Train.Cars[CarIndex].FrontBogie.RearAxle.Position = -temp; } else { double temp = Train.Cars[CarIndex].RearBogie.FrontAxle.Position; Train.Cars[CarIndex].RearBogie.FrontAxle.Position = -Train.Cars[CarIndex].RearBogie.RearAxle.Position; Train.Cars[CarIndex].RearBogie.RearAxle.Position = -temp; } } if (BogieObjects[i] is StaticObject) { StaticObject obj = (StaticObject)BogieObjects[i].Clone(); obj.ApplyScale(-1.0, 1.0, -1.0); BogieObjects[i] = obj; } else if (BogieObjects[i] is AnimatedObjectCollection) { AnimatedObjectCollection obj = (AnimatedObjectCollection)BogieObjects[i].Clone(); obj.Reverse(); BogieObjects[i] = obj; } else { throw new NotImplementedException(); } } } } if (carObjects > 0 & carObjects < Train.Cars.Length) { Plugin.currentHost.AddMessage(MessageType.Warning, false, "An incomplete set of exterior objects was provided in file " + FileName); } if (bogieObjects > 0 & bogieObjects < Train.Cars.Length * 2) { Plugin.currentHost.AddMessage(MessageType.Warning, false, "An incomplete set of bogie objects was provided in file " + FileName); } } }
private static void ParseExtensionsConfig(string filePath, Encoding encoding, out UnifiedObject[] carObjects, out UnifiedObject[] bogieObjects, out UnifiedObject[] couplerObjects, out double[] axleLocations, out double[] couplerDistances, out TrainManager.Train train, bool loadObjects) { CultureInfo culture = CultureInfo.InvariantCulture; carObjects = new UnifiedObject[0]; bogieObjects = new UnifiedObject[0]; couplerObjects = new UnifiedObject[0]; axleLocations = new double[0]; couplerDistances = new double[0]; train = new TrainManager.Train { Cars = new CarBase[0] }; if (!File.Exists(filePath)) { return; } bool[] carObjectsReversed = new bool[0]; bool[] bogieObjectsReversed = new bool[0]; bool[] carsDefined = new bool[0]; bool[] bogiesDefined = new bool[0]; bool[] couplerDefined = new bool[0]; string trainPath = System.IO.Path.GetDirectoryName(filePath); if (encoding == null) { encoding = TextEncoding.GetSystemEncodingFromFile(trainPath); } string[] lines = File.ReadAllLines(filePath, encoding); for (int i = 0; i < lines.Length; i++) { int j = lines[i].IndexOf(';'); if (j >= 0) { lines[i] = lines[i].Substring(0, j).Trim(new char[] { }); } else { lines[i] = lines[i].Trim(new char[] { }); } } for (int i = 0; i < lines.Length; i++) { if (lines[i].Length != 0) { switch (lines[i].ToLowerInvariant()) { case "[exterior]": // exterior i++; while (i < lines.Length && !lines[i].StartsWith("[", StringComparison.Ordinal) & !lines[i].EndsWith("]", StringComparison.Ordinal)) { if (lines[i].Length != 0) { int j = lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { // ReSharper disable RedundantExplicitParamsArrayCreation string a = lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = lines[i].Substring(j + 1).TrimStart(new char[] { }); // ReSharper restore RedundantExplicitParamsArrayCreation int n; if (int.TryParse(a, NumberStyles.Integer, culture, out n)) { if (n >= 0) { if (n >= train.Cars.Length) { Array.Resize(ref train.Cars, n + 1); train.Cars[n] = new CarBase(train, n); Array.Resize(ref carObjects, n + 1); Array.Resize(ref bogieObjects, (n + 1) * 2); Array.Resize(ref couplerObjects, n); Array.Resize(ref carObjectsReversed, n + 1); Array.Resize(ref bogieObjectsReversed, (n + 1) * 2); Array.Resize(ref carsDefined, n + 1); Array.Resize(ref bogiesDefined, (n + 1) * 2); Array.Resize(ref couplerDefined, n); Array.Resize(ref axleLocations, (n + 1) * 2); Array.Resize(ref couplerDistances, n); } if (Path.ContainsInvalidChars(b)) { Interface.AddMessage(MessageType.Error, false, $"File contains illegal characters at line {(i + 1).ToString(culture)} in file {filePath}"); } else { string file = Path.CombineFile(trainPath, b); if (File.Exists(file)) { if (loadObjects) { Program.CurrentHost.LoadObject(file, encoding, out carObjects[n]); } } else { Interface.AddMessage(MessageType.Error, true, $"The car object {file} does not exist at line {(i + 1).ToString(culture)} in file {filePath}"); } } } else { Interface.AddMessage(MessageType.Error, false, $"The car index {a} does not reference an existing car at line {(i + 1).ToString(culture)} in file {filePath}"); } } else { Interface.AddMessage(MessageType.Error, false, $"The car index is expected to be an integer at line {(i + 1).ToString(culture)} in file {filePath}"); } } else { Interface.AddMessage(MessageType.Error, false, $"Invalid statement {lines[i]} encountered at line {(i + 1).ToString(culture)} in file {filePath}"); } } i++; } i--; break; default: if (lines[i].StartsWith("[car", StringComparison.OrdinalIgnoreCase) & lines[i].EndsWith("]", StringComparison.Ordinal)) { // car string t = lines[i].Substring(4, lines[i].Length - 5); int n; if (int.TryParse(t, NumberStyles.Integer, culture, out n)) { if (n >= 0) { if (n >= train.Cars.Length) { Array.Resize(ref train.Cars, n + 1); train.Cars[n] = new CarBase(train, n); Array.Resize(ref carObjects, n + 1); Array.Resize(ref bogieObjects, (n + 1) * 2); Array.Resize(ref carObjectsReversed, n + 1); Array.Resize(ref bogieObjectsReversed, (n + 1) * 2); Array.Resize(ref couplerObjects, n); Array.Resize(ref carsDefined, n + 1); Array.Resize(ref bogiesDefined, (n + 1) * 2); Array.Resize(ref couplerDefined, n); Array.Resize(ref axleLocations, (n + 1) * 2); Array.Resize(ref couplerDistances, n); } if (carsDefined[n]) { Interface.AddMessage(MessageType.Error, false, $"Car {n.ToString(culture)} has already been declared at line {(i + 1).ToString(culture)} in file {filePath}"); } carsDefined[n] = true; i++; while (i < lines.Length && !lines[i].StartsWith("[", StringComparison.Ordinal) & !lines[i].EndsWith("]", StringComparison.Ordinal)) { if (lines[i].Length != 0) { int j = lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { // ReSharper disable RedundantExplicitParamsArrayCreation string a = lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = lines[i].Substring(j + 1).TrimStart(new char[] { }); // ReSharper restore RedundantExplicitParamsArrayCreation switch (a.ToLowerInvariant()) { case "object": if (string.IsNullOrEmpty(b)) { Interface.AddMessage(MessageType.Error, true, $"An empty car object was supplied at line {(i + 1).ToString(culture)} in file {filePath}"); break; } if (Path.ContainsInvalidChars(b)) { Interface.AddMessage(MessageType.Error, false, $"File contains illegal characters at line {(i + 1).ToString(culture)} in file {filePath}"); } else { string file = Path.CombineFile(trainPath, b); if (File.Exists(file)) { if (loadObjects) { Program.CurrentHost.LoadObject(file, encoding, out carObjects[n]); } } else { Interface.AddMessage(MessageType.Error, true, $"The car object {file} does not exist at line {(i + 1).ToString(culture)} in file {filePath}"); } } break; case "length": { double m; if (double.TryParse(b, NumberStyles.Float, culture, out m)) { if (m > 0.0) { train.Cars[n].Length = m; } else { Interface.AddMessage(MessageType.Error, false, $"Value is expected to be a positive floating-point number in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } } else { Interface.AddMessage(MessageType.Error, false, $"Value is expected to be a positive floating-point number in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } } break; case "reversed": carObjectsReversed[n] = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; case "axles": int k = b.IndexOf(','); if (k >= 0) { // ReSharper disable RedundantExplicitParamsArrayCreation string c = b.Substring(0, k).TrimEnd(new char[] { }); string d = b.Substring(k + 1).TrimStart(new char[] { }); // ReSharper restore RedundantExplicitParamsArrayCreation double rear, front; if (!double.TryParse(c, NumberStyles.Float, culture, out rear)) { Interface.AddMessage(MessageType.Error, false, $"Rear is expected to be a floating-point number in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } else if (!double.TryParse(d, NumberStyles.Float, culture, out front)) { Interface.AddMessage(MessageType.Error, false, $"Front is expected to be a floating-point number in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } else if (rear >= front) { Interface.AddMessage(MessageType.Error, false, $"Rear is expected to be less than Front in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } else { if (n == 0) { axleLocations[n] = rear; axleLocations[n + 1] = front; } else { axleLocations[n * 2] = rear; axleLocations[n * 2 + 1] = front; } } } else { Interface.AddMessage(MessageType.Error, false, $"An argument-separating comma is expected in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } break; default: Interface.AddMessage(MessageType.Warning, false, $"Unsupported key-value pair {a} encountered at line {(i + 1).ToString(culture)} in file {filePath}"); break; } } else { Interface.AddMessage(MessageType.Error, false, $"Invalid statement {lines[i]} encountered at line {(i + 1).ToString(culture)} in file {filePath}"); } } i++; } i--; } else { Interface.AddMessage(MessageType.Error, false, $"The car index {t} does not reference an existing car at line {(i + 1).ToString(culture)} in file {filePath}"); } } else { Interface.AddMessage(MessageType.Error, false, $"The car index is expected to be an integer at line {(i + 1).ToString(culture)} in file {filePath}"); } } else if (lines[i].StartsWith("[bogie", StringComparison.OrdinalIgnoreCase) & lines[i].EndsWith("]", StringComparison.Ordinal)) { // bogie string t = lines[i].Substring(6, lines[i].Length - 7); int n; if (int.TryParse(t, NumberStyles.Integer, culture, out n)) { if (n >= train.Cars.Length * 2) { Array.Resize(ref train.Cars, n / 2 + 1); if (n == 0) { train.Cars[0] = new CarBase(train, 0); Array.Resize(ref axleLocations, 2); } else { train.Cars[n / 2] = new CarBase(train, n / 2); Array.Resize(ref axleLocations, (n / 2 + 1) * 2); } Array.Resize(ref carObjects, n / 2 + 1); Array.Resize(ref bogieObjects, n + 2); Array.Resize(ref couplerObjects, n / 2); Array.Resize(ref carObjectsReversed, n / 2 + 1); Array.Resize(ref bogieObjectsReversed, n + 2); Array.Resize(ref carsDefined, n / 2 + 1); Array.Resize(ref bogiesDefined, n + 2); Array.Resize(ref couplerDefined, n / 2); Array.Resize(ref couplerDistances, n / 2); } if (n > bogiesDefined.Length - 1) { continue; } if (bogiesDefined[n]) { Interface.AddMessage(MessageType.Error, false, $"Bogie {n.ToString(culture)} has already been declared at line {(i + 1).ToString(culture)} in file {filePath}"); } bogiesDefined[n] = true; //Assuming that there are two bogies per car if (n >= 0 & n < train.Cars.Length * 2) { i++; while (i < lines.Length && !lines[i].StartsWith("[", StringComparison.Ordinal) & !lines[i].EndsWith("]", StringComparison.Ordinal)) { if (lines[i].Length != 0) { int j = lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { // ReSharper disable RedundantExplicitParamsArrayCreation string a = lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = lines[i].Substring(j + 1).TrimStart(new char[] { }); // ReSharper restore RedundantExplicitParamsArrayCreation switch (a.ToLowerInvariant()) { case "object": if (Path.ContainsInvalidChars(b)) { Interface.AddMessage(MessageType.Error, false, $"File contains illegal characters at line {(i + 1).ToString(culture)} in file {filePath}"); } else { if (string.IsNullOrEmpty(b)) { Interface.AddMessage(MessageType.Error, true, $"An empty bogie object was supplied at line {(i + 1).ToString(culture)} in file {filePath}"); break; } string file = Path.CombineFile(trainPath, b); if (File.Exists(file)) { if (loadObjects) { Program.CurrentHost.LoadObject(file, encoding, out bogieObjects[n]); } } else { Interface.AddMessage(MessageType.Error, true, $"The bogie object {file} does not exist at line {(i + 1).ToString(culture)} in file {filePath}"); } } break; case "length": { Interface.AddMessage(MessageType.Error, false, $"A defined length is not supported for bogies at line {(i + 1).ToString(culture)} in file {filePath}"); } break; case "reversed": bogieObjectsReversed[n] = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; case "axles": //Axles aren't used in bogie positioning, just in rotation break; default: Interface.AddMessage(MessageType.Warning, false, $"Unsupported key-value pair {a} encountered at line {(i + 1).ToString(culture)} in file {filePath}"); break; } } else { Interface.AddMessage(MessageType.Error, false, $"Invalid statement {lines[i]} encountered at line {(i + 1).ToString(culture)} in file {filePath}"); } } i++; } i--; } else { Interface.AddMessage(MessageType.Error, false, $"The bogie index {t} does not reference an existing bogie at line {(i + 1).ToString(culture)} in file {filePath}"); } } else { Interface.AddMessage(MessageType.Error, false, $"The bogie index is expected to be an integer at line {(i + 1).ToString(culture)} in file {filePath}"); } } else if (lines[i].StartsWith("[coupler", StringComparison.OrdinalIgnoreCase) & lines[i].EndsWith("]", StringComparison.Ordinal)) { // coupler string t = lines[i].Substring(8, lines[i].Length - 9); int n; if (int.TryParse(t, NumberStyles.Integer, culture, out n)) { if (n >= 0) { if (n >= train.Cars.Length - 1) { Array.Resize(ref train.Cars, n + 2); train.Cars[n + 1] = new CarBase(train, n + 1); Array.Resize(ref carObjects, n + 2); Array.Resize(ref bogieObjects, (n + 2) * 2); Array.Resize(ref carObjectsReversed, n + 2); Array.Resize(ref bogieObjectsReversed, (n + 2) * 2); Array.Resize(ref couplerObjects, n + 1); Array.Resize(ref carsDefined, n + 2); Array.Resize(ref bogiesDefined, (n + 2) * 2); Array.Resize(ref couplerDefined, n + 1); Array.Resize(ref axleLocations, (n + 2) * 2); Array.Resize(ref couplerDistances, n + 1); } if (couplerDefined[n]) { Interface.AddMessage(MessageType.Error, false, $"Coupler {n.ToString(culture)} has already been declared at line {(i + 1).ToString(culture)} in file {filePath}"); } couplerDefined[n] = true; i++; while (i < lines.Length && !lines[i].StartsWith("[", StringComparison.Ordinal) & !lines[i].EndsWith("]", StringComparison.Ordinal)) { if (lines[i].Length != 0) { int j = lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { // ReSharper disable RedundantExplicitParamsArrayCreation string a = lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = lines[i].Substring(j + 1).TrimStart(new char[] { }); // ReSharper restore RedundantExplicitParamsArrayCreation switch (a.ToLowerInvariant()) { case "distances": { int k = b.IndexOf(','); if (k >= 0) { // ReSharper disable RedundantExplicitParamsArrayCreation string c = b.Substring(0, k).TrimEnd(new char[] { }); string d = b.Substring(k + 1).TrimStart(new char[] { }); // ReSharper restore RedundantExplicitParamsArrayCreation double min, max; if (!double.TryParse(c, NumberStyles.Float, culture, out min)) { Interface.AddMessage(MessageType.Error, false, $"Minimum is expected to be a floating-point number in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } else if (!double.TryParse(d, NumberStyles.Float, culture, out max)) { Interface.AddMessage(MessageType.Error, false, $"Maximum is expected to be a floating-point number in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } else if (min > max) { Interface.AddMessage(MessageType.Error, false, $"Minimum is expected to be less than Maximum in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } else { couplerDistances[n] = 0.5 * (min + max); } } else { Interface.AddMessage(MessageType.Error, false, $"An argument-separating comma is expected in {a} at line {(i + 1).ToString(culture)} in file {filePath}"); } } break; case "object": if (string.IsNullOrEmpty(b)) { Interface.AddMessage(MessageType.Error, true, $"An empty coupler object was supplied at line {(i + 1).ToString(culture)} in file {filePath}"); break; } if (Path.ContainsInvalidChars(b)) { Interface.AddMessage(MessageType.Error, false, $"File contains illegal characters at line {(i + 1).ToString(culture)} in file {filePath}"); } else { string file = Path.CombineFile(trainPath, b); if (File.Exists(file)) { if (loadObjects) { Program.CurrentHost.LoadObject(file, encoding, out couplerObjects[n]); } } else { Interface.AddMessage(MessageType.Error, true, $"The coupler object {file} does not exist at line {(i + 1).ToString(culture)} in file {filePath}"); } } break; default: Interface.AddMessage(MessageType.Warning, false, $"Unsupported key-value pair {a} encountered at line {(i + 1).ToString(culture)} in file {filePath}"); break; } } else { Interface.AddMessage(MessageType.Error, false, $"Invalid statement {lines[i]} encountered at line {(i + 1).ToString(culture)} in file {filePath}"); } } i++; } i--; } else { Interface.AddMessage(MessageType.Error, false, $"The coupler index {t} does not reference an existing coupler at line {(i + 1).ToString(culture)} in file {filePath}"); } } else { Interface.AddMessage(MessageType.Error, false, $"The coupler index is expected to be an integer at line {(i + 1).ToString(culture)} in file {filePath}"); } } else { // default if (lines.Length == 1 && encoding.Equals(Encoding.Unicode)) { /* * If only one line, there's a good possibility that our file is NOT Unicode at all * and that the misdetection has turned it into garbage * * Try again with ASCII instead */ ParseExtensionsConfig(filePath, Encoding.GetEncoding(1252), out carObjects, out bogieObjects, out couplerObjects, out axleLocations, out couplerDistances, out train, loadObjects); return; } Interface.AddMessage(MessageType.Error, false, $"Invalid statement {lines[i]} encountered at line {(i + 1).ToString(culture)} in file {filePath}"); } break; } } } // check for car objects and reverse if necessary int carObjectsCount = 0; for (int i = 0; i < train.Cars.Length; i++) { if (carObjects[i] != null) { carObjectsCount++; if (carObjectsReversed[i] && loadObjects) { if (carObjects[i] is StaticObject) { StaticObject obj = (StaticObject)carObjects[i].Clone(); obj.ApplyScale(-1.0, 1.0, -1.0); carObjects[i] = obj; } else if (carObjects[i] is AnimatedObjectCollection) { AnimatedObjectCollection obj = (AnimatedObjectCollection)carObjects[i].Clone(); obj.Reverse(); carObjects[i] = obj; } else { throw new NotImplementedException(); } } } } //Check for bogie objects and reverse if necessary..... int bogieObjectsCount = 0; for (int i = 0; i < train.Cars.Length * 2; i++) { if (bogieObjects[i] != null) { bogieObjectsCount++; if (bogieObjectsReversed[i] && loadObjects) { if (bogieObjects[i] is StaticObject) { StaticObject obj = (StaticObject)bogieObjects[i]; obj.ApplyScale(-1.0, 1.0, -1.0); } else if (bogieObjects[i] is AnimatedObjectCollection) { AnimatedObjectCollection obj = (AnimatedObjectCollection)bogieObjects[i]; obj.Reverse(); } else { throw new NotImplementedException(); } } } } if (carObjectsCount > 0 & carObjectsCount < train.Cars.Length) { Interface.AddMessage(MessageType.Warning, false, $"An incomplete set of exterior objects was provided in file {filePath}"); } if (bogieObjectsCount > 0 & bogieObjectsCount < train.Cars.Length * 2) { Interface.AddMessage(MessageType.Warning, false, $"An incomplete set of bogie objects was provided in file {filePath}"); } }