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.
This commit is contained in:
maj00r
2026-09-04 23:17:17 +02:00
parent 1939493b47
commit cef00c4e3c
13 changed files with 115 additions and 55 deletions

View File

@@ -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);
}
}

View File

@@ -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
};
}
}

View File

@@ -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<Dynamic> Vehicles;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -118,14 +118,6 @@
<ctl:SettingRow ConfigKey="starter.expandtree">
<CheckBox x:Name="AutoExpandTreeCb" Content="{Binding [AutoExpandSceneryTree], Source={x:Static starterNg:App.Loc}}" />
</ctl:SettingRow>
<ctl:SettingRow ConfigKey="starter.batterydefault" Label="{Binding [BatteryDefault], Source={x:Static starterNg:App.Loc}}"
Description="{Binding [BatteryDefaultDesc], Source={x:Static starterNg:App.Loc}}">
<ComboBox x:Name="BatteryDefaultCb" SelectedIndex="0">
<ComboBoxItem Content="{Binding [BatteryDefaultScenario], Source={x:Static starterNg:App.Loc}}" />
<ComboBoxItem Content="{Binding [BatteryDefaultOff], Source={x:Static starterNg:App.Loc}}" />
<ComboBoxItem Content="{Binding [BatteryDefaultOn], Source={x:Static starterNg:App.Loc}}" />
</ComboBox>
</ctl:SettingRow>
</StackPanel>
</ctl:SettingsSection>

View File

@@ -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)

View File

@@ -214,6 +214,8 @@
<String key="LoadToAllShort">Na následující</String>
<String key="LoadMaxShort">Maximální</String>
<String key="ConsistLoadTools">Náklad celé soupravy</String>
<String key="ConsistReady">Consist ready to go</String>
<String key="ConsistReadyDesc">The consist starts prepared: brake pipe charged, driver’s brake valve set, batteries closed. Unchecked it starts cold and braked.</String>
<String key="ConsistRandomType">Náhodný druh</String>
<String key="ConsistMaxAmount">Maximální množství</String>
<String key="ConsistRandomAmount">Náhodné množství</String>
@@ -440,10 +442,6 @@
<String key="AutoCloseStarter">Automaticky zavřít starter</String>
<String key="LargeThumbnails">Velké náhledy</String>
<String key="AutoExpandSceneryTree">Automaticky rozbalit strom scenérií</String>
<String key="BatteryDefault">Baterie při spuštění</String>
<String key="BatteryDefaultScenario">Podle scénáře</String>
<String key="BatteryDefaultOff">Vždy vypnutá</String>
<String key="BatteryDefaultOn">Vždy zapnutá</String>
<!-- Controls / key bindings -->
<String key="Controls">Ovládání</String>

View File

@@ -214,6 +214,8 @@
<String key="LoadToAllShort">To following</String>
<String key="LoadMaxShort">Maximum</String>
<String key="ConsistLoadTools">Whole-consist load</String>
<String key="ConsistReady">Consist ready to go</String>
<String key="ConsistReadyDesc">The consist starts prepared: brake pipe charged, driver’s brake valve set, batteries closed. Unchecked it starts cold and braked.</String>
<String key="ConsistRandomType">Random type</String>
<String key="ConsistMaxAmount">Maximum amount</String>
<String key="ConsistRandomAmount">Random amount</String>
@@ -474,11 +476,6 @@
<String key="AutoCloseStarter">Automatically close starter</String>
<String key="LargeThumbnails">Large thumbnails</String>
<String key="AutoExpandSceneryTree">Automatically expand scenery tree</String>
<String key="BatteryDefault">Battery on launch</String>
<String key="BatteryDefaultDesc">Battery state when the simulation starts, unless the scenario decides otherwise.</String>
<String key="BatteryDefaultScenario">Scenario default</String>
<String key="BatteryDefaultOff">Always off</String>
<String key="BatteryDefaultOn">Always on</String>
<!-- Controls / key bindings -->
<String key="Controls">Controls</String>

View File

