From e9ad8ea6b91cc255c1e1a397ce6fadb273fb8538 Mon Sep 17 00:00:00 2001 From: snixtho Date: Sat, 5 Oct 2024 11:59:51 +0200 Subject: [PATCH 1/4] Don't start manual tracking if automatic tracking is enabled. (#312) --- .../MatchTrackerEventController.cs | 10 +++++++++- .../MatchTrackerEventControllerTests.cs | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Modules/MatchTrackerModule/Controllers/MatchTrackerEventController.cs b/src/Modules/MatchTrackerModule/Controllers/MatchTrackerEventController.cs index 5ba23a519..ffa353ba7 100644 --- a/src/Modules/MatchTrackerModule/Controllers/MatchTrackerEventController.cs +++ b/src/Modules/MatchTrackerModule/Controllers/MatchTrackerEventController.cs @@ -31,7 +31,15 @@ public Task OnBeginMatchAsync(object sender, EventArgs args) } [Subscribe(FlowControlEvent.MatchStarted)] - public Task OnMatchStarted(object sender, EventArgs args) => tracker.BeginMatchAsync(); + public Task OnMatchStarted(object sender, EventArgs args) + { + if (settings.AutomaticTracking) + { + return Task.CompletedTask; + } + + return tracker.BeginMatchAsync(); + } [Subscribe(FlowControlEvent.MatchEnded)] public Task OnMatchEnded(object sender, EventArgs args) => tracker.EndMatchAsync(); diff --git a/tests/Modules/MatchTrackerModule.Tests/Controllers/MatchTrackerEventControllerTests.cs b/tests/Modules/MatchTrackerModule.Tests/Controllers/MatchTrackerEventControllerTests.cs index 0dcfaaed3..da20eaf4d 100644 --- a/tests/Modules/MatchTrackerModule.Tests/Controllers/MatchTrackerEventControllerTests.cs +++ b/tests/Modules/MatchTrackerModule.Tests/Controllers/MatchTrackerEventControllerTests.cs @@ -49,4 +49,24 @@ public async Task Begin_Match_Is_Tracked_Depending_On_Settings(bool automaticTra _tracker.Verify(m => m.BeginMatchAsync(), timesCalled); } + + [Fact] + public async Task Manual_Match_Tracking_Is_Disabled_If_Automatic_Tracking_Is_Enabled() + { + _settings.Setup(m => m.AutomaticTracking).Returns(true); + + await Controller.OnMatchStarted(null, null); + + _tracker.Verify(m => m.BeginMatchAsync(), Times.Never); + } + + [Fact] + public async Task Manual_Match_Tracking_Is_Triggered_If_Automatic_Tracking_Is_Disabled() + { + _settings.Setup(m => m.AutomaticTracking).Returns(false); + + await Controller.OnMatchStarted(null, null); + + _tracker.Verify(m => m.BeginMatchAsync(), Times.Once); + } } From 8f7dedbad3289e01bfc54eec2ac0e4d8548128cf Mon Sep 17 00:00:00 2001 From: Kjell Dankert <46489624+kdankert@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:17:40 -0700 Subject: [PATCH 2/4] Add time difference to local record announcement (#307) --- .../Services/LocalRecordsService.cs | 63 +++++++++++++------ .../Services/LocalRecordsServiceTests.cs | 33 +++++++++- 2 files changed, 75 insertions(+), 21 deletions(-) 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] From c7f6e4d97c4165c1fbcaa21fdfbc09cb87f84244 Mon Sep 17 00:00:00 2001 From: braker <7152322+araszka@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:49:37 +0200 Subject: [PATCH 3/4] 276 spec info (#313) Co-authored-by: snixtho --- .../SpectatorTargetInfoEventController.cs | 21 ++- .../SpectatorTargetInfoManialinkController.cs | 2 +- .../Interfaces/ISpectatorTargetInfoService.cs | 33 ++++- .../Models/CheckpointsGroup.cs | 7 + .../Services/SpectatorTargetInfoService.cs | 135 ++++++++++++++---- .../Templates/Scripts/SpectatorTargetInfo.ms | 2 +- .../SpectatorTargetEventControllerTests.cs | 54 +++++-- ...tatorTargetInfoManialinkControllerTests.cs | 12 +- .../Models/CheckpointsGroupTests.cs | 37 ++++- .../SpectatorTargetInfoServiceTests.cs | 79 ++++++++-- 10 files changed, 309 insertions(+), 73 deletions(-) diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs index 7631644ea..533fc9b98 100644 --- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs @@ -15,11 +15,14 @@ public class SpectatorTargetInfoEventController(ISpectatorTargetInfoService spec { [Subscribe(GbxRemoteEvent.PlayerDisconnect)] public Task OnPlayerDisconnectAsync(object sender, PlayerGbxEventArgs eventArgs) => - spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(eventArgs.Login); + spectatorTargetInfoService.RemovePlayerAsync(eventArgs.Login); [Subscribe(GbxRemoteEvent.BeginMap)] - public Task OnBeginMapAsync(object sender, MapGbxEventArgs eventArgs) => - spectatorTargetInfoService.UpdateIsTeamsModeAsync(); + public async Task OnBeginMapAsync(object sender, MapGbxEventArgs eventArgs) + { + await spectatorTargetInfoService.DetectIsTeamsModeAsync(); + await spectatorTargetInfoService.DetectIsTimeAttackModeAsync(); + } [Subscribe(ModeScriptEvent.WayPoint)] public Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) => @@ -44,4 +47,16 @@ public async Task OnNewWarmUpRoundAsync(object sender, WarmUpRoundEventArgs roun await spectatorTargetInfoService.FetchAndCacheTeamInfoAsync(); await spectatorTargetInfoService.ResetWidgetForSpectatorsAsync(); } + + [Subscribe(ModeScriptEvent.WarmUpStart)] + public Task OnWarmUpStartAsync(object sender, EventArgs args) => + spectatorTargetInfoService.UpdateIsTimeAttackModeAsync(true); + + [Subscribe(ModeScriptEvent.WarmUpEnd)] + public Task OnWarmUpEndAsync(object sender, EventArgs args) => + spectatorTargetInfoService.DetectIsTimeAttackModeAsync(); + + [Subscribe(ModeScriptEvent.GiveUp)] + public Task OnPlayerGiveUpAsync(object sender, PlayerUpdateEventArgs args) => + spectatorTargetInfoService.ClearCheckpointsAsync(args.Login); } diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoManialinkController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoManialinkController.cs index 04b17fb77..47f6c5fd2 100644 --- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoManialinkController.cs +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoManialinkController.cs @@ -19,7 +19,7 @@ public async Task ReportSpectatorTargetAsync(string targetLogin) } else { - await spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(spectatorLogin); + await spectatorTargetInfoService.RemovePlayerAsync(spectatorLogin); await spectatorTargetInfoService.HideSpectatorInfoWidgetAsync(spectatorLogin); } } diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index 3be2de777..dcc42f2b1 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -25,6 +25,12 @@ public interface ISpectatorTargetInfoService /// /// public Task ClearCheckpointsAsync(); + + /// + /// Clears all registered checkpoint times of the given player. + /// + /// + public Task ClearCheckpointsAsync(string playerLogin); /// /// Retrieve an IOnlinePlayer instance by their login. @@ -59,9 +65,9 @@ public interface ISpectatorTargetInfoService /// /// Remove a player from the spectators list. /// - /// + /// /// - public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin); + public Task RemovePlayerAsync(string playerLogin); /// /// Gets the logins of a players spectating the given target. @@ -98,6 +104,12 @@ public interface ISpectatorTargetInfoService /// public Dictionary GetCheckpointTimes(); + /// + /// Returns the current spectator targets. + /// + /// + public Dictionary GetSpectatorTargets(); + /// /// Resets the widget for all spectating players. /// @@ -166,10 +178,23 @@ public interface ISpectatorTargetInfoService public Task FetchAndCacheTeamInfoAsync(); /// - /// Updates whether team mode is active or not. + /// Updates whether team mode is active. + /// + /// + public Task DetectIsTeamsModeAsync(); + + /// + /// Detects whether time attack mode is active. + /// + /// + public Task DetectIsTimeAttackModeAsync(); + + /// + /// Manually sets active state of time attack mode. /// + /// /// - public Task UpdateIsTeamsModeAsync(); + public Task UpdateIsTimeAttackModeAsync(bool isTimeAttack); /// /// Hides the default game mode UI. diff --git a/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs b/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs index 07f882846..b2db04336 100644 --- a/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs +++ b/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs @@ -18,9 +18,16 @@ public int GetRank(string playerLogin) { return rank; } + rank++; } return rank; } + + public bool ForgetPlayer(string playerLogin) => + (from checkpointData in this + where checkpointData.player.GetLogin() == playerLogin + select this.Remove(checkpointData) + ).FirstOrDefault(); } diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index 9af83f349..f8489efd6 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -33,14 +33,18 @@ ILogger logger private const string ReportTargetTemplate = "SpectatorTargetInfoModule.ReportSpecTarget"; private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo"; + private readonly object _checkpointTimesMutex = new(); + private readonly object _spectatorTargetsMutex = new(); private readonly Dictionary _checkpointTimes = new(); // cp-id -> CheckpointsGroup private readonly Dictionary _spectatorTargets = new(); // login -> IOnlinePlayer private readonly Dictionary _teamInfos = new(); + private bool _isTimeAttackMode; private bool _isTeamsMode; public async Task InitializeAsync() { - await UpdateIsTeamsModeAsync(); + await DetectIsTeamsModeAsync(); + await DetectIsTimeAttackModeAsync(); await FetchAndCacheTeamInfoAsync(); await SendReportSpectatorTargetManialinkAsync(); await HideGameModeUiAsync(); @@ -53,15 +57,18 @@ public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, in { var player = await GetOnlinePlayerByLoginAsync(playerLogin); var newCheckpointData = new CheckpointData(player, checkpointTime); + CheckpointsGroup checkpointsGroup = []; - if (!_checkpointTimes.TryGetValue(checkpointIndex, out var checkpointGroup)) + lock (_checkpointTimesMutex) { - checkpointGroup = []; - _checkpointTimes.Add(checkpointIndex, checkpointGroup); - } + if (_checkpointTimes.TryGetValue(checkpointIndex, out var existingCheckpointGroup)) + { + checkpointsGroup = existingCheckpointGroup; + } - checkpointGroup.Add(newCheckpointData); - _checkpointTimes[checkpointIndex] = checkpointGroup; + checkpointsGroup.Add(newCheckpointData); + _checkpointTimes[checkpointIndex] = checkpointsGroup; + } var spectatorLogins = GetLoginsOfPlayersSpectatingTarget(player).ToList(); if (spectatorLogins.IsNullOrEmpty()) @@ -69,16 +76,42 @@ public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, in return; } - var leadingCheckpointData = checkpointGroup.First(); + var leadingCheckpointData = checkpointsGroup.First(); var timeDifference = GetTimeDifference(leadingCheckpointData.time, newCheckpointData.time); - await SendSpectatorInfoWidgetAsync(spectatorLogins, player, checkpointGroup.GetRank(playerLogin), - timeDifference); + await SendSpectatorInfoWidgetAsync( + spectatorLogins, + player, + checkpointsGroup.GetRank(playerLogin), + timeDifference + ); } public Task ClearCheckpointsAsync() { - _checkpointTimes.Clear(); + lock (_checkpointTimesMutex) + { + _checkpointTimes.Clear(); + } + + return Task.CompletedTask; + } + + public Task ClearCheckpointsAsync(string playerLogin) + { + if (!_isTimeAttackMode) + { + //New round event is going to clear the entries. + return Task.CompletedTask; + } + + lock (_checkpointTimesMutex) + { + foreach (var checkpointGroup in _checkpointTimes.Values) + { + checkpointGroup.ForgetPlayer(playerLogin); + } + } return Task.CompletedTask; } @@ -101,12 +134,15 @@ public Task ClearCheckpointsAsync() var targetPlayer = await GetOnlinePlayerByLoginAsync(targetLogin); - if (_spectatorTargets.TryGetValue(spectatorLogin, out var target) && target == targetPlayer) + lock (_spectatorTargetsMutex) { - return null; //Player is already spectating target - } + if (_spectatorTargets.TryGetValue(spectatorLogin, out var target) && target == targetPlayer) + { + return null; //Player is already spectating target + } - _spectatorTargets[spectatorLogin] = targetPlayer; + _spectatorTargets[spectatorLogin] = targetPlayer; + } logger.LogTrace("Updated spectator target {spectatorLogin} -> {targetLogin}.", spectatorLogin, targetLogin); @@ -123,11 +159,26 @@ public async Task SetSpectatorTargetAndSendAsync(string spectatorLogin, string t } } - public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin) + public Task RemovePlayerAsync(string playerLogin) { - if (_spectatorTargets.Remove(spectatorLogin)) + lock (_spectatorTargetsMutex) { - logger.LogTrace("Removed spectator {spectatorLogin}.", spectatorLogin); + if (_spectatorTargets.Remove(playerLogin)) + { + //Player was spectator + logger.LogTrace("Removed spectator {spectatorLogin}.", playerLogin); + + return Task.CompletedTask; + } + + //Player is driver, get all spectators + var spectatorLoginsToRemove = _spectatorTargets.Where(kv => kv.Value.GetLogin() == playerLogin) + .Select(kv => kv.Key); + + foreach (var spectatorLogin in spectatorLoginsToRemove) + { + _spectatorTargets.Remove(spectatorLogin); + } } return Task.CompletedTask; @@ -135,13 +186,14 @@ public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin) public IEnumerable GetLoginsOfPlayersSpectatingTarget(IOnlinePlayer targetPlayer) { - return _spectatorTargets.Where(specTarget => specTarget.Value.AccountId == targetPlayer.AccountId) + return GetSpectatorTargets() + .Where(specTarget => specTarget.Value.AccountId == targetPlayer.AccountId) .Select(specTarget => specTarget.Key); } public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime) { - return targetCheckpointTime - leadingCheckpointTime; + return int.Abs(targetCheckpointTime - leadingCheckpointTime); } public string GetTeamColor(PlayerTeam team) @@ -152,7 +204,8 @@ public string GetTeamColor(PlayerTeam team) public int GetLastCheckpointIndexOfPlayer(IOnlinePlayer player) { var playerLogin = player.GetLogin(); - foreach (var (checkpointIndex, checkpointsGroup) in _checkpointTimes.Reverse()) + + foreach (var (checkpointIndex, checkpointsGroup) in GetCheckpointTimes().Reverse()) { if (checkpointsGroup.GetPlayerCheckpointData(playerLogin) != null) { @@ -163,12 +216,25 @@ public int GetLastCheckpointIndexOfPlayer(IOnlinePlayer player) return -1; } - public Dictionary GetCheckpointTimes() => - _checkpointTimes; + public Dictionary GetCheckpointTimes() + { + lock (_checkpointTimesMutex) + { + return _checkpointTimes; + } + } + + public Dictionary GetSpectatorTargets() + { + lock (_spectatorTargetsMutex) + { + return _spectatorTargets; + } + } public async Task ResetWidgetForSpectatorsAsync() { - foreach (var (spectatorLogin, targetPlayer) in _spectatorTargets) + foreach (var (spectatorLogin, targetPlayer) in GetSpectatorTargets()) { var widgetData = GetWidgetData(targetPlayer, 1, 0); await SendSpectatorInfoWidgetAsync(spectatorLogin, targetPlayer, widgetData); @@ -182,7 +248,7 @@ public async Task SendSpectatorInfoWidgetAsync(string spectatorLogin, IOnlinePla var targetRank = 1; var timeDifference = 0; - if (_checkpointTimes.TryGetValue(checkpointIndex, out var checkpointsGroup)) + if (GetCheckpointTimes().TryGetValue(checkpointIndex, out var checkpointsGroup)) { var leadingCpData = checkpointsGroup.First(); var targetCpData = checkpointsGroup.GetPlayerCheckpointData(targetLogin); @@ -236,13 +302,22 @@ public async Task FetchAndCacheTeamInfoAsync() _teamInfos[PlayerTeam.Team2] = await server.Remote.GetTeamInfoAsync((int)PlayerTeam.Team2 + 1); } - public async Task UpdateIsTeamsModeAsync() + public async Task DetectIsTeamsModeAsync() { - _isTeamsMode = - await matchSettingsService.GetCurrentModeAsync() is DefaultModeScriptName.Teams - or DefaultModeScriptName.TmwtTeams; + _isTeamsMode = await matchSettingsService.GetCurrentModeAsync() is DefaultModeScriptName.Teams + or DefaultModeScriptName.TmwtTeams; + } - logger.LogInformation("Team mode is {state}", _isTeamsMode ? "active" : "not active"); + public async Task DetectIsTimeAttackModeAsync() + { + _isTimeAttackMode = await matchSettingsService.GetCurrentModeAsync() is DefaultModeScriptName.TimeAttack; + } + + public Task UpdateIsTimeAttackModeAsync(bool isTimeAttack) + { + _isTimeAttackMode = isTimeAttack; + + return Task.CompletedTask; } public Task HideGameModeUiAsync() => diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/Scripts/SpectatorTargetInfo.ms b/src/Modules/SpectatorTargetInfoModule/Templates/Scripts/SpectatorTargetInfo.ms index aba5f6be8..1f4e2d572 100644 --- a/src/Modules/SpectatorTargetInfoModule/Templates/Scripts/SpectatorTargetInfo.ms +++ b/src/Modules/SpectatorTargetInfoModule/Templates/Scripts/SpectatorTargetInfo.ms @@ -73,7 +73,7 @@ Void FocusPlayer(CSmPlayer _Player) { Void SpecPrevious(CMlLabel button, Boolean focus){ AnimatePop(button); declare CSmPlayer target <=> GetNextSpawnedPlayer(); - if(target == Null && focus){ + if(target != Null && focus){ FocusPlayer(target); } } diff --git a/tests/Modules/SpectatorTargetInfoModule.Tests/Controllers/SpectatorTargetEventControllerTests.cs b/tests/Modules/SpectatorTargetInfoModule.Tests/Controllers/SpectatorTargetEventControllerTests.cs index b2e30ce90..62441c19a 100644 --- a/tests/Modules/SpectatorTargetInfoModule.Tests/Controllers/SpectatorTargetEventControllerTests.cs +++ b/tests/Modules/SpectatorTargetInfoModule.Tests/Controllers/SpectatorTargetEventControllerTests.cs @@ -24,27 +24,28 @@ public async Task Removes_Player_From_Spectators_On_Disconnect() { var login = "*fakeplayer_unittest*"; - await Controller.OnPlayerDisconnectAsync(null, new PlayerGbxEventArgs { Login = login }); + await Controller.OnPlayerDisconnectAsync(null!, new PlayerGbxEventArgs { Login = login }); - _spectatorTargetService.Verify(sts => sts.RemovePlayerFromSpectatorsListAsync(login)); + _spectatorTargetService.Verify(sts => sts.RemovePlayerAsync(login)); } [Fact] - public async Task Updates_Team_Mode_On_New_Map() + public async Task Detects_Team_And_TimeAttack_Mode_On_New_Map() { - await Controller.OnBeginMapAsync(null, new MapGbxEventArgs()); + await Controller.OnBeginMapAsync(null!, new MapGbxEventArgs()); - _spectatorTargetService.Verify(sts => sts.UpdateIsTeamsModeAsync()); + _spectatorTargetService.Verify(sts => sts.DetectIsTeamsModeAsync()); + _spectatorTargetService.Verify(sts => sts.DetectIsTimeAttackModeAsync()); } [Fact] - public async Task Registers_Ceckpoint_Times() + public async Task Registers_Checkpoint_Times() { var login = "*fakeplayer_unittest*"; var checkpointId = 3; var lapTime = 1234; - await Controller.OnWayPointAsync(null, + await Controller.OnWayPointAsync(null!, new WayPointEventArgs { Login = login, @@ -55,9 +56,9 @@ await Controller.OnWayPointAsync(null, CheckpointInLap = checkpointId, IsEndRace = false, IsEndLap = false, - CurrentRaceCheckpoints = null, - CurrentLapCheckpoints = null, - BlockId = null, + CurrentRaceCheckpoints = [], + CurrentLapCheckpoints = [], + BlockId = "", Speed = 0, Time = 0 }); @@ -68,18 +69,47 @@ await Controller.OnWayPointAsync(null, [Fact] public async Task Resets_Collected_Data_On_New_Round() { - await Controller.OnNewRoundAsync(null, new RoundEventArgs { Count = 0, Time = 0 }); + await Controller.OnNewRoundAsync(null!, new RoundEventArgs { Count = 0, Time = 0 }); _spectatorTargetService.Verify(sts => sts.ClearCheckpointsAsync()); _spectatorTargetService.Verify(sts => sts.FetchAndCacheTeamInfoAsync()); + _spectatorTargetService.Verify(sts => sts.ResetWidgetForSpectatorsAsync()); } [Fact] public async Task Resets_Collected_Data_On_New_Warm_Up_Round() { - await Controller.OnNewWarmUpRoundAsync(null, new WarmUpRoundEventArgs { Total = 3, Current = 1 }); + await Controller.OnNewWarmUpRoundAsync(null!, new WarmUpRoundEventArgs { Total = 3, Current = 1 }); _spectatorTargetService.Verify(sts => sts.ClearCheckpointsAsync()); _spectatorTargetService.Verify(sts => sts.FetchAndCacheTeamInfoAsync()); + _spectatorTargetService.Verify(sts => sts.ResetWidgetForSpectatorsAsync()); + } + + [Fact] + public async Task Sets_TimeAttack_Mode_To_Active_At_Warm_Up_Start() + { + await Controller.OnWarmUpStartAsync(null!, EventArgs.Empty); + + _spectatorTargetService.Verify(sts => sts.UpdateIsTimeAttackModeAsync(true)); + } + + [Fact] + public async Task Detects_TimeAttack_Mode_At_Warm_Up_End() + { + await Controller.OnWarmUpEndAsync(null!, EventArgs.Empty); + + _spectatorTargetService.Verify(sts => sts.DetectIsTimeAttackModeAsync()); + } + + [Fact] + public async Task Clears_Checkpoints_Of_Player_On_Give_Up() + { + var eventArgs = new PlayerUpdateEventArgs { Login = "*fakeplayer1*", AccountId = "*fakeplayer1*", Time = 0 }; + + await _spectatorTargetService.Object.UpdateIsTimeAttackModeAsync(true); + await Controller.OnPlayerGiveUpAsync(null!, eventArgs); + + _spectatorTargetService.Verify(sts => sts.ClearCheckpointsAsync(eventArgs.Login)); } } diff --git a/tests/Modules/SpectatorTargetInfoModule.Tests/Controllers/SpectatorTargetInfoManialinkControllerTests.cs b/tests/Modules/SpectatorTargetInfoModule.Tests/Controllers/SpectatorTargetInfoManialinkControllerTests.cs index 355a7f8cb..6be76d32c 100644 --- a/tests/Modules/SpectatorTargetInfoModule.Tests/Controllers/SpectatorTargetInfoManialinkControllerTests.cs +++ b/tests/Modules/SpectatorTargetInfoModule.Tests/Controllers/SpectatorTargetInfoManialinkControllerTests.cs @@ -22,7 +22,7 @@ public SpectatorTargetInfoManialinkControllerTests() } [Fact] - public async Task SetsSpectatorTargetIfGivenLoginIsValid() + public async Task Sets_Spectator_Target_If_Given_Login_Is_Valid() { _actor.Setup(actor => actor.AccountId) .Returns("*fakeplayer_spectator*"); @@ -33,12 +33,12 @@ public async Task SetsSpectatorTargetIfGivenLoginIsValid() await Controller.ReportSpectatorTargetAsync(targetLogin); _spectatorTargetService.Verify(st => st.SetSpectatorTargetAndSendAsync(spectatorLogin, targetLogin), Times.Once); - _spectatorTargetService.Verify(st => st.RemovePlayerFromSpectatorsListAsync(spectatorLogin), Times.Never); + _spectatorTargetService.Verify(st => st.RemovePlayerAsync(spectatorLogin), Times.Never); _spectatorTargetService.Verify(st => st.HideSpectatorInfoWidgetAsync(spectatorLogin), Times.Never); } [Fact] - public async Task RemoveSpectatorIfTargetIsEmpty() + public async Task Remove_Spectator_If_Target_Is_Empty() { _actor.Setup(actor => actor.AccountId) .Returns("*fakeplayer_spectator*"); @@ -49,12 +49,12 @@ public async Task RemoveSpectatorIfTargetIsEmpty() await Controller.ReportSpectatorTargetAsync(targetLogin); _spectatorTargetService.Verify(st => st.SetSpectatorTargetAndSendAsync(spectatorLogin, targetLogin), Times.Never); - _spectatorTargetService.Verify(st => st.RemovePlayerFromSpectatorsListAsync(spectatorLogin), Times.Once); + _spectatorTargetService.Verify(st => st.RemovePlayerAsync(spectatorLogin), Times.Once); _spectatorTargetService.Verify(st => st.HideSpectatorInfoWidgetAsync(spectatorLogin), Times.Once); } [Fact] - public async Task RemoveSpectatorIfTargetIsSpectatorThemselvesEmpty() + public async Task Remove_Spectator_If_Target_Is_Spectator_Themselves() { _actor.Setup(actor => actor.AccountId) .Returns("*fakeplayer_spectator*"); @@ -65,7 +65,7 @@ public async Task RemoveSpectatorIfTargetIsSpectatorThemselvesEmpty() await Controller.ReportSpectatorTargetAsync(targetLogin); _spectatorTargetService.Verify(st => st.SetSpectatorTargetAndSendAsync(spectatorLogin, targetLogin), Times.Never); - _spectatorTargetService.Verify(st => st.RemovePlayerFromSpectatorsListAsync(spectatorLogin), Times.Once); + _spectatorTargetService.Verify(st => st.RemovePlayerAsync(spectatorLogin), Times.Once); _spectatorTargetService.Verify(st => st.HideSpectatorInfoWidgetAsync(spectatorLogin), Times.Once); } } diff --git a/tests/Modules/SpectatorTargetInfoModule.Tests/Models/CheckpointsGroupTests.cs b/tests/Modules/SpectatorTargetInfoModule.Tests/Models/CheckpointsGroupTests.cs index 4d8c077fc..f83318498 100644 --- a/tests/Modules/SpectatorTargetInfoModule.Tests/Models/CheckpointsGroupTests.cs +++ b/tests/Modules/SpectatorTargetInfoModule.Tests/Models/CheckpointsGroupTests.cs @@ -21,7 +21,7 @@ public Task Gets_Player_Data_From_Group() new CheckpointData(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer12*" }, 2)); var time = checkpointsGroup.GetPlayerCheckpointData(targetPlayer.GetLogin())?.time; - + Assert.Equal(1, time); return Task.CompletedTask; @@ -51,7 +51,7 @@ public Task Gets_Rank_Of_Player() new CheckpointData(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer12*" }, 2)); var rank = checkpointsGroup.GetRank(targetPlayer.AccountId); - + Assert.Equal(2, rank); return Task.CompletedTask; @@ -72,7 +72,7 @@ public Task Gets_Rank_Of_Player_Correctly_If_Another_Player_Has_The_Same_Time_Be new CheckpointData(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer12*" }, 2)); var rank = checkpointsGroup.GetRank(targetPlayer.AccountId); - + Assert.Equal(3, rank); return Task.CompletedTask; @@ -93,9 +93,38 @@ public Task Gets_Rank_Of_Player_Correctly_If_Another_Player_Has_The_Same_Time_Af new CheckpointData(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer12*" }, 2)); var rank = checkpointsGroup.GetRank(targetPlayer.AccountId); - + Assert.Equal(2, rank); return Task.CompletedTask; } + + [Fact] + public Task Forgets_Given_Player() + { + var checkpointsGroup = new CheckpointsGroup(); + var targetPlayer = new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer1*" }; + + checkpointsGroup.Add(new CheckpointData(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer10*" }, 0)); + checkpointsGroup.Add(new CheckpointData(targetPlayer, 1)); + checkpointsGroup.Add(new CheckpointData(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer11*" }, 1)); + checkpointsGroup.Add(new CheckpointData(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer12*" }, 2)); + + var entryRemoved = checkpointsGroup.ForgetPlayer(targetPlayer.GetLogin()); + + Assert.True(entryRemoved); + + return Task.CompletedTask; + } + + [Fact] + public Task Does_Not_Forget_Non_Existent_Player() + { + var checkpointsGroup = new CheckpointsGroup(); + var entryRemoved = checkpointsGroup.ForgetPlayer("*fakeplayer1*"); + + Assert.False(entryRemoved); + + return Task.CompletedTask; + } } diff --git a/tests/Modules/SpectatorTargetInfoModule.Tests/Services/SpectatorTargetInfoServiceTests.cs b/tests/Modules/SpectatorTargetInfoModule.Tests/Services/SpectatorTargetInfoServiceTests.cs index 66f1e7f8e..8fb9cb192 100644 --- a/tests/Modules/SpectatorTargetInfoModule.Tests/Services/SpectatorTargetInfoServiceTests.cs +++ b/tests/Modules/SpectatorTargetInfoModule.Tests/Services/SpectatorTargetInfoServiceTests.cs @@ -72,7 +72,7 @@ public async Task Adds_And_Clears_Checkpoint_Data() await spectatorTargetService.ClearCheckpointsAsync(); var checkpointTime = spectatorTargetService.GetCheckpointTimes(); - + Assert.Empty(checkpointTime); } @@ -143,6 +143,58 @@ public async Task Removes_Spectator_If_Target_Login_Is_Null() Assert.DoesNotContain("*fakeplayer1*", spectatorOfPlayer); } + [Fact] + public async Task Removes_Spectator_From_Spectator_Target_Repository() + { + var spectatorTargetService = ServiceMock(); + var spectatorLogin = "*fakeplayer_spec*"; + + await spectatorTargetService.AddCheckpointAsync("*fakeplayer1*", 1, 1); + await spectatorTargetService.SetSpectatorTargetAsync(spectatorLogin, "*fakeplayer1*"); + await spectatorTargetService.RemovePlayerAsync(spectatorLogin); + + var spectatorInRepo = spectatorTargetService + .GetSpectatorTargets() + .ContainsKey(spectatorLogin); + + Assert.False(spectatorInRepo); + } + + [Fact] + public async Task Removes_Driver_From_Spectator_Target_Repository() + { + var spectatorTargetService = ServiceMock(); + var spectatorLogin1 = "*fakeplayer99*"; + var spectatorLogin2 = "*fakeplayer98*"; + var targetLogin = "*fakeplayer2*"; + + _playerManager.Setup(pm => pm.GetOnlinePlayerAsync("*fakeplayer1*")) + .ReturnsAsync(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer1*" }); + _playerManager.Setup(pm => pm.GetOnlinePlayerAsync("*fakeplayer2*")) + .ReturnsAsync(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer2*" }); + _playerManager.Setup(pm => pm.GetOnlinePlayerAsync("*fakeplayer3*")) + .ReturnsAsync(new OnlinePlayer { State = PlayerState.Playing, AccountId = "*fakeplayer3*" }); + + await spectatorTargetService.AddCheckpointAsync("*fakeplayer1*", 1, 1); + await spectatorTargetService.AddCheckpointAsync(targetLogin, 2, 2); + await spectatorTargetService.AddCheckpointAsync("*fakeplayer3*", 3, 3); + await spectatorTargetService.SetSpectatorTargetAsync(spectatorLogin1, targetLogin); + await spectatorTargetService.SetSpectatorTargetAsync(spectatorLogin2, targetLogin); + + await spectatorTargetService.RemovePlayerAsync(targetLogin); + + var targetPlayerInRepo = spectatorTargetService + .GetSpectatorTargets() + .Any(kv => kv.Value.GetLogin() == targetLogin); + + var spectatorsOfTargetInRepo = spectatorTargetService + .GetSpectatorTargets() + .Any(kv => kv.Key == spectatorLogin1 || kv.Key == spectatorLogin2); + + Assert.False(targetPlayerInRepo); + Assert.False(spectatorsOfTargetInRepo); + } + [Fact] public async Task Gets_Logins_Spectating_The_Given_Target() { @@ -201,7 +253,7 @@ public Task Gets_Rank_From_Sorted_Checkpoints_List() var player2Rank = checkpointsList.GetRank("*fakeplayer2*"); var player3Rank = checkpointsList.GetRank("*fakeplayer3*"); var player4Rank = checkpointsList.GetRank("*fakeplayer4*"); - + Assert.Equal(1, player1Rank); Assert.Equal(2, player2Rank); Assert.Equal(4, player3Rank); @@ -213,7 +265,7 @@ public Task Gets_Rank_From_Sorted_Checkpoints_List() [Theory] [InlineData(900, 1_000, 100)] [InlineData(100, 999, 899)] - [InlineData(400, 200, -200)] + [InlineData(400, 200, 200)] public Task Calculates_Time_Difference(int leadingTime, int trailingTime, int expectedTime) { var spectatorTargetService = ServiceMock(); @@ -265,12 +317,12 @@ public async Task Gets_The_Team_Color() _server.Remote.Setup(remote => remote.GetTeamInfoAsync(2)) .ReturnsAsync(new TmTeamInfo { RGB = "111111" }); - await spectatorTargetService.UpdateIsTeamsModeAsync(); + await spectatorTargetService.DetectIsTeamsModeAsync(); await spectatorTargetService.FetchAndCacheTeamInfoAsync(); var team1Color = spectatorTargetService.GetTeamColor(PlayerTeam.Team1); var team2Color = spectatorTargetService.GetTeamColor(PlayerTeam.Team2); - + Assert.Equal("FF0066", team1Color); Assert.Equal("111111", team2Color); } @@ -304,12 +356,13 @@ public async Task Sends_The_Widget_To_The_Given_Player_With_Arguments() .ReturnsAsync(new TmTeamInfo { RGB = "FF0066" }); var spectatorTargetService = ServiceMock(); - await spectatorTargetService.UpdateIsTeamsModeAsync(); + await spectatorTargetService.DetectIsTeamsModeAsync(); await spectatorTargetService.FetchAndCacheTeamInfoAsync(); var widgetData = spectatorTargetService.GetWidgetData(targetPlayer, 2, 150); await spectatorTargetService.SendSpectatorInfoWidgetAsync(spectatorLogin, targetPlayer, widgetData); - _manialinkManager.Verify(mm => mm.SendManialinkAsync(spectatorLogin, "SpectatorTargetInfoModule.SpectatorTargetInfo",widgetData)); + _manialinkManager.Verify(mm => + mm.SendManialinkAsync(spectatorLogin, "SpectatorTargetInfoModule.SpectatorTargetInfo", widgetData)); } [Fact] @@ -332,7 +385,7 @@ public async Task Sends_The_Widget_To_The_Given_Players_With_Arguments() .ReturnsAsync(new TmTeamInfo { RGB = "111111" }); var spectatorTargetService = ServiceMock(); - await spectatorTargetService.UpdateIsTeamsModeAsync(); + await spectatorTargetService.DetectIsTeamsModeAsync(); await spectatorTargetService.FetchAndCacheTeamInfoAsync(); await spectatorTargetService.SendSpectatorInfoWidgetAsync(spectatorLogins, targetPlayer, 2, 150); @@ -373,15 +426,17 @@ public async Task Sends_The_Widget_To_The_Given_Player_Without_Time_And_Checkpoi .ReturnsAsync(otherPlayer); var spectatorTargetService = ServiceMock(); - await spectatorTargetService.UpdateIsTeamsModeAsync(); + await spectatorTargetService.DetectIsTeamsModeAsync(); await spectatorTargetService.FetchAndCacheTeamInfoAsync(); await spectatorTargetService.AddCheckpointAsync(otherPlayer.GetLogin(), 2, 1000); await spectatorTargetService.AddCheckpointAsync(targetPlayer.GetLogin(), 2, 1234); await spectatorTargetService.SendSpectatorInfoWidgetAsync(spectatorLogin, targetPlayer); - + _manialinkManager.Verify(mm => - mm.SendManialinkAsync(spectatorLogin, "SpectatorTargetInfoModule.SpectatorTargetInfo", It.IsAny()), Times.Once); + mm.SendManialinkAsync(spectatorLogin, "SpectatorTargetInfoModule.SpectatorTargetInfo", + It.IsAny()), + Times.Once); } [Fact] @@ -404,7 +459,7 @@ public async Task Resets_Widget_For_Spectators() .ReturnsAsync(targetPlayer); var spectatorTargetService = ServiceMock(); - await spectatorTargetService.UpdateIsTeamsModeAsync(); + await spectatorTargetService.DetectIsTeamsModeAsync(); await spectatorTargetService.FetchAndCacheTeamInfoAsync(); await spectatorTargetService.SetSpectatorTargetAsync(spectatorLogin, targetPlayer.GetLogin()); await spectatorTargetService.ResetWidgetForSpectatorsAsync(); From cfee0670c46b2716d8a2102434255e964c902e63 Mon Sep 17 00:00:00 2001 From: Chris <50419942+flaeri@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:17:50 +0200 Subject: [PATCH 4/4] WRM: Handle Flurl http exceptions (#309) --- .../Interfaces/IWorldRecordService.cs | 7 +++--- .../Services/WorldRecordService.cs | 23 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Modules/WorldRecordModule/Interfaces/IWorldRecordService.cs b/src/Modules/WorldRecordModule/Interfaces/IWorldRecordService.cs index dca17f43f..baf11cf64 100644 --- a/src/Modules/WorldRecordModule/Interfaces/IWorldRecordService.cs +++ b/src/Modules/WorldRecordModule/Interfaces/IWorldRecordService.cs @@ -6,18 +6,19 @@ namespace EvoSC.Modules.Official.WorldRecordModule.Interfaces; public interface IWorldRecordService { /// - /// Trigger a fetch for records from trackmania.io + /// Trigger a fetch for records from trackmania.io. + /// If an error occurs during the API fetch, the method will log the error and use AT instead. /// /// The UID of the map to load records from. /// public Task FetchRecordAsync(string mapUid); - + /// /// Clears the currently loaded world record. /// /// public Task ClearRecordAsync(); - + /// /// Gets the currently loaded world record or null. /// diff --git a/src/Modules/WorldRecordModule/Services/WorldRecordService.cs b/src/Modules/WorldRecordModule/Services/WorldRecordService.cs index b67273a5e..a0786f1b7 100644 --- a/src/Modules/WorldRecordModule/Services/WorldRecordService.cs +++ b/src/Modules/WorldRecordModule/Services/WorldRecordService.cs @@ -31,18 +31,17 @@ public async Task FetchRecordAsync(string mapUid) User_Agent = "EvoSC# / World Record Grabber / Discord: chris92" }) .GetJsonAsync(); - } catch (FlurlHttpException ex) { - logger.LogError(ex, "Invalid response from Openplanet. Maybe API issues?"); - throw; + // Log error regardless of the status code, since any exception means an unexpected outcome + logger.LogError(ex, "Error fetching data from trackmania.io API. Status code: {StatusCode}", ex.Call.Response?.StatusCode); } - + logger.LogDebug("Loaded records for map."); - if (res is {tops.Count: > 0}) + if (res is { tops.Count: > 0 }) { var bestRecord = res.tops.First(); var newWorldRecord = new WorldRecord @@ -56,7 +55,7 @@ public async Task FetchRecordAsync(string mapUid) newWorldRecord.PlayerName, newWorldRecord.Time.ToString() ); - + await OverwriteRecordAsync(newWorldRecord); } else @@ -69,12 +68,12 @@ public async Task FetchRecordAsync(string mapUid) Time = RaceTime.FromMilliseconds(mapInfo.AuthorTime), Source = WorldRecordSource.AuthorTime }; - + logger.LogDebug("Couldn't load World Record, using Author Time instead."); - + await OverwriteRecordAsync(newWorldRecord); } - + } public Task ClearRecordAsync() @@ -105,14 +104,14 @@ public async Task DetectNewWorldRecordThroughScoresAsync(ScoresEventArgs scoresE { await OverwriteRecordAsync(new WorldRecord { - PlayerName = score.Name, - Time = RaceTime.FromMilliseconds(score.BestRaceTime), + PlayerName = score.Name, + Time = RaceTime.FromMilliseconds(score.BestRaceTime), Source = WorldRecordSource.Local }); } } } - + private async Task OverwriteRecordAsync(WorldRecord newRecord) { lock (_currentWorldRecordLock)