diff --git a/ElectronicObserver/Window/Tools/SortieRecordViewer/Replay/ReplayExtensions.cs b/ElectronicObserver/Window/Tools/SortieRecordViewer/Replay/ReplayExtensions.cs index bc33832af..999c4f74f 100644 --- a/ElectronicObserver/Window/Tools/SortieRecordViewer/Replay/ReplayExtensions.cs +++ b/ElectronicObserver/Window/Tools/SortieRecordViewer/Replay/ReplayExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using ElectronicObserver.Core.Types; using ElectronicObserver.Database.Sortie; @@ -31,44 +32,12 @@ public static class ReplayExtensions Fleetnum = sortie.FleetData.FleetId, World = sortie.World, Mapnum = sortie.Map, - Fleet1 = sortie.FleetData.Fleets.Skip(0).FirstOrDefault()?.Ships - .Select(s => new ReplayShip - { - ShipId = s.Id, - Level = s.Level, - Morale = s.Condition, - Kyouka = s.Kyouka, - Equip = s.EquipmentSlots.Append(s.ExpansionSlot).Select(e => ((int?)e?.Equipment?.Id) ?? 0).ToList(), - }).ToList() ?? new(), - Fleet2 = sortie.FleetData.Fleets.Skip(1).FirstOrDefault()?.Ships - .Select(s => new ReplayShip - { - ShipId = s.Id, - Level = s.Level, - Morale = s.Condition, - Kyouka = s.Kyouka, - Equip = s.EquipmentSlots.Append(s.ExpansionSlot).Select(e => ((int?)e?.Equipment?.Id) ?? 0).ToList(), - }).ToList() ?? new(), - Fleet3 = sortie.FleetData.Fleets.Skip(2).FirstOrDefault()?.Ships - .Select(s => new ReplayShip - { - ShipId = s.Id, - Level = s.Level, - Morale = s.Condition, - Kyouka = s.Kyouka, - Equip = s.EquipmentSlots.Append(s.ExpansionSlot).Select(e => ((int?)e?.Equipment?.Id) ?? 0).ToList(), - }).ToList() ?? new(), - Fleet4 = sortie.FleetData.Fleets.Skip(3).FirstOrDefault()?.Ships - .Select(s => new ReplayShip - { - ShipId = s.Id, - Level = s.Level, - Morale = s.Condition, - Kyouka = s.Kyouka, - Equip = s.EquipmentSlots.Append(s.ExpansionSlot).Select(e => ((int?)e?.Equipment?.Id) ?? 0).ToList(), - }).ToList() ?? new(), + Fleet1 = sortie.FleetData.Fleets.Skip(0).FirstOrDefault().ToReplayFleet(), + Fleet2 = sortie.FleetData.Fleets.Skip(1).FirstOrDefault().ToReplayFleet(), + Fleet3 = sortie.FleetData.Fleets.Skip(2).FirstOrDefault().ToReplayFleet(), + Fleet4 = sortie.FleetData.Fleets.Skip(3).FirstOrDefault().ToReplayFleet(), AirBases = sortie.FleetData.AirBases - .Where(b => b.MapAreaId == sortie.Map) + .Where(b => b.MapAreaId == sortie.World) .Select(b => new ReplayAirBase { Rid = b.AirCorpsId, @@ -95,4 +64,24 @@ public static class ReplayExtensions }).ToList(), Battles = new(), }; + + private static List ToReplayFleet(this SortieFleet? fleet) + => fleet?.Ships.Select(ToReplayShip).ToList() ?? new(); + + private static ReplayShip ToReplayShip(SortieShip ship) + { + List slots = [.. ship.EquipmentSlots, ship.ExpansionSlot]; + + return new() + { + ShipId = ship.Id, + Level = ship.Level, + Morale = ship.Condition, + Kyouka = ship.Kyouka, + // equip, stars and ace have to stay index aligned + Equip = slots.Select(s => (int)(s?.Equipment?.Id ?? EquipmentId.Unknown)).ToList(), + Stars = slots.Select(s => s?.Equipment?.Level ?? 0).ToList(), + Ace = slots.Select(s => s?.Equipment?.AircraftLevel ?? 0).ToList(), + }; + } } diff --git a/ElectronicObserver/Window/Tools/SortieRecordViewer/Replay/ReplayShip.cs b/ElectronicObserver/Window/Tools/SortieRecordViewer/Replay/ReplayShip.cs index 6659ed560..33792a056 100644 --- a/ElectronicObserver/Window/Tools/SortieRecordViewer/Replay/ReplayShip.cs +++ b/ElectronicObserver/Window/Tools/SortieRecordViewer/Replay/ReplayShip.cs @@ -20,4 +20,16 @@ public class ReplayShip [JsonPropertyName("equip")] public List Equip { get; set; } + + /// + /// Equipment improvement levels, same order as . + /// + [JsonPropertyName("stars")] + public List Stars { get; set; } = new(); + + /// + /// Equipment proficiency levels, same order as . + /// + [JsonPropertyName("ace")] + public List Ace { get; set; } = new(); } diff --git a/ElectronicObserverCoreTests/Replay/ReplayExportTests.cs b/ElectronicObserverCoreTests/Replay/ReplayExportTests.cs new file mode 100644 index 000000000..755425132 --- /dev/null +++ b/ElectronicObserverCoreTests/Replay/ReplayExportTests.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; +using ElectronicObserver.Database.Sortie; +using ElectronicObserver.Window.Tools.SortieRecordViewer.Replay; +using Xunit; + +namespace ElectronicObserverCoreTests.Replay; + +public class ReplayExportTests +{ + private static string DirectoryName => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; + private static string RelativePath => "SortieDetail"; + private static string BasePath => Path.Join(DirectoryName, RelativePath); + + private static async Task> GetSortieRecords(string fileName) + { + await using Stream stream = File.OpenRead(Path.Join(BasePath, fileName)); + List? data = await JsonSerializer.DeserializeAsync>(stream); + + Assert.NotNull(data); + Assert.NotEmpty(data); + + return data; + } + + [Theory] + [InlineData("SortieDetailTest1.json")] + [InlineData("SortieDetailTest2.json")] + [InlineData("SortieDetailTest4.json")] + public async Task EquipmentImprovementIsExported(string fileName) + { + SortieRecord sortie = (await GetSortieRecords(fileName)).First(); + + ReplayData replay = sortie.ToReplayData(); + + List ships = new[] { replay.Fleet1, replay.Fleet2, replay.Fleet3, replay.Fleet4 } + .SelectMany(f => f ?? new()) + .ToList(); + + Assert.NotEmpty(ships); + + foreach (ReplayShip ship in ships) + { + Assert.Equal(ship.Equip.Count, ship.Stars.Count); + Assert.Equal(ship.Equip.Count, ship.Ace.Count); + } + + Assert.Contains(ships, s => s.Stars.Any(v => v > 0)); + } + + [Fact] + public async Task ImprovementMatchesTheSourceRecord() + { + SortieRecord sortie = (await GetSortieRecords("SortieDetailTest2.json")).First(); + + ReplayData replay = sortie.ToReplayData(); + + List replayShips = replay.Fleet1!; + List sourceShips = sortie.FleetData.Fleets[0]!.Ships; + + Assert.Equal(sourceShips.Count, replayShips.Count); + + foreach ((SortieShip source, ReplayShip exported) in sourceShips.Zip(replayShips)) + { + List slots = [.. source.EquipmentSlots, source.ExpansionSlot]; + + Assert.Equal(slots.Select(s => (int)(s?.Equipment?.Id ?? 0)), exported.Equip); + Assert.Equal(slots.Select(s => s?.Equipment?.Level ?? 0), exported.Stars); + Assert.Equal(slots.Select(s => s?.Equipment?.AircraftLevel ?? 0), exported.Ace); + } + } + + [Fact] + public async Task AirBasesAreExportedWhenWorldDiffersFromMap() + { + // world 58, map 4 - the filter used to compare MapAreaId against the map number + SortieRecord sortie = (await GetSortieRecords("SortieDetailTest1.json")).First(); + + Assert.NotEqual(sortie.World, sortie.Map); + Assert.NotEmpty(sortie.FleetData.AirBases); + + ReplayData replay = sortie.ToReplayData(); + + Assert.NotNull(replay.AirBases); + Assert.Equal(sortie.FleetData.AirBases.Count, replay.AirBases!.Count); + } +}