@@ -214,6 +214,8 @@
<String key="LoadToAllShort">A következőkre</String>
<String key="LoadMaxShort">Legnagyobb</String>
<String key="ConsistLoadTools">A teljes szerelvény rakománya</String>
<String key="ConsistReady">Consist ready to go</String>
<String key="ConsistReadyDesc">The consist starts prepared: brake pipe charged, driver’s brake valve set, batteries closed. Unchecked it starts cold and braked.</String>
<String key="ConsistRandomType">Véletlen típus</String>
<String key="ConsistMaxAmount">Legnagyobb mennyiség</String>
<String key="ConsistRandomAmount">Véletlen mennyiség</String>
@@ -440,10 +442,6 @@
<String key="AutoCloseStarter">A starter automatikus bezárása</String>
<String key="LargeThumbnails">Nagy előnézetek</String>
<String key="AutoExpandSceneryTree">Pályafa automatikus kibontása</String>
<String key="BatteryDefault">Akkumulátor indításkor</String>
<String key="BatteryDefaultScenario">Forgatókönyv szerint</String>
<String key="BatteryDefaultOff">Mindig ki</String>
<String key="BatteryDefaultOn">Mindig be</String>
<!-- Controls / key bindings -->
<String key="Controls">Irányítás</String>

View File

@@ -214,6 +214,8 @@
<String key="LoadToAllShort">Do następnych</String>
<String key="LoadMaxShort">Maksymalny</String>
<String key="ConsistLoadTools">Ładunki zestawienia</String>
<String key="ConsistReady">Skład gotowy do jazdy</String>
<String key="ConsistReadyDesc">Skład startuje przygotowany: przewód główny napełniony, kran maszynisty ustawiony, akumulatory załączone. Odznaczony startuje zimny i zahamowany.</String>
<String key="ConsistRandomType">Losowy rodzaj</String>
<String key="ConsistMaxAmount">Maksymalna ilość</String>
<String key="ConsistRandomAmount">Losowa ilość</String>
@@ -474,11 +476,6 @@
<String key="AutoCloseStarter">Zamknij starter automatycznie</String>
<String key="LargeThumbnails">Duże miniaturki</String>
<String key="AutoExpandSceneryTree">Automatycznie rozwijanie drzewka scenerii</String>
<String key="BatteryDefault">Akumulator przy starcie</String>
<String key="BatteryDefaultDesc">Stan akumulatora przy starcie symulacji, o ile scenariusz nie zdecyduje inaczej.</String>
<String key="BatteryDefaultScenario">Domyślnie ze scenerii</String>
<String key="BatteryDefaultOff">Zawsze wyłączony</String>
<String key="BatteryDefaultOn">Zawsze włączony</String>
<!-- Sterowanie / przypisania klawiszy -->
<String key="Controls">Sterowanie</String>

View File

@@ -214,6 +214,8 @@
<String key="LoadToAllShort">На следующие</String>
<String key="LoadMaxShort">Максимум</String>
<String key="ConsistLoadTools">Груз всего состава</String>
<String key="ConsistReady">Consist ready to go</String>
<String key="ConsistReadyDesc">The consist starts prepared: brake pipe charged, driver’s brake valve set, batteries closed. Unchecked it starts cold and braked.</String>
<String key="ConsistRandomType">Случайный тип</String>
<String key="ConsistMaxAmount">Максимальное количество</String>
<String key="ConsistRandomAmount">Случайное количество</String>
@@ -440,10 +442,6 @@
<String key="AutoCloseStarter">Автоматически закрывать стартер</String>
<String key="LargeThumbnails">Крупные миниатюры</String>
<String key="AutoExpandSceneryTree">Автоматически разворачивать дерево сценерий</String>
<String key="BatteryDefault">Батарея при запуске</String>
<String key="BatteryDefaultScenario">По сценарию</String>
<String key="BatteryDefaultOff">Всегда выключена</String>
<String key="BatteryDefaultOn">Всегда включена</String>
<!-- Controls / key bindings -->
<String key="Controls">Управление</String>