From 1f61b150d329838f9e5dd46f7362608e6ffb4eff Mon Sep 17 00:00:00 2001 From: Hirek Date: Thu, 12 Feb 2026 00:42:51 +0100 Subject: [PATCH] Add trainset parser --- StarterNG/Classes/Trainset.cs | 87 +++++++++++++++++------------- StarterNG/Views/Scenarios.axaml | 12 ++--- StarterNG/Views/Scenarios.axaml.cs | 65 ++++++++++++++++++++++ 3 files changed, 120 insertions(+), 44 deletions(-) diff --git a/StarterNG/Classes/Trainset.cs b/StarterNG/Classes/Trainset.cs index 9679db9..12ab3dd 100644 --- a/StarterNG/Classes/Trainset.cs +++ b/StarterNG/Classes/Trainset.cs @@ -9,8 +9,8 @@ namespace StarterNG.Classes; public enum eDriverType { Headdriver, - Reardriver, - Passanger, + Reardriver, + Passenger, Nobody } @@ -20,57 +20,69 @@ public class Trainset public string Track; public float Offset; public float Velocity; + public string Description; public List Vehicles; public Trainset(string trainsetEntry) { Vehicles = new List(); - + Description = ""; List tokens = Regex - .Matches(trainsetEntry, @"\r\n|\n|[^\s]+") + .Matches(trainsetEntry, @"/\*[\s\S]*?\*/|//[^\r\n]*|[^\s\r\n]+") .Select(m => m.Value) + .Where(t => !t.StartsWith("//") && !t.StartsWith("/*")) .ToList(); + // handle specific descriptions + // //$o + var match = Regex.Match( + trainsetEntry, + @"^\s*//\$o\s*(.*)$", + RegexOptions.Multiline + ); + if (match.Success) + this.Description = match.Groups[1].Value.Trim(); + + // parse trainset + for (int i = 0; i < tokens.Count; i++) { - // handle comments - if (tokens[i].StartsWith("//")) - { - while (tokens[i] != "\n" && tokens.Count < i) i++; - } - - + if (tokens[i] == "endtrainset") + break; + // trainset properties if (tokens[i] == "trainset") { - i++; - this.Name = tokens[i++]; - this.Track = tokens[i++]; - this.Offset = float.Parse(tokens[i++], CultureInfo.InvariantCulture); - this.Velocity = float.Parse(tokens[i++], CultureInfo.InvariantCulture); + this.Name = tokens[++i]; + this.Track = tokens[++i]; + this.Offset = float.Parse(tokens[++i], CultureInfo.InvariantCulture); + this.Velocity = float.Parse(tokens[++i], CultureInfo.InvariantCulture); continue; } - + // skip entire assignments block if (tokens[i] == "assignments") { - while (tokens[i] != "endassignment") i++; + while (i < tokens.Count && tokens[i] != "endassignment") + i++; + i++; // jump over endassignment + continue; } // load vehicles if (tokens[i] == "node") { - i++; // node keyword + //i++; // node keyword Dynamic nodeDynamic = new Dynamic(); - nodeDynamic.RangeMax = float.Parse(tokens[i++], CultureInfo.InvariantCulture); - nodeDynamic.RangeMin = float.Parse(tokens[i++], CultureInfo.InvariantCulture); - nodeDynamic.Name = tokens[i++]; + nodeDynamic.RangeMax = float.Parse(tokens[++i], CultureInfo.InvariantCulture); + nodeDynamic.RangeMin = float.Parse(tokens[++i], CultureInfo.InvariantCulture); + nodeDynamic.Name = tokens[++i]; i++; // dynamic keyword - nodeDynamic.DataFolder = tokens[i++]; - nodeDynamic.SkinFile = tokens[i++]; - nodeDynamic.MmdFile = tokens[i++]; - i++; // skip offset - string driverType = tokens[i++]; + nodeDynamic.DataFolder = tokens[++i]; + nodeDynamic.SkinFile = tokens[++i]; + nodeDynamic.MmdFile = tokens[++i]; + nodeDynamic.Offset = float.Parse(tokens[++i], CultureInfo.InvariantCulture); + string driverType = tokens[++i]; switch (driverType) { case "headdriver": @@ -79,22 +91,22 @@ public class Trainset case "reardriver": nodeDynamic.DriverType = eDriverType.Reardriver; break; - case "passanger": - nodeDynamic.DriverType = eDriverType.Passanger; + case "passenger": + nodeDynamic.DriverType = eDriverType.Passenger; break; default: nodeDynamic.DriverType = eDriverType.Nobody; break; } - nodeDynamic.couplingData = (byte)int.Parse(tokens[i++].Split('.')[0]); + nodeDynamic.couplingData = (byte)int.Parse(tokens[++i].Split('.')[0]); nodeDynamic.Tail = ""; - while (tokens[i] != "enddynamic") + while (i + 1 < tokens.Count && tokens[i + 1] != "enddynamic") { - nodeDynamic.Tail += " " + tokens[i]; - i++; + nodeDynamic.Tail += " " + tokens[++i]; } + i++; // jump over enddynamic Vehicles.Add(nodeDynamic); } @@ -122,8 +134,8 @@ public class Trainset case eDriverType.Reardriver: driverType = "reardriver"; break; - case eDriverType.Passanger: - driverType = "passanger"; + case eDriverType.Passenger: + driverType = "passenger"; break; default: driverType = "nobody"; @@ -131,7 +143,10 @@ public class Trainset } entry += - $"node {vehicle.RangeMax} {vehicle.RangeMin} {vehicle.Name} dynamic {vehicle.DataFolder} {vehicle.SkinFile} {vehicle.MmdFile} {driverType} {vehicle.couplingData} {vehicle.Tail}\n"; + $"node {vehicle.RangeMax} {vehicle.RangeMin} {vehicle.Name} dynamic " + + $"{vehicle.DataFolder} {vehicle.SkinFile} {vehicle.MmdFile} " + + $"{vehicle.Offset.ToString(CultureInfo.InvariantCulture)} " + + $"{driverType} {vehicle.couplingData}{vehicle.Tail} enddynamic\n"; } diff --git a/StarterNG/Views/Scenarios.axaml b/StarterNG/Views/Scenarios.axaml index aa41533..5340a71 100644 --- a/StarterNG/Views/Scenarios.axaml +++ b/StarterNG/Views/Scenarios.axaml @@ -17,20 +17,17 @@ - + - - EP07P-2003 - EP07-1049 - EN57AKM-1562 + - + Prowadzimy RegioExpress RPE23119 z Dębicy do Sandomierza. Misję rozpoczynamy w lokomotywowni w Dębicy. Jedziemy zgodnie ze wskazaniami tarcz manewrowych na bocznicę z drugiej strony stacji, gdzie przypinamy wagony do naszego składu, po czym wciągamy je w peron. Następnie jedziemy do Sandomierza, zatrzymując się po drodze tylko w Turowie. Po przyjeździe do Sandomierza odczepiamy lokomotywę i zjeżdżamy na tor postojowy, gdzie kończymy służbę. Czas misji: ok. 80 minut. @@ -41,8 +38,7 @@ - - + diff --git a/StarterNG/Views/Scenarios.axaml.cs b/StarterNG/Views/Scenarios.axaml.cs index 0da08ae..4852c6f 100644 --- a/StarterNG/Views/Scenarios.axaml.cs +++ b/StarterNG/Views/Scenarios.axaml.cs @@ -4,6 +4,7 @@ using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; using StarterNG.Classes; namespace StarterNG.Views; @@ -66,8 +67,72 @@ public partial class Scenarios : UserControl Tag = i }); } + } + private void SceneryList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + var item = sceneryList.SelectedItem as TreeViewItem; + if (item?.Tag is not int tag) + return; + Scenery selectedScn = sceneries[tag]; + vehicleList.Items.Clear(); + // add all trainsets to list + for (int i = 0; i < selectedScn.Trainsets.Count; i++) + { + string trainsetName = string.Join( + " + ", + selectedScn.Trainsets[i].Vehicles.Select(dyn => dyn.Name) + ); + if (string.IsNullOrWhiteSpace(trainsetName)) continue; // skip empty + vehicleList.Items.Add(new ListBoxItem + { + Content = trainsetName, + Tag = i + }); + } + + } + + private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + consistStack.Children.Clear(); + // get selected scenery + var tItem = sceneryList.SelectedItem as TreeViewItem; + if (tItem?.Tag is not int tTag) + return; + Scenery selectedScn = sceneries[tTag]; + + // get selected trainset + ListBoxItem vItem = vehicleList.SelectedItem as ListBoxItem; + if (vItem?.Tag is not int vTag) + return; + Trainset selectedTrainset = selectedScn.Trainsets[vTag]; + foreach (var train in selectedTrainset.Vehicles) + { + string path = Path.Combine( + "textures", + "mini", + train.SkinFile + ".bmp" + ); + + if (!File.Exists(path)) + { + // fallback + continue; + } + + var bitmap = new Bitmap(path); + var image = new Image + { + Source = bitmap, + Height = 45, + Stretch = Avalonia.Media.Stretch.Uniform, + Margin = new Thickness(0) + }; + consistStack.Children.Add(image); + } + missionDescription.Text = selectedTrainset.Description; } } \ No newline at end of file