mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-01 20:34:57 +01:00
172 lines
6.3 KiB
C#
172 lines
6.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using StarterNG.Application.Abstractions;
|
|
using StarterNG.Classes;
|
|
using StarterNG.Domain;
|
|
using StarterNG.Domain.Settings;
|
|
using StarterNG.Domain.Vehicles;
|
|
using StarterNG.Infrastructure.Adapters;
|
|
using StarterNG.Infrastructure.Settings;
|
|
|
|
namespace StarterNG.Application;
|
|
|
|
public enum SimulationStartOutcome
|
|
{
|
|
Started,
|
|
|
|
NothingSelected,
|
|
|
|
ExportFailed,
|
|
|
|
ExecutableProblem,
|
|
|
|
LaunchFailed
|
|
}
|
|
|
|
public readonly record struct SimulationStartResult(
|
|
SimulationStartOutcome Outcome,
|
|
IProcessHandle? Process = null,
|
|
string ExecutablePath = "",
|
|
ExeProblem Problem = ExeProblem.None,
|
|
string? Detail = null);
|
|
|
|
public sealed class StartSimulation
|
|
{
|
|
private const char ExportPrefix = '$';
|
|
|
|
|
|
private readonly AppState _state;
|
|
private readonly SettingsStore _settings;
|
|
private readonly VehicleCatalog _vehicles;
|
|
private readonly ExecutableLocator _executables;
|
|
private readonly IFileSystem _files;
|
|
private readonly IGamePaths _paths;
|
|
private readonly IProcessLauncher _processes;
|
|
private readonly IRandomSource _random;
|
|
private readonly IDiagnosticsLog _log;
|
|
|
|
public StartSimulation(AppState state, SettingsStore settings, VehicleCatalog vehicles,
|
|
ExecutableLocator executables, IFileSystem files, IGamePaths paths,
|
|
IProcessLauncher processes, IRandomSource random, IDiagnosticsLog log)
|
|
{
|
|
_state = state;
|
|
_settings = settings;
|
|
_vehicles = vehicles;
|
|
_executables = executables;
|
|
_files = files;
|
|
_paths = paths;
|
|
_processes = processes;
|
|
_random = random;
|
|
_log = log;
|
|
}
|
|
|
|
public static string? StartableVehicle(Trainset? trainset, string? preferred)
|
|
{
|
|
if (trainset is null || trainset.Vehicles.Count == 0)
|
|
return null;
|
|
|
|
static bool CanStart(Dynamic vehicle) =>
|
|
vehicle.DriverType is eDriverType.Headdriver or eDriverType.Reardriver or eDriverType.Passenger;
|
|
|
|
if (!string.IsNullOrEmpty(preferred))
|
|
{
|
|
var picked = trainset.Vehicles.FirstOrDefault(vehicle =>
|
|
string.Equals(vehicle.Name, preferred, StringComparison.OrdinalIgnoreCase));
|
|
if (picked is not null && CanStart(picked))
|
|
return picked.Name;
|
|
}
|
|
|
|
return trainset.Vehicles.FirstOrDefault(CanStart)?.Name;
|
|
}
|
|
|
|
public SimulationStartResult Execute(bool freeFly, bool saveSettings)
|
|
{
|
|
var scenery = _state.CurrentScenery;
|
|
var trainset = _state.CurrentTrainset;
|
|
if (scenery is null || trainset is null)
|
|
return new SimulationStartResult(SimulationStartOutcome.NothingSelected);
|
|
|
|
// The depot checkbox is the consist's own state; this setting is a blanket
|
|
// override for people who always want the same thing, and it wins.
|
|
switch (_settings.Settings.ReadyOverride)
|
|
{
|
|
case ConsistReadyOverride.AlwaysCold:
|
|
trainset.ReadyToGo = false;
|
|
break;
|
|
case ConsistReadyOverride.AlwaysReady:
|
|
trainset.ReadyToGo = true;
|
|
break;
|
|
}
|
|
|
|
string? vehicle = TrainsetDisplay.UniquifyForLaunch(trainset, scenery, _state.StartingVehicleName, _vehicles)
|
|
?? StartableVehicle(trainset, _state.StartingVehicleName);
|
|
_state.StartingVehicleName = vehicle;
|
|
|
|
scenery.Weather.Dirty = true;
|
|
|
|
string exportName = ExportPrefix + Path.GetFileName(scenery.Path);
|
|
string exportPath = Path.Combine(Path.GetDirectoryName(scenery.Path) ?? "scenery", exportName);
|
|
try
|
|
{
|
|
_files.WriteAllText(exportPath,
|
|
scenery.BuildExportContent(_settings.Settings.IgnoreIrrelevantTrains, _random),
|
|
LegacyText.CodePage1250);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_log.Log($"writing {exportPath}", ex);
|
|
return new SimulationStartResult(SimulationStartOutcome.ExportFailed, Detail: ex.Message,
|
|
ExecutablePath: exportPath);
|
|
}
|
|
|
|
if (saveSettings)
|
|
_settings.CaptureAndSave();
|
|
|
|
string[] arguments = freeFly || string.IsNullOrEmpty(vehicle)
|
|
? new[] { "-s", exportName }
|
|
: new[] { "-s", exportName, "-v", vehicle };
|
|
|
|
return Launch(arguments);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the selected scenery in the simulator's editor. The scenery file itself,
|
|
/// not an export: what the editor saves has to land in the scenery.
|
|
/// </summary>
|
|
public SimulationStartResult ExecuteEditor()
|
|
{
|
|
if (_state.CurrentScenery is not { } scenery)
|
|
return new SimulationStartResult(SimulationStartOutcome.NothingSelected);
|
|
|
|
return Launch(new[] { "-edit", Path.GetFileName(scenery.Path) });
|
|
}
|
|
|
|
private SimulationStartResult Launch(IReadOnlyList<string> arguments)
|
|
{
|
|
string executable = Path.GetFullPath(_settings.ResolveExecutable(out var problem));
|
|
|
|
// A binary for the other system is let through: on Linux a Windows build
|
|
// runs under Wine, started below, or Proton, and only the launch can tell.
|
|
if (problem is not (ExeProblem.None or ExeProblem.WrongPlatform))
|
|
return new SimulationStartResult(SimulationStartOutcome.ExecutableProblem, ExecutablePath: executable,
|
|
Problem: problem);
|
|
|
|
// The installation, not the folder of the binary: a simulator picked from a
|
|
// build tree lives beside the data, and the data is what it opens.
|
|
var (program, programArguments) = _executables.LaunchCommand(executable, arguments);
|
|
// Its error stream is kept for a report on a crash - unless the starter closes
|
|
// now, leaving nobody to read it or to report.
|
|
bool watched = !_settings.Settings.AutoCloseStarter;
|
|
var process = _processes.Start(program, programArguments, _paths.Root, watched, out string? error);
|
|
if (process is null)
|
|
return new SimulationStartResult(SimulationStartOutcome.LaunchFailed, ExecutablePath: executable,
|
|
Detail: error);
|
|
|
|
return new SimulationStartResult(SimulationStartOutcome.Started, process, executable);
|
|
}
|
|
|
|
}
|