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] 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();