mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-07-17 17:09:19 +02:00
consist editor improvements
This commit is contained in:
@@ -1,10 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Markup.Xaml.Styling;
|
using Avalonia.Markup.Xaml.Styling;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using StarterNG.Classes;
|
||||||
using StarterNG.Services;
|
using StarterNG.Services;
|
||||||
|
|
||||||
namespace StarterNG;
|
namespace StarterNG;
|
||||||
@@ -54,7 +58,30 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
desktop.MainWindow = new MainWindow();
|
// Code page 1250 is needed to parse scenery files (done during load).
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
|
||||||
|
var splash = new SplashWindow();
|
||||||
|
desktop.MainWindow = splash;
|
||||||
|
splash.Show();
|
||||||
|
|
||||||
|
// Progress<T> marshals callbacks back to this (UI) thread.
|
||||||
|
var progress = new Progress<LoadStatus>(splash.Report);
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
GameData.Instance.Load(progress);
|
||||||
|
await Task.Delay(400); // let the completed bar be visible briefly
|
||||||
|
}).ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
var main = new MainWindow();
|
||||||
|
desktop.MainWindow = main; // keep a main window before closing the splash
|
||||||
|
main.Show();
|
||||||
|
splash.Close();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
|||||||
@@ -33,11 +33,36 @@
|
|||||||
<x:String x:Key="CatDMU">DMU</x:String>
|
<x:String x:Key="CatDMU">DMU</x:String>
|
||||||
<x:String x:Key="CatCarriagesA">Carriages A</x:String>
|
<x:String x:Key="CatCarriagesA">Carriages A</x:String>
|
||||||
<x:String x:Key="CatCarriagesB">Carriages B</x:String>
|
<x:String x:Key="CatCarriagesB">Carriages B</x:String>
|
||||||
|
<x:String x:Key="CatElectricLoco">Electric locomotive</x:String>
|
||||||
|
<x:String x:Key="CatDieselLoco">Diesel locomotive</x:String>
|
||||||
|
<x:String x:Key="CatSteamLoco">Steam locomotive</x:String>
|
||||||
|
<x:String x:Key="CatRailbus">Railbus</x:String>
|
||||||
|
<x:String x:Key="CatDraisine">Draisine</x:String>
|
||||||
|
<x:String x:Key="CatWork">Work vehicles</x:String>
|
||||||
|
<x:String x:Key="CatPrototype">Prototype</x:String>
|
||||||
|
<x:String x:Key="CatTram">Tram</x:String>
|
||||||
|
<x:String x:Key="CatCar">Car</x:String>
|
||||||
|
<x:String x:Key="CatBus">Bus</x:String>
|
||||||
|
<x:String x:Key="CatTruck">Truck</x:String>
|
||||||
|
<x:String x:Key="CatPeople">People</x:String>
|
||||||
|
<x:String x:Key="CatAnimals">Animals</x:String>
|
||||||
|
<x:String x:Key="CatWagons">Wagons</x:String>
|
||||||
|
<x:String x:Key="CatOther">Other</x:String>
|
||||||
<x:String x:Key="Brakes">Brakes</x:String>
|
<x:String x:Key="Brakes">Brakes</x:String>
|
||||||
<x:String x:Key="Loads">Loads</x:String>
|
<x:String x:Key="Loads">Loads</x:String>
|
||||||
<x:String x:Key="Coupling">Coupling</x:String>
|
<x:String x:Key="Coupling">Coupling</x:String>
|
||||||
<x:String x:Key="SelectVehicleHint">Select a vehicle in the consist to edit it.</x:String>
|
<x:String x:Key="SelectVehicleHint">Select a vehicle in the consist to edit it.</x:String>
|
||||||
<x:String x:Key="ComingSoon">Editing options coming soon.</x:String>
|
<x:String x:Key="ComingSoon">Editing options coming soon.</x:String>
|
||||||
|
<x:String x:Key="Split">Split</x:String>
|
||||||
|
<x:String x:Key="TipSplit">Split the unit into individual cars</x:String>
|
||||||
|
<x:String x:Key="AddVehicle">Add</x:String>
|
||||||
|
<x:String x:Key="TipAddVehicle">Add this vehicle to the consist</x:String>
|
||||||
|
<x:String x:Key="TipAddUnit">Add the whole unit to the consist</x:String>
|
||||||
|
|
||||||
|
<!-- Loading splash -->
|
||||||
|
<x:String x:Key="LoadingVehicles">Loading vehicle database</x:String>
|
||||||
|
<x:String x:Key="LoadingSceneries">Loading sceneries</x:String>
|
||||||
|
<x:String x:Key="LoadingDone">Starting…</x:String>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<x:String x:Key="General">General</x:String>
|
<x:String x:Key="General">General</x:String>
|
||||||
|
|||||||
@@ -33,11 +33,36 @@
|
|||||||
<x:String x:Key="CatDMU">SZT</x:String>
|
<x:String x:Key="CatDMU">SZT</x:String>
|
||||||
<x:String x:Key="CatCarriagesA">Wagony A</x:String>
|
<x:String x:Key="CatCarriagesA">Wagony A</x:String>
|
||||||
<x:String x:Key="CatCarriagesB">Wagony B</x:String>
|
<x:String x:Key="CatCarriagesB">Wagony B</x:String>
|
||||||
|
<x:String x:Key="CatElectricLoco">Lokomotywa elektryczna</x:String>
|
||||||
|
<x:String x:Key="CatDieselLoco">Lokomotywa spalinowa</x:String>
|
||||||
|
<x:String x:Key="CatSteamLoco">Parowóz</x:String>
|
||||||
|
<x:String x:Key="CatRailbus">Szynobus</x:String>
|
||||||
|
<x:String x:Key="CatDraisine">Drezyna</x:String>
|
||||||
|
<x:String x:Key="CatWork">Pojazdy robocze</x:String>
|
||||||
|
<x:String x:Key="CatPrototype">Prototyp</x:String>
|
||||||
|
<x:String x:Key="CatTram">Tramwaj</x:String>
|
||||||
|
<x:String x:Key="CatCar">Samochód osobowy</x:String>
|
||||||
|
<x:String x:Key="CatBus">Autobus</x:String>
|
||||||
|
<x:String x:Key="CatTruck">Samochód ciężarowy</x:String>
|
||||||
|
<x:String x:Key="CatPeople">Ludzie</x:String>
|
||||||
|
<x:String x:Key="CatAnimals">Zwierzęta</x:String>
|
||||||
|
<x:String x:Key="CatWagons">Wagony</x:String>
|
||||||
|
<x:String x:Key="CatOther">Inne</x:String>
|
||||||
<x:String x:Key="Brakes">Hamulce</x:String>
|
<x:String x:Key="Brakes">Hamulce</x:String>
|
||||||
<x:String x:Key="Loads">Ładunki</x:String>
|
<x:String x:Key="Loads">Ładunki</x:String>
|
||||||
<x:String x:Key="Coupling">Sprzęg</x:String>
|
<x:String x:Key="Coupling">Sprzęg</x:String>
|
||||||
<x:String x:Key="SelectVehicleHint">Wybierz pojazd w składzie, aby go edytować.</x:String>
|
<x:String x:Key="SelectVehicleHint">Wybierz pojazd w składzie, aby go edytować.</x:String>
|
||||||
<x:String x:Key="ComingSoon">Opcje edycji wkrótce.</x:String>
|
<x:String x:Key="ComingSoon">Opcje edycji wkrótce.</x:String>
|
||||||
|
<x:String x:Key="Split">Rozdziel</x:String>
|
||||||
|
<x:String x:Key="TipSplit">Rozdziel zespół na pojedyncze człony</x:String>
|
||||||
|
<x:String x:Key="AddVehicle">Dodaj</x:String>
|
||||||
|
<x:String x:Key="TipAddVehicle">Dodaj ten pojazd do składu</x:String>
|
||||||
|
<x:String x:Key="TipAddUnit">Dodaj cały zespół do składu</x:String>
|
||||||
|
|
||||||
|
<!-- Ekran ładowania -->
|
||||||
|
<x:String x:Key="LoadingVehicles">Wczytywanie bazy pojazdów</x:String>
|
||||||
|
<x:String x:Key="LoadingSceneries">Wczytywanie scenerii</x:String>
|
||||||
|
<x:String x:Key="LoadingDone">Uruchamianie…</x:String>
|
||||||
|
|
||||||
<!-- Ustawienia -->
|
<!-- Ustawienia -->
|
||||||
<x:String x:Key="General">Ogólne</x:String>
|
<x:String x:Key="General">Ogólne</x:String>
|
||||||
|
|||||||
15
StarterNG/Classes/AppState.cs
Normal file
15
StarterNG/Classes/AppState.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace StarterNG.Classes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shared selection state across views: the scenery and trainset currently in
|
||||||
|
/// focus. The depot edits this trainset in place, so changes made in the depot
|
||||||
|
/// are reflected on the scenery (both views share the same Scenery/Trainset
|
||||||
|
/// objects from <see cref="GameData"/>).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AppState
|
||||||
|
{
|
||||||
|
public static AppState Instance { get; } = new();
|
||||||
|
|
||||||
|
public Scenery? CurrentScenery { get; set; }
|
||||||
|
public Trainset? CurrentTrainset { get; set; }
|
||||||
|
}
|
||||||
83
StarterNG/Classes/GameData.cs
Normal file
83
StarterNG/Classes/GameData.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace StarterNG.Classes;
|
||||||
|
|
||||||
|
public enum LoadPhase
|
||||||
|
{
|
||||||
|
Vehicles,
|
||||||
|
Sceneries,
|
||||||
|
Done
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A single progress update emitted while the game data loads.</summary>
|
||||||
|
public readonly record struct LoadStatus(double Fraction, LoadPhase Phase, string? Detail);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads and holds the data shared across the launcher (vehicle database and
|
||||||
|
/// parsed sceneries) so it is parsed once, at startup, behind the splash —
|
||||||
|
/// instead of inside each view's constructor.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GameData
|
||||||
|
{
|
||||||
|
public static GameData Instance { get; } = new();
|
||||||
|
|
||||||
|
public VehicleDatabase Vehicles { get; } = new();
|
||||||
|
public List<Scenery> Sceneries { get; } = new();
|
||||||
|
|
||||||
|
public bool Loaded { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the vehicle database and all sceneries, reporting progress.
|
||||||
|
/// Runs synchronously; call it from a background thread. Never throws.
|
||||||
|
/// </summary>
|
||||||
|
public void Load(IProgress<LoadStatus>? progress = null,
|
||||||
|
string vehiclesDir = "databases/vehicles/",
|
||||||
|
string sceneryDir = "scenery/")
|
||||||
|
{
|
||||||
|
if (Loaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var vehicleFiles = VehicleDatabase.EnumerateFiles(vehiclesDir);
|
||||||
|
var scnFiles = EnumerateScenery(sceneryDir);
|
||||||
|
int total = Math.Max(1, vehicleFiles.Count + scnFiles.Count);
|
||||||
|
int done = 0;
|
||||||
|
|
||||||
|
// Phase 1 - vehicle database
|
||||||
|
Vehicles.BeginLoad();
|
||||||
|
foreach (string file in vehicleFiles)
|
||||||
|
{
|
||||||
|
progress?.Report(new LoadStatus((double)done / total, LoadPhase.Vehicles,
|
||||||
|
Path.GetFileName(file)));
|
||||||
|
try { Vehicles.LoadFile(file); }
|
||||||
|
catch { /* skip malformed file */ }
|
||||||
|
done++;
|
||||||
|
}
|
||||||
|
Vehicles.EndLoad();
|
||||||
|
|
||||||
|
// Phase 2 - sceneries
|
||||||
|
foreach (string file in scnFiles)
|
||||||
|
{
|
||||||
|
progress?.Report(new LoadStatus((double)done / total, LoadPhase.Sceneries,
|
||||||
|
Path.GetFileName(file)));
|
||||||
|
try { Sceneries.Add(new Scenery(file)); }
|
||||||
|
catch { /* skip unreadable scenery */ }
|
||||||
|
done++;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress?.Report(new LoadStatus(1.0, LoadPhase.Done, null));
|
||||||
|
Loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> EnumerateScenery(string sceneryDir)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(sceneryDir))
|
||||||
|
return new List<string>();
|
||||||
|
|
||||||
|
return Directory.GetFiles(sceneryDir, "*.scn")
|
||||||
|
.Where(p => !Path.GetFileName(p).StartsWith("$"))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,11 @@ public class Scenery
|
|||||||
public List<Trainset> Trainsets;
|
public List<Trainset> Trainsets;
|
||||||
public string Group;
|
public string Group;
|
||||||
public string Path;
|
public string Path;
|
||||||
|
|
||||||
|
// The file content with each trainset block replaced by a {{i}} placeholder,
|
||||||
|
// used to rebuild the .scn on export.
|
||||||
|
private readonly string _template;
|
||||||
|
|
||||||
public Scenery(string path)
|
public Scenery(string path)
|
||||||
{
|
{
|
||||||
this.Path = path;
|
this.Path = path;
|
||||||
@@ -19,7 +24,7 @@ public class Scenery
|
|||||||
throw new FileNotFoundException(path);
|
throw new FileNotFoundException(path);
|
||||||
var encoding = Encoding.GetEncoding(1250); // Windows-1250
|
var encoding = Encoding.GetEncoding(1250); // Windows-1250
|
||||||
string content = File.ReadAllText(path, encoding);
|
string content = File.ReadAllText(path, encoding);
|
||||||
|
|
||||||
// property scanning
|
// property scanning
|
||||||
var match = Regex.Match(
|
var match = Regex.Match(
|
||||||
content,
|
content,
|
||||||
@@ -27,9 +32,9 @@ public class Scenery
|
|||||||
RegexOptions.Multiline
|
RegexOptions.Multiline
|
||||||
);
|
);
|
||||||
this.Group = match.Success ? match.Groups[1].Value : null;
|
this.Group = match.Success ? match.Groups[1].Value : null;
|
||||||
|
|
||||||
|
|
||||||
// parsing trainsets
|
// parsing trainsets
|
||||||
List<string> trainsetEntries = new List<string>();
|
List<string> trainsetEntries = new List<string>();
|
||||||
Regex regex = new Regex(
|
Regex regex = new Regex(
|
||||||
@"trainset\b[\s\S]*?\bendtrainset\b",
|
@"trainset\b[\s\S]*?\bendtrainset\b",
|
||||||
@@ -41,18 +46,23 @@ public class Scenery
|
|||||||
trainsetEntries.Add(match.Value);
|
trainsetEntries.Add(match.Value);
|
||||||
return $"{{{{{idx++}}}}}";
|
return $"{{{{{idx++}}}}}";
|
||||||
});
|
});
|
||||||
|
_template = content;
|
||||||
|
|
||||||
|
// 1:1 with placeholders - the Trainset ctor never throws (unparsable
|
||||||
|
// blocks are kept verbatim), so indices stay aligned for export.
|
||||||
foreach (string trainsetEntry in trainsetEntries)
|
foreach (string trainsetEntry in trainsetEntries)
|
||||||
{
|
Trainsets.Add(new Trainset(trainsetEntry));
|
||||||
try
|
}
|
||||||
{
|
|
||||||
Trainsets.Add(new Trainset(trainsetEntry));
|
/// <summary>
|
||||||
}
|
/// Rebuilds the full .scn content with the (possibly modified) trainsets
|
||||||
catch
|
/// substituted back into their original positions.
|
||||||
{
|
/// </summary>
|
||||||
// skip malformed trainset entries so one bad scenery
|
public string BuildExportContent()
|
||||||
// doesn't break loading the rest
|
{
|
||||||
}
|
string result = _template;
|
||||||
}
|
for (int i = 0; i < Trainsets.Count; i++)
|
||||||
|
result = result.Replace("{{" + i + "}}", Trainsets[i].ToSceneryEntry());
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
51
StarterNG/Classes/Settings.cs
Normal file
51
StarterNG/Classes/Settings.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
namespace StarterNG.Classes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application settings backing the Settings view. Load/Save are stubs for now;
|
||||||
|
/// the real persistence will be implemented later.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Settings
|
||||||
|
{
|
||||||
|
public static Settings Instance { get; } = new();
|
||||||
|
|
||||||
|
// General
|
||||||
|
public string Language = "English";
|
||||||
|
public bool Fullscreen;
|
||||||
|
public bool PauseWhenInactive;
|
||||||
|
public bool PauseOnStart;
|
||||||
|
public int CursorSensitivity = 3;
|
||||||
|
public bool InvertMouseHorizontal;
|
||||||
|
public bool InvertMouseVertical;
|
||||||
|
|
||||||
|
// Other
|
||||||
|
public string ExecutablePath = "eu07.exe"; // game executable used to launch
|
||||||
|
public bool SelectExeAutomatically = true;
|
||||||
|
public bool DebugMode;
|
||||||
|
public bool VirtualShunting;
|
||||||
|
|
||||||
|
// Graphics (subset)
|
||||||
|
public string Resolution = "1280x720";
|
||||||
|
public bool VSync = true;
|
||||||
|
public bool RenderShadows = true;
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
public bool SoundEnabled = true;
|
||||||
|
public int Volume = 100;
|
||||||
|
|
||||||
|
// Starter
|
||||||
|
public bool AutoCloseStarter;
|
||||||
|
public bool LargeThumbnails;
|
||||||
|
public bool AutoExpandSceneryTree;
|
||||||
|
|
||||||
|
/// <summary>Loads settings from disk. TODO: implement persistence.</summary>
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
// intentionally empty for now
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Persists settings to disk. TODO: implement persistence.</summary>
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
// intentionally empty for now
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,11 +22,31 @@ public class Trainset
|
|||||||
public float Velocity;
|
public float Velocity;
|
||||||
public string Description;
|
public string Description;
|
||||||
public List<Dynamic> Vehicles;
|
public List<Dynamic> Vehicles;
|
||||||
|
|
||||||
|
/// <summary>Original .scn text of this trainset block (incl. endtrainset).</summary>
|
||||||
|
public string RawEntry;
|
||||||
|
|
||||||
|
/// <summary>True if the block parsed cleanly; false blocks are exported verbatim.</summary>
|
||||||
|
public bool Parsed;
|
||||||
|
|
||||||
public Trainset(string trainsetEntry)
|
public Trainset(string trainsetEntry)
|
||||||
{
|
{
|
||||||
|
RawEntry = trainsetEntry;
|
||||||
Vehicles = new List<Dynamic>();
|
Vehicles = new List<Dynamic>();
|
||||||
Description = "";
|
Description = "";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ParseEntry(trainsetEntry);
|
||||||
|
Parsed = true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Parsed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseEntry(string trainsetEntry)
|
||||||
|
{
|
||||||
List<string> tokens = Regex
|
List<string> tokens = Regex
|
||||||
.Matches(trainsetEntry, @"/\*[\s\S]*?\*/|//[^\r\n]*|[^\s\r\n]+")
|
.Matches(trainsetEntry, @"/\*[\s\S]*?\*/|//[^\r\n]*|[^\s\r\n]+")
|
||||||
.Select(m => m.Value)
|
.Select(m => m.Value)
|
||||||
@@ -113,6 +133,13 @@ public class Trainset
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The .scn text for this trainset for export. Unparsed blocks are written
|
||||||
|
/// back verbatim; parsed blocks are regenerated from the current vehicles.
|
||||||
|
/// </summary>
|
||||||
|
public string ToSceneryEntry() =>
|
||||||
|
Parsed ? GetTrainsetEntry() + "endtrainset\n" : RawEntry;
|
||||||
|
|
||||||
public string GetTrainsetEntry()
|
public string GetTrainsetEntry()
|
||||||
{
|
{
|
||||||
string entry = "";
|
string entry = "";
|
||||||
@@ -120,8 +147,8 @@ public class Trainset
|
|||||||
entry += "trainset ";
|
entry += "trainset ";
|
||||||
entry += this.Name + " ";
|
entry += this.Name + " ";
|
||||||
entry += this.Track + " ";
|
entry += this.Track + " ";
|
||||||
entry += this.Offset + " ";
|
entry += this.Offset.ToString(CultureInfo.InvariantCulture) + " ";
|
||||||
entry += this.Velocity + " ";
|
entry += this.Velocity.ToString(CultureInfo.InvariantCulture) + " ";
|
||||||
entry += "\n";
|
entry += "\n";
|
||||||
foreach (Dynamic vehicle in Vehicles)
|
foreach (Dynamic vehicle in Vehicles)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ public class VehicleDatabase
|
|||||||
/// <summary>Texture-uuid -> automatic-consist set that contains it.</summary>
|
/// <summary>Texture-uuid -> automatic-consist set that contains it.</summary>
|
||||||
public Dictionary<string, VehicleSet> SetByTextureUuid { get; } = new();
|
public Dictionary<string, VehicleSet> SetByTextureUuid { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>Texture-uuid -> texture (includes wrecks, so set refs resolve).</summary>
|
||||||
|
public Dictionary<string, VehicleTexture> TextureByUuid { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>skinfile (without extension, lower-case) -> texture.</summary>
|
||||||
|
public Dictionary<string, VehicleTexture> TextureBySkin { get; } = new();
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions JsonOpts = new()
|
private static readonly JsonSerializerOptions JsonOpts = new()
|
||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true,
|
PropertyNameCaseInsensitive = true,
|
||||||
@@ -102,32 +108,55 @@ public class VehicleDatabase
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the database. When a merged vehicles.json is present it is used,
|
/// Loads the database in one call. When a merged vehicles.json is present
|
||||||
/// otherwise every per-vehicle *.json file in the directory is read.
|
/// it is used, otherwise every per-vehicle *.json file is read.
|
||||||
/// Never throws: unreadable files are skipped.
|
/// Never throws: unreadable files are skipped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Load(string directory = "databases/vehicles/")
|
public void Load(string directory = "databases/vehicles/")
|
||||||
|
{
|
||||||
|
BeginLoad();
|
||||||
|
foreach (string file in EnumerateFiles(directory))
|
||||||
|
LoadFile(file);
|
||||||
|
EndLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Resets the aggregate before an incremental load.</summary>
|
||||||
|
public void BeginLoad()
|
||||||
{
|
{
|
||||||
Textures.Clear();
|
Textures.Clear();
|
||||||
GroupsById.Clear();
|
GroupsById.Clear();
|
||||||
Sets.Clear();
|
Sets.Clear();
|
||||||
SetByTextureUuid.Clear();
|
SetByTextureUuid.Clear();
|
||||||
|
TextureByUuid.Clear();
|
||||||
|
TextureBySkin.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Finalises an incremental load (builds lookup indexes).</summary>
|
||||||
|
public void EndLoad() => BuildSetIndex();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Files that make up the database: the merged vehicles.json if present,
|
||||||
|
/// otherwise every per-vehicle *.json. Used to drive load progress.
|
||||||
|
/// </summary>
|
||||||
|
public static List<string> EnumerateFiles(string directory = "databases/vehicles/")
|
||||||
|
{
|
||||||
if (!Directory.Exists(directory))
|
if (!Directory.Exists(directory))
|
||||||
return;
|
return new List<string>();
|
||||||
|
|
||||||
string merged = Path.Combine(directory, "vehicles.json");
|
string merged = Path.Combine(directory, "vehicles.json");
|
||||||
if (File.Exists(merged))
|
if (File.Exists(merged))
|
||||||
{
|
return new List<string> { merged };
|
||||||
LoadMerged(merged);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (string file in Directory.GetFiles(directory, "*.json"))
|
|
||||||
LoadEntryFile(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildSetIndex();
|
return Directory.GetFiles(directory, "*.json").ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ingests a single database file (merged or per-vehicle).</summary>
|
||||||
|
public void LoadFile(string file)
|
||||||
|
{
|
||||||
|
if (string.Equals(Path.GetFileName(file), "vehicles.json", StringComparison.OrdinalIgnoreCase))
|
||||||
|
LoadMerged(file);
|
||||||
|
else
|
||||||
|
LoadEntryFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadMerged(string file)
|
private void LoadMerged(string file)
|
||||||
@@ -171,6 +200,12 @@ public class VehicleDatabase
|
|||||||
|
|
||||||
foreach (var texture in entry.Textures)
|
foreach (var texture in entry.Textures)
|
||||||
{
|
{
|
||||||
|
// index every texture (wrecks too) so set / skin references resolve
|
||||||
|
if (!string.IsNullOrEmpty(texture.Uuid))
|
||||||
|
TextureByUuid[texture.Uuid!] = texture;
|
||||||
|
if (!string.IsNullOrEmpty(texture.Skinfile))
|
||||||
|
TextureBySkin.TryAdd(Path.GetFileNameWithoutExtension(texture.Skinfile).ToLowerInvariant(), texture);
|
||||||
|
|
||||||
// skip wrecks from the standard browser list
|
// skip wrecks from the standard browser list
|
||||||
if (texture.Wreck) continue;
|
if (texture.Wreck) continue;
|
||||||
Textures.Add(texture);
|
Textures.Add(texture);
|
||||||
@@ -179,6 +214,25 @@ public class VehicleDatabase
|
|||||||
Sets.AddRange(entry.Sets);
|
Sets.AddRange(entry.Sets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the texture belongs to an automatic-consist set, returns all of that
|
||||||
|
/// set's textures in their defined order; otherwise null.
|
||||||
|
/// </summary>
|
||||||
|
public List<VehicleTexture>? ResolveSet(VehicleTexture texture)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(texture.Uuid)) return null;
|
||||||
|
if (!SetByTextureUuid.TryGetValue(texture.Uuid!, out var set) || set.TextureRefs is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var cars = new List<VehicleTexture>();
|
||||||
|
foreach (string uuid in set.TextureRefs)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(uuid) && TextureByUuid.TryGetValue(uuid, out var tex))
|
||||||
|
cars.Add(tex);
|
||||||
|
}
|
||||||
|
return cars.Count > 0 ? cars : null;
|
||||||
|
}
|
||||||
|
|
||||||
private void BuildSetIndex()
|
private void BuildSetIndex()
|
||||||
{
|
{
|
||||||
foreach (var set in Sets)
|
foreach (var set in Sets)
|
||||||
@@ -192,14 +246,36 @@ public class VehicleDatabase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Best miniature name for a texture (texture_mini -> mini_ref -> group mini).</summary>
|
/// <summary>
|
||||||
|
/// Miniature name for a texture: the texture_mini property, but if no .bmp
|
||||||
|
/// for it exists, falls back to the mini of the group it belongs to.
|
||||||
|
/// </summary>
|
||||||
public string? ResolveMiniName(VehicleTexture texture)
|
public string? ResolveMiniName(VehicleTexture texture)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(texture.TextureMini)) return texture.TextureMini;
|
if (!string.IsNullOrEmpty(texture.TextureMini) && MiniPath(texture.TextureMini) != null)
|
||||||
if (!string.IsNullOrEmpty(texture.MiniRef)) return texture.MiniRef;
|
return texture.TextureMini;
|
||||||
if (texture.Group is not null && GroupsById.TryGetValue(texture.Group, out var grp))
|
|
||||||
|
if (texture.Group != null && GroupsById.TryGetValue(texture.Group, out var grp)
|
||||||
|
&& !string.IsNullOrEmpty(grp.Mini))
|
||||||
return grp.Mini;
|
return grp.Mini;
|
||||||
return null;
|
|
||||||
|
return texture.TextureMini;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Resolved mini for a skin file (matched case-insensitively), or null.</summary>
|
||||||
|
public string? MiniForSkin(string? skinFile)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(skinFile)) return null;
|
||||||
|
string key = Path.GetFileNameWithoutExtension(skinFile).ToLowerInvariant();
|
||||||
|
return TextureBySkin.TryGetValue(key, out var tex) ? ResolveMiniName(tex) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The texture for a skin file (matched case-insensitively), or null.</summary>
|
||||||
|
public VehicleTexture? TextureForSkin(string? skinFile)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(skinFile)) return null;
|
||||||
|
string key = Path.GetFileNameWithoutExtension(skinFile).ToLowerInvariant();
|
||||||
|
return TextureBySkin.TryGetValue(key, out var tex) ? tex : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Header label for the group a texture belongs to.</summary>
|
/// <summary>Header label for the group a texture belongs to.</summary>
|
||||||
@@ -213,12 +289,30 @@ public class VehicleDatabase
|
|||||||
return groupId ?? "";
|
return groupId ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Resolves a miniature .bmp path under textures/mini/, or null if missing.</summary>
|
// Case-insensitive index of mini .bmp files (built once).
|
||||||
|
private static Dictionary<string, string>? _miniIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves a miniature .bmp path under textures/mini/ case-insensitively,
|
||||||
|
/// or null if missing. The directory is indexed once and reused.
|
||||||
|
/// </summary>
|
||||||
public static string? MiniPath(string? miniName, string miniDir = "textures/mini/")
|
public static string? MiniPath(string? miniName, string miniDir = "textures/mini/")
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(miniName)) return null;
|
if (string.IsNullOrEmpty(miniName)) return null;
|
||||||
string path = Path.Combine(miniDir, miniName + ".bmp");
|
|
||||||
return File.Exists(path) ? path : null;
|
var index = _miniIndex ??= BuildMiniIndex(miniDir);
|
||||||
|
return index.TryGetValue(miniName!.ToLowerInvariant(), out var path) ? path : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> BuildMiniIndex(string miniDir)
|
||||||
|
{
|
||||||
|
var index = new Dictionary<string, string>();
|
||||||
|
if (!Directory.Exists(miniDir))
|
||||||
|
return index;
|
||||||
|
|
||||||
|
foreach (string file in Directory.GetFiles(miniDir, "*.bmp"))
|
||||||
|
index[Path.GetFileNameWithoutExtension(file).ToLowerInvariant()] = file;
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
StarterNG/SplashWindow.axaml
Normal file
25
StarterNG/SplashWindow.axaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:starterNg="clr-namespace:StarterNG"
|
||||||
|
x:Class="StarterNG.SplashWindow"
|
||||||
|
SystemDecorations="None"
|
||||||
|
CanResize="False"
|
||||||
|
ShowInTaskbar="True"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
Width="520" Height="300"
|
||||||
|
Background="#15171B">
|
||||||
|
<Grid>
|
||||||
|
<Image Source="/Assets/Logo/splash.png" Stretch="UniformToFill" />
|
||||||
|
|
||||||
|
<Border VerticalAlignment="Bottom" Background="#CC0E0F12" Padding="18,12">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<TextBlock x:Name="statusText"
|
||||||
|
Foreground="#EEEEEE" FontSize="12"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
Text="{Binding [LoadingVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ProgressBar x:Name="progressBar"
|
||||||
|
Minimum="0" Maximum="1" Value="0" Height="6" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
30
StarterNG/SplashWindow.axaml.cs
Normal file
30
StarterNG/SplashWindow.axaml.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using StarterNG.Classes;
|
||||||
|
|
||||||
|
namespace StarterNG;
|
||||||
|
|
||||||
|
public partial class SplashWindow : Window
|
||||||
|
{
|
||||||
|
public SplashWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Updates the bar and status line. Must be called on the UI thread.</summary>
|
||||||
|
public void Report(LoadStatus status)
|
||||||
|
{
|
||||||
|
progressBar.Value = status.Fraction;
|
||||||
|
statusText.Text = Format(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Format(LoadStatus s)
|
||||||
|
{
|
||||||
|
string detail = string.IsNullOrEmpty(s.Detail) ? "" : $": {s.Detail}";
|
||||||
|
return s.Phase switch
|
||||||
|
{
|
||||||
|
LoadPhase.Vehicles => App.Loc["LoadingVehicles"] + detail,
|
||||||
|
LoadPhase.Sceneries => App.Loc["LoadingSceneries"] + detail,
|
||||||
|
_ => App.Loc["LoadingDone"]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,8 @@
|
|||||||
<AvaloniaResource Include="Assets\Logo\maszyna_white.svg" />
|
<AvaloniaResource Include="Assets\Logo\maszyna_white.svg" />
|
||||||
<None Remove="Assets\Logo\Other\Discord.svg" />
|
<None Remove="Assets\Logo\Other\Discord.svg" />
|
||||||
<AvaloniaResource Include="Assets\Logo\Other\Discord.svg" />
|
<AvaloniaResource Include="Assets\Logo\Other\Discord.svg" />
|
||||||
|
<None Remove="Assets\Logo\splash.png" />
|
||||||
|
<AvaloniaResource Include="Assets\Logo\splash.png" />
|
||||||
<None Remove="Assets\Test\ep07p.bmp" />
|
<None Remove="Assets\Test\ep07p.bmp" />
|
||||||
<AvaloniaResource Include="Assets\Test\ep07p.bmp" />
|
<AvaloniaResource Include="Assets\Test\ep07p.bmp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
xmlns:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
xmlns:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:starterNg="clr-namespace:StarterNG"
|
xmlns:starterNg="clr-namespace:StarterNG"
|
||||||
|
xmlns:views="clr-namespace:StarterNG.Views"
|
||||||
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
|
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
|
||||||
x:Class="StarterNG.Views.Depot">
|
x:Class="StarterNG.Views.Depot">
|
||||||
<Grid Margin="16" ColumnSpacing="16" RowSpacing="16">
|
<Grid Margin="16" ColumnSpacing="16" RowSpacing="16">
|
||||||
@@ -30,6 +31,8 @@
|
|||||||
<TextBox Name="searchBox" DockPanel.Dock="Top" Margin="0,0,0,8"
|
<TextBox Name="searchBox" DockPanel.Dock="Top" Margin="0,0,0,8"
|
||||||
Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
|
Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
|
||||||
TextChanged="SearchBox_OnTextChanged" />
|
TextChanged="SearchBox_OnTextChanged" />
|
||||||
|
<!-- Folded groups (built in code-behind); collapsed groups hold no
|
||||||
|
controls, so the list stays light and scrolls smoothly. -->
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
<StackPanel Name="browserStack" Spacing="2" />
|
<StackPanel Name="browserStack" Spacing="2" />
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
@@ -47,6 +50,7 @@
|
|||||||
<ComboBox Name="sceneryConsistCombo" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
|
<ComboBox Name="sceneryConsistCombo" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
|
||||||
HorizontalAlignment="Stretch" MaxDropDownHeight="420"
|
HorizontalAlignment="Stretch" MaxDropDownHeight="420"
|
||||||
PlaceholderText="{Binding [LoadFromScenery], Source={x:Static starterNg:App.Loc}}"
|
PlaceholderText="{Binding [LoadFromScenery], Source={x:Static starterNg:App.Loc}}"
|
||||||
|
DropDownOpened="SceneryConsistCombo_OnDropDownOpened"
|
||||||
SelectionChanged="SceneryConsistCombo_OnSelectionChanged" />
|
SelectionChanged="SceneryConsistCombo_OnSelectionChanged" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</sukiUi:GroupBox>
|
</sukiUi:GroupBox>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -45,8 +45,9 @@
|
|||||||
|
|
||||||
<Button Grid.Column="0" Height="36" Classes="Flat"
|
<Button Grid.Column="0" Height="36" Classes="Flat"
|
||||||
VerticalAlignment="Bottom" Grid.Row="3" Grid.ColumnSpan="2"
|
VerticalAlignment="Bottom" Grid.Row="3" Grid.ColumnSpan="2"
|
||||||
|
Click="StartButton_OnClick"
|
||||||
Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}">
|
Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using StarterNG.Classes;
|
using StarterNG.Classes;
|
||||||
@@ -16,20 +19,10 @@ public partial class Scenarios : UserControl
|
|||||||
public Scenarios()
|
public Scenarios()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
sceneries = new List<Scenery>();
|
|
||||||
|
// Sceneries are parsed once at startup (behind the splash).
|
||||||
// load sceneries
|
sceneries = GameData.Instance.Sceneries;
|
||||||
List<string> scnFiles = Directory.GetFiles("scenery/", "*.scn").ToList();
|
|
||||||
foreach (string scnFile in scnFiles)
|
|
||||||
{
|
|
||||||
// skip temp sceneries
|
|
||||||
if (Path.GetFileName(scnFile).StartsWith("$"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Parse all files
|
|
||||||
sceneries.Add(new Scenery(scnFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupNodes = new Dictionary<string, TreeViewItem>();
|
var groupNodes = new Dictionary<string, TreeViewItem>();
|
||||||
|
|
||||||
for (int i = 0; i < sceneries.Count; i++)
|
for (int i = 0; i < sceneries.Count; i++)
|
||||||
@@ -109,20 +102,23 @@ public partial class Scenarios : UserControl
|
|||||||
if (vItem?.Tag is not int vTag)
|
if (vItem?.Tag is not int vTag)
|
||||||
return;
|
return;
|
||||||
Trainset selectedTrainset = selectedScn.Trainsets[vTag];
|
Trainset selectedTrainset = selectedScn.Trainsets[vTag];
|
||||||
|
|
||||||
|
// share the selection so the depot edits this consist in place
|
||||||
|
AppState.Instance.CurrentScenery = selectedScn;
|
||||||
|
AppState.Instance.CurrentTrainset = selectedTrainset;
|
||||||
|
|
||||||
|
var db = GameData.Instance.Vehicles;
|
||||||
foreach (var train in selectedTrainset.Vehicles)
|
foreach (var train in selectedTrainset.Vehicles)
|
||||||
{
|
{
|
||||||
string path = Path.Combine(
|
// thumbnail name comes from the matching texture's texture_mini
|
||||||
"textures",
|
string miniName = db.MiniForSkin(train.SkinFile) ?? train.SkinFile;
|
||||||
"mini",
|
string? path = VehicleDatabase.MiniPath(miniName);
|
||||||
train.SkinFile + ".bmp"
|
if (path is null)
|
||||||
);
|
|
||||||
|
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
{
|
||||||
// fallback
|
// fallback
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bitmap = new Bitmap(path);
|
var bitmap = new Bitmap(path);
|
||||||
var image = new Image
|
var image = new Image
|
||||||
{
|
{
|
||||||
@@ -135,4 +131,46 @@ public partial class Scenarios : UserControl
|
|||||||
}
|
}
|
||||||
missionDescription.Text = selectedTrainset.Description;
|
missionDescription.Text = selectedTrainset.Description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exports the (possibly depot-modified) scenery to a $-prefixed copy and
|
||||||
|
// launches the game on it with the selected consist's driven vehicle.
|
||||||
|
private void StartButton_OnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var scenery = AppState.Instance.CurrentScenery;
|
||||||
|
if (scenery is null)
|
||||||
|
return;
|
||||||
|
var trainset = AppState.Instance.CurrentTrainset;
|
||||||
|
|
||||||
|
// write scenery/$<name>.scn with the replaced trainsets
|
||||||
|
string dir = System.IO.Path.GetDirectoryName(scenery.Path) ?? "scenery";
|
||||||
|
string exportName = "$" + System.IO.Path.GetFileName(scenery.Path);
|
||||||
|
string exportPath = System.IO.Path.Combine(dir, exportName);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(exportPath, scenery.BuildExportContent(), Encoding.GetEncoding(1250));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return; // couldn't write the scenery file
|
||||||
|
}
|
||||||
|
|
||||||
|
// the player's vehicle is the node name of the driven car in the consist
|
||||||
|
string? vehicle = trainset?.Vehicles
|
||||||
|
.FirstOrDefault(v => v.DriverType is eDriverType.Headdriver or eDriverType.Reardriver)?.Name
|
||||||
|
?? trainset?.Vehicles.FirstOrDefault()?.Name;
|
||||||
|
|
||||||
|
// launch the game: -s $<name>.scn -v <vehicle>
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(StarterNG.Classes.Settings.Instance.ExecutablePath)
|
||||||
|
{
|
||||||
|
Arguments = $"-s {exportName} -v {vehicle}",
|
||||||
|
UseShellExecute = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// executable not found / not configured yet
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user