diff --git a/src/Modules/LocalRecordsModule/Services/LocalRecordsService.cs b/src/Modules/LocalRecordsModule/Services/LocalRecordsService.cs index a5239abc8..726d6fee2 100644 --- a/src/Modules/LocalRecordsModule/Services/LocalRecordsService.cs +++ b/src/Modules/LocalRecordsModule/Services/LocalRecordsService.cs @@ -29,7 +29,7 @@ public class LocalRecordsService( IPlayerRecordsRepository playerRecordsRepository) : ILocalRecordsService { private const string WidgetName = "LocalRecordsModule.LocalRecordsWidget"; - + public async Task GetLocalsOfCurrentMapAsync() { var currentMap = await mapService.GetCurrentMapAsync(); @@ -39,7 +39,7 @@ public async Task GetLocalsOfCurrentMapAsync() throw new InvalidOperationException("Failed to get current map"); } - var records = (IEnumerable)await localRecordRepository.GetLocalRecordsOfMapByIdAsync(currentMap.Id); + IEnumerable records = await localRecordRepository.GetLocalRecordsOfMapByIdAsync(currentMap.Id); return records.ToArray(); } @@ -64,7 +64,7 @@ public async Task ShowWidgetToAllAsync() var playerRecords = GetRecordsWithPlayer(player, records); await transaction.SendManialinkAsync(player, WidgetName, new { currentPlayer = player, records = playerRecords }); } - + await transaction.CommitAsync(); } catch (Exception ex) @@ -83,9 +83,9 @@ public async Task UpdatePbAsync(IPlayerRecord record) // player did not get a local record good enough to be registered return; } - + var localRaceTime = RaceTime.FromMilliseconds(localRecord.Record.Score).ToString(); - + if (localRaceTime == null) { throw new InvalidOperationException($"Failed to convert {localRecord.Record.Score} to race time"); @@ -106,18 +106,43 @@ await server.InfoMessageAsync(new TextFormatter() if (record.Score < oldRecord.Record.Score) { - await server.InfoMessageAsync(new TextFormatter() - .AddText(record.Player.NickName) - .AddText(" improved the ") - .AddText($"{localRecord.Position}.", s => s.WithColor(themeManager.Theme.Info)) - .AddText(" local record ") - .AddText(localRaceTime, s => s.WithColor(themeManager.Theme.Info)) - .AddText(" (") - .AddText($"{oldRecord.Position}.", s => s.WithColor(themeManager.Theme.Info)) - .AddText(" - ") - .AddText($"{localRecord.Position}.", s => s.WithColor(themeManager.Theme.Info)) - .AddText(" )") - .ToString()); + var timeDifference = RaceTime.FromMilliseconds(oldRecord.Record.Score - record.Score); + var timeDifferenceStr = timeDifference.ToString(); + + if (timeDifferenceStr == null) + { + throw new InvalidOperationException($"Failed to convert {timeDifference} to race time difference"); + } + + if (localRecord.Position < oldRecord.Position) + { + await server.InfoMessageAsync(new TextFormatter() + .AddText(record.Player.NickName) + .AddText(" claimed ") + .AddText($"{localRecord.Position}.", s => s.WithColor(themeManager.Theme.Info)) + .AddText(" (from ") + .AddText($"{oldRecord.Position}.", s => s.WithColor(themeManager.Theme.Info)) + .AddText(") local record ") + .AddText(localRaceTime, s => s.WithColor(themeManager.Theme.Info)) + .AddText(" (-") + .AddText(timeDifferenceStr, s => s.WithColor(themeManager.Theme.Info)) + .AddText(")") + .ToString()); + } + else + { + await server.InfoMessageAsync(new TextFormatter() + .AddText(record.Player.NickName) + .AddText(" improved their ") + .AddText($"{localRecord.Position}.", s => s.WithColor(themeManager.Theme.Info)) + .AddText(" local record ") + .AddText(localRaceTime, s => s.WithColor(themeManager.Theme.Info)) + .AddText(" (-") + .AddText(timeDifferenceStr, s => s.WithColor(themeManager.Theme.Info)) + .AddText(")") + .ToString()); + } + await ShowWidgetToAllAsync(); } else if (record.Score == localRecord.Record.Score) @@ -151,7 +176,7 @@ private ILocalRecord[] GetRecordsWithPlayer(IPlayer player, ILocalRecord[] recor { return records; } - + var playerRecord = records.FirstOrDefault(r => r.Record.Player.Id == player.Id); var topMaxRows = Math.Min(settings.MaxWidgetRows, records.Length); @@ -166,7 +191,7 @@ private ILocalRecord[] GetRecordsWithPlayer(IPlayer player, ILocalRecord[] recor { return records[..topMaxRows]; } - + // return top records + records around the player var topRecords = records[..Math.Min(settings.WidgetShowTop, records.Length)]; diff --git a/tests/Modules/LocalRecordsModule.Tests/Services/LocalRecordsServiceTests.cs b/tests/Modules/LocalRecordsModule.Tests/Services/LocalRecordsServiceTests.cs index d21380c3a..274d8af7c 100644 --- a/tests/Modules/LocalRecordsModule.Tests/Services/LocalRecordsServiceTests.cs +++ b/tests/Modules/LocalRecordsModule.Tests/Services/LocalRecordsServiceTests.cs @@ -264,7 +264,36 @@ public async Task New_Pb_Is_New_Local_Record() } [Fact] - public async Task New_Pb_Is_Improved_From_Old_Record() + public async Task New_Pb_Is_Improved_From_Old_Record_With_Position_Change() + { + var mock = NewLocalRecordsServiceMock(); + var mockSetup = SetupMockRecords(mock); + var newPb = new DbPlayerRecord + { + PlayerId = 1, Score = 12345, RecordType = PlayerRecordType.Time, DbPlayer = new DbPlayer(mockSetup.Player), + }; + var oldPlayerRecord = new DbPlayerRecord() + { + Score = 123456, RecordType = PlayerRecordType.Time, DbPlayer = new DbPlayer(mockSetup.Player) + }; + var oldRecord = new DbLocalRecord { DbRecord = oldPlayerRecord, Position = 1337 }; + var localRecord = new DbLocalRecord { DbRecord = newPb, Position = 420 }; + + mock.LocalRecordRepository + .Setup(m => m.GetRecordOfPlayerInMapAsync(newPb.Player, newPb.Map)) + .ReturnsAsync((DbLocalRecord?)oldRecord); + + mock.LocalRecordRepository + .Setup(m => m.AddOrUpdateRecordAsync(newPb.Map, newPb)) + .ReturnsAsync(localRecord); + + await mock.Service.UpdatePbAsync(newPb); + + mock.Server.Chat.Verify(m => m.InfoMessageAsync(It.Is(s => s.Contains("claimed"))), Times.Once); + } + + [Fact] + public async Task New_Pb_Is_Improved_From_Old_Record_Without_Position_Change() { var mock = NewLocalRecordsServiceMock(); var mockSetup = SetupMockRecords(mock); @@ -289,7 +318,7 @@ public async Task New_Pb_Is_Improved_From_Old_Record() await mock.Service.UpdatePbAsync(newPb); - mock.Server.Chat.Verify(m => m.InfoMessageAsync(It.Is(s => s.Contains("improved the"))), Times.Once); + mock.Server.Chat.Verify(m => m.InfoMessageAsync(It.Is(s => s.Contains("improved their"))), Times.Once); } [Fact]