From cef00c4e3c6a36bb356922753b103237f4799372 Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 4 Sep 2026 23:17:17 +0200 Subject: [PATCH] Move the battery option to the depot as a consist property It was a global preference that rewrote the trainset speed at launch, while what it really sets is whether this consist starts prepared or cold. Now a checkbox in the consist-scope section, acting on the consist being edited. --- StarterNG.Tests/TrainsetReadyTests.cs | 56 +++++++++++++++++++ StarterNG/Application/StartSimulation.cs | 17 ------ StarterNG/Classes/Trainset.cs | 23 ++++++++ .../Domain/Settings/SimulatorSettings.cs | 3 - .../Settings/SettingsSerializer.cs | 2 - StarterNG/Views/Depot/VehicleDetails.cs | 27 ++++++++- StarterNG/Views/Settings.axaml | 8 --- StarterNG/Views/Settings.axaml.cs | 2 - StarterNG/startercfg/lang/cz.xml | 6 +- StarterNG/startercfg/lang/en.xml | 7 +-- StarterNG/startercfg/lang/hu.xml | 6 +- StarterNG/startercfg/lang/pl.xml | 7 +-- StarterNG/startercfg/lang/ru.xml | 6 +- 13 files changed, 115 insertions(+), 55 deletions(-) create mode 100644 StarterNG.Tests/TrainsetReadyTests.cs diff --git a/StarterNG.Tests/TrainsetReadyTests.cs b/StarterNG.Tests/TrainsetReadyTests.cs new file mode 100644 index 0000000..f0fc00d --- /dev/null +++ b/StarterNG.Tests/TrainsetReadyTests.cs @@ -0,0 +1,56 @@ +using StarterNG.Classes; + +namespace StarterNG.Tests; + +public class TrainsetReadyTests +{ + private static Trainset Parse(string velocity) => + new($"trainset pociag tor_1 10 {velocity}\n" + + "node -1 -1 ep07-001 dynamic PKP/EP07 ep07 ep07.mmd 0 headdriver 0\n" + + "endtrainset"); + + [Fact] + public void A_consist_authored_standing_still_is_not_ready() + { + Assert.False(Parse("0").ReadyToGo); + } + + [Fact] + public void Marking_a_standing_consist_ready_gives_it_the_prepared_creep() + { + var trainset = Parse("0"); + + trainset.ReadyToGo = true; + + Assert.True(trainset.ReadyToGo); + Assert.Equal(0.1f, trainset.Velocity, 3); + } + + [Fact] + public void A_consist_authored_on_the_move_is_ready_and_keeps_its_speed() + { + var trainset = Parse("40"); + + Assert.True(trainset.ReadyToGo); + + trainset.ReadyToGo = false; + Assert.Equal(0f, trainset.Velocity); + + trainset.ReadyToGo = true; + Assert.Equal(40f, trainset.Velocity); + } + + [Fact] + public void A_consist_running_the_other_way_keeps_its_direction() + { + // The sign of the speed is what DynObj.cpp turns into the running + // direction, so restoring readiness must not flip it. + var trainset = Parse("-40"); + + trainset.ReadyToGo = false; + trainset.ReadyToGo = true; + + Assert.Equal(-40f, trainset.Velocity); + Assert.True(trainset.ReadyToGo); + } +} diff --git a/StarterNG/Application/StartSimulation.cs b/StarterNG/Application/StartSimulation.cs index fcfe53c..18697c5 100644 --- a/StarterNG/Application/StartSimulation.cs +++ b/StarterNG/Application/StartSimulation.cs @@ -34,7 +34,6 @@ public sealed class StartSimulation { private const char ExportPrefix = '$'; - private const float BatteryOnVelocity = 0.1f; private readonly AppState _state; private readonly SettingsStore _settings; @@ -80,8 +79,6 @@ public sealed class StartSimulation if (scenery is null || trainset is null) return new SimulationStartResult(SimulationStartOutcome.NothingSelected); - ApplyBatteryDefault(trainset); - string? vehicle = TrainsetDisplay.UniquifyForLaunch(trainset, scenery, _state.StartingVehicleName) ?? StartableVehicle(trainset, _state.StartingVehicleName); _state.StartingVehicleName = vehicle; @@ -123,18 +120,4 @@ public sealed class StartSimulation return new SimulationStartResult(SimulationStartOutcome.Started, process, executable); } - private void ApplyBatteryDefault(Trainset trainset) - { - if (trainset.Vehicles.Count == 0) - return; - - trainset.Velocity = _settings.Settings.BatteryDefault switch - { - BatteryDefault.AlwaysOff => 0f, - BatteryDefault.AlwaysOn => MathF.Abs(trainset.OriginalVelocity) < 0.01f - ? BatteryOnVelocity - : trainset.OriginalVelocity, - _ => trainset.OriginalVelocity - }; - } } diff --git a/StarterNG/Classes/Trainset.cs b/StarterNG/Classes/Trainset.cs index 2d106a2..47ed387 100644 --- a/StarterNG/Classes/Trainset.cs +++ b/StarterNG/Classes/Trainset.cs @@ -22,6 +22,29 @@ public class Trainset public float Offset; public float Velocity; public float OriginalVelocity; + + // The scenery format has no battery field: a trainset with a non-zero speed is + // the prepared one. DynObj.cpp derives ReadyFlag from it, and that branch of + // CheckLocomotiveParameters is what charges the pipes, sets the brake handle + // and closes the battery contactor. The sign carries the direction, so a + // consist authored on the move keeps its own speed. + private const float StandingStill = 0.01f; + private const float PreparedCreep = 0.1f; + + public bool ReadyToGo + { + get => MathF.Abs(Velocity) >= StandingStill; + set + { + if (!value) + { + Velocity = 0f; + return; + } + + Velocity = MathF.Abs(OriginalVelocity) < StandingStill ? PreparedCreep : OriginalVelocity; + } + } public string Description; public List Vehicles; diff --git a/StarterNG/Domain/Settings/SimulatorSettings.cs b/StarterNG/Domain/Settings/SimulatorSettings.cs index 2ba822b..4aa48c2 100644 --- a/StarterNG/Domain/Settings/SimulatorSettings.cs +++ b/StarterNG/Domain/Settings/SimulatorSettings.cs @@ -2,8 +2,6 @@ using System; namespace StarterNG.Domain.Settings; -public enum BatteryDefault { Default = 0, AlwaysOff = 1, AlwaysOn = 2 } - public enum ExeProblem { None, NotFound, NotExecutable, WrongPlatform } public sealed class SimulatorSettings @@ -107,7 +105,6 @@ public sealed class SimulatorSettings public bool AutoCloseStarter; public bool LargeThumbnails; public bool AutoExpandSceneryTree; - public BatteryDefault BatteryDefault = BatteryDefault.Default; public bool ShowAiVehicles = true; public bool DrivableOnly = true; diff --git a/StarterNG/Infrastructure/Settings/SettingsSerializer.cs b/StarterNG/Infrastructure/Settings/SettingsSerializer.cs index de02a80..38561aa 100644 --- a/StarterNG/Infrastructure/Settings/SettingsSerializer.cs +++ b/StarterNG/Infrastructure/Settings/SettingsSerializer.cs @@ -136,7 +136,6 @@ public sealed class SettingsSerializer s.AutoCloseStarter = c.GetBool("starter.autoclose", false); s.LargeThumbnails = c.GetBool("starter.largethumbnails", false); s.AutoExpandSceneryTree = c.GetBool("starter.expandtree", false); - s.BatteryDefault = (BatteryDefault)Clamp(c.GetInt("starter.batterydefault", 0), 0, 2); s.ShowAiVehicles = c.GetBool("starter.show.ai", true); s.DrivableOnly = c.GetBool("starter.drivableonly", true); s.ShowArchivalSceneries = c.GetBool("starter.show.archival", true); @@ -265,7 +264,6 @@ public sealed class SettingsSerializer c.SetBool("starter.autoclose", s.AutoCloseStarter); c.SetBool("starter.largethumbnails", s.LargeThumbnails); c.SetBool("starter.expandtree", s.AutoExpandSceneryTree); - c.SetInt("starter.batterydefault", (int)s.BatteryDefault); c.SetBool("starter.show.ai", s.ShowAiVehicles); c.SetBool("starter.drivableonly", s.DrivableOnly); c.SetBool("starter.show.archival", s.ShowArchivalSceneries); diff --git a/StarterNG/Views/Depot/VehicleDetails.cs b/StarterNG/Views/Depot/VehicleDetails.cs index 97d5c03..7fe8475 100644 --- a/StarterNG/Views/Depot/VehicleDetails.cs +++ b/StarterNG/Views/Depot/VehicleDetails.cs @@ -555,7 +555,7 @@ public sealed class VehicleDetails if (_consist.Count == 0) return; - var buttons = Stack( + var tools = Stack( LoadToolButton(App.Loc["ConsistRandomType"], () => { _cargo.RandomTypes(_consist); _redraw(); }, secondary: true), LoadToolButton(App.Loc["ConsistMaxAmount"], @@ -563,6 +563,8 @@ public sealed class VehicleDetails LoadToolButton(App.Loc["ConsistRandomAmount"], () => { _cargo.RandomAmounts(_consist); _redraw(); }, secondary: true)); + var buttons = Stack(ReadyToGoRow(), tools); + var count = new TextBlock { Text = $"{App.Loc["VehicleCount"]}: {_consist.Count}", @@ -574,6 +576,29 @@ public sealed class VehicleDetails loadsPanel.Children.Add(Section(App.Loc["ConsistLoadTools"], buttons, count)); } + private Control ReadyToGoRow() + { + var trainset = _consist.EditingTrainset; + var ready = new CheckBox + { + Content = App.Loc["ConsistReady"], + FontSize = 12, + IsEnabled = trainset is not null, + IsChecked = trainset?.ReadyToGo ?? false + }; + ToolTip.SetTip(ready, App.Loc["ConsistReadyDesc"]); + + ready.IsCheckedChanged += (_, _) => + { + if (trainset is null) + return; + trainset.ReadyToGo = ready.IsChecked == true; + _redraw(); + }; + + return Row(content: ready); + } + private Button LoadToolButton(string text, Action onClick, bool secondary = false) { var b = new Button diff --git a/StarterNG/Views/Settings.axaml b/StarterNG/Views/Settings.axaml index 650ec33..cf176fe 100644 --- a/StarterNG/Views/Settings.axaml +++ b/StarterNG/Views/Settings.axaml @@ -118,14 +118,6 @@ - - - - - - - diff --git a/StarterNG/Views/Settings.axaml.cs b/StarterNG/Views/Settings.axaml.cs index 5580b24..8e480cf 100644 --- a/StarterNG/Views/Settings.axaml.cs +++ b/StarterNG/Views/Settings.axaml.cs @@ -260,7 +260,6 @@ public partial class Settings : UserControl, ISettingsCapture AutoCloseStarterCb.IsChecked = s.AutoCloseStarter; LargeThumbnailsCb.IsChecked = s.LargeThumbnails; AutoExpandTreeCb.IsChecked = s.AutoExpandSceneryTree; - BatteryDefaultCb.SelectedIndex = (int)s.BatteryDefault; } finally { @@ -358,7 +357,6 @@ public partial class Settings : UserControl, ISettingsCapture s.AutoCloseStarter = IsChecked(AutoCloseStarterCb); s.LargeThumbnails = IsChecked(LargeThumbnailsCb); s.AutoExpandSceneryTree = IsChecked(AutoExpandTreeCb); - s.BatteryDefault = (BatteryDefault)Math.Max(0, BatteryDefaultCb.SelectedIndex); } private void ComButton_OnClick(object? sender, RoutedEventArgs e) diff --git a/StarterNG/startercfg/lang/cz.xml b/StarterNG/startercfg/lang/cz.xml index 4ca1d41..109f347 100644 --- a/StarterNG/startercfg/lang/cz.xml +++ b/StarterNG/startercfg/lang/cz.xml @@ -214,6 +214,8 @@ Na následující Maximální Náklad celé soupravy + Consist ready to go + The consist starts prepared: brake pipe charged, driver’s brake valve set, batteries closed. Unchecked it starts cold and braked. Náhodný druh Maximální množství Náhodné množství @@ -440,10 +442,6 @@ Automaticky zavřít starter Velké náhledy Automaticky rozbalit strom scenérií - Baterie při spuštění - Podle scénáře - Vždy vypnutá - Vždy zapnutá Ovládání diff --git a/StarterNG/startercfg/lang/en.xml b/StarterNG/startercfg/lang/en.xml index 300d28d..4199795 100644 --- a/StarterNG/startercfg/lang/en.xml +++ b/StarterNG/startercfg/lang/en.xml @@ -214,6 +214,8 @@ To following Maximum Whole-consist load + Consist ready to go + The consist starts prepared: brake pipe charged, driver’s brake valve set, batteries closed. Unchecked it starts cold and braked. Random type Maximum amount Random amount @@ -474,11 +476,6 @@ Automatically close starter Large thumbnails Automatically expand scenery tree - Battery on launch - Battery state when the simulation starts, unless the scenario decides otherwise. - Scenario default - Always off - Always on Controls diff --git a/StarterNG/startercfg/lang/hu.xml b/StarterNG/startercfg/lang/hu.xml index f58fd46..a8a22f4 100644 --- a/StarterNG/startercfg/lang/hu.xml +++ b/StarterNG/startercfg/lang/hu.xml @@ -214,6 +214,8 @@ A következőkre Legnagyobb A teljes szerelvény rakománya + Consist ready to go + The consist starts prepared: brake pipe charged, driver’s brake valve set, batteries closed. Unchecked it starts cold and braked. Véletlen típus Legnagyobb mennyiség Véletlen mennyiség @@ -440,10 +442,6 @@ A starter automatikus bezárása Nagy előnézetek Pályafa automatikus kibontása - Akkumulátor indításkor - Forgatókönyv szerint - Mindig ki - Mindig be Irányítás diff --git a/StarterNG/startercfg/lang/pl.xml b/StarterNG/startercfg/lang/pl.xml index 9e6a098..b78f82c 100644 --- a/StarterNG/startercfg/lang/pl.xml +++ b/StarterNG/startercfg/lang/pl.xml @@ -214,6 +214,8 @@ Do następnych Maksymalny Ładunki zestawienia + Skład gotowy do jazdy + Skład startuje przygotowany: przewód główny napełniony, kran maszynisty ustawiony, akumulatory załączone. Odznaczony startuje zimny i zahamowany. Losowy rodzaj Maksymalna ilość Losowa ilość @@ -474,11 +476,6 @@ Zamknij starter automatycznie Duże miniaturki Automatycznie rozwijanie drzewka scenerii - Akumulator przy starcie - Stan akumulatora przy starcie symulacji, o ile scenariusz nie zdecyduje inaczej. - Domyślnie ze scenerii - Zawsze wyłączony - Zawsze włączony Sterowanie diff --git a/StarterNG/startercfg/lang/ru.xml b/StarterNG/startercfg/lang/ru.xml index 21d2a8a..1ecee1c 100644 --- a/StarterNG/startercfg/lang/ru.xml +++ b/StarterNG/startercfg/lang/ru.xml @@ -214,6 +214,8 @@ На следующие Максимум Груз всего состава + Consist ready to go + The consist starts prepared: brake pipe charged, driver’s brake valve set, batteries closed. Unchecked it starts cold and braked. Случайный тип Максимальное количество Случайное количество @@ -440,10 +442,6 @@ Автоматически закрывать стартер Крупные миниатюры Автоматически разворачивать дерево сценерий - Батарея при запуске - По сценарию - Всегда выключена - Всегда включена Управление