diff --git a/StarterNG.Tests/ConsistCrewTests.cs b/StarterNG.Tests/ConsistCrewTests.cs index 8873441..c694884 100644 --- a/StarterNG.Tests/ConsistCrewTests.cs +++ b/StarterNG.Tests/ConsistCrewTests.cs @@ -20,7 +20,7 @@ public class ConsistCrewTests installation.Vehicles.Load(installation.Library.Vehicles); _catalog = installation.Library.Vehicles; - _consist = new Consist(_catalog, new VehicleInfo(_catalog)); + _consist = new Consist(_catalog, new VehicleInfo(_catalog, installation.Physics)); } [Fact] diff --git a/StarterNG.Tests/ConsistOrientationTests.cs b/StarterNG.Tests/ConsistOrientationTests.cs new file mode 100644 index 0000000..1aeaa05 --- /dev/null +++ b/StarterNG.Tests/ConsistOrientationTests.cs @@ -0,0 +1,82 @@ +using StarterNG.Classes; +using StarterNG.Domain; +using StarterNG.Domain.Vehicles; +using StarterNG.Tests.Fakes; + +namespace StarterNG.Tests; + +public class ConsistOrientationTests +{ + private readonly VehicleCatalog _catalog; + private readonly TestInstallation _installation; + + public ConsistOrientationTests() + { + var installation = new TestInstallation(new InMemoryFileSystem() + .WithFile(TestInstallation.At("dynamic", "pkp", "et40", "textures.txt"), + "!=e\n^2\net40-10-a.mat=et40a,ET40-A\net40-10-b.mat=et40b,ET40-B\n" + + "et40-16-a.mat=et40a,ET40-A\net40-16-b.mat=et40b,ET40-B")); + installation.Vehicles.Load(installation.Library.Vehicles); + _catalog = installation.Library.Vehicles; + _installation = installation; + } + + [Fact] + public void Randomly_turned_unit_comes_out_like_a_flipped_one() + { + var flipped = NewConsist(out var unit); + flipped.Flip(unit); + + var randomized = NewConsist(out _); + randomized.RandomizeOrientation(new AlwaysTurn()); + + Assert.Equal(Describe(flipped), Describe(randomized)); + } + + [Fact] + public void A_turned_unit_has_its_cars_reversed_and_each_one_turned() + { + var consist = NewConsist(out _); + + consist.RandomizeOrientation(new AlwaysTurn()); + + // B leads, A trails, and both face backwards - not A-B reordered but facing forward. + Assert.Equal(new[] { "et40-10-b -1", "et40-10-a -1" }, Describe(consist)); + } + + [Fact] + public void A_unit_put_in_place_of_a_turned_one_faces_the_same_way() + { + var consist = NewConsist(out var unit); + consist.Flip(unit); + + consist.Replace(0, Unit(consist, "et40-16-a")); + + Assert.Equal(new[] { "et40-16-b -1", "et40-16-a -1" }, Describe(consist)); + } + + private Consist NewConsist(out ConsistItem unit) + { + var consist = new Consist(_catalog, new VehicleInfo(_catalog, _installation.Physics)); + unit = Unit(consist, "et40-10-a"); + consist.Insert(0, unit); + return consist; + } + + private ConsistItem Unit(Consist consist, string lead) => new() + { + Cars = _catalog.ResolveSet(_catalog.TextureForSkin(lead)!)! + .Select(texture => consist.MakeDynamic(texture, null)) + .ToList(), + Grouped = true + }; + + // The export order, each car with the way it faces. + private static string[] Describe(Consist consist) => + consist.Flatten().Select(car => $"{car.SkinFile} {(car.Offset < 0 ? -1 : 1)}").ToArray(); + + private sealed class AlwaysTurn : Random + { + public override int Next(int maxValue) => 0; + } +} diff --git a/StarterNG/Domain/Consist.cs b/StarterNG/Domain/Consist.cs index 877bac0..77c00ee 100644 --- a/StarterNG/Domain/Consist.cs +++ b/StarterNG/Domain/Consist.cs @@ -256,9 +256,9 @@ public sealed class Consist : IReadOnlyList } /// - /// 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. + /// Puts item in place of the vehicle at index, facing the same way. 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) { @@ -266,6 +266,10 @@ public sealed class Consist : IReadOnlyList _items[index] = item; Staff(item, index); + // Facing the way the replaced vehicle did - cars and all, as Flip would. + if (item.Flipped != replaced.Flipped) + Turn(index, index); + var crew = replaced.Driver; if (crew == eDriverType.Passenger || (crew is eDriverType.Headdriver or eDriverType.Reardriver && IsPowered(item))) @@ -339,6 +343,16 @@ public sealed class Consist : IReadOnlyList int index = _items.IndexOf(item); if (index < 0) return; + var (first, last) = UnitAround(index); + Turn(first, last); + + AutoConnectAll(); + Raise(); + } + + /// The cards locked together with the one at index, as the range they span. + private (int First, int Last) UnitAround(int index) + { int first = index; while (first > 0 && HoldsTail(_items[first - 1])) first--; @@ -347,6 +361,17 @@ public sealed class Consist : IReadOnlyList while (last + 1 < _items.Count && HoldsTail(_items[last])) last++; + return (first, last); + } + + /// + /// Turns the cards first..last end for end - the flag on each card, the cars it + /// holds and the order of the cards. All three or nothing: the export writes the + /// cars in card order and each car with its own orientation, so a flag turned + /// alone couples a multi-car unit back to front. + /// + private void Turn(int first, int last) + { for (int k = first; k <= last; k++) { var card = _items[k]; @@ -357,9 +382,6 @@ public sealed class Consist : IReadOnlyList if (last > first) _items.Reverse(first, last - first + 1); - - AutoConnectAll(); - Raise(); } public void CycleDriver(ConsistItem item) @@ -492,9 +514,15 @@ public sealed class Consist : IReadOnlyList if (start < 0) start = 0; if (start >= _items.Count) return; - for (int i = start; i < _items.Count; i++) + // Unit by unit, as Flip turns them: half a locked unit turned on its own + // would be coupled back to front. + for (int i = start; i < _items.Count;) + { + var (first, last) = UnitAround(i); if (rng.Next(2) == 0) - _items[i].Flipped = !_items[i].Flipped; + Turn(first, last); + i = last + 1; + } AutoConnectAll(); Raise(); diff --git a/StarterNG/Domain/VehicleInfo.cs b/StarterNG/Domain/VehicleInfo.cs index 1193e58..537d31c 100644 --- a/StarterNG/Domain/VehicleInfo.cs +++ b/StarterNG/Domain/VehicleInfo.cs @@ -1,14 +1,19 @@ using StarterNG.Classes; using StarterNG.Domain.Vehicles; -using StarterNG.Application; +using StarterNG.Application.Abstractions; namespace StarterNG.Domain; public sealed class VehicleInfo { private readonly VehicleCatalog _db; + private readonly IPhysicsRepository _physics; - public VehicleInfo(VehicleCatalog db) => _db = db; + public VehicleInfo(VehicleCatalog db, IPhysicsRepository physics) + { + _db = db; + _physics = physics; + } public static string ClassOf(VehicleTexture t) => t.ResolvedClass; @@ -23,14 +28,14 @@ public sealed class VehicleInfo TextureFor(car) is { } t ? CategoryOf(t) : null; public VehiclePhysics? PhysicsFor(VehicleTexture texture) => - AppServices.Current.Physics.For(texture.Directory, texture.Model) - ?? AppServices.Current.Physics.For(texture.Directory, texture.Skinfile); + _physics.For(texture.Directory, texture.Model) + ?? _physics.For(texture.Directory, texture.Skinfile); public VehiclePhysics? PhysicsFor(Dynamic car) { string? dbModel = TextureFor(car)?.Model; - return AppServices.Current.Physics.For(car.DataFolder, dbModel) - ?? AppServices.Current.Physics.For(car.DataFolder, car.MmdFile) - ?? AppServices.Current.Physics.For(car.DataFolder, car.SkinFile); + return _physics.For(car.DataFolder, dbModel) + ?? _physics.For(car.DataFolder, car.MmdFile) + ?? _physics.For(car.DataFolder, car.SkinFile); } } diff --git a/StarterNG/Views/Depot.axaml.cs b/StarterNG/Views/Depot.axaml.cs index 028d143..b5ded2f 100644 --- a/StarterNG/Views/Depot.axaml.cs +++ b/StarterNG/Views/Depot.axaml.cs @@ -56,7 +56,7 @@ public partial class Depot : UserControl public Depot() { - _info = new VehicleInfo(_db); + _info = new VehicleInfo(_db, AppServices.Current.Physics); _consist = new Consist(_db, _info); _cargo = new Cargo(_info, _rng); _consist.Changed += RebuildConsist; @@ -572,8 +572,7 @@ public partial class Depot : UserControl var item = new ConsistItem { Cars = unit.Select(NewVehicle).ToList(), - Grouped = unit.Count > 1, - Flipped = _consist.Selected.Flipped + Grouped = unit.Count > 1 }; _consist.Replace(i, item); _consist.Selected = item;