mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-12 00:58:12 +01:00
fix(tapes): a tape written after another run began is not the launch's before it, and a session with only a black box shows no other session's tape
This commit is contained in:
@@ -121,6 +121,30 @@ public class BlackBoxListingTests
|
||||
Assert.EndsWith(own, Assert.Single(launched.BlackBoxes).Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_tape_written_after_another_run_began_is_not_taken_for_the_launch_before()
|
||||
{
|
||||
// The starter launched Bałtyk; later a run of another scenery, not launched from it, wrote its tape and black box.
|
||||
var files = new InMemoryFileSystem();
|
||||
var installation = new TestInstallation(files);
|
||||
installation.Clock.Now = Launched;
|
||||
installation.Tapes.RecordLaunch("baltyk.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ep07", new[] { "ep07" }, "Bałtyk");
|
||||
string box = Box("td", Launched.AddHours(5));
|
||||
files.WithFile(Log("ep07.dat"), "t").WrittenAt(Log("ep07.dat"), Launched.AddMinutes(50))
|
||||
.WithFile(Log("bb-su45.dat"), "t").WrittenAt(Log("bb-su45.dat"), Launched.AddHours(5).AddMinutes(8))
|
||||
.WithFile(Log(box), "b").WrittenAt(Log(box), Launched.AddHours(5).AddMinutes(8));
|
||||
|
||||
var sessions = installation.Tapes.Sessions();
|
||||
|
||||
var baltyk = Assert.Single(sessions, session => session.Launch is not null);
|
||||
Assert.Equal("ep07", Assert.Single(baltyk.Tapes).Vehicle);
|
||||
Assert.Empty(baltyk.BlackBoxes);
|
||||
var other = Assert.Single(sessions, session => session.Launch is null);
|
||||
Assert.Equal("bb-su45", Assert.Single(other.Tapes).Vehicle);
|
||||
Assert.Single(other.BlackBoxes);
|
||||
Assert.Null(installation.Tapes.ContextOf(other.Tapes[0]).StartClock);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_black_box_started_before_a_launch_is_not_taken_for_it()
|
||||
{
|
||||
|
||||
@@ -227,7 +227,7 @@ public sealed class HaslerTapes
|
||||
|
||||
public TapeContext ContextOf(TapeFile file)
|
||||
{
|
||||
var launch = Launches().LastOrDefault(l => l.Covers(file.LastWriteUtc));
|
||||
var launch = LaunchOf(file, Launches(), ReadBlackBoxes());
|
||||
if (launch is null)
|
||||
return new TapeContext(null, Recorders.Guess(null, file.Vehicle));
|
||||
|
||||
@@ -243,8 +243,9 @@ public sealed class HaslerTapes
|
||||
|
||||
/// <summary>
|
||||
/// The runs the tapes came from, the running or latest one first. A tape belongs
|
||||
/// to the last launch before it was written; one written before any known launch
|
||||
/// goes with those written about the same time, as a run writes them all at its end.
|
||||
/// to the last launch before it was written, unless a run the starter didn't launch
|
||||
/// began between them; one with no launch goes with those written about the same
|
||||
/// time, as a run writes them all at its end.
|
||||
/// </summary>
|
||||
public IReadOnlyList<TapeSession> Sessions()
|
||||
{
|
||||
@@ -254,12 +255,11 @@ public sealed class HaslerTapes
|
||||
var tapes = _files.DirectoryExists(Folder)
|
||||
? _files.GetFiles(Folder, "*.dat").Select(ReadTape).ToList()
|
||||
: new List<TapeFile>();
|
||||
var behind = tapes.GroupBy(tape => launches.LastOrDefault(l => l.Covers(tape.LastWriteUtc)))
|
||||
var boxes = ReadBlackBoxes();
|
||||
var tapeLaunch = tapes.ToDictionary(tape => tape, tape => LaunchOf(tape, launches, boxes));
|
||||
var behind = tapes.GroupBy(tape => tapeLaunch[tape])
|
||||
.Where(group => group.Key is not null)
|
||||
.ToDictionary(group => group.Key!, group => group.ToList());
|
||||
var boxes = _files.DirectoryExists(Folder)
|
||||
? _files.GetFiles(Folder, "*.blackbox").Select(ReadBlackBox).ToList()
|
||||
: new List<BlackBoxRecording>();
|
||||
var boxesBehind = boxes.GroupBy(box => LaunchOf(box, launches))
|
||||
.Where(group => group.Key is not null)
|
||||
.ToDictionary(group => group.Key!, group => group.ToList());
|
||||
@@ -279,7 +279,7 @@ public sealed class HaslerTapes
|
||||
}
|
||||
|
||||
// What no launch covers goes together by when it was written: a run writes it all at its end.
|
||||
var loose = tapes.Where(t => !launches.Any(l => l.Covers(t.LastWriteUtc)))
|
||||
var loose = tapes.Where(t => tapeLaunch[t] is null)
|
||||
.Select(t => (Written: t.LastWriteUtc, Tape: (TapeFile?)t, Box: (BlackBoxRecording?)null))
|
||||
.Concat(boxes.Where(b => LaunchOf(b, launches) is null)
|
||||
.Select(b => (Written: b.LastWriteUtc, Tape: (TapeFile?)null, Box: (BlackBoxRecording?)b)))
|
||||
@@ -318,6 +318,27 @@ public sealed class HaslerTapes
|
||||
private TapeFile ReadTape(string path) =>
|
||||
new(path, Path.GetFileNameWithoutExtension(path), _files.GetLastWriteTimeUtc(path), _files.GetFileSize(path));
|
||||
|
||||
private List<BlackBoxRecording> ReadBlackBoxes() =>
|
||||
_files.DirectoryExists(Folder)
|
||||
? _files.GetFiles(Folder, "*.blackbox").Select(ReadBlackBox).ToList()
|
||||
: new List<BlackBoxRecording>();
|
||||
|
||||
/// <summary>
|
||||
/// The launch a tape came from. A tape tells nothing of its run but when it was written: the last launch
|
||||
/// before then, unless a black box shows another run, one the starter didn't launch, began between the two.
|
||||
/// That run wrote the tape, not the launch's.
|
||||
/// </summary>
|
||||
public static TapeLaunch? LaunchOf(TapeFile tape, IReadOnlyList<TapeLaunch> launches, IReadOnlyList<BlackBoxRecording> boxes)
|
||||
{
|
||||
var launch = launches.LastOrDefault(l => l.Covers(tape.LastWriteUtc));
|
||||
if (launch is null)
|
||||
return null;
|
||||
bool another = boxes.Any(box => NamedBy(box.Path) is { } named
|
||||
&& named.StartedUtc > launch.LaunchedUtc && named.StartedUtc < tape.LastWriteUtc
|
||||
&& LaunchOf(box, launches) != launch);
|
||||
return another ? null : launch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The launch a black box came from. Unlike a tape, it says which: its name has the scenery and the moment
|
||||
/// the recording started. So it is the last launch before that moment, and only if that launch was of the
|
||||
|
||||
@@ -121,19 +121,28 @@ public sealed class HaslerTapeWindow : Window
|
||||
};
|
||||
_list.SelectionChanged += async (_, _) =>
|
||||
{
|
||||
// A black box opens in a window of its own on a click or Enter, not when the arrows go over it; the tape shown stays.
|
||||
if (_list.SelectedItem is TreeViewItem { Tag: BlackBoxRecording })
|
||||
return;
|
||||
// A session stands for its first tape, the player's own if it has one.
|
||||
if (_list.SelectedItem is TreeViewItem { Tag: TapeSession } session)
|
||||
session.IsExpanded = true;
|
||||
var file = _list.SelectedItem switch
|
||||
if (_list.SelectedItem is TreeViewItem { Tag: TapeSession } node)
|
||||
node.IsExpanded = true;
|
||||
// A black box opens in a window of its own on a click or Enter, not when the arrows go over it; the
|
||||
// tape beside it is one of its session's, the one shown if it is.
|
||||
var session = _list.SelectedItem switch
|
||||
{
|
||||
TreeViewItem { Tag: TapeFile tape } => tape,
|
||||
TreeViewItem { Tag: TapeSession { Tapes.Count: > 0 } run } => run.Tapes[0],
|
||||
TreeViewItem { Tag: TapeSession run } => run,
|
||||
TreeViewItem { Tag: BlackBoxRecording box } =>
|
||||
_sessions.FirstOrDefault(s => s.BlackBoxes.Any(b => b.Path == box.Path)),
|
||||
_ => null
|
||||
};
|
||||
if (file is null || file == _shown)
|
||||
if (_list.SelectedItem is TreeViewItem { Tag: BlackBoxRecording } && session?.Tapes.Any(t => t.Path == _shown?.Path) == true)
|
||||
return;
|
||||
var file = _list.SelectedItem is TreeViewItem { Tag: TapeFile tape } ? tape : session?.Tapes.FirstOrDefault();
|
||||
if (file is null)
|
||||
{
|
||||
if (session is not null)
|
||||
ShowNoTape();
|
||||
return;
|
||||
}
|
||||
if (file == _shown)
|
||||
return;
|
||||
|
||||
ApplyContext(_tapes.ContextOf(file));
|
||||
@@ -586,6 +595,16 @@ public sealed class HaslerTapeWindow : Window
|
||||
_recorder.SelectedIndex = context.Recorder == RecorderKind.Electronic ? 1 : 0;
|
||||
}
|
||||
|
||||
/// <summary>A session with a black box and no tape: no tape of another session left beside it.</summary>
|
||||
private void ShowNoTape()
|
||||
{
|
||||
_shown = null;
|
||||
_view.Tape = null;
|
||||
_view.Mark = null;
|
||||
_cursor.Text = "";
|
||||
_summary.Text = App.Loc["HaslerSessionNoTape"];
|
||||
}
|
||||
|
||||
private async Task ShowAsync(TapeFile file, bool scrollToEnd)
|
||||
{
|
||||
if (_loading)
|
||||
|
||||
@@ -853,6 +853,7 @@
|
||||
<String key="HaslerBlackBox">Černá skříňka</String>
|
||||
<String key="HaslerBlackBoxTip">Celá relace na mapě: jízda oproti AI, povely, návěstidla, kabina</String>
|
||||
<String key="HaslerClickBlackBox">klikněte na pásku: černá skříňka v tom okamžiku</String>
|
||||
<String key="HaslerSessionNoTape">Tato relace nemá pásku, jen černou skříňku.</String>
|
||||
<String key="WarehouseSaved">Ve skladu: {0}</String>
|
||||
<String key="WarehouseNothingToAdd">Vyberte soupravu s vozidly</String>
|
||||
<String key="GfxGroupLevelTip">Úroveň jen pro tuto část grafiky</String>
|
||||
|
||||
@@ -1082,6 +1082,7 @@
|
||||
<String key="HaslerBlackBox">Black box</String>
|
||||
<String key="HaslerBlackBoxTip">The whole session on a map: the driving against the AI, the commands, the signals, the cab</String>
|
||||
<String key="HaslerClickBlackBox">click the tape: the black box at that moment</String>
|
||||
<String key="HaslerSessionNoTape">This session has no tape, only the black box.</String>
|
||||
<String key="WarehouseSaved">In the warehouse: {0}</String>
|
||||
<String key="WarehouseNothingToAdd">Pick a consist with vehicles</String>
|
||||
<String key="GfxGroupLevelTip">Level for this part of the graphics only</String>
|
||||
|
||||
@@ -853,6 +853,7 @@
|
||||
<String key="HaslerBlackBox">Fekete doboz</String>
|
||||
<String key="HaslerBlackBoxTip">A teljes menet a térképen: vezetés az AI-hoz képest, parancsok, jelzők, vezetőfülke</String>
|
||||
<String key="HaslerClickBlackBox">kattintson a szalagra: a fekete doboz abban a pillanatban</String>
|
||||
<String key="HaslerSessionNoTape">Ebben a munkamenetben nincs szalag, csak a fekete doboz.</String>
|
||||
<String key="WarehouseSaved">A raktárban: {0}</String>
|
||||
<String key="WarehouseNothingToAdd">Válassz járműveket tartalmazó szerelvényt</String>
|
||||
<String key="GfxGroupLevelTip">Csak a grafika ezen részének szintje</String>
|
||||
|
||||
@@ -904,6 +904,7 @@
|
||||
<String key="HaslerBlackBox">Czarna skrzynka</String>
|
||||
<String key="HaslerBlackBoxTip">Cała sesja na mapie: jazda względem AI, komendy, sygnalizatory, kabina</String>
|
||||
<String key="HaslerClickBlackBox">kliknij taśmę: czarna skrzynka w tym momencie</String>
|
||||
<String key="HaslerSessionNoTape">W tej sesji nie ma taśmy, jest tylko czarna skrzynka.</String>
|
||||
<String key="WarehouseSaved">W magazynie: {0}</String>
|
||||
<String key="WarehouseNothingToAdd">Wybierz skład z pojazdami</String>
|
||||
<String key="GfxGroupLevelTip">Poziom tylko tej części grafiki</String>
|
||||
|
||||
@@ -853,6 +853,7 @@
|
||||
<String key="HaslerBlackBox">Чёрный ящик</String>
|
||||
<String key="HaslerBlackBoxTip">Вся сессия на карте: езда по сравнению с ИИ, команды, светофоры, кабина</String>
|
||||
<String key="HaslerClickBlackBox">щёлкните по ленте: чёрный ящик в этот момент</String>
|
||||
<String key="HaslerSessionNoTape">В этом сеансе нет ленты, только чёрный ящик.</String>
|
||||
<String key="WarehouseSaved">На складе: {0}</String>
|
||||
<String key="WarehouseNothingToAdd">Выберите состав с транспортом</String>
|
||||
<String key="GfxGroupLevelTip">Уровень только для этой части графики</String>
|
||||
|
||||
Reference in New Issue
Block a user