/// <summary>Parses a track following object</summary> /// <param name="FileName">The XML file to parse</param> internal static TrainManager.TrackFollowingObject ParseTrackFollowingObject(string FileName, string ObjectPath) { // The current XML file to load XDocument CurrentXML = XDocument.Load(FileName, LoadOptions.SetLineInfo); string Path = System.IO.Path.GetDirectoryName(FileName); // Check for null if (CurrentXML.Root == null) { // We couldn't find any valid XML, so return false throw new System.IO.InvalidDataException(); } IEnumerable <XElement> DocumentElements = CurrentXML.Root.Elements("TrackFollowingObject"); // Check this file actually contains OpenBVE other train definition elements if (DocumentElements == null || !DocumentElements.Any()) { // We couldn't find any valid XML, so return false throw new System.IO.InvalidDataException(); } TrainManager.TrackFollowingObject Train = new TrainManager.TrackFollowingObject(TrainState.Pending); foreach (XElement Element in DocumentElements) { ParseTrackFollowingObjectNode(Element, FileName, Path, ObjectPath, Train); } return(Train); }
/// <summary>Parses a track following object</summary> /// <param name="ObjectPath">Absolute path to the object folder of route data</param> /// <param name="FileName">The XML file to parse</param> internal static TrainManager.TrackFollowingObject ParseTrackFollowingObject(string ObjectPath, string FileName) { // The current XML file to load XDocument CurrentXML = XDocument.Load(FileName, LoadOptions.SetLineInfo); List <XElement> TfoElements = CurrentXML.XPathSelectElements("/openBVE/TrackFollowingObject").ToList(); // Check this file actually contains OpenBVE other train definition elements if (!TfoElements.Any()) { // We couldn't find any valid XML, so return false throw new InvalidDataException(); } TrainManager.TrackFollowingObject Train = new TrainManager.TrackFollowingObject(TrainState.Pending); foreach (XElement Element in TfoElements) { ParseTrackFollowingObjectNode(ObjectPath, FileName, Element, Train); } return(Train); }
internal TrackFollowingObjectAI(TrainManager.TrackFollowingObject train, TravelData[] data) { Train = train; Data = data; TimeLastProcessed = 0.0; }
/// <summary>Parses a base track following object node</summary> /// <param name="Element">The XElement to parse</param> /// <param name="FileName">The filename of the containing XML file</param> /// <param name="Path">The path of the containing XML file</param> /// <param name="Train">The track following object to parse this node into</param> private static void ParseTrackFollowingObjectNode(XElement Element, string FileName, string Path, string ObjectPath, TrainManager.TrackFollowingObject Train) { System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; string TrainDirectory = string.Empty; bool consistReversed = false; List <Game.TravelData> Data = new List <Game.TravelData>(); foreach (XElement SectionElement in Element.Elements()) { string Section = SectionElement.Name.LocalName; switch (SectionElement.Name.LocalName.ToLowerInvariant()) { case "stops": ParseStopNode(SectionElement, FileName, Data); break; case "train": foreach (XElement KeyNode in SectionElement.Elements()) { string Key = KeyNode.Name.LocalName; string Value = KeyNode.Value; int LineNumber = ((IXmlLineInfo)KeyNode).LineNumber; switch (Key.ToLowerInvariant()) { case "directory": { string trainDirectory = OpenBveApi.Path.CombineDirectory(Path, Value); if (!System.IO.Directory.Exists(trainDirectory)) { trainDirectory = OpenBveApi.Path.CombineFile(Program.FileSystem.InitialTrainFolder, Value); } if (!System.IO.Directory.Exists(trainDirectory)) { trainDirectory = OpenBveApi.Path.CombineFile(Program.FileSystem.TrainInstallationDirectory, Value); } if (!System.IO.Directory.Exists(trainDirectory)) { trainDirectory = OpenBveApi.Path.CombineFile(ObjectPath, Value); } if (!System.IO.Directory.Exists(trainDirectory)) { Interface.AddMessage(MessageType.Error, false, "Directory was not found in " + Key + " in " + Section + " at line " + LineNumber.ToString(Culture) + " in " + FileName); } else { TrainDirectory = trainDirectory; } } break; case "reversed": int n; NumberFormats.TryParseIntVb6(Value, out n); if (n == 1 || Value.ToLowerInvariant() == "true") { consistReversed = true; } break; } } break; case "definition": foreach (XElement KeyNode in SectionElement.Elements()) { string Key = KeyNode.Name.LocalName; string Value = KeyNode.Value; int LineNumber = ((IXmlLineInfo)KeyNode).LineNumber; switch (Key.ToLowerInvariant()) { case "appearancetime": if (Value.Length != 0 && !Interface.TryParseTime(Value, out Train.AppearanceTime)) { Interface.AddMessage(MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + LineNumber.ToString(Culture) + " in " + FileName); } break; case "appearancestartposition": if (Value.Length != 0 && !NumberFormats.TryParseDoubleVb6(Value, out Train.AppearanceStartPosition)) { Interface.AddMessage(MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + LineNumber.ToString(Culture) + " in " + FileName); } break; case "appearanceendposition": if (Value.Length != 0 && !NumberFormats.TryParseDoubleVb6(Value, out Train.AppearanceEndPosition)) { Interface.AddMessage(MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + LineNumber.ToString(Culture) + " in " + FileName); } break; case "leavetime": if (Value.Length != 0 && !Interface.TryParseTime(Value, out Train.LeaveTime)) { Interface.AddMessage(MessageType.Error, false, "Value is invalid in " + Key + " in " + Section + " at line " + LineNumber.ToString(Culture) + " in " + FileName); } break; } } break; } } if (!Data.Any() || string.IsNullOrEmpty(TrainDirectory)) { return; } /* * First check for a train.ai file- Functionally identical, but allows for differently configured AI * trains not to show up as driveable */ string TrainData = OpenBveApi.Path.CombineFile(TrainDirectory, "train.ai"); if (!System.IO.File.Exists(TrainData)) { // Check for the standard driveable train.dat TrainData = OpenBveApi.Path.CombineFile(TrainDirectory, "train.dat"); } string ExteriorFile = OpenBveApi.Path.CombineFile(TrainDirectory, "extensions.cfg"); if (!System.IO.File.Exists(TrainData) || !System.IO.File.Exists(ExteriorFile)) { Interface.AddMessage(MessageType.Error, true, "The supplied train folder in TrackFollowingObject " + FileName + " did not contain a complete set of data."); return; } TrainDatParser.ParseTrainData(TrainData, TextEncoding.GetSystemEncodingFromFile(TrainData), Train); SoundCfgParser.ParseSoundConfig(TrainDirectory, Train); Train.AI = new Game.TrackFollowingObjectAI(Train, Data); UnifiedObject[] CarObjects = new UnifiedObject[Train.Cars.Length]; UnifiedObject[] BogieObjects = new UnifiedObject[Train.Cars.Length * 2]; UnifiedObject[] CouplerObjects = new UnifiedObject[Train.Cars.Length - 1]; ExtensionsCfgParser.ParseExtensionsConfig(System.IO.Path.GetDirectoryName(ExteriorFile), TextEncoding.GetSystemEncodingFromFile(ExteriorFile), ref CarObjects, ref BogieObjects, ref CouplerObjects, Train, true); int currentBogieObject = 0; for (int i = 0; i < Train.Cars.Length; i++) { if (CarObjects[i] == null) { // load default exterior object string file = OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("Compatibility"), "exterior.csv"); StaticObject so = ObjectManager.LoadStaticObject(file, System.Text.Encoding.UTF8, false); if (so == null) { CarObjects[i] = null; } else { double sx = Train.Cars[i].Width; double sy = Train.Cars[i].Height; double sz = Train.Cars[i].Length; so.ApplyScale(sx, sy, sz); CarObjects[i] = so; } } if (CarObjects[i] != null) { // add object Train.Cars[i].LoadCarSections(CarObjects[i]); } //Load bogie objects if (BogieObjects[currentBogieObject] != null) { Train.Cars[i].FrontBogie.LoadCarSections(BogieObjects[currentBogieObject]); } currentBogieObject++; if (BogieObjects[currentBogieObject] != null) { Train.Cars[i].RearBogie.LoadCarSections(BogieObjects[currentBogieObject]); } currentBogieObject++; } // door open/close speed foreach (var Car in Train.Cars) { if (Car.Specs.DoorOpenFrequency <= 0.0) { if (Car.Doors[0].OpenSound.Buffer != null & Car.Doors[1].OpenSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].OpenSound.Buffer); Program.Sounds.LoadBuffer(Car.Doors[1].OpenSound.Buffer); double a = Car.Doors[0].OpenSound.Buffer.Duration; double b = Car.Doors[1].OpenSound.Buffer.Duration; Car.Specs.DoorOpenFrequency = a + b > 0.0 ? 2.0 / (a + b) : 0.8; } else if (Car.Doors[0].OpenSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].OpenSound.Buffer); double a = Car.Doors[0].OpenSound.Buffer.Duration; Car.Specs.DoorOpenFrequency = a > 0.0 ? 1.0 / a : 0.8; } else if (Car.Doors[1].OpenSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].OpenSound.Buffer); double b = Car.Doors[1].OpenSound.Buffer.Duration; Car.Specs.DoorOpenFrequency = b > 0.0 ? 1.0 / b : 0.8; } else { Car.Specs.DoorOpenFrequency = 0.8; } } if (Car.Specs.DoorCloseFrequency <= 0.0) { if (Car.Doors[0].CloseSound.Buffer != null & Car.Doors[1].CloseSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].CloseSound.Buffer); Program.Sounds.LoadBuffer(Car.Doors[1].CloseSound.Buffer); double a = Car.Doors[0].CloseSound.Buffer.Duration; double b = Car.Doors[1].CloseSound.Buffer.Duration; Car.Specs.DoorCloseFrequency = a + b > 0.0 ? 2.0 / (a + b) : 0.8; } else if (Car.Doors[0].CloseSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].CloseSound.Buffer); double a = Car.Doors[0].CloseSound.Buffer.Duration; Car.Specs.DoorCloseFrequency = a > 0.0 ? 1.0 / a : 0.8; } else if (Car.Doors[1].CloseSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].CloseSound.Buffer); double b = Car.Doors[1].CloseSound.Buffer.Duration; Car.Specs.DoorCloseFrequency = b > 0.0 ? 1.0 / b : 0.8; } else { Car.Specs.DoorCloseFrequency = 0.8; } } const double f = 0.015; const double g = 2.75; Car.Specs.DoorOpenPitch = Math.Exp(f * Math.Tan(g * (Program.RandomNumberGenerator.NextDouble() - 0.5))); Car.Specs.DoorClosePitch = Math.Exp(f * Math.Tan(g * (Program.RandomNumberGenerator.NextDouble() - 0.5))); Car.Specs.DoorOpenFrequency /= Car.Specs.DoorOpenPitch; Car.Specs.DoorCloseFrequency /= Car.Specs.DoorClosePitch; /* * Remove the following two lines, then the pitch at which doors play * takes their randomized opening and closing times into account. * */ Car.Specs.DoorOpenPitch = 1.0; Car.Specs.DoorClosePitch = 1.0; } foreach (var Car in Train.Cars) { Car.FrontAxle.Follower.TrackIndex = Data[0].RailIndex; Car.RearAxle.Follower.TrackIndex = Data[0].RailIndex; Car.FrontBogie.FrontAxle.Follower.TrackIndex = Data[0].RailIndex; Car.FrontBogie.RearAxle.Follower.TrackIndex = Data[0].RailIndex; Car.RearBogie.FrontAxle.Follower.TrackIndex = Data[0].RailIndex; Car.RearBogie.RearAxle.Follower.TrackIndex = Data[0].RailIndex; } if (consistReversed) { Train.Reverse(); } Train.PlaceCars(Data[0].StopPosition); }
/// <summary>Parses a base track following object node</summary> /// <param name="ObjectPath">Absolute path to the object folder of route data</param> /// <param name="FileName">The filename of the containing XML file</param> /// <param name="SectionElement">The XElement to parse</param> /// <param name="Train">The track following object to parse this node into</param> private static void ParseTrackFollowingObjectNode(string ObjectPath, string FileName, XElement SectionElement, TrainManager.TrackFollowingObject Train) { string Section = SectionElement.Name.LocalName; string TrainDirectory = string.Empty; bool ConsistReversed = false; List <Game.TravelData> Data = new List <Game.TravelData>(); foreach (XElement KeyNode in SectionElement.Elements()) { string Key = KeyNode.Name.LocalName; int LineNumber = ((IXmlLineInfo)KeyNode).LineNumber; switch (Key.ToLowerInvariant()) { case "definition": ParseDefinitionNode(FileName, KeyNode, Train); break; case "train": ParseTrainNode(ObjectPath, FileName, KeyNode, ref TrainDirectory, ref ConsistReversed); break; case "points": case "stops": ParseTravelDataNodes(FileName, KeyNode, Data); break; default: Interface.AddMessage(MessageType.Warning, false, $"Unsupported key {Key} encountered in {Section} at line {LineNumber.ToString(culture)} in {FileName}"); break; } } if (Data.Count < 2) { Interface.AddMessage(MessageType.Error, false, $"There must be at least two points to go through in {FileName}"); return; } if (!(Data.First() is Game.TravelStopData) || !(Data.Last() is Game.TravelStopData)) { Interface.AddMessage(MessageType.Error, false, $"The first and the last point to go through must be the \"Stop\" node in {FileName}"); return; } if (string.IsNullOrEmpty(TrainDirectory)) { Interface.AddMessage(MessageType.Error, false, $"No train has been specified in {FileName}"); return; } /* * First check for a train.ai file- Functionally identical, but allows for differently configured AI * trains not to show up as drivable */ string TrainData = OpenBveApi.Path.CombineFile(TrainDirectory, "train.ai"); if (!File.Exists(TrainData)) { // Check for the standard drivable train.dat TrainData = OpenBveApi.Path.CombineFile(TrainDirectory, "train.dat"); } string ExteriorFile = OpenBveApi.Path.CombineFile(TrainDirectory, "extensions.cfg"); if (!File.Exists(TrainData) || !File.Exists(ExteriorFile)) { Interface.AddMessage(MessageType.Error, true, $"The supplied train folder in TrackFollowingObject {FileName} did not contain a complete set of data."); return; } TrainDatParser.ParseTrainData(TrainData, TextEncoding.GetSystemEncodingFromFile(TrainData), Train); SoundCfgParser.ParseSoundConfig(TrainDirectory, Train); Train.AI = new Game.TrackFollowingObjectAI(Train, Data.ToArray()); UnifiedObject[] CarObjects = new UnifiedObject[Train.Cars.Length]; UnifiedObject[] BogieObjects = new UnifiedObject[Train.Cars.Length * 2]; UnifiedObject[] CouplerObjects = new UnifiedObject[Train.Cars.Length - 1]; bool[] VisibleFromInterior = new bool[Train.Cars.Length]; ExtensionsCfgParser.ParseExtensionsConfig(System.IO.Path.GetDirectoryName(ExteriorFile), TextEncoding.GetSystemEncodingFromFile(ExteriorFile), ref CarObjects, ref BogieObjects, ref CouplerObjects, ref VisibleFromInterior, Train, true); int currentBogieObject = 0; for (int i = 0; i < Train.Cars.Length; i++) { if (CarObjects[i] == null) { // load default exterior object string file = OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("Compatibility"), "exterior.csv"); StaticObject so; Program.CurrentHost.LoadStaticObject(file, Encoding.UTF8, false, out so); if (so == null) { CarObjects[i] = null; } else { so.ApplyScale(Train.Cars[i].Width, Train.Cars[i].Height, Train.Cars[i].Length); CarObjects[i] = so; } } if (CarObjects[i] != null) { // add object Train.Cars[i].LoadCarSections(CarObjects[i], false); } //Load bogie objects if (BogieObjects[currentBogieObject] != null) { Train.Cars[i].FrontBogie.LoadCarSections(BogieObjects[currentBogieObject], false); } currentBogieObject++; if (BogieObjects[currentBogieObject] != null) { Train.Cars[i].RearBogie.LoadCarSections(BogieObjects[currentBogieObject], false); } currentBogieObject++; } // door open/close speed foreach (var Car in Train.Cars) { if (Car.Specs.DoorOpenFrequency <= 0.0) { if (Car.Doors[0].OpenSound.Buffer != null & Car.Doors[1].OpenSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].OpenSound.Buffer); Program.Sounds.LoadBuffer(Car.Doors[1].OpenSound.Buffer); double a = Car.Doors[0].OpenSound.Buffer.Duration; double b = Car.Doors[1].OpenSound.Buffer.Duration; Car.Specs.DoorOpenFrequency = a + b > 0.0 ? 2.0 / (a + b) : 0.8; } else if (Car.Doors[0].OpenSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].OpenSound.Buffer); double a = Car.Doors[0].OpenSound.Buffer.Duration; Car.Specs.DoorOpenFrequency = a > 0.0 ? 1.0 / a : 0.8; } else if (Car.Doors[1].OpenSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].OpenSound.Buffer); double b = Car.Doors[1].OpenSound.Buffer.Duration; Car.Specs.DoorOpenFrequency = b > 0.0 ? 1.0 / b : 0.8; } else { Car.Specs.DoorOpenFrequency = 0.8; } } if (Car.Specs.DoorCloseFrequency <= 0.0) { if (Car.Doors[0].CloseSound.Buffer != null & Car.Doors[1].CloseSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].CloseSound.Buffer); Program.Sounds.LoadBuffer(Car.Doors[1].CloseSound.Buffer); double a = Car.Doors[0].CloseSound.Buffer.Duration; double b = Car.Doors[1].CloseSound.Buffer.Duration; Car.Specs.DoorCloseFrequency = a + b > 0.0 ? 2.0 / (a + b) : 0.8; } else if (Car.Doors[0].CloseSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].CloseSound.Buffer); double a = Car.Doors[0].CloseSound.Buffer.Duration; Car.Specs.DoorCloseFrequency = a > 0.0 ? 1.0 / a : 0.8; } else if (Car.Doors[1].CloseSound.Buffer != null) { Program.Sounds.LoadBuffer(Car.Doors[0].CloseSound.Buffer); double b = Car.Doors[1].CloseSound.Buffer.Duration; Car.Specs.DoorCloseFrequency = b > 0.0 ? 1.0 / b : 0.8; } else { Car.Specs.DoorCloseFrequency = 0.8; } } const double f = 0.015; const double g = 2.75; Car.Specs.DoorOpenPitch = Math.Exp(f * Math.Tan(g * (Program.RandomNumberGenerator.NextDouble() - 0.5))); Car.Specs.DoorClosePitch = Math.Exp(f * Math.Tan(g * (Program.RandomNumberGenerator.NextDouble() - 0.5))); Car.Specs.DoorOpenFrequency /= Car.Specs.DoorOpenPitch; Car.Specs.DoorCloseFrequency /= Car.Specs.DoorClosePitch; /* * Remove the following two lines, then the pitch at which doors play * takes their randomized opening and closing times into account. * */ Car.Specs.DoorOpenPitch = 1.0; Car.Specs.DoorClosePitch = 1.0; } foreach (var Car in Train.Cars) { Car.FrontAxle.Follower.TrackIndex = Data[0].RailIndex; Car.RearAxle.Follower.TrackIndex = Data[0].RailIndex; Car.FrontBogie.FrontAxle.Follower.TrackIndex = Data[0].RailIndex; Car.FrontBogie.RearAxle.Follower.TrackIndex = Data[0].RailIndex; Car.RearBogie.FrontAxle.Follower.TrackIndex = Data[0].RailIndex; Car.RearBogie.RearAxle.Follower.TrackIndex = Data[0].RailIndex; } if (ConsistReversed) { Train.Reverse(); } Train.PlaceCars(Data[0].Position); }
/// <summary> /// Function to parse TFO definition /// </summary> /// <param name="FileName">The filename of the containing XML file</param> /// <param name="SectionElement">The XElement to parse</param> /// <param name="Train">The track following object to parse this node into</param> private static void ParseDefinitionNode(string FileName, XElement SectionElement, TrainManager.TrackFollowingObject Train) { string Section = SectionElement.Name.LocalName; foreach (XElement KeyNode in SectionElement.Elements()) { string Key = KeyNode.Name.LocalName; string Value = KeyNode.Value; int LineNumber = ((IXmlLineInfo)KeyNode).LineNumber; switch (Key.ToLowerInvariant()) { case "appearancetime": if (Value.Any() && !Interface.TryParseTime(Value, out Train.AppearanceTime)) { Interface.AddMessage(MessageType.Error, false, $"Value is invalid in {Key} in {Section} at line {LineNumber.ToString(culture)} in {FileName}"); } break; case "appearancestartposition": if (Value.Any() && !NumberFormats.TryParseDoubleVb6(Value, out Train.AppearanceStartPosition)) { Interface.AddMessage(MessageType.Error, false, $"Value is invalid in {Key} in {Section} at line {LineNumber.ToString(culture)} in {FileName}"); } break; case "appearanceendposition": if (Value.Any() && !NumberFormats.TryParseDoubleVb6(Value, out Train.AppearanceEndPosition)) { Interface.AddMessage(MessageType.Error, false, $"Value is invalid in {Key} in {Section} at line {LineNumber.ToString(culture)} in {FileName}"); } break; case "leavetime": if (Value.Any() && !Interface.TryParseTime(Value, out Train.LeaveTime)) { Interface.AddMessage(MessageType.Error, false, $"Value is invalid in {Key} in {Section} at line {LineNumber.ToString(culture)} in {FileName}"); } break; default: Interface.AddMessage(MessageType.Warning, false, $"Unsupported key {Key} encountered in {Section} at line {LineNumber.ToString(culture)} in {FileName}"); break; } } }