From 717b7f82d15df226dca84f52e2c109a4de4ef757 Mon Sep 17 00:00:00 2001 From: snixtho Date: Sat, 10 Aug 2024 16:48:05 +0200 Subject: [PATCH 1/2] Add ability to set team points and pause/unpause the match. --- .../Controllers/MatchCommandsController.cs | 54 +++++++++++++++++++ .../MatchManagerModule/Events/AuditEvents.cs | 17 +++++- .../Interfaces/IMatchControlService.cs | 40 +++++++++++++- .../Permissions/MatchControlPermissions.cs | 10 +++- .../Services/MatchControlService.cs | 16 ++++++ 5 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/Modules/MatchManagerModule/Controllers/MatchCommandsController.cs b/src/Modules/MatchManagerModule/Controllers/MatchCommandsController.cs index 15b0eb51d..7df025ef1 100644 --- a/src/Modules/MatchManagerModule/Controllers/MatchCommandsController.cs +++ b/src/Modules/MatchManagerModule/Controllers/MatchCommandsController.cs @@ -4,6 +4,7 @@ using EvoSC.Common.Controllers.Attributes; using EvoSC.Common.Interfaces; using EvoSC.Common.Interfaces.Localization; +using EvoSC.Common.Interfaces.Models; using EvoSC.Modules.Official.MatchManagerModule.Events; using EvoSC.Modules.Official.MatchManagerModule.Interfaces; using EvoSC.Modules.Official.MatchManagerModule.Permissions; @@ -70,4 +71,57 @@ public async Task SkipMapAsync() .WithEventName(AuditEvents.SkipMap) .Comment(_locale.Audit_MapSkipped); } + + [ChatCommand("setteamroundpoints", "Set the round points of a team.", MatchControlPermissions.SetTeamPoints)] + public async Task SetRoundPointsAsync(int team, int points) + { + var playerTeam = team == 0 ? PlayerTeam.Team1 : PlayerTeam.Team2; + await matchControl.SetTeamRoundPointsAsync(playerTeam, points); + + Context.AuditEvent.Success() + .WithEventName(AuditEvents.TeamRoundPointsSet) + .HavingProperties(new { Points = points, Team = playerTeam }); + + await server.SuccessMessageAsync(Context.Player, $"Round points for team {playerTeam} was set to {points}."); + } + + [ChatCommand("setteammappoints", "Set the map points of a team.", MatchControlPermissions.SetTeamPoints)] + public async Task SetMapPointsAsync(int team, int points) + { + var playerTeam = team == 0 ? PlayerTeam.Team1 : PlayerTeam.Team2; + await matchControl.SetTeamMapPointsAsync(playerTeam, points); + + Context.AuditEvent.Success() + .WithEventName(AuditEvents.TeamMapPointsSet) + .HavingProperties(new { Points = points, Team = playerTeam }); + + await server.SuccessMessageAsync(Context.Player, $"Map points for team {playerTeam} was set to {points}."); + } + + [ChatCommand("setteammatchpoints", "Set the match points of a team.", MatchControlPermissions.SetTeamPoints)] + public async Task SetMatchPointsAsync(int team, int points) + { + var playerTeam = team == 0 ? PlayerTeam.Team1 : PlayerTeam.Team2; + await matchControl.SetTeamMatchPointsAsync(playerTeam, points); + + Context.AuditEvent.Success() + .WithEventName(AuditEvents.TeamMatchPointsSet) + .HavingProperties(new { Points = points, Team = playerTeam }); + + await server.SuccessMessageAsync(Context.Player, $"Match points for team {playerTeam} was set to {points}."); + } + + [ChatCommand("pause", "Pause the current match.", MatchControlPermissions.PauseMatch)] + public async Task PauseMatchAsync() + { + await matchControl.PauseMatchAsync(); + Context.AuditEvent.Success().WithEventName(AuditEvents.MatchPaused); + } + + [ChatCommand("unpause", "Unpause the current match.", MatchControlPermissions.PauseMatch)] + public async Task UnpauseMatchAsync() + { + await matchControl.UnpauseMatchAsync(); + Context.AuditEvent.Success().WithEventName(AuditEvents.MatchUnpaused); + } } diff --git a/src/Modules/MatchManagerModule/Events/AuditEvents.cs b/src/Modules/MatchManagerModule/Events/AuditEvents.cs index 0959bc1f0..9c96dd79f 100644 --- a/src/Modules/MatchManagerModule/Events/AuditEvents.cs +++ b/src/Modules/MatchManagerModule/Events/AuditEvents.cs @@ -26,5 +26,20 @@ public enum AuditEvents MatchSettingsLoaded, [Identifier(Name = "MatchManager:ScriptSettingsSet")] - ScriptSettingsModified + ScriptSettingsModified, + + [Identifier(Name = "MatchManager:TeamRoundPointsSet")] + TeamRoundPointsSet, + + [Identifier(Name = "MatchManager:TeamRoundMapSet")] + TeamMapPointsSet, + + [Identifier(Name = "MatchManager:TeamRoundMatchSet")] + TeamMatchPointsSet, + + [Identifier(Name = "MatchManager:MatchPaused")] + MatchPaused, + + [Identifier(Name = "MatchManager:MatchUnpaused")] + MatchUnpaused, } diff --git a/src/Modules/MatchManagerModule/Interfaces/IMatchControlService.cs b/src/Modules/MatchManagerModule/Interfaces/IMatchControlService.cs index f89cebb93..0309ff2e6 100644 --- a/src/Modules/MatchManagerModule/Interfaces/IMatchControlService.cs +++ b/src/Modules/MatchManagerModule/Interfaces/IMatchControlService.cs @@ -1,4 +1,6 @@ -namespace EvoSC.Modules.Official.MatchManagerModule.Interfaces; +using EvoSC.Common.Interfaces.Models; + +namespace EvoSC.Modules.Official.MatchManagerModule.Interfaces; public interface IMatchControlService { @@ -23,4 +25,40 @@ public interface IMatchControlService /// /// public Task SkipMapAsync(); + + /// + /// Sets the round points for a team. + /// + /// Team to set the round points for. + /// Points to set. + /// + public Task SetTeamRoundPointsAsync(PlayerTeam team, int points); + + /// + /// Sets the map points for a team. + /// + /// Team to set the map points for. + /// Points to set. + /// + public Task SetTeamMapPointsAsync(PlayerTeam team, int points); + + /// + /// Sets the match points for a team. + /// + /// Team to set the match points for. + /// Points to set. + /// + public Task SetTeamMatchPointsAsync(PlayerTeam team, int points); + + /// + /// Pause the current match. Only works on round-based modes. + /// + /// + public Task PauseMatchAsync(); + + /// + /// Unpause the current match. Only works on round-based modes. + /// + /// + public Task UnpauseMatchAsync(); } diff --git a/src/Modules/MatchManagerModule/Permissions/MatchControlPermissions.cs b/src/Modules/MatchManagerModule/Permissions/MatchControlPermissions.cs index de81e35bf..fd396c8bf 100644 --- a/src/Modules/MatchManagerModule/Permissions/MatchControlPermissions.cs +++ b/src/Modules/MatchManagerModule/Permissions/MatchControlPermissions.cs @@ -15,7 +15,15 @@ public enum MatchControlPermissions [Description("[Permission.SkipMap]")] SkipMap, + [Description("[Permission.StartMatch]")] StartMatch, - EndMatch + [Description("[Permission.EndMatch]")] + EndMatch, + + [Description("[Permission.SetTeamPoints]")] + SetTeamPoints, + + [Description("[Permission.PauseMatch]")] + PauseMatch } diff --git a/src/Modules/MatchManagerModule/Services/MatchControlService.cs b/src/Modules/MatchManagerModule/Services/MatchControlService.cs index b74ad11a6..afb966777 100644 --- a/src/Modules/MatchManagerModule/Services/MatchControlService.cs +++ b/src/Modules/MatchManagerModule/Services/MatchControlService.cs @@ -1,4 +1,5 @@ using EvoSC.Common.Interfaces; +using EvoSC.Common.Interfaces.Models; using EvoSC.Common.Services.Attributes; using EvoSC.Common.Services.Models; using EvoSC.Modules.Official.MatchManagerModule.Events; @@ -42,4 +43,19 @@ public async Task SkipMapAsync() await events.RaiseAsync(FlowControlEvent.MapSkipped, EventArgs.Empty); } + + public Task SetTeamRoundPointsAsync(PlayerTeam team, int points) => + server.Remote.TriggerModeScriptEventArrayAsync("Trackmania.SetTeamPoints", ((int)team).ToString(), points.ToString(), "", ""); + + public Task SetTeamMapPointsAsync(PlayerTeam team, int points) => + server.Remote.TriggerModeScriptEventArrayAsync("Trackmania.SetTeamPoints", ((int)team).ToString(), "", points.ToString(), ""); + + public Task SetTeamMatchPointsAsync(PlayerTeam team, int points) => + server.Remote.TriggerModeScriptEventArrayAsync("Trackmania.SetTeamPoints", ((int)team).ToString(), "", "", points.ToString()); + + public Task PauseMatchAsync() => + server.Remote.TriggerModeScriptEventArrayAsync("Maniaplanet.Pause.SetActive", "true"); + + public Task UnpauseMatchAsync() => + server.Remote.TriggerModeScriptEventArrayAsync("Maniaplanet.Pause.SetActive", "false"); } From f36d4bf72f2c091255cf15afa6a9bba52fc91e90 Mon Sep 17 00:00:00 2001 From: snixtho Date: Sat, 10 Aug 2024 17:01:49 +0200 Subject: [PATCH 2/2] add tests --- .../MatchCommandsControllerTests.cs | 57 ++++++++++++ .../Services/MatchControlServiceTests.cs | 86 +++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 tests/Modules/MatchManagerModule.Tests/Services/MatchControlServiceTests.cs diff --git a/tests/Modules/MatchManagerModule.Tests/Controllers/MatchCommandsControllerTests.cs b/tests/Modules/MatchManagerModule.Tests/Controllers/MatchCommandsControllerTests.cs index 7cbeda121..2bfaaf14a 100644 --- a/tests/Modules/MatchManagerModule.Tests/Controllers/MatchCommandsControllerTests.cs +++ b/tests/Modules/MatchManagerModule.Tests/Controllers/MatchCommandsControllerTests.cs @@ -2,6 +2,7 @@ using EvoSC.Common.Interfaces.Localization; using EvoSC.Common.Interfaces.Models; using EvoSC.Modules.Official.MatchManagerModule.Controllers; +using EvoSC.Modules.Official.MatchManagerModule.Events; using EvoSC.Modules.Official.MatchManagerModule.Interfaces; using EvoSC.Testing; using EvoSC.Testing.Controllers; @@ -73,4 +74,60 @@ public async Task Map_Is_Skipped_And_Audited() _matchControl.Verify(m => m.SkipMapAsync(), Times.Once); AuditEventBuilder.Verify(m => m.Success(), Times.Once); } + + [Theory] + [InlineData(0, PlayerTeam.Team1)] + [InlineData(1, PlayerTeam.Team2)] + public async Task Round_Points_Set_And_Audited(int team, PlayerTeam expectedTeam) + { + await Controller.SetRoundPointsAsync(team, 1337); + + _matchControl.Verify(m => m.SetTeamRoundPointsAsync(expectedTeam, 1337)); + AuditEventBuilder.Verify(m => m.Success()); + AuditEventBuilder.Verify(m => m.WithEventName(AuditEvents.TeamRoundPointsSet)); + } + + [Theory] + [InlineData(0, PlayerTeam.Team1)] + [InlineData(1, PlayerTeam.Team2)] + public async Task Round_Map_Set_And_Audited(int team, PlayerTeam expectedTeam) + { + await Controller.SetMapPointsAsync(team, 1337); + + _matchControl.Verify(m => m.SetTeamMapPointsAsync(expectedTeam, 1337)); + AuditEventBuilder.Verify(m => m.Success()); + AuditEventBuilder.Verify(m => m.WithEventName(AuditEvents.TeamMapPointsSet)); + } + + [Theory] + [InlineData(0, PlayerTeam.Team1)] + [InlineData(1, PlayerTeam.Team2)] + public async Task Round_Match_Set_And_Audited(int team, PlayerTeam expectedTeam) + { + await Controller.SetMatchPointsAsync(team, 1337); + + _matchControl.Verify(m => m.SetTeamMatchPointsAsync(expectedTeam, 1337)); + AuditEventBuilder.Verify(m => m.Success()); + AuditEventBuilder.Verify(m => m.WithEventName(AuditEvents.TeamMatchPointsSet)); + } + + [Fact] + public async Task Pause_Match_Pauses_Match_And_Audits() + { + await Controller.PauseMatchAsync(); + + _matchControl.Verify(m => m.PauseMatchAsync()); + AuditEventBuilder.Verify(m => m.Success()); + AuditEventBuilder.Verify(m => m.WithEventName(AuditEvents.MatchPaused)); + } + + [Fact] + public async Task Unpause_Match_Unpauses_Match_And_Audits() + { + await Controller.UnpauseMatchAsync(); + + _matchControl.Verify(m => m.UnpauseMatchAsync()); + AuditEventBuilder.Verify(m => m.Success()); + AuditEventBuilder.Verify(m => m.WithEventName(AuditEvents.MatchUnpaused)); + } } diff --git a/tests/Modules/MatchManagerModule.Tests/Services/MatchControlServiceTests.cs b/tests/Modules/MatchManagerModule.Tests/Services/MatchControlServiceTests.cs new file mode 100644 index 000000000..f37b5787e --- /dev/null +++ b/tests/Modules/MatchManagerModule.Tests/Services/MatchControlServiceTests.cs @@ -0,0 +1,86 @@ +using EvoSC.Common.Interfaces; +using EvoSC.Common.Interfaces.Models; +using EvoSC.Modules.Official.MatchManagerModule.Interfaces; +using EvoSC.Modules.Official.MatchManagerModule.Services; +using EvoSC.Testing; +using GbxRemoteNet.Interfaces; +using Moq; + +namespace MatchManagerModule.Tests.Services; + +public class MatchControlServiceTests +{ + private ( + IMatchControlService MatchControlService, + (Mock Client, Mock Remote) Server, + Mock EventManager + ) NewMatchControlServiceMock() + { + var server = Mocking.NewServerClientMock(); + var events = new Mock(); + + var service = new MatchControlService(server.Client.Object, events.Object); + + return ( + service, + server, + events + ); + } + + [Theory] + [InlineData(PlayerTeam.Team1, "0")] + [InlineData(PlayerTeam.Team2, "1")] + public async Task SetTeamRoundPoints_Triggers_Correct_ModeScript_Callback(PlayerTeam team, string expectedTeam) + { + var mock = NewMatchControlServiceMock(); + + await mock.MatchControlService.SetTeamRoundPointsAsync(team, 123); + + mock.Server.Remote.Verify(m => m.TriggerModeScriptEventArrayAsync("Trackmania.SetTeamPoints", expectedTeam, "123", "", "")); + } + + [Theory] + [InlineData(PlayerTeam.Team1, "0")] + [InlineData(PlayerTeam.Team2, "1")] + public async Task SetTeamMapPoints_Triggers_Correct_ModeScript_Callback(PlayerTeam team, string expectedTeam) + { + var mock = NewMatchControlServiceMock(); + + await mock.MatchControlService.SetTeamMapPointsAsync(team, 123); + + mock.Server.Remote.Verify(m => m.TriggerModeScriptEventArrayAsync("Trackmania.SetTeamPoints", expectedTeam, "", "123", "")); + } + + [Theory] + [InlineData(PlayerTeam.Team1, "0")] + [InlineData(PlayerTeam.Team2, "1")] + public async Task SetTeamMatchPoints_Triggers_Correct_ModeScript_Callback(PlayerTeam team, string expectedTeam) + { + var mock = NewMatchControlServiceMock(); + + await mock.MatchControlService.SetTeamMatchPointsAsync(team, 123); + + mock.Server.Remote.Verify(m => m.TriggerModeScriptEventArrayAsync("Trackmania.SetTeamPoints", expectedTeam, "", "", "123")); + } + + [Fact] + public async Task PauseMatch_Triggers_Pause_ModeScript_Method() + { + var mock = NewMatchControlServiceMock(); + + await mock.MatchControlService.PauseMatchAsync(); + + mock.Server.Remote.Verify(m => m.TriggerModeScriptEventArrayAsync("Maniaplanet.Pause.SetActive", "true")); + } + + [Fact] + public async Task UnpauseMatch_Triggers_Unpause_ModeScript_Method() + { + var mock = NewMatchControlServiceMock(); + + await mock.MatchControlService.UnpauseMatchAsync(); + + mock.Server.Remote.Verify(m => m.TriggerModeScriptEventArrayAsync("Maniaplanet.Pause.SetActive", "false")); + } +}