mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-13 20:03:19 +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);
|
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]
|
[Fact]
|
||||||
public void A_black_box_started_before_a_launch_is_not_taken_for_it()
|
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)
|
public TapeContext ContextOf(TapeFile file)
|
||||||
{
|
{
|
||||||
var launch = Launches().LastOrDefault(l => l.Covers(file.LastWriteUtc));
|
var launch = LaunchOf(file, Launches(), ReadBlackBoxes());
|
||||||
if (launch is null)
|
if (launch is null)
|
||||||
return new TapeContext(null, Recorders.Guess(null, file.Vehicle));
|
return new TapeContext(null, Recorders.Guess(null, file.Vehicle));
|
||||||
|
|
||||||
@@ -243,8 +243,9 @@ public sealed class HaslerTapes
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The runs the tapes came from, the running or latest one first. A tape belongs
|
/// 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
|
/// to the last launch before it was written, unless a run the starter didn't launch
|
||||||
/// goes with those written about the same time, as a run writes them all at its end.
|
/// 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>
|
/// </summary>
|
||||||
public IReadOnlyList<TapeSession> Sessions()
|
public IReadOnlyList<TapeSession> Sessions()
|
||||||
{
|
{
|
||||||
@@ -254,12 +255,11 @@ public sealed class HaslerTapes
|
|||||||
var tapes = _files.DirectoryExists(Folder)
|
var tapes = _files.DirectoryExists(Folder)
|
||||||
? _files.GetFiles(Folder, "*.dat").Select(ReadTape).ToList()
|
? _files.GetFiles(Folder, "*.dat").Select(ReadTape).ToList()
|
||||||
: new List<TapeFile>();
|
: 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)
|
.Where(group => group.Key is not null)
|
||||||
.ToDictionary(group => group.Key!, group => group.ToList());
|
.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))
|
var boxesBehind = boxes.GroupBy(box => LaunchOf(box, launches))
|
||||||
.Where(group => group.Key is not null)
|
.Where(group => group.Key is not null)
|
||||||
.ToDictionary(group => group.Key!, group => group.ToList());
|
.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.
|
// 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))
|
.Select(t => (Written: t.LastWriteUtc, Tape: (TapeFile?)t, Box: (BlackBoxRecording?)null))
|
||||||
.Concat(boxes.Where(b => LaunchOf(b, launches) is null)
|
.Concat(boxes.Where(b => LaunchOf(b, launches) is null)
|
||||||
.Select(b => (Written: b.LastWriteUtc, Tape: (TapeFile?)null, Box: (BlackBoxRecording?)b)))
|
.Select(b => (Written: b.LastWriteUtc, Tape: (TapeFile?)null, Box: (BlackBoxRecording?)b)))
|
||||||
@@ -318,6 +318,27 @@ public sealed class HaslerTapes
|
|||||||
private TapeFile ReadTape(string path) =>
|
private TapeFile ReadTape(string path) =>
|
||||||
new(path, Path.GetFileNameWithoutExtension(path), _files.GetLastWriteTimeUtc(path), _files.GetFileSize(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>
|
/// <summary>
|
||||||
/// The launch a black box came from. Unlike a tape, it says which: its name has the scenery and the moment
|
/// 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
|
/// 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 (_, _) =>
|
_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.
|
// A session stands for its first tape, the player's own if it has one.
|
||||||
if (_list.SelectedItem is TreeViewItem { Tag: TapeSession } session)
|
if (_list.SelectedItem is TreeViewItem { Tag: TapeSession } node)
|
||||||
session.IsExpanded = true;
|
node.IsExpanded = true;
|
||||||
var file = _list.SelectedItem switch
|
// 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 run } => run,
|
||||||
TreeViewItem { Tag: TapeSession { Tapes.Count: > 0 } run } => run.Tapes[0],
|
TreeViewItem { Tag: BlackBoxRecording box } =>
|
||||||
|
_sessions.FirstOrDefault(s => s.BlackBoxes.Any(b => b.Path == box.Path)),
|
||||||
_ => null
|
_ => 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;
|
return;
|
||||||
|
|
||||||
ApplyContext(_tapes.ContextOf(file));
|
ApplyContext(_tapes.ContextOf(file));
|
||||||
@@ -586,6 +595,16 @@ public sealed class HaslerTapeWindow : Window
|
|||||||
_recorder.SelectedIndex = context.Recorder == RecorderKind.Electronic ? 1 : 0;
|
_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)
|
private async Task ShowAsync(TapeFile file, bool scrollToEnd)
|
||||||
{
|
{
|
||||||
if (_loading)
|
if (_loading)
|
||||||
|
|||||||
@@ -853,6 +853,7 @@
|
|||||||
<String key="HaslerBlackBox">Černá skříňka</String>
|
<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="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="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="WarehouseSaved">Ve skladu: {0}</String>
|
||||||
<String key="WarehouseNothingToAdd">Vyberte soupravu s vozidly</String>
|
<String key="WarehouseNothingToAdd">Vyberte soupravu s vozidly</String>
|
||||||
<String key="GfxGroupLevelTip">Úroveň jen pro tuto část grafiky</String>
|
<String key="GfxGroupLevelTip">Úroveň jen pro tuto část grafiky</String>
|
||||||
|
|||||||
@@ -1082,6 +1082,7 @@
|
|||||||
<String key="HaslerBlackBox">Black box</String>
|
<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="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="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="WarehouseSaved">In the warehouse: {0}</String>
|
||||||
<String key="WarehouseNothingToAdd">Pick a consist with vehicles</String>
|
<String key="WarehouseNothingToAdd">Pick a consist with vehicles</String>
|
||||||
<String key="GfxGroupLevelTip">Level for this part of the graphics only</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="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="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="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="WarehouseSaved">A raktárban: {0}</String>
|
||||||
<String key="WarehouseNothingToAdd">Válassz járműveket tartalmazó szerelvényt</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>
|
<String key="GfxGroupLevelTip">Csak a grafika ezen részének szintje</String>
|
||||||
|
|||||||
@@ -904,6 +904,7 @@
|
|||||||
<String key="HaslerBlackBox">Czarna skrzynka</String>
|
<String key="HaslerBlackBox">Czarna skrzynka</String>
|
||||||
<String key="HaslerBlackBoxTip">Cała sesja na mapie: jazda względem AI, komendy, sygnalizatory, kabina</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="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="WarehouseSaved">W magazynie: {0}</String>
|
||||||
<String key="WarehouseNothingToAdd">Wybierz skład z pojazdami</String>
|
<String key="WarehouseNothingToAdd">Wybierz skład z pojazdami</String>
|
||||||
<String key="GfxGroupLevelTip">Poziom tylko tej części grafiki</String>
|
<String key="GfxGroupLevelTip">Poziom tylko tej części grafiki</String>
|
||||||
|
|||||||
@@ -853,6 +853,7 @@
|
|||||||
<String key="HaslerBlackBox">Чёрный ящик</String>
|
<String key="HaslerBlackBox">Чёрный ящик</String>
|
||||||
<String key="HaslerBlackBoxTip">Вся сессия на карте: езда по сравнению с ИИ, команды, светофоры, кабина</String>
|
<String key="HaslerBlackBoxTip">Вся сессия на карте: езда по сравнению с ИИ, команды, светофоры, кабина</String>
|
||||||
<String key="HaslerClickBlackBox">щёлкните по ленте: чёрный ящик в этот момент</String>
|
<String key="HaslerClickBlackBox">щёлкните по ленте: чёрный ящик в этот момент</String>
|
||||||
|
<String key="HaslerSessionNoTape">В этом сеансе нет ленты, только чёрный ящик.</String>
|
||||||
<String key="WarehouseSaved">На складе: {0}</String>
|
<String key="WarehouseSaved">На складе: {0}</String>
|
||||||
<String key="WarehouseNothingToAdd">Выберите состав с транспортом</String>
|
<String key="WarehouseNothingToAdd">Выберите состав с транспортом</String>
|
||||||
<String key="GfxGroupLevelTip">Уровень только для этой части графики</String>
|
<String key="GfxGroupLevelTip">Уровень только для этой части графики</String>
|
||||||
|
|||||||
Reference in New Issue
Block a user