Add trainset parser

This commit is contained in:
2026-02-12 00:42:51 +01:00
parent a9c30bbdfe
commit 1f61b150d3
3 changed files with 120 additions and 44 deletions

View File

@@ -9,8 +9,8 @@ namespace StarterNG.Classes;
public enum eDriverType public enum eDriverType
{ {
Headdriver, Headdriver,
Reardriver, Reardriver,
Passanger, Passenger,
Nobody Nobody
} }
@@ -20,57 +20,69 @@ public class Trainset
public string Track; public string Track;
public float Offset; public float Offset;
public float Velocity; public float Velocity;
public string Description;
public List<Dynamic> Vehicles; public List<Dynamic> Vehicles;
public Trainset(string trainsetEntry) public Trainset(string trainsetEntry)
{ {
Vehicles = new List<Dynamic>(); Vehicles = new List<Dynamic>();
Description = "";
List<string> tokens = Regex List<string> tokens = Regex
.Matches(trainsetEntry, @"\r\n|\n|[^\s]+") .Matches(trainsetEntry, @"/\*[\s\S]*?\*/|//[^\r\n]*|[^\s\r\n]+")
.Select(m => m.Value) .Select(m => m.Value)
.Where(t => !t.StartsWith("//") && !t.StartsWith("/*"))
.ToList(); .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++) for (int i = 0; i < tokens.Count; i++)
{ {
// handle comments if (tokens[i] == "endtrainset")
if (tokens[i].StartsWith("//")) break;
{
while (tokens[i] != "\n" && tokens.Count < i) i++;
}
// trainset properties // trainset properties
if (tokens[i] == "trainset") if (tokens[i] == "trainset")
{ {
i++; this.Name = tokens[++i];
this.Name = tokens[i++]; this.Track = tokens[++i];
this.Track = tokens[i++]; this.Offset = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
this.Offset = float.Parse(tokens[i++], CultureInfo.InvariantCulture); this.Velocity = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
this.Velocity = float.Parse(tokens[i++], CultureInfo.InvariantCulture);
continue; continue;
} }
// skip entire assignments block // skip entire assignments block
if (tokens[i] == "assignments") if (tokens[i] == "assignments")
{ {
while (tokens[i] != "endassignment") i++; while (i < tokens.Count && tokens[i] != "endassignment")
i++;
i++; // jump over endassignment
continue;
} }
// load vehicles // load vehicles
if (tokens[i] == "node") if (tokens[i] == "node")
{ {
i++; // node keyword //i++; // node keyword
Dynamic nodeDynamic = new Dynamic(); Dynamic nodeDynamic = new Dynamic();
nodeDynamic.RangeMax = float.Parse(tokens[i++], CultureInfo.InvariantCulture); nodeDynamic.RangeMax = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
nodeDynamic.RangeMin = float.Parse(tokens[i++], CultureInfo.InvariantCulture); nodeDynamic.RangeMin = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
nodeDynamic.Name = tokens[i++]; nodeDynamic.Name = tokens[++i];
i++; // dynamic keyword i++; // dynamic keyword
nodeDynamic.DataFolder = tokens[i++]; nodeDynamic.DataFolder = tokens[++i];
nodeDynamic.SkinFile = tokens[i++]; nodeDynamic.SkinFile = tokens[++i];
nodeDynamic.MmdFile = tokens[i++]; nodeDynamic.MmdFile = tokens[++i];
i++; // skip offset nodeDynamic.Offset = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
string driverType = tokens[i++]; string driverType = tokens[++i];
switch (driverType) switch (driverType)
{ {
case "headdriver": case "headdriver":
@@ -79,22 +91,22 @@ public class Trainset
case "reardriver": case "reardriver":
nodeDynamic.DriverType = eDriverType.Reardriver; nodeDynamic.DriverType = eDriverType.Reardriver;
break; break;
case "passanger": case "passenger":
nodeDynamic.DriverType = eDriverType.Passanger; nodeDynamic.DriverType = eDriverType.Passenger;
break; break;
default: default:
nodeDynamic.DriverType = eDriverType.Nobody; nodeDynamic.DriverType = eDriverType.Nobody;
break; break;
} }
nodeDynamic.couplingData = (byte)int.Parse(tokens[i++].Split('.')[0]); nodeDynamic.couplingData = (byte)int.Parse(tokens[++i].Split('.')[0]);
nodeDynamic.Tail = ""; nodeDynamic.Tail = "";
while (tokens[i] != "enddynamic") while (i + 1 < tokens.Count && tokens[i + 1] != "enddynamic")
{ {
nodeDynamic.Tail += " " + tokens[i]; nodeDynamic.Tail += " " + tokens[++i];
i++;
} }
i++; // jump over enddynamic
Vehicles.Add(nodeDynamic); Vehicles.Add(nodeDynamic);
} }
@@ -122,8 +134,8 @@ public class Trainset
case eDriverType.Reardriver: case eDriverType.Reardriver:
driverType = "reardriver"; driverType = "reardriver";
break; break;
case eDriverType.Passanger: case eDriverType.Passenger:
driverType = "passanger"; driverType = "passenger";
break; break;
default: default:
driverType = "nobody"; driverType = "nobody";
@@ -131,7 +143,10 @@ public class Trainset
} }
entry += 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";
} }

View File

@@ -17,20 +17,17 @@
<RowDefinition Height="40" /> <RowDefinition Height="40" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<sukiUi:GroupBox Grid.RowSpan="2" Header="Scenerie"> <sukiUi:GroupBox Grid.RowSpan="2" Header="Scenerie">
<TreeView Name="sceneryList"> <TreeView Name="sceneryList" SelectionChanged="SceneryList_OnSelectionChanged">
</TreeView> </TreeView>
</sukiUi:GroupBox> </sukiUi:GroupBox>
<sukiUi:GroupBox Grid.Column="1" Header="Pojazdy"> <sukiUi:GroupBox Grid.Column="1" Header="Pojazdy">
<ListBox> <ListBox Name="vehicleList" SelectionChanged="VehicleList_OnSelectionChanged">
<ListBoxItem>EP07P-2003</ListBoxItem>
<ListBoxItem>EP07-1049</ListBoxItem>
<ListBoxItem>EN57AKM-1562</ListBoxItem>
</ListBox> </ListBox>
</sukiUi:GroupBox> </sukiUi:GroupBox>
<sukiUi:GroupBox Grid.Column="1" Grid.Row="1" Header="Opis scenariusza"> <sukiUi:GroupBox Grid.Column="1" Grid.Row="1" Header="Opis scenariusza">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock TextWrapping="Wrap"> <TextBlock TextWrapping="Wrap" Name="missionDescription">
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. 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.
</TextBlock> </TextBlock>
</ScrollViewer> </ScrollViewer>
@@ -41,8 +38,7 @@
<ScrollViewer VerticalAlignment="Bottom" <ScrollViewer VerticalAlignment="Bottom"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled"> VerticalScrollBarVisibility="Disabled">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal" Name="consistStack">
<Image Source="avares://StarterNG/Assets/Test/ep07p.bmp" Height="50"></Image>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</sukiUi:GroupBox> </sukiUi:GroupBox>

View File

@@ -4,6 +4,7 @@ using System.Linq;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using StarterNG.Classes; using StarterNG.Classes;
namespace StarterNG.Views; namespace StarterNG.Views;
@@ -66,8 +67,72 @@ public partial class Scenarios : UserControl
Tag = i 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;
} }
} }