diff --git a/StarterNG.Tests/ConsistCrewTests.cs b/StarterNG.Tests/ConsistCrewTests.cs new file mode 100644 index 0000000..8873441 --- /dev/null +++ b/StarterNG.Tests/ConsistCrewTests.cs @@ -0,0 +1,107 @@ +using StarterNG.Classes; +using StarterNG.Domain; +using StarterNG.Domain.Vehicles; +using StarterNG.Tests.Fakes; + +namespace StarterNG.Tests; + +public class ConsistCrewTests +{ + private readonly Consist _consist; + private readonly VehicleCatalog _catalog; + + public ConsistCrewTests() + { + var installation = new TestInstallation(new InMemoryFileSystem() + .WithFile(TestInstallation.At("dynamic", "pkp", "ep07", "textures.txt"), + "!=e\nep07-001.mat=ep07,EP07\nep07-002.mat=ep07,EP07") + .WithFile(TestInstallation.At("dynamic", "pkp", "b111", "textures.txt"), + "!=b\nb111-01.mat=b111,B111")); + installation.Vehicles.Load(installation.Library.Vehicles); + + _catalog = installation.Library.Vehicles; + _consist = new Consist(_catalog, new VehicleInfo(_catalog)); + } + + [Fact] + public void A_locomotive_added_at_the_head_gets_a_driver_in_cab_a() + { + var loco = Add("ep07-001", 0); + + Assert.Equal(eDriverType.Headdriver, loco.Cars[0].DriverType); + } + + [Fact] + public void Replacing_the_locomotive_keeps_the_driver_on_the_new_one() + { + Add("ep07-001", 0); + Add("b111-01", 1); + + var replacement = Replace(0, "ep07-002"); + + Assert.Equal(eDriverType.Headdriver, replacement.Cars[0].DriverType); + Assert.Equal(eDriverType.Headdriver, replacement.Driver); + } + + [Fact] + public void An_unstaffed_locomotive_replaced_at_the_head_is_staffed_in_cab_a() + { + var loco = Add("ep07-001", 0); + loco.Cars[0].DriverType = eDriverType.Nobody; + loco.Driver = eDriverType.Nobody; + + var replacement = Replace(0, "ep07-002"); + + Assert.Equal(eDriverType.Headdriver, replacement.Cars[0].DriverType); + } + + [Fact] + public void A_driver_in_cab_b_stays_in_cab_b() + { + var loco = Add("ep07-001", 0); + loco.Cars[0].DriverType = eDriverType.Reardriver; + loco.Driver = eDriverType.Reardriver; + + var replacement = Replace(0, "ep07-002"); + + Assert.Equal(eDriverType.Reardriver, replacement.Cars[0].DriverType); + } + + [Fact] + public void A_wagon_put_in_place_of_the_locomotive_gets_no_driver() + { + Add("ep07-001", 0); + + var wagon = Replace(0, "b111-01"); + + Assert.Equal(eDriverType.Nobody, wagon.Cars[0].DriverType); + } + + [Fact] + public void A_second_locomotive_behind_a_staffed_one_stays_unstaffed() + { + Add("ep07-001", 0); + + var second = Add("ep07-002", 1); + + Assert.Equal(eDriverType.Nobody, second.Cars[0].DriverType); + } + + private ConsistItem Add(string skin, int at) + { + var item = Item(skin); + _consist.Staff(item, at); + _consist.Insert(at, item); + return item; + } + + private ConsistItem Replace(int at, string skin) + { + var item = Item(skin); + _consist.Replace(at, item); + return item; + } + + private ConsistItem Item(string skin) => + new() { Cars = new List { _consist.MakeDynamic(_catalog.TextureForSkin(skin)!, null) } }; +} diff --git a/StarterNG/Domain/Consist.cs b/StarterNG/Domain/Consist.cs index a41d0d0..877bac0 100644 --- a/StarterNG/Domain/Consist.cs +++ b/StarterNG/Domain/Consist.cs @@ -236,6 +236,57 @@ public sealed class Consist : IReadOnlyList public void Insert(int at, ConsistItem item) => _items.Insert(Math.Clamp(at, 0, _items.Count), item); + /// + /// Crew for a vehicle joining at position: a driver in cab A when it is powered + /// and either leads or the consist has no driver yet, nobody otherwise. + /// + public void Staff(ConsistItem item, int position) + { + item.Driver = eDriverType.Nobody; + foreach (var car in item.Cars) + car.DriverType = eDriverType.Nobody; + + if (!IsPowered(item)) + return; + + bool staffed = _items.Any(other => !ReferenceEquals(other, item) && + other.Driver is eDriverType.Headdriver or eDriverType.Reardriver); + if (position == 0 || !staffed) + Crew(item, eDriverType.Headdriver); + } + + /// + /// Puts item in place of the vehicle at index. Its crew moves over where the + /// replacement can take it - a driver only into something powered - and the + /// rest is staffed as a newly added vehicle would be. + /// + public void Replace(int index, ConsistItem item) + { + var replaced = _items[index]; + _items[index] = item; + Staff(item, index); + + var crew = replaced.Driver; + if (crew == eDriverType.Passenger || + (crew is eDriverType.Headdriver or eDriverType.Reardriver && IsPowered(item))) + Crew(item, crew); + } + + // The crew lives on the cars - they are what is shown and exported - and the + // card only mirrors it. + private static void Crew(ConsistItem item, eDriverType crew) + { + if (item.Cars.Count == 0) + return; + + item.Cars[0].DriverType = crew; + item.Driver = crew; + } + + private bool IsPowered(ConsistItem item) => + item.Cars.Count > 0 && _db.TextureForSkin(item.Cars[0].SkinFile) is { } texture && + VehicleInfo.IsPoweredCategory(VehicleInfo.CategoryOf(texture)); + public void MoveLeft(ConsistItem item) { int i = _items.IndexOf(item); diff --git a/StarterNG/Views/Depot.axaml.cs b/StarterNG/Views/Depot.axaml.cs index ffce1a8..028d143 100644 --- a/StarterNG/Views/Depot.axaml.cs +++ b/StarterNG/Views/Depot.axaml.cs @@ -573,10 +573,9 @@ public partial class Depot : UserControl { Cars = unit.Select(NewVehicle).ToList(), Grouped = unit.Count > 1, - Driver = _consist.Selected.Driver, Flipped = _consist.Selected.Flipped }; - _consist[i] = item; + _consist.Replace(i, item); _consist.Selected = item; _consist.AutoConnectAll(); RebuildConsist(); @@ -607,36 +606,13 @@ public partial class Depot : UserControl }; at = Math.Clamp(at, 0, _consist.Count); - MatchOccupancy(item, at); + _consist.Staff(item, at); _consist.Insert(at, item); _consist.Selected = item; _consist.AutoConnectAll(); RebuildConsist(); } - private void MatchOccupancy(ConsistItem item, int position) - { - item.Driver = eDriverType.Nobody; - var lead = item.Cars.FirstOrDefault(); - if (lead is null) return; - - foreach (var car in item.Cars) - car.DriverType = eDriverType.Nobody; - - var tex = _db.TextureForSkin(lead.SkinFile); - string? cat = tex != null ? VehicleInfo.CategoryOf(tex) : null; - if (!VehicleInfo.IsPoweredCategory(cat)) - return; - - bool staffed = _consist.Any(i => - i.Driver is eDriverType.Headdriver or eDriverType.Reardriver); - if (position == 0 || !staffed) - { - item.Driver = eDriverType.Headdriver; - lead.DriverType = eDriverType.Headdriver; - } - } - private async void TextureBaseButton_OnClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { if (TopLevel.GetTopLevel(this) is not Window owner) return;