From 676e4758a3f907c60190154119d7e98f899617e3 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Tue, 20 Aug 2024 20:13:36 +0200 Subject: [PATCH 01/73] Update current map design to fit mockup --- .../Services/CurrentMapService.cs | 17 ++++--- .../Templates/CurrentMapWidget.mt | 50 +++++++++++-------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/Modules/CurrentMapModule/Services/CurrentMapService.cs b/src/Modules/CurrentMapModule/Services/CurrentMapService.cs index 5ce9d636f..3e4ce7032 100644 --- a/src/Modules/CurrentMapModule/Services/CurrentMapService.cs +++ b/src/Modules/CurrentMapModule/Services/CurrentMapService.cs @@ -25,12 +25,12 @@ ICurrentMapSettings settings public async Task ShowWidgetAsync() { var map = await client.Remote.GetCurrentMapInfoAsync(); - await ShowManialinkAsync(map.UId); + await ShowManialinkAsync(map.UId, map.AuthorTime); } public async Task ShowWidgetAsync(MapGbxEventArgs args) { - await ShowManialinkAsync(args.Map.Uid); + await ShowManialinkAsync(args.Map.Uid, args.Map.AuthorTime); } public async Task HideWidgetAsync() @@ -39,7 +39,7 @@ public async Task HideWidgetAsync() logger.LogDebug("Hiding current map widget"); } - private async Task ShowManialinkAsync(string mapUId) + private async Task ShowManialinkAsync(string mapUId, int authorTime) { var dbMap = await mapRepository.GetMapByUidAsync(mapUId); @@ -48,16 +48,19 @@ private async Task ShowManialinkAsync(string mapUId) return; } - var author = dbMap.Author?.NickName; + var authorNickName = dbMap.Author?.NickName ?? "Unknown"; - if (dbMap.Author?.NickName == dbMap.Author?.AccountId) + if (authorNickName == dbMap.Author?.AccountId) { var serverMap = await client.Remote.GetCurrentMapInfoAsync(); - author = serverMap.AuthorNickname.Length > 0 ? serverMap.AuthorNickname : serverMap.Author; + authorNickName = serverMap.AuthorNickname.Length > 0 ? serverMap.AuthorNickname : serverMap.Author; } + logger.LogDebug("Current map {mapName} by {author} ({authorTime}).", dbMap.Name, authorNickName, authorTime); + await manialinkManager.SendPersistentManialinkAsync("CurrentMapModule.CurrentMapWidget", - new { map = dbMap, mapAuthor = author, settings }); + new { map = dbMap, mapAuthor = authorNickName, authorTime, settings }); + logger.LogDebug("Showing current map widget"); } } diff --git a/src/Modules/CurrentMapModule/Templates/CurrentMapWidget.mt b/src/Modules/CurrentMapModule/Templates/CurrentMapWidget.mt index eb3816eb2..4ee877e8f 100644 --- a/src/Modules/CurrentMapModule/Templates/CurrentMapWidget.mt +++ b/src/Modules/CurrentMapModule/Templates/CurrentMapWidget.mt @@ -6,30 +6,40 @@ - - + + + From f669ff6f06cc846b584165c01ee1b963be1f3ed6 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Tue, 20 Aug 2024 20:21:04 +0200 Subject: [PATCH 02/73] Update next map widget design --- .../Controllers/NextMapEventController.cs | 2 +- .../NextMapModule/Templates/NextMap.mt | 40 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Modules/NextMapModule/Controllers/NextMapEventController.cs b/src/Modules/NextMapModule/Controllers/NextMapEventController.cs index 924198a57..543449b28 100644 --- a/src/Modules/NextMapModule/Controllers/NextMapEventController.cs +++ b/src/Modules/NextMapModule/Controllers/NextMapEventController.cs @@ -25,7 +25,7 @@ public async Task ShowNextMapOnPodiumStartAsync(object sender, PodiumEventArgs a { var nextMap = await nextMapService.GetNextMapAsync(); await manialinkManager.SendManialinkAsync(Template, - new { mapName = nextMap.Name, author = nextMap.Author?.NickName, settings }); + new { mapName = nextMap.Name, mapAuthor = nextMap.Author?.NickName ?? "Unknown", settings }); } [Subscribe(GbxRemoteEvent.BeginMap)] diff --git a/src/Modules/NextMapModule/Templates/NextMap.mt b/src/Modules/NextMapModule/Templates/NextMap.mt index a601d30e8..4c4f60b76 100644 --- a/src/Modules/NextMapModule/Templates/NextMap.mt +++ b/src/Modules/NextMapModule/Templates/NextMap.mt @@ -6,30 +6,30 @@ - - + + From fe7f07c1a81d4a4a30525f514d251f1eb301bca0 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Tue, 20 Aug 2024 20:22:13 +0200 Subject: [PATCH 03/73] Center author name in next map widget --- src/Modules/NextMapModule/Templates/NextMap.mt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Modules/NextMapModule/Templates/NextMap.mt b/src/Modules/NextMapModule/Templates/NextMap.mt index 4c4f60b76..fe7781b15 100644 --- a/src/Modules/NextMapModule/Templates/NextMap.mt +++ b/src/Modules/NextMapModule/Templates/NextMap.mt @@ -27,7 +27,8 @@ textfont="{{ Font.Thin }}" class="text-primary" textsize="0.35" - pos="2.0 -5.5" + halign="center" + pos="{{ settings.Width/2.0 }} -5.5" size="{{ settings.Width/2.0 }} 5" /> From 7dd5d850e82220d05cdd4f537f7adc746de606eb Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Sat, 24 Aug 2024 15:07:54 +0200 Subject: [PATCH 04/73] Show correct diffs in spectator target info and update design --- .../Config/ISpectatorTargetInfoSettings.cs | 12 + .../SpectatorTargetInfoCommandController.cs | 14 + .../SpectatorTargetInfoEventController.cs | 2 +- .../Interfaces/ISpectatorTargetInfoService.cs | 4 +- .../Services/SpectatorTargetInfoService.cs | 21 +- .../SpectatorTargetInfoModule.cs | 2 +- .../Templates/NewCpTime.mt | 2 +- .../Templates/SpectatorTargetInfo.mt | 307 +++++++----------- 8 files changed, 167 insertions(+), 197 deletions(-) create mode 100644 src/Modules/SpectatorTargetInfoModule/Config/ISpectatorTargetInfoSettings.cs create mode 100644 src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoCommandController.cs diff --git a/src/Modules/SpectatorTargetInfoModule/Config/ISpectatorTargetInfoSettings.cs b/src/Modules/SpectatorTargetInfoModule/Config/ISpectatorTargetInfoSettings.cs new file mode 100644 index 000000000..12fcd1da3 --- /dev/null +++ b/src/Modules/SpectatorTargetInfoModule/Config/ISpectatorTargetInfoSettings.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; +using Config.Net; +using EvoSC.Modules.Attributes; + +namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; + +[Settings] +public interface ISpectatorTargetInfoSettings +{ + [Option(DefaultValue = -66.0), Description("Defines the vertical position of the widget.")] + public double Y { get; set; } +} diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoCommandController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoCommandController.cs new file mode 100644 index 000000000..6d6b24967 --- /dev/null +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoCommandController.cs @@ -0,0 +1,14 @@ +using EvoSC.Commands.Attributes; +using EvoSC.Commands.Interfaces; +using EvoSC.Common.Controllers; +using EvoSC.Common.Controllers.Attributes; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; + +namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Controllers; + +[Controller] +public class SpectatorTargetInfoCommandController(ISpectatorTargetInfoService spectatorTargetInfoService): EvoScController +{ + [ChatCommand("fakeplayer", "Creates fakeplayer.")] + public Task ShowPb() => spectatorTargetInfoService.AddFakePlayerAsync(); +} diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs index e6d35e4f3..da5748716 100644 --- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs @@ -4,8 +4,8 @@ using EvoSC.Common.Events.Attributes; using EvoSC.Common.Remote; using EvoSC.Common.Remote.EventArgsModels; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; using GbxRemoteNet.Events; -using SpectatorTargetInfo.Interfaces; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Controllers; diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index 32d2d9148..e281f9ea3 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -1,6 +1,6 @@ using EvoSC.Common.Remote.EventArgsModels; -namespace SpectatorTargetInfo.Interfaces; +namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; public interface ISpectatorTargetInfoService { @@ -43,4 +43,6 @@ public interface ISpectatorTargetInfoService /// Sends players DNF to clients. /// public Task ForwardDnfToClientsAsync(PlayerUpdateEventArgs playerUpdateEventArgs); + + public Task AddFakePlayerAsync(); } diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index 3ba7e5144..becc67cf7 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -5,24 +5,30 @@ using EvoSC.Common.Services.Models; using EvoSC.Common.Util; using EvoSC.Manialinks.Interfaces; -using SpectatorTargetInfo.Interfaces; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Services; [Service(LifeStyle = ServiceLifeStyle.Singleton)] public class SpectatorTargetInfoService - (IManialinkManager manialinks, IServerClient server, IPlayerManagerService playerManagerService) : ISpectatorTargetInfoService + (IManialinkManager manialinks, IServerClient server, IPlayerManagerService playerManagerService, ISpectatorTargetInfoSettings settings) : ISpectatorTargetInfoService { private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo"; public async Task SendManiaLinkAsync() => - await manialinks.SendManialinkAsync(WidgetTemplate); + await manialinks.SendManialinkAsync(WidgetTemplate, new + { + settings + }); public async Task SendManiaLinkAsync(string playerLogin) { - var player = await playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(playerLogin)); - await manialinks.SendManialinkAsync(player, WidgetTemplate); + await manialinks.SendManialinkAsync(playerLogin, WidgetTemplate, new + { + settings + }); } public async Task HideManiaLinkAsync() => @@ -96,4 +102,9 @@ public Task ForwardDnfToClientsAsync(PlayerUpdateEventArgs playerUpdateEventArgs cpIndex = -1 }); } + + public async Task AddFakePlayerAsync() + { + await server.Remote.ConnectFakePlayerAsync(); + } } diff --git a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs index d96983417..233feda86 100644 --- a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs +++ b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs @@ -1,6 +1,6 @@ using EvoSC.Modules.Attributes; using EvoSC.Modules.Interfaces; -using SpectatorTargetInfo.Interfaces; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule; diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt b/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt index 49ecc4b76..5b11d141c 100644 --- a/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt +++ b/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt @@ -21,4 +21,4 @@ EvoCheckpointTimesUpdate = Now; } --> - \ No newline at end of file + diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt index 2728ff2bc..8463e4ef0 100644 --- a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt +++ b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt @@ -1,210 +1,134 @@  + + - + + + + - - - - + + - + From 9b94fc469e0f5e0984a7d5d11ff7ffe906daf8a4 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Mon, 26 Aug 2024 19:17:18 +0200 Subject: [PATCH 05/73] Stash --- .../SpectatorTargetInfoEventController.cs | 52 ++++-- .../Interfaces/ISpectatorTargetInfoService.cs | 26 +++ .../Models/CheckpointData.cs | 5 + .../Models/SpectatorInfo.cs | 9 + .../Services/SpectatorTargetInfoService.cs | 160 ++++++++++++++++-- .../SpectatorTargetInfoModule.cs | 6 +- .../Templates/SpectatorTargetInfo.mt | 159 ++--------------- .../Controllers/TeamInfoEventController.cs | 14 +- 8 files changed, 257 insertions(+), 174 deletions(-) create mode 100644 src/Modules/SpectatorTargetInfoModule/Models/CheckpointData.cs create mode 100644 src/Modules/SpectatorTargetInfoModule/Models/SpectatorInfo.cs diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs index da5748716..72d11b1ce 100644 --- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs @@ -10,22 +10,54 @@ namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Controllers; [Controller] -public class SpectatorTargetInfoEventController - (ISpectatorTargetInfoService spectatorTargetInfoService) : EvoScController +public class SpectatorTargetInfoEventController(ISpectatorTargetInfoService spectatorTargetInfoService) + : EvoScController { + // [Subscribe(ModeScriptEvent.GiveUp)] + // public Task OnPlayerGiveUpAsync(object sender, PlayerUpdateEventArgs playerUpdateEventArgs) => + // spectatorTargetInfoService.ForwardDnfToClientsAsync(playerUpdateEventArgs); + + /** + * + */ + [Subscribe(GbxRemoteEvent.PlayerConnect)] - public Task OnPlayerConnectAsync(object x, PlayerConnectGbxEventArgs args) => - spectatorTargetInfoService.SendManiaLinkAsync(args.Login); + public async Task OnPlayerConnectAsync(object x, PlayerConnectGbxEventArgs args) + { + if (!args.IsSpectator) + { + return; + } + await spectatorTargetInfoService.SendManiaLinkAsync(args.Login); + } [Subscribe(ModeScriptEvent.WayPoint)] - public Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) => - spectatorTargetInfoService.ForwardCheckpointTimeToClientsAsync(wayPointEventArgs); + public async Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) + { + await spectatorTargetInfoService.AddCheckpointAsync( + wayPointEventArgs.Login, + wayPointEventArgs.CheckpointInLap, + wayPointEventArgs.LapTime + ); + } [Subscribe(ModeScriptEvent.StartRoundStart)] public Task OnNewRoundAsync(object sender, RoundEventArgs roundEventArgs) => - spectatorTargetInfoService.ResetCheckpointTimesAsync(); + spectatorTargetInfoService.ClearCheckpointsAsync(); + + [Subscribe(GbxRemoteEvent.PlayerInfoChanged)] + public async Task OnPlayerInfoChangedAsync(object sender, PlayerInfoChangedGbxEventArgs eventArgs) + { + var spectatorInfo = spectatorTargetInfoService.ParseSpectatorStatus(eventArgs.PlayerInfo.SpectatorStatus); + var spectatorLogin = eventArgs.PlayerInfo.Login; - [Subscribe(ModeScriptEvent.GiveUp)] - public Task OnPlayerGiveUpAsync(object sender, PlayerUpdateEventArgs playerUpdateEventArgs) => - spectatorTargetInfoService.ForwardDnfToClientsAsync(playerUpdateEventArgs); + if (spectatorInfo.IsSpectator) + { + await spectatorTargetInfoService.UpdateSpectatorTargetAsync(spectatorLogin, spectatorInfo.TargetPlayerId); + } + else + { + await spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(spectatorLogin); + } + } } diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index e281f9ea3..2f95b5dd5 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -1,9 +1,35 @@ using EvoSC.Common.Remote.EventArgsModels; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; public interface ISpectatorTargetInfoService { + /* + * Consume new waypoint time (/) + * Clear waypoint times + * Cache spectator targets + */ + + public Task AddCheckpointAsync(string playerLogin, int checkpointIndex, int checkpointTime); + + public Task ClearCheckpointsAsync(); + + public Task GetLoginOfDedicatedPlayerAsync(int targetPlayerIdDedicated); + + public Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPlayerIdDedicated); + + public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin); + + public Task UpdateWidgetAsync(List playerLogins, CheckpointData leadingCheckpointData, CheckpointData targetCheckpointData, int targetPlayerRank); + + public SpectatorInfo ParseSpectatorStatus(int spectatorStatus); + + public int GetRankFromCheckpointList(List sortedCheckpointTimes, string targetPlayerLogin); + + public int GetTimeDifference(CheckpointData leadingCheckpointData, CheckpointData spectatorCheckpointData); + + /// /// Sends the manialink to all players. /// diff --git a/src/Modules/SpectatorTargetInfoModule/Models/CheckpointData.cs b/src/Modules/SpectatorTargetInfoModule/Models/CheckpointData.cs new file mode 100644 index 000000000..11a604d3d --- /dev/null +++ b/src/Modules/SpectatorTargetInfoModule/Models/CheckpointData.cs @@ -0,0 +1,5 @@ +using EvoSC.Common.Interfaces.Models; + +namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; + +public record CheckpointData(IOnlinePlayer player, int time); diff --git a/src/Modules/SpectatorTargetInfoModule/Models/SpectatorInfo.cs b/src/Modules/SpectatorTargetInfoModule/Models/SpectatorInfo.cs new file mode 100644 index 000000000..7b4758bc8 --- /dev/null +++ b/src/Modules/SpectatorTargetInfoModule/Models/SpectatorInfo.cs @@ -0,0 +1,9 @@ +namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; + +public record SpectatorInfo( + bool IsSpectator, + bool IsTemporarySpectator, + bool IsPureSpectator, + bool IsAutoTarget, + int TargetPlayerId +); diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index becc67cf7..668486f14 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -7,28 +7,160 @@ using EvoSC.Manialinks.Interfaces; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; +using LinqToDB.Common; +using Microsoft.Extensions.Logging; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Services; [Service(LifeStyle = ServiceLifeStyle.Singleton)] -public class SpectatorTargetInfoService - (IManialinkManager manialinks, IServerClient server, IPlayerManagerService playerManagerService, ISpectatorTargetInfoSettings settings) : ISpectatorTargetInfoService +public class SpectatorTargetInfoService( + IManialinkManager manialinks, + IServerClient server, + IPlayerManagerService playerManagerService, + ISpectatorTargetInfoSettings settings, + ILogger logger) : ISpectatorTargetInfoService { private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo"; - public async Task SendManiaLinkAsync() => - await manialinks.SendManialinkAsync(WidgetTemplate, new + private readonly Dictionary> _checkpointTimes = new(); // cp-id -> data + private readonly Dictionary _spectatorTargets = new(); // login -> login + + public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, int checkpointTime) + { + var player = await playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(playerLogin)); + + if (!_checkpointTimes.ContainsKey(checkpointIndex)) { - settings - }); + _checkpointTimes.Add(checkpointIndex, []); + } + logger.LogInformation("Adding waypoint data ({login}) [{cpId}] {time}.", playerLogin, checkpointIndex, + checkpointTime); - public async Task SendManiaLinkAsync(string playerLogin) + var newCheckpointData = new CheckpointData(player, checkpointTime); + + _checkpointTimes[checkpointIndex].Add(newCheckpointData); + _checkpointTimes[checkpointIndex] = _checkpointTimes[checkpointIndex].OrderBy(cpData => cpData.time).ToList(); + + // var spectatorLoginsWatchingPlayer = _spectatorTargets.Where(x => x.Value == playerLogin) + // .Select(x => x.Key) + // .ToList(); + // + // if (spectatorLoginsWatchingPlayer.IsNullOrEmpty()) + // { + // return; + // } + + var spectatorLoginsWatchingPlayer = new List + { + playerLogin + }; + + await UpdateWidgetAsync( + spectatorLoginsWatchingPlayer, + _checkpointTimes[checkpointIndex].First(), + newCheckpointData, + GetRankFromCheckpointList(_checkpointTimes[checkpointIndex], playerLogin) + ); + } + + public Task ClearCheckpointsAsync() + { + _checkpointTimes.Clear(); + + return Task.CompletedTask; + } + + public async Task GetLoginOfDedicatedPlayerAsync(int targetPlayerIdDedicated) + { + var serverPlayers = await server.Remote.GetPlayerListAsync(); + + return serverPlayers.Where(player => player.PlayerId == targetPlayerIdDedicated) + .Select(player => player.Login) + .FirstOrDefault(); + } + + public async Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPlayerIdDedicated) { - await manialinks.SendManialinkAsync(playerLogin, WidgetTemplate, new + var targetLogin = await GetLoginOfDedicatedPlayerAsync(targetPlayerIdDedicated); + + if (targetLogin == null) { - settings - }); + await RemovePlayerFromSpectatorsListAsync(spectatorLogin); + return; + } + + _spectatorTargets[spectatorLogin] = targetLogin; + + logger.LogInformation("Updated spec target: {spectator} -> ({playerIdDedicated}) {target}.", spectatorLogin, + targetPlayerIdDedicated, targetLogin); + + //TODO: update manialink for user + } + + public async Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin) + { + _spectatorTargets.Remove(spectatorLogin); + await manialinks.HideManialinkAsync(spectatorLogin); + + logger.LogInformation("Removed spectator: {spectator}.", spectatorLogin); + } + + public async Task UpdateWidgetAsync(List playerLogins, CheckpointData leadingCheckpointData, + CheckpointData targetCheckpointData, int targetPlayerRank) + { + var timeDifference = GetTimeDifference(leadingCheckpointData, targetCheckpointData); + + foreach (var spectatorLogin in playerLogins) + { + logger.LogInformation("Updating widget for {login}.", spectatorLogin); + + await manialinks.SendManialinkAsync(spectatorLogin, WidgetTemplate, new + { + settings, + timeDifference, + playerRank = targetPlayerRank, + playerName = targetCheckpointData.player.NickName + }); + } + } + + public SpectatorInfo ParseSpectatorStatus(int spectatorStatus) + { + return new SpectatorInfo( + Convert.ToBoolean(spectatorStatus % 10), + Convert.ToBoolean((spectatorStatus / 10) % 10), + Convert.ToBoolean((spectatorStatus / 100) % 10), + Convert.ToBoolean((spectatorStatus / 1000) % 10), + spectatorStatus / 10000 + ); + } + + public int GetRankFromCheckpointList(List sortedCheckpointTimes, string targetPlayerLogin) + { + var rank = 1; + foreach (var checkpointData in sortedCheckpointTimes) + { + if (checkpointData.player.GetLogin() == targetPlayerLogin) return rank; + rank++; + } + + return -1; + } + + public int GetTimeDifference(CheckpointData leadingCheckpointData, CheckpointData spectatorCheckpointData) + { + return leadingCheckpointData.time - spectatorCheckpointData.time; + } + + public async Task SendManiaLinkAsync() => + await manialinks.SendManialinkAsync(WidgetTemplate, new { settings }); + + + public async Task SendManiaLinkAsync(string playerLogin) + { + await manialinks.SendManialinkAsync(playerLogin, WidgetTemplate, new { settings }); } public async Task HideManiaLinkAsync() => @@ -95,12 +227,8 @@ public Task ResetCheckpointTimesAsync() public Task ForwardDnfToClientsAsync(PlayerUpdateEventArgs playerUpdateEventArgs) { - return manialinks.SendManialinkAsync("SpectatorTargetInfoModule.NewCpTime", new - { - accountId = playerUpdateEventArgs.AccountId, - time = 0, - cpIndex = -1 - }); + return manialinks.SendManialinkAsync("SpectatorTargetInfoModule.NewCpTime", + new { accountId = playerUpdateEventArgs.AccountId, time = 0, cpIndex = -1 }); } public async Task AddFakePlayerAsync() diff --git a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs index 233feda86..d7ffb6fa3 100644 --- a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs +++ b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs @@ -8,10 +8,10 @@ namespace EvoSC.Modules.Official.SpectatorTargetInfoModule; public class SpectatorTargetInfoModule(ISpectatorTargetInfoService spectatorTargetInfoService) : EvoScModule, IToggleable { - public Task EnableAsync() + public async Task EnableAsync() { - spectatorTargetInfoService.HideNadeoSpectatorInfoAsync(); - return spectatorTargetInfoService.SendManiaLinkAsync(); + await spectatorTargetInfoService.HideNadeoSpectatorInfoAsync(); + // return spectatorTargetInfoService.SendManiaLinkAsync(); } public Task DisableAsync() => spectatorTargetInfoService.ShowNadeoSpectatorInfoAsync(); diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt index 8463e4ef0..a33521eb1 100644 --- a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt +++ b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt @@ -5,18 +5,17 @@ - - - - - - + + + + + - - \ No newline at end of file diff --git a/src/Modules/TeamInfoModule/Controllers/TeamInfoEventController.cs b/src/Modules/TeamInfoModule/Controllers/TeamInfoEventController.cs index 9aaa59571..545730c80 100644 --- a/src/Modules/TeamInfoModule/Controllers/TeamInfoEventController.cs +++ b/src/Modules/TeamInfoModule/Controllers/TeamInfoEventController.cs @@ -8,11 +8,12 @@ using EvoSC.Modules.Official.TeamSettingsModule.Events; using EvoSC.Modules.Official.TeamSettingsModule.Events.EventArgs; using GbxRemoteNet.Events; +using Microsoft.Extensions.Logging; namespace EvoSC.Modules.Official.TeamInfoModule.Controllers; [Controller] -public class TeamInfoEventController(ITeamInfoService teamInfoService) : EvoScController +public class TeamInfoEventController(ITeamInfoService teamInfoService, ILogger logger) : EvoScController { [Subscribe(ModeScriptEvent.Scores)] public async Task OnScoresAsync(object sender, ScoresEventArgs eventArgs) @@ -37,6 +38,17 @@ public async Task OnScoresAsync(object sender, ScoresEventArgs eventArgs) await teamInfoService.SetModeIsTeamsAsync(true); } + var team1 = eventArgs.Teams.ToList()[0]; + if (team1 != null) + { + logger.LogInformation("[TEAM 1] Match: {matchPoints} Map: {mapPoints}", team1.MatchPoints, team1.MapPoints); + } + var team2 = eventArgs.Teams.ToList()[1]; + if (team2 != null) + { + logger.LogInformation("[TEAM 2] Match: {matchPoints} Map: {mapPoints}", team2.MatchPoints, team2.MapPoints); + } + if (teamInfoService.ShouldUpdateTeamPoints(eventArgs.Section)) { await teamInfoService.UpdatePointsAsync( From face066cce9136b884dbfc42be23c6d6919f9fb1 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Sun, 1 Sep 2024 11:40:03 +0200 Subject: [PATCH 06/73] Add SpectatorTargetInfo tests --- EvoSC.sln | 7 ++ .../Interfaces/ISpectatorTargetInfoService.cs | 4 + .../Services/SpectatorTargetInfoService.cs | 50 ++++++------ .../SpectatorTargetInfoServiceTests.cs | 79 +++++++++++++++++++ .../SpectatorTargetInfo.Tests.csproj | 24 ++++++ 5 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs create mode 100644 tests/Modules/SpectatorTargetInfo.Tests/SpectatorTargetInfo.Tests.csproj diff --git a/EvoSC.sln b/EvoSC.sln index 95a966060..63227e62c 100644 --- a/EvoSC.sln +++ b/EvoSC.sln @@ -138,6 +138,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameModeUiModule.Tests", "t EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveRankingModule.Tests", "tests\Modules\LiveRankingModule.Tests\LiveRankingModule.Tests.csproj", "{4AA1890A-1423-4831-95B2-E29B9A7A58D1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpectatorTargetInfo.Tests", "tests\Modules\SpectatorTargetInfo.Tests\SpectatorTargetInfo.Tests.csproj", "{E4BF17BE-A517-4D3C-8DCA-DA99A100EBFE}" +EndProject @@ -417,6 +419,10 @@ Global {4AA1890A-1423-4831-95B2-E29B9A7A58D1}.Debug|Any CPU.Build.0 = Debug|Any CPU {4AA1890A-1423-4831-95B2-E29B9A7A58D1}.Release|Any CPU.ActiveCfg = Release|Any CPU {4AA1890A-1423-4831-95B2-E29B9A7A58D1}.Release|Any CPU.Build.0 = Release|Any CPU + {E4BF17BE-A517-4D3C-8DCA-DA99A100EBFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4BF17BE-A517-4D3C-8DCA-DA99A100EBFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4BF17BE-A517-4D3C-8DCA-DA99A100EBFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4BF17BE-A517-4D3C-8DCA-DA99A100EBFE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -482,5 +488,6 @@ Global {5BA1FF1B-8CB0-4FF5-B6C0-4E2E323D446E} = {DC47658A-F421-4BA4-B617-090A7DFB3900} {5B515690-0F0B-44D1-B644-3524A83A17CE} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A} {4AA1890A-1423-4831-95B2-E29B9A7A58D1} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A} + {E4BF17BE-A517-4D3C-8DCA-DA99A100EBFE} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A} EndGlobalSection EndGlobal diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index 2f95b5dd5..3dd060a74 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -18,9 +18,13 @@ public interface ISpectatorTargetInfoService public Task GetLoginOfDedicatedPlayerAsync(int targetPlayerIdDedicated); public Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPlayerIdDedicated); + + public Task UpdateSpectatorTargetAsync(string spectatorLogin, string targetLogin); public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin); + public IEnumerable GetLoginsSpectatingTarget(string targetPlayerLogin); + public Task UpdateWidgetAsync(List playerLogins, CheckpointData leadingCheckpointData, CheckpointData targetCheckpointData, int targetPlayerRank); public SpectatorInfo ParseSpectatorStatus(int spectatorStatus); diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index 668486f14..e74947764 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -8,8 +8,6 @@ using EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; -using LinqToDB.Common; -using Microsoft.Extensions.Logging; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Services; @@ -18,8 +16,8 @@ public class SpectatorTargetInfoService( IManialinkManager manialinks, IServerClient server, IPlayerManagerService playerManagerService, - ISpectatorTargetInfoSettings settings, - ILogger logger) : ISpectatorTargetInfoService + ISpectatorTargetInfoSettings settings +) : ISpectatorTargetInfoService { private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo"; @@ -35,9 +33,6 @@ public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, in _checkpointTimes.Add(checkpointIndex, []); } - logger.LogInformation("Adding waypoint data ({login}) [{cpId}] {time}.", playerLogin, checkpointIndex, - checkpointTime); - var newCheckpointData = new CheckpointData(player, checkpointTime); _checkpointTimes[checkpointIndex].Add(newCheckpointData); @@ -52,10 +47,7 @@ public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, in // return; // } - var spectatorLoginsWatchingPlayer = new List - { - playerLogin - }; + var spectatorLoginsWatchingPlayer = new List { playerLogin }; await UpdateWidgetAsync( spectatorLoginsWatchingPlayer, @@ -91,38 +83,44 @@ public async Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPl return; } - _spectatorTargets[spectatorLogin] = targetLogin; + await UpdateSpectatorTargetAsync(spectatorLogin, targetLogin); + } - logger.LogInformation("Updated spec target: {spectator} -> ({playerIdDedicated}) {target}.", spectatorLogin, - targetPlayerIdDedicated, targetLogin); + public Task UpdateSpectatorTargetAsync(string spectatorLogin, string targetLogin) + { + _spectatorTargets[spectatorLogin] = targetLogin; + //TODO: update manialink for user(s) - //TODO: update manialink for user + return Task.CompletedTask; } public async Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin) { _spectatorTargets.Remove(spectatorLogin); await manialinks.HideManialinkAsync(spectatorLogin); + } - logger.LogInformation("Removed spectator: {spectator}.", spectatorLogin); + public IEnumerable GetLoginsSpectatingTarget(string targetPlayerLogin) + { + return _spectatorTargets.Where(specTarget => specTarget.Value == targetPlayerLogin) + .Select(specTarget => specTarget.Key); } public async Task UpdateWidgetAsync(List playerLogins, CheckpointData leadingCheckpointData, CheckpointData targetCheckpointData, int targetPlayerRank) { var timeDifference = GetTimeDifference(leadingCheckpointData, targetCheckpointData); - + foreach (var spectatorLogin in playerLogins) { - logger.LogInformation("Updating widget for {login}.", spectatorLogin); - - await manialinks.SendManialinkAsync(spectatorLogin, WidgetTemplate, new - { - settings, - timeDifference, - playerRank = targetPlayerRank, - playerName = targetCheckpointData.player.NickName - }); + await manialinks.SendManialinkAsync(spectatorLogin, WidgetTemplate, + new + { + settings, + timeDifference, + playerRank = targetPlayerRank, + playerName = targetCheckpointData.player.NickName + }); } } diff --git a/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs b/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs new file mode 100644 index 000000000..213d42b50 --- /dev/null +++ b/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs @@ -0,0 +1,79 @@ +using EvoSC.Common.Interfaces; +using EvoSC.Common.Interfaces.Services; +using EvoSC.Manialinks.Interfaces; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Services; +using EvoSC.Testing; +using GbxRemoteNet.Interfaces; +using GbxRemoteNet.Structs; +using Moq; +using Xunit; + +namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Tests.Services; + +public class SpectatorTargetInfoServiceTests +{ + private readonly Mock _manialinkManager = new(); + private readonly Mock _settings = new(); + private readonly Mock _playerManager = new(); + + private readonly (Mock Client, Mock Remote) + _server = Mocking.NewServerClientMock(); + + private ISpectatorTargetInfoService ServiceMock() + { + return new SpectatorTargetInfoService( + _manialinkManager.Object, + _server.Client.Object, + _playerManager.Object, + _settings.Object + ); + } + + [Fact] + public async Task Updates_Spectator_Target_With_Dedicated_Player_Id() + { + var spectatorTargetService = ServiceMock(); + _server.Remote.Setup(s => s.GetPlayerListAsync()) + .ReturnsAsync([ + new TmPlayerInfo { PlayerId = 22, Login = "UnitTest" } + ]); + + await spectatorTargetService.UpdateSpectatorTargetAsync("player1", 22); + var spectatorOfPlayer = spectatorTargetService.GetLoginsSpectatingTarget("UnitTest").ToList(); + + Assert.Single(spectatorOfPlayer); + Assert.Contains("player1", spectatorOfPlayer); + } + + [Fact] + public async Task Removes_Spectator_If_Target_Login_Is_Null() + { + var spectatorTargetService = ServiceMock(); + + await spectatorTargetService.UpdateSpectatorTargetAsync("player1", "UnitTest"); + await spectatorTargetService.UpdateSpectatorTargetAsync("player1", 1111); + var spectatorOfPlayer = spectatorTargetService.GetLoginsSpectatingTarget("UnitTest").ToList(); + + Assert.Empty(spectatorOfPlayer); + Assert.DoesNotContain("player1", spectatorOfPlayer); + } + + [Fact] + public async Task Gets_Spectating_Logins_For_Given_Target() + { + var spectatorTargetService = ServiceMock(); + + await spectatorTargetService.UpdateSpectatorTargetAsync("player1", "player10"); + await spectatorTargetService.UpdateSpectatorTargetAsync("player2", "player10"); + await spectatorTargetService.UpdateSpectatorTargetAsync("player3", "player20"); + + var spectatorOfPlayer = spectatorTargetService.GetLoginsSpectatingTarget("player10").ToList(); + + Assert.Equal(2, spectatorOfPlayer.Count); + Assert.Contains("player1", spectatorOfPlayer); + Assert.Contains("player2", spectatorOfPlayer); + Assert.DoesNotContain("player3", spectatorOfPlayer); + } +} diff --git a/tests/Modules/SpectatorTargetInfo.Tests/SpectatorTargetInfo.Tests.csproj b/tests/Modules/SpectatorTargetInfo.Tests/SpectatorTargetInfo.Tests.csproj new file mode 100644 index 000000000..ca49a6a38 --- /dev/null +++ b/tests/Modules/SpectatorTargetInfo.Tests/SpectatorTargetInfo.Tests.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + EvoSC.Modules.Official.SpectatorTargetInfoModule.Tests + + + + + + + + + + + + + ..\..\..\..\..\.nuget\packages\moq\4.20.70\lib\net6.0\Moq.dll + + + + From 8795740dda25db19bf80ede007587710ae8d0e47 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Sun, 1 Sep 2024 12:19:29 +0200 Subject: [PATCH 07/73] Add more spectator target info tests --- .../Interfaces/ISpectatorTargetInfoService.cs | 13 +- .../Services/SpectatorTargetInfoService.cs | 51 ++++---- .../SpectatorTargetInfoServiceTests.cs | 121 +++++++++++++++++- 3 files changed, 146 insertions(+), 39 deletions(-) diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index 3dd060a74..252ec07a4 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -5,12 +5,6 @@ namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; public interface ISpectatorTargetInfoService { - /* - * Consume new waypoint time (/) - * Clear waypoint times - * Cache spectator targets - */ - public Task AddCheckpointAsync(string playerLogin, int checkpointIndex, int checkpointTime); public Task ClearCheckpointsAsync(); @@ -31,8 +25,11 @@ public interface ISpectatorTargetInfoService public int GetRankFromCheckpointList(List sortedCheckpointTimes, string targetPlayerLogin); - public int GetTimeDifference(CheckpointData leadingCheckpointData, CheckpointData spectatorCheckpointData); - + public int GetTimeDifference(CheckpointData leadingCheckpointData, CheckpointData targetCheckpointData); + + public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime); + + public Task>> GetCheckpointTimesAsync(); /// /// Sends the manialink to all players. diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index e74947764..3daa8250f 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -28,33 +28,22 @@ public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, in { var player = await playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(playerLogin)); - if (!_checkpointTimes.ContainsKey(checkpointIndex)) + if (!_checkpointTimes.TryGetValue(checkpointIndex, out var value)) { - _checkpointTimes.Add(checkpointIndex, []); + value = []; + _checkpointTimes.Add(checkpointIndex, value); } - var newCheckpointData = new CheckpointData(player, checkpointTime); + value.Add(new CheckpointData(player, checkpointTime)); + _checkpointTimes[checkpointIndex] = value.OrderBy(cpData => cpData.time).ToList(); - _checkpointTimes[checkpointIndex].Add(newCheckpointData); - _checkpointTimes[checkpointIndex] = _checkpointTimes[checkpointIndex].OrderBy(cpData => cpData.time).ToList(); - - // var spectatorLoginsWatchingPlayer = _spectatorTargets.Where(x => x.Value == playerLogin) - // .Select(x => x.Key) - // .ToList(); - // - // if (spectatorLoginsWatchingPlayer.IsNullOrEmpty()) - // { - // return; - // } - - var spectatorLoginsWatchingPlayer = new List { playerLogin }; - - await UpdateWidgetAsync( - spectatorLoginsWatchingPlayer, - _checkpointTimes[checkpointIndex].First(), - newCheckpointData, - GetRankFromCheckpointList(_checkpointTimes[checkpointIndex], playerLogin) - ); + // var spectatorLoginsWatchingPlayer = new List { playerLogin }; + // await UpdateWidgetAsync( + // spectatorLoginsWatchingPlayer, + // _checkpointTimes[checkpointIndex].First(), + // newCheckpointData, + // GetRankFromCheckpointList(_checkpointTimes[checkpointIndex], playerLogin) + // ); } public Task ClearCheckpointsAsync() @@ -131,7 +120,7 @@ public SpectatorInfo ParseSpectatorStatus(int spectatorStatus) Convert.ToBoolean((spectatorStatus / 10) % 10), Convert.ToBoolean((spectatorStatus / 100) % 10), Convert.ToBoolean((spectatorStatus / 1000) % 10), - spectatorStatus / 10000 + spectatorStatus / 10_000 ); } @@ -147,9 +136,19 @@ public int GetRankFromCheckpointList(List sortedCheckpointTimes, return -1; } - public int GetTimeDifference(CheckpointData leadingCheckpointData, CheckpointData spectatorCheckpointData) + public int GetTimeDifference(CheckpointData leadingCheckpointData, CheckpointData targetCheckpointData) + { + return GetTimeDifference(leadingCheckpointData.time, targetCheckpointData.time); + } + + public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime) + { + return targetCheckpointTime - leadingCheckpointTime; + } + + public Task>> GetCheckpointTimesAsync() { - return leadingCheckpointData.time - spectatorCheckpointData.time; + return Task.FromResult(_checkpointTimes); } public async Task SendManiaLinkAsync() => diff --git a/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs b/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs index 213d42b50..029c3fca4 100644 --- a/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs +++ b/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs @@ -1,8 +1,14 @@ -using EvoSC.Common.Interfaces; +using EvoSC.Common.Database.Models.Player; +using EvoSC.Common.Interfaces; +using EvoSC.Common.Interfaces.Models; +using EvoSC.Common.Interfaces.Models.Enums; using EvoSC.Common.Interfaces.Services; +using EvoSC.Common.Models.Players; +using EvoSC.Common.Util; using EvoSC.Manialinks.Interfaces; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; +using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Services; using EvoSC.Testing; using GbxRemoteNet.Interfaces; @@ -31,6 +37,44 @@ private ISpectatorTargetInfoService ServiceMock() ); } + [Fact] + public async Task Adds_Clears_Checkpoint_Data_And_Sorts_Times() + { + var spectatorTargetService = ServiceMock(); + _playerManager.Setup(s => s.GetOnlinePlayerAsync(It.IsAny())) + .ReturnsAsync(new OnlinePlayer { State = PlayerState.Spectating, }); + + await spectatorTargetService.AddCheckpointAsync("*fakeplayer1*", 2, 1400); + await spectatorTargetService.AddCheckpointAsync("*fakeplayer2*", 2, 1200); + await spectatorTargetService.AddCheckpointAsync("*fakeplayer3*", 2, 1000); + await spectatorTargetService.AddCheckpointAsync("*fakeplayer4*", 1, 1000); + + var cpTimes = await spectatorTargetService.GetCheckpointTimesAsync(); + Assert.Equal(2, cpTimes.Count); + Assert.Single(cpTimes[1]); + Assert.Equal(1000, cpTimes[1][0].time); + Assert.Equal(3, cpTimes[2].Count); + Assert.Equal(1000, cpTimes[2][0].time); + Assert.Equal(1200, cpTimes[2][1].time); + Assert.Equal(1400, cpTimes[2][2].time); + + await spectatorTargetService.ClearCheckpointsAsync(); + Assert.Empty(await spectatorTargetService.GetCheckpointTimesAsync()); + } + + [Fact] + public async Task Gets_Login_By_Dedicated_Player_Id() + { + var spectatorTargetService = ServiceMock(); + _server.Remote.Setup(s => s.GetPlayerListAsync()) + .ReturnsAsync([ + new TmPlayerInfo { PlayerId = 22, Login = "UnitTest" } + ]); + + var login = await spectatorTargetService.GetLoginOfDedicatedPlayerAsync(22); + Assert.Equal("UnitTest", login); + } + [Fact] public async Task Updates_Spectator_Target_With_Dedicated_Player_Id() { @@ -42,7 +86,7 @@ public async Task Updates_Spectator_Target_With_Dedicated_Player_Id() await spectatorTargetService.UpdateSpectatorTargetAsync("player1", 22); var spectatorOfPlayer = spectatorTargetService.GetLoginsSpectatingTarget("UnitTest").ToList(); - + Assert.Single(spectatorOfPlayer); Assert.Contains("player1", spectatorOfPlayer); } @@ -51,17 +95,17 @@ public async Task Updates_Spectator_Target_With_Dedicated_Player_Id() public async Task Removes_Spectator_If_Target_Login_Is_Null() { var spectatorTargetService = ServiceMock(); - + await spectatorTargetService.UpdateSpectatorTargetAsync("player1", "UnitTest"); await spectatorTargetService.UpdateSpectatorTargetAsync("player1", 1111); var spectatorOfPlayer = spectatorTargetService.GetLoginsSpectatingTarget("UnitTest").ToList(); - + Assert.Empty(spectatorOfPlayer); Assert.DoesNotContain("player1", spectatorOfPlayer); } [Fact] - public async Task Gets_Spectating_Logins_For_Given_Target() + public async Task Gets_Logins_Spectating_The_Given_Target() { var spectatorTargetService = ServiceMock(); @@ -76,4 +120,71 @@ public async Task Gets_Spectating_Logins_For_Given_Target() Assert.Contains("player2", spectatorOfPlayer); Assert.DoesNotContain("player3", spectatorOfPlayer); } + + [Theory] + [InlineData(10_000, 1)] + [InlineData(2_550_000, 255)] + [InlineData(1_230_000, 123)] + public Task Parses_Target_Player_Id_From_Spectator_Status(int spectatorStatus, int expectedPlayerId) + { + var spectatorTargetService = ServiceMock(); + var parsedStatus = spectatorTargetService.ParseSpectatorStatus(spectatorStatus); + + Assert.Equal(expectedPlayerId, parsedStatus.TargetPlayerId); + + return Task.CompletedTask; + } + + [Fact] + public Task Gets_Rank_From_Sorted_Checkpoints_List() + { + var spectatorTargetService = ServiceMock(); + var checkpointsList = new List + { + new( + new OnlinePlayer + { + AccountId = PlayerUtils.ConvertLoginToAccountId("*fakeplayer1*"), State = PlayerState.Spectating + }, 1_000 + ), + new( + new OnlinePlayer + { + AccountId = PlayerUtils.ConvertLoginToAccountId("*fakeplayer2*"), State = PlayerState.Spectating + }, 1_000 + ), + new( + new OnlinePlayer + { + AccountId = PlayerUtils.ConvertLoginToAccountId("*fakeplayer4*"), State = PlayerState.Spectating + }, 1_001 + ), + new( + new OnlinePlayer + { + AccountId = PlayerUtils.ConvertLoginToAccountId("*fakeplayer3*"), State = PlayerState.Spectating + }, 1_111 + ), + }; + + Assert.Equal(1, spectatorTargetService.GetRankFromCheckpointList(checkpointsList, "*fakeplayer1*")); + Assert.Equal(2, spectatorTargetService.GetRankFromCheckpointList(checkpointsList, "*fakeplayer2*")); + Assert.Equal(3, spectatorTargetService.GetRankFromCheckpointList(checkpointsList, "*fakeplayer4*")); + Assert.Equal(4, spectatorTargetService.GetRankFromCheckpointList(checkpointsList, "*fakeplayer3*")); + + return Task.CompletedTask; + } + + [Theory] + [InlineData(900, 1_000, 100)] + [InlineData(100, 999, 899)] + [InlineData(400, 200, -200)] + public Task Calculates_Time_Difference(int leadingTime, int trailingTime, int expectedTime) + { + var spectatorTargetService = ServiceMock(); + + Assert.Equal(expectedTime, spectatorTargetService.GetTimeDifference(leadingTime, trailingTime)); + + return Task.CompletedTask; + } } From 3b4644d83d9e5efece2ef5a2faafee15592d0215 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Sun, 1 Sep 2024 13:57:33 +0200 Subject: [PATCH 08/73] Send updated widget to spectators if target player changes or is updated --- .../SpectatorTargetInfoEventController.cs | 16 +- .../Interfaces/ISpectatorTargetInfoService.cs | 74 ++----- .../Models/CheckpointsGroup.cs | 35 +++ .../Services/SpectatorTargetInfoService.cs | 203 +++++++----------- .../SpectatorTargetInfoModule.cs | 15 +- .../SpectatorTargetInfoServiceTests.cs | 47 ++-- 6 files changed, 177 insertions(+), 213 deletions(-) create mode 100644 src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs index 72d11b1ce..a7b79f76d 100644 --- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs @@ -29,8 +29,12 @@ public async Task OnPlayerConnectAsync(object x, PlayerConnectGbxEventArgs args) return; } - await spectatorTargetInfoService.SendManiaLinkAsync(args.Login); + //TODO: get spectator target + //TODO: show widget + + // await spectatorTargetInfoService.SendManiaLinkAsync(args.Login); } + [Subscribe(ModeScriptEvent.WayPoint)] public async Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) { @@ -39,11 +43,17 @@ await spectatorTargetInfoService.AddCheckpointAsync( wayPointEventArgs.CheckpointInLap, wayPointEventArgs.LapTime ); + + //TODO: get players spectating + //TODO: send widget to spectating players } [Subscribe(ModeScriptEvent.StartRoundStart)] - public Task OnNewRoundAsync(object sender, RoundEventArgs roundEventArgs) => - spectatorTargetInfoService.ClearCheckpointsAsync(); + public async Task OnNewRoundAsync(object sender, RoundEventArgs roundEventArgs) + { + await spectatorTargetInfoService.ClearCheckpointsAsync(); + //TODO: update widgets for all spectating players + } [Subscribe(GbxRemoteEvent.PlayerInfoChanged)] public async Task OnPlayerInfoChangedAsync(object sender, PlayerInfoChangedGbxEventArgs eventArgs) diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index 252ec07a4..24481463c 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -1,75 +1,43 @@ -using EvoSC.Common.Remote.EventArgsModels; +using EvoSC.Common.Interfaces.Models; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; public interface ISpectatorTargetInfoService { + public Task GetOnlinePlayerByLoginAsync(string playerLogin); + public Task AddCheckpointAsync(string playerLogin, int checkpointIndex, int checkpointTime); public Task ClearCheckpointsAsync(); public Task GetLoginOfDedicatedPlayerAsync(int targetPlayerIdDedicated); - + public Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPlayerIdDedicated); - - public Task UpdateSpectatorTargetAsync(string spectatorLogin, string targetLogin); - + + public Task SetSpectatorTargetLoginAsync(string spectatorLogin, string targetLogin); + public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin); - public IEnumerable GetLoginsSpectatingTarget(string targetPlayerLogin); - - public Task UpdateWidgetAsync(List playerLogins, CheckpointData leadingCheckpointData, CheckpointData targetCheckpointData, int targetPlayerRank); - + public IEnumerable GetLoginsOfPlayersSpectatingTarget(string targetPlayerLogin); + public SpectatorInfo ParseSpectatorStatus(int spectatorStatus); - - public int GetRankFromCheckpointList(List sortedCheckpointTimes, string targetPlayerLogin); public int GetTimeDifference(CheckpointData leadingCheckpointData, CheckpointData targetCheckpointData); - + public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime); - public Task>> GetCheckpointTimesAsync(); - - /// - /// Sends the manialink to all players. - /// - public Task SendManiaLinkAsync(); - - /// - /// Sends the manialink to a specific player. - /// - public Task SendManiaLinkAsync(string playerLogin); - - /// - /// Hides the manialink for all players. - /// - public Task HideManiaLinkAsync(); - - /// - /// Hides the default spectator info. - /// - public Task HideNadeoSpectatorInfoAsync(); - - /// - /// Shows the default spectator info. - /// - public Task ShowNadeoSpectatorInfoAsync(); - - /// - /// Maps wayPointEventArgs and sends data to clients. - /// - public Task ForwardCheckpointTimeToClientsAsync(WayPointEventArgs wayPointEventArgs); - - /// - /// Clears the checkpoint times for the clients. - /// - public Task ResetCheckpointTimesAsync(); - - /// - /// Sends players DNF to clients. - /// - public Task ForwardDnfToClientsAsync(PlayerUpdateEventArgs playerUpdateEventArgs); + public int GetLastCheckpointIndexOfPlayer(string playerLogin); + + public Dictionary GetCheckpointTimes(); + + public Task SendWidgetAsync(IEnumerable playerLogins, IOnlinePlayer targetPlayer, int targetPlayerRank, int timeDifference); + + public Task SendWidgetAsync(string playerLogin, IOnlinePlayer targetPlayer, int targetPlayerRank, int timeDifference); + + public Task HideWidgetAsync(); + + public Task HideWidgetAsync(string playerLogin); public Task AddFakePlayerAsync(); } diff --git a/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs b/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs new file mode 100644 index 000000000..68f3573e6 --- /dev/null +++ b/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs @@ -0,0 +1,35 @@ +using EvoSC.Common.Util; + +namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; + +public class CheckpointsGroup : List +{ + public CheckpointsGroup() { } + + public CheckpointsGroup(IEnumerable checkpoints) + { + this.AddRange(checkpoints); + } + + public CheckpointsGroup ToSortedGroup() + { + return new CheckpointsGroup(this.OrderBy(cpData => cpData.time)); + } + + public CheckpointData? GetPlayer(string playerLogin) + { + return this.FirstOrDefault(cpData => cpData.player.GetLogin() == playerLogin); + } + + public int GetRank(string playerLogin) + { + var rank = 1; + foreach (var checkpointData in this) + { + if (checkpointData.player.GetLogin() == playerLogin) return rank; + rank++; + } + + return -1; + } +} diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index 3daa8250f..eb2f61c94 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -1,6 +1,6 @@ using EvoSC.Common.Interfaces; +using EvoSC.Common.Interfaces.Models; using EvoSC.Common.Interfaces.Services; -using EvoSC.Common.Remote.EventArgsModels; using EvoSC.Common.Services.Attributes; using EvoSC.Common.Services.Models; using EvoSC.Common.Util; @@ -8,6 +8,7 @@ using EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; +using LinqToDB.Common; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Services; @@ -21,29 +22,45 @@ ISpectatorTargetInfoSettings settings { private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo"; - private readonly Dictionary> _checkpointTimes = new(); // cp-id -> data + private readonly Dictionary _checkpointTimes = new(); // cp-id -> data private readonly Dictionary _spectatorTargets = new(); // login -> login + /* + * Spectator select new target -> update widget for them + * Driver passes new checkpoint -> update widget for all spectators of that player + * New round starts -> clear diffs in widgets + * Hide on podium + * Re-send widget after map change + */ + + public Task GetOnlinePlayerByLoginAsync(string playerLogin) + => playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(playerLogin)); + public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, int checkpointTime) { - var player = await playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(playerLogin)); - - if (!_checkpointTimes.TryGetValue(checkpointIndex, out var value)) + var player = await GetOnlinePlayerByLoginAsync(playerLogin); + var newCheckpointData = new CheckpointData(player, checkpointTime); + + if (!_checkpointTimes.TryGetValue(checkpointIndex, out var checkpointGroup)) { - value = []; - _checkpointTimes.Add(checkpointIndex, value); + checkpointGroup = []; + _checkpointTimes.Add(checkpointIndex, checkpointGroup); + } + checkpointGroup.Add(newCheckpointData); + checkpointGroup = checkpointGroup.ToSortedGroup(); + _checkpointTimes[checkpointIndex] = checkpointGroup; + + var playerLogins = GetLoginsOfPlayersSpectatingTarget(playerLogin).ToList(); + if (playerLogins.IsNullOrEmpty()) + { + return; } - value.Add(new CheckpointData(player, checkpointTime)); - _checkpointTimes[checkpointIndex] = value.OrderBy(cpData => cpData.time).ToList(); + var leadingCheckpointData = checkpointGroup.First(); + var rank = checkpointGroup.GetRank(playerLogin); + var timeDifference = GetTimeDifference(leadingCheckpointData, newCheckpointData); - // var spectatorLoginsWatchingPlayer = new List { playerLogin }; - // await UpdateWidgetAsync( - // spectatorLoginsWatchingPlayer, - // _checkpointTimes[checkpointIndex].First(), - // newCheckpointData, - // GetRankFromCheckpointList(_checkpointTimes[checkpointIndex], playerLogin) - // ); + await SendWidgetAsync(playerLogins, player, rank, timeDifference); } public Task ClearCheckpointsAsync() @@ -72,47 +89,43 @@ public async Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPl return; } - await UpdateSpectatorTargetAsync(spectatorLogin, targetLogin); + await SetSpectatorTargetLoginAsync(spectatorLogin, targetLogin); } - public Task UpdateSpectatorTargetAsync(string spectatorLogin, string targetLogin) + public async Task SetSpectatorTargetLoginAsync(string spectatorLogin, string targetLogin) { _spectatorTargets[spectatorLogin] = targetLogin; - //TODO: update manialink for user(s) - return Task.CompletedTask; + var checkpointIndex = GetLastCheckpointIndexOfPlayer(targetLogin); + if (!_checkpointTimes.ContainsKey(checkpointIndex)) + { + //TODO: error? + return; + } + + var targetPlayer = await GetOnlinePlayerByLoginAsync(targetLogin); + var checkpointsGroup = _checkpointTimes[checkpointIndex]; + var leadingCpData = checkpointsGroup.First(); + var targetCpData = checkpointsGroup.GetPlayer(targetLogin); + var targetRank = checkpointsGroup.GetRank(targetLogin); + var timeDifference = GetTimeDifference(leadingCpData, targetCpData!); + + await SendWidgetAsync(spectatorLogin, targetPlayer, targetRank, timeDifference); } public async Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin) { _spectatorTargets.Remove(spectatorLogin); - await manialinks.HideManialinkAsync(spectatorLogin); + + await HideWidgetAsync(spectatorLogin); } - public IEnumerable GetLoginsSpectatingTarget(string targetPlayerLogin) + public IEnumerable GetLoginsOfPlayersSpectatingTarget(string targetPlayerLogin) { return _spectatorTargets.Where(specTarget => specTarget.Value == targetPlayerLogin) .Select(specTarget => specTarget.Key); } - public async Task UpdateWidgetAsync(List playerLogins, CheckpointData leadingCheckpointData, - CheckpointData targetCheckpointData, int targetPlayerRank) - { - var timeDifference = GetTimeDifference(leadingCheckpointData, targetCheckpointData); - - foreach (var spectatorLogin in playerLogins) - { - await manialinks.SendManialinkAsync(spectatorLogin, WidgetTemplate, - new - { - settings, - timeDifference, - playerRank = targetPlayerRank, - playerName = targetCheckpointData.player.NickName - }); - } - } - public SpectatorInfo ParseSpectatorStatus(int spectatorStatus) { return new SpectatorInfo( @@ -124,18 +137,6 @@ public SpectatorInfo ParseSpectatorStatus(int spectatorStatus) ); } - public int GetRankFromCheckpointList(List sortedCheckpointTimes, string targetPlayerLogin) - { - var rank = 1; - foreach (var checkpointData in sortedCheckpointTimes) - { - if (checkpointData.player.GetLogin() == targetPlayerLogin) return rank; - rank++; - } - - return -1; - } - public int GetTimeDifference(CheckpointData leadingCheckpointData, CheckpointData targetCheckpointData) { return GetTimeDifference(leadingCheckpointData.time, targetCheckpointData.time); @@ -146,90 +147,44 @@ public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime return targetCheckpointTime - leadingCheckpointTime; } - public Task>> GetCheckpointTimesAsync() + public int GetLastCheckpointIndexOfPlayer(string playerLogin) { - return Task.FromResult(_checkpointTimes); - } - - public async Task SendManiaLinkAsync() => - await manialinks.SendManialinkAsync(WidgetTemplate, new { settings }); - + foreach (var (checkpointIndex, checkpointsGroup) in _checkpointTimes.Reverse()) + { + if (checkpointsGroup.GetPlayer(playerLogin) != null) + { + return checkpointIndex; + } + } - public async Task SendManiaLinkAsync(string playerLogin) - { - await manialinks.SendManialinkAsync(playerLogin, WidgetTemplate, new { settings }); + return -1; } - public async Task HideManiaLinkAsync() => - await manialinks.HideManialinkAsync(WidgetTemplate); - - public Task HideNadeoSpectatorInfoAsync() + public Dictionary GetCheckpointTimes() { - var hudSettings = new List - { - @"{""uimodules"": [ - { - ""id"": ""Race_SpectatorBase_Name"", - ""visible"": false, - ""visible_update"": true, - }, - { - ""id"": ""Race_SpectatorBase_Commands"", - ""visible"": true, - ""visible_update"": true, - } - ]}" - }; - - return server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", hudSettings.ToArray()); + return _checkpointTimes; } - public Task ShowNadeoSpectatorInfoAsync() + public async Task SendWidgetAsync(IEnumerable playerLogins, IOnlinePlayer targetPlayer, + int targetPlayerRank, int timeDifference) { - var hudSettings = new List + foreach (var playerLogin in playerLogins) { - @"{""uimodules"": [ - { - ""id"": ""Race_SpectatorBase_Name"", - ""visible"": true, - ""visible_update"": true, - }, - { - ""id"": ""Race_SpectatorBase_Commands"", - ""visible"": true, - ""visible_update"": true, - } - ]}" - }; - - return server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", hudSettings.ToArray()); + await SendWidgetAsync(playerLogin, targetPlayer, targetPlayerRank, timeDifference); + } } - public Task ForwardCheckpointTimeToClientsAsync(WayPointEventArgs wayPointEventArgs) - { - return manialinks.SendManialinkAsync("SpectatorTargetInfoModule.NewCpTime", - new - { - accountId = wayPointEventArgs.AccountId, - time = wayPointEventArgs.RaceTime, - cpIndex = wayPointEventArgs.CheckpointInRace - }); - } + public Task SendWidgetAsync(string playerLogin, IOnlinePlayer targetPlayer, int targetPlayerRank, + int timeDifference) => + manialinks.SendManialinkAsync(playerLogin, WidgetTemplate, + new { settings, timeDifference, playerRank = targetPlayerRank, playerName = targetPlayer.NickName }); - public Task ResetCheckpointTimesAsync() - { - manialinks.HideManialinkAsync("SpectatorTargetInfoModule.NewCpTime"); - return manialinks.SendManialinkAsync("SpectatorTargetInfoModule.ResetCpTimes"); - } + public Task HideWidgetAsync() + => manialinks.HideManialinkAsync(WidgetTemplate); - public Task ForwardDnfToClientsAsync(PlayerUpdateEventArgs playerUpdateEventArgs) - { - return manialinks.SendManialinkAsync("SpectatorTargetInfoModule.NewCpTime", - new { accountId = playerUpdateEventArgs.AccountId, time = 0, cpIndex = -1 }); - } + public Task HideWidgetAsync(string playerLogin) + => manialinks.HideManialinkAsync(playerLogin, WidgetTemplate); - public async Task AddFakePlayerAsync() - { - await server.Remote.ConnectFakePlayerAsync(); - } + public Task AddFakePlayerAsync() => //TODO: remove before mergin into master + server.Remote.ConnectFakePlayerAsync(); } diff --git a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs index d7ffb6fa3..e6b45fd22 100644 --- a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs +++ b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs @@ -5,14 +5,13 @@ namespace EvoSC.Modules.Official.SpectatorTargetInfoModule; [Module(IsInternal = true)] -public class SpectatorTargetInfoModule(ISpectatorTargetInfoService spectatorTargetInfoService) : EvoScModule, - IToggleable +public class SpectatorTargetInfoModule(ISpectatorTargetInfoService spectatorTargetInfoService) : EvoScModule { - public async Task EnableAsync() - { - await spectatorTargetInfoService.HideNadeoSpectatorInfoAsync(); - // return spectatorTargetInfoService.SendManiaLinkAsync(); - } + // public async Task EnableAsync() + // { + // await spectatorTargetInfoService.HideNadeoSpectatorInfoAsync(); + // return spectatorTargetInfoService.SendManiaLinkAsync(); + // } - public Task DisableAsync() => spectatorTargetInfoService.ShowNadeoSpectatorInfoAsync(); + // public Task DisableAsync() => spectatorTargetInfoService.ShowNadeoSpectatorInfoAsync(); } diff --git a/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs b/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs index 029c3fca4..5b233c1a5 100644 --- a/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs +++ b/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs @@ -1,6 +1,4 @@ -using EvoSC.Common.Database.Models.Player; -using EvoSC.Common.Interfaces; -using EvoSC.Common.Interfaces.Models; +using EvoSC.Common.Interfaces; using EvoSC.Common.Interfaces.Models.Enums; using EvoSC.Common.Interfaces.Services; using EvoSC.Common.Models.Players; @@ -49,7 +47,7 @@ public async Task Adds_Clears_Checkpoint_Data_And_Sorts_Times() await spectatorTargetService.AddCheckpointAsync("*fakeplayer3*", 2, 1000); await spectatorTargetService.AddCheckpointAsync("*fakeplayer4*", 1, 1000); - var cpTimes = await spectatorTargetService.GetCheckpointTimesAsync(); + var cpTimes = spectatorTargetService.GetCheckpointTimes(); Assert.Equal(2, cpTimes.Count); Assert.Single(cpTimes[1]); Assert.Equal(1000, cpTimes[1][0].time); @@ -59,7 +57,7 @@ public async Task Adds_Clears_Checkpoint_Data_And_Sorts_Times() Assert.Equal(1400, cpTimes[2][2].time); await spectatorTargetService.ClearCheckpointsAsync(); - Assert.Empty(await spectatorTargetService.GetCheckpointTimesAsync()); + Assert.Empty(spectatorTargetService.GetCheckpointTimes()); } [Fact] @@ -84,11 +82,11 @@ public async Task Updates_Spectator_Target_With_Dedicated_Player_Id() new TmPlayerInfo { PlayerId = 22, Login = "UnitTest" } ]); - await spectatorTargetService.UpdateSpectatorTargetAsync("player1", 22); - var spectatorOfPlayer = spectatorTargetService.GetLoginsSpectatingTarget("UnitTest").ToList(); + await spectatorTargetService.UpdateSpectatorTargetAsync("*fakeplayer1*", 22); + var spectatorOfPlayer = spectatorTargetService.GetLoginsOfPlayersSpectatingTarget("UnitTest").ToList(); Assert.Single(spectatorOfPlayer); - Assert.Contains("player1", spectatorOfPlayer); + Assert.Contains("*fakeplayer1*", spectatorOfPlayer); } [Fact] @@ -96,12 +94,12 @@ public async Task Removes_Spectator_If_Target_Login_Is_Null() { var spectatorTargetService = ServiceMock(); - await spectatorTargetService.UpdateSpectatorTargetAsync("player1", "UnitTest"); - await spectatorTargetService.UpdateSpectatorTargetAsync("player1", 1111); - var spectatorOfPlayer = spectatorTargetService.GetLoginsSpectatingTarget("UnitTest").ToList(); + await spectatorTargetService.SetSpectatorTargetLoginAsync("*fakeplayer1*", "UnitTest"); + await spectatorTargetService.UpdateSpectatorTargetAsync("*fakeplayer1*", 1111); + var spectatorOfPlayer = spectatorTargetService.GetLoginsOfPlayersSpectatingTarget("UnitTest").ToList(); Assert.Empty(spectatorOfPlayer); - Assert.DoesNotContain("player1", spectatorOfPlayer); + Assert.DoesNotContain("*fakeplayer1*", spectatorOfPlayer); } [Fact] @@ -109,16 +107,16 @@ public async Task Gets_Logins_Spectating_The_Given_Target() { var spectatorTargetService = ServiceMock(); - await spectatorTargetService.UpdateSpectatorTargetAsync("player1", "player10"); - await spectatorTargetService.UpdateSpectatorTargetAsync("player2", "player10"); - await spectatorTargetService.UpdateSpectatorTargetAsync("player3", "player20"); + await spectatorTargetService.SetSpectatorTargetLoginAsync("*fakeplayer1*", "*fakeplayer10*"); + await spectatorTargetService.SetSpectatorTargetLoginAsync("*fakeplayer2*", "*fakeplayer10*"); + await spectatorTargetService.SetSpectatorTargetLoginAsync("*fakeplayer3*", "*fakeplayer20*"); - var spectatorOfPlayer = spectatorTargetService.GetLoginsSpectatingTarget("player10").ToList(); + var spectatorOfPlayer = spectatorTargetService.GetLoginsOfPlayersSpectatingTarget("*fakeplayer10*").ToList(); Assert.Equal(2, spectatorOfPlayer.Count); - Assert.Contains("player1", spectatorOfPlayer); - Assert.Contains("player2", spectatorOfPlayer); - Assert.DoesNotContain("player3", spectatorOfPlayer); + Assert.Contains("*fakeplayer1*", spectatorOfPlayer); + Assert.Contains("*fakeplayer2*", spectatorOfPlayer); + Assert.DoesNotContain("*fakeplayer3*", spectatorOfPlayer); } [Theory] @@ -138,8 +136,7 @@ public Task Parses_Target_Player_Id_From_Spectator_Status(int spectatorStatus, i [Fact] public Task Gets_Rank_From_Sorted_Checkpoints_List() { - var spectatorTargetService = ServiceMock(); - var checkpointsList = new List + var checkpointsList = new CheckpointsGroup { new( new OnlinePlayer @@ -167,10 +164,10 @@ public Task Gets_Rank_From_Sorted_Checkpoints_List() ), }; - Assert.Equal(1, spectatorTargetService.GetRankFromCheckpointList(checkpointsList, "*fakeplayer1*")); - Assert.Equal(2, spectatorTargetService.GetRankFromCheckpointList(checkpointsList, "*fakeplayer2*")); - Assert.Equal(3, spectatorTargetService.GetRankFromCheckpointList(checkpointsList, "*fakeplayer4*")); - Assert.Equal(4, spectatorTargetService.GetRankFromCheckpointList(checkpointsList, "*fakeplayer3*")); + Assert.Equal(1, checkpointsList.GetRank("*fakeplayer1*")); + Assert.Equal(2, checkpointsList.GetRank("*fakeplayer2*")); + Assert.Equal(3, checkpointsList.GetRank("*fakeplayer4*")); + Assert.Equal(4, checkpointsList.GetRank("*fakeplayer3*")); return Task.CompletedTask; } From 05c66142faaef500e25dbf9bab3b14f9694b8bfc Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Sun, 1 Sep 2024 14:08:08 +0200 Subject: [PATCH 09/73] Reset spectator info widgets on new round and hide on podium start --- .../SpectatorTargetInfoEventController.cs | 40 +++++++++---------- .../Interfaces/ISpectatorTargetInfoService.cs | 2 + .../Services/SpectatorTargetInfoService.cs | 23 +++++++---- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs index a7b79f76d..3961ad7f8 100644 --- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs @@ -20,41 +20,37 @@ public class SpectatorTargetInfoEventController(ISpectatorTargetInfoService spec /** * */ - - [Subscribe(GbxRemoteEvent.PlayerConnect)] - public async Task OnPlayerConnectAsync(object x, PlayerConnectGbxEventArgs args) - { - if (!args.IsSpectator) - { - return; - } - - //TODO: get spectator target - //TODO: show widget - - // await spectatorTargetInfoService.SendManiaLinkAsync(args.Login); - } + // [Subscribe(GbxRemoteEvent.PlayerConnect)] + // public async Task OnPlayerConnectAsync(object x, PlayerConnectGbxEventArgs args) + // { + // if (!args.IsSpectator) + // { + // return; + // } + // + // //TODO: do nothing? + // // await spectatorTargetInfoService.SendManiaLinkAsync(args.Login); + // } [Subscribe(ModeScriptEvent.WayPoint)] - public async Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) - { - await spectatorTargetInfoService.AddCheckpointAsync( + public Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) => + spectatorTargetInfoService.AddCheckpointAsync( wayPointEventArgs.Login, wayPointEventArgs.CheckpointInLap, wayPointEventArgs.LapTime ); - - //TODO: get players spectating - //TODO: send widget to spectating players - } [Subscribe(ModeScriptEvent.StartRoundStart)] public async Task OnNewRoundAsync(object sender, RoundEventArgs roundEventArgs) { await spectatorTargetInfoService.ClearCheckpointsAsync(); - //TODO: update widgets for all spectating players + await spectatorTargetInfoService.ResetWidgetForSpectatorsAsync(); } + [Subscribe(ModeScriptEvent.PodiumStart)] + public Task OnPodiumStartAsync() => + spectatorTargetInfoService.HideWidgetAsync(); + [Subscribe(GbxRemoteEvent.PlayerInfoChanged)] public async Task OnPlayerInfoChangedAsync(object sender, PlayerInfoChangedGbxEventArgs eventArgs) { diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index 24481463c..04c158ccd 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -31,6 +31,8 @@ public interface ISpectatorTargetInfoService public Dictionary GetCheckpointTimes(); + public Task ResetWidgetForSpectatorsAsync(); + public Task SendWidgetAsync(IEnumerable playerLogins, IOnlinePlayer targetPlayer, int targetPlayerRank, int timeDifference); public Task SendWidgetAsync(string playerLogin, IOnlinePlayer targetPlayer, int targetPlayerRank, int timeDifference); diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index eb2f61c94..c8eb7f65e 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -22,8 +22,8 @@ ISpectatorTargetInfoSettings settings { private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo"; - private readonly Dictionary _checkpointTimes = new(); // cp-id -> data - private readonly Dictionary _spectatorTargets = new(); // login -> login + private readonly Dictionary _checkpointTimes = new(); // cp-id -> CheckpointsGroup + private readonly Dictionary _spectatorTargets = new(); // login -> IOnlinePlayer /* * Spectator select new target -> update widget for them @@ -40,16 +40,17 @@ public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, in { var player = await GetOnlinePlayerByLoginAsync(playerLogin); var newCheckpointData = new CheckpointData(player, checkpointTime); - + if (!_checkpointTimes.TryGetValue(checkpointIndex, out var checkpointGroup)) { checkpointGroup = []; _checkpointTimes.Add(checkpointIndex, checkpointGroup); } + checkpointGroup.Add(newCheckpointData); checkpointGroup = checkpointGroup.ToSortedGroup(); _checkpointTimes[checkpointIndex] = checkpointGroup; - + var playerLogins = GetLoginsOfPlayersSpectatingTarget(playerLogin).ToList(); if (playerLogins.IsNullOrEmpty()) { @@ -94,7 +95,8 @@ public async Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPl public async Task SetSpectatorTargetLoginAsync(string spectatorLogin, string targetLogin) { - _spectatorTargets[spectatorLogin] = targetLogin; + var targetPlayer = await GetOnlinePlayerByLoginAsync(targetLogin); + _spectatorTargets[spectatorLogin] = targetPlayer; var checkpointIndex = GetLastCheckpointIndexOfPlayer(targetLogin); if (!_checkpointTimes.ContainsKey(checkpointIndex)) @@ -103,7 +105,6 @@ public async Task SetSpectatorTargetLoginAsync(string spectatorLogin, string tar return; } - var targetPlayer = await GetOnlinePlayerByLoginAsync(targetLogin); var checkpointsGroup = _checkpointTimes[checkpointIndex]; var leadingCpData = checkpointsGroup.First(); var targetCpData = checkpointsGroup.GetPlayer(targetLogin); @@ -122,7 +123,7 @@ public async Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin) public IEnumerable GetLoginsOfPlayersSpectatingTarget(string targetPlayerLogin) { - return _spectatorTargets.Where(specTarget => specTarget.Value == targetPlayerLogin) + return _spectatorTargets.Where(specTarget => specTarget.Value.GetLogin() == targetPlayerLogin) .Select(specTarget => specTarget.Key); } @@ -165,6 +166,14 @@ public Dictionary GetCheckpointTimes() return _checkpointTimes; } + public async Task ResetWidgetForSpectatorsAsync() + { + foreach (var (spectatorLogin, targetPlayer) in _spectatorTargets) + { + await SendWidgetAsync(spectatorLogin, targetPlayer, 1, 0); + } + } + public async Task SendWidgetAsync(IEnumerable playerLogins, IOnlinePlayer targetPlayer, int targetPlayerRank, int timeDifference) { From 31e8358a5c1c281b0a8ec126f7b1d37a24040ba4 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Sun, 1 Sep 2024 14:57:10 +0200 Subject: [PATCH 10/73] Remove spectators on disconnect and do not update widget if spec target didn't change but player changed event is called --- .../SpectatorTargetInfoEventController.cs | 25 +++++++--- .../Interfaces/ISpectatorTargetInfoService.cs | 2 - .../Services/SpectatorTargetInfoService.cs | 46 +++++++++---------- .../Templates/NewCpTime.mt | 24 ---------- .../Templates/ResetCpTimes.mt | 22 --------- .../Templates/SpectatorTargetInfo.mt | 2 +- 6 files changed, 41 insertions(+), 80 deletions(-) delete mode 100644 src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt delete mode 100644 src/Modules/SpectatorTargetInfoModule/Templates/ResetCpTimes.mt diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs index 3961ad7f8..d7cc6f1dd 100644 --- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs @@ -6,11 +6,14 @@ using EvoSC.Common.Remote.EventArgsModels; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; using GbxRemoteNet.Events; +using Microsoft.Extensions.Logging; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Controllers; [Controller] -public class SpectatorTargetInfoEventController(ISpectatorTargetInfoService spectatorTargetInfoService) +public class SpectatorTargetInfoEventController( + ISpectatorTargetInfoService spectatorTargetInfoService, + ILogger logger) : EvoScController { // [Subscribe(ModeScriptEvent.GiveUp)] @@ -31,7 +34,10 @@ public class SpectatorTargetInfoEventController(ISpectatorTargetInfoService spec // //TODO: do nothing? // // await spectatorTargetInfoService.SendManiaLinkAsync(args.Login); // } - + [Subscribe(GbxRemoteEvent.PlayerDisconnect)] + public Task OnPlayerDisconnect(object sender, PlayerGbxEventArgs eventArgs) => + spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(eventArgs.Login); + [Subscribe(ModeScriptEvent.WayPoint)] public Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) => spectatorTargetInfoService.AddCheckpointAsync( @@ -59,11 +65,16 @@ public async Task OnPlayerInfoChangedAsync(object sender, PlayerInfoChangedGbxEv if (spectatorInfo.IsSpectator) { - await spectatorTargetInfoService.UpdateSpectatorTargetAsync(spectatorLogin, spectatorInfo.TargetPlayerId); - } - else - { - await spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(spectatorLogin); + var targetLogin = + await spectatorTargetInfoService.GetLoginOfDedicatedPlayerAsync(spectatorInfo.TargetPlayerId); + if (targetLogin != null) + { + await spectatorTargetInfoService.SetSpectatorTargetLoginAsync(spectatorLogin, targetLogin); + return; + } } + + await spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(spectatorLogin); + await spectatorTargetInfoService.HideWidgetAsync(spectatorLogin); } } diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index 04c158ccd..4e3444d73 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -13,8 +13,6 @@ public interface ISpectatorTargetInfoService public Task GetLoginOfDedicatedPlayerAsync(int targetPlayerIdDedicated); - public Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPlayerIdDedicated); - public Task SetSpectatorTargetLoginAsync(string spectatorLogin, string targetLogin); public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin); diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index c8eb7f65e..2f962231c 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -9,6 +9,7 @@ using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; using LinqToDB.Common; +using Microsoft.Extensions.Logging; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Services; @@ -17,8 +18,8 @@ public class SpectatorTargetInfoService( IManialinkManager manialinks, IServerClient server, IPlayerManagerService playerManagerService, - ISpectatorTargetInfoSettings settings -) : ISpectatorTargetInfoService + ISpectatorTargetInfoSettings settings, + ILogger logger) : ISpectatorTargetInfoService { private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo"; @@ -80,45 +81,42 @@ public Task ClearCheckpointsAsync() .FirstOrDefault(); } - public async Task UpdateSpectatorTargetAsync(string spectatorLogin, int targetPlayerIdDedicated) + public async Task SetSpectatorTargetLoginAsync(string spectatorLogin, string targetLogin) { - var targetLogin = await GetLoginOfDedicatedPlayerAsync(targetPlayerIdDedicated); - - if (targetLogin == null) + if (_spectatorTargets.TryGetValue(spectatorLogin, out var target) && target.GetLogin() == targetLogin) { - await RemovePlayerFromSpectatorsListAsync(spectatorLogin); return; } - await SetSpectatorTargetLoginAsync(spectatorLogin, targetLogin); - } - - public async Task SetSpectatorTargetLoginAsync(string spectatorLogin, string targetLogin) - { var targetPlayer = await GetOnlinePlayerByLoginAsync(targetLogin); _spectatorTargets[spectatorLogin] = targetPlayer; var checkpointIndex = GetLastCheckpointIndexOfPlayer(targetLogin); - if (!_checkpointTimes.ContainsKey(checkpointIndex)) + var targetRank = 0; + var timeDifference = 0; + + if (_checkpointTimes.TryGetValue(checkpointIndex, out var checkpointsGroup)) { - //TODO: error? - return; + var leadingCpData = checkpointsGroup.First(); + var targetCpData = checkpointsGroup.GetPlayer(targetLogin); + targetRank = checkpointsGroup.GetRank(targetLogin); + timeDifference = GetTimeDifference(leadingCpData, targetCpData!); } - var checkpointsGroup = _checkpointTimes[checkpointIndex]; - var leadingCpData = checkpointsGroup.First(); - var targetCpData = checkpointsGroup.GetPlayer(targetLogin); - var targetRank = checkpointsGroup.GetRank(targetLogin); - var timeDifference = GetTimeDifference(leadingCpData, targetCpData!); - await SendWidgetAsync(spectatorLogin, targetPlayer, targetRank, timeDifference); + + logger.LogDebug("Updated spectator target {spectatorLogin} -> {targetLogin}.", spectatorLogin, + targetLogin); } - public async Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin) + public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin) { - _spectatorTargets.Remove(spectatorLogin); + if (_spectatorTargets.Remove(spectatorLogin)) + { + logger.LogDebug("Removed spectator {spectatorLogin}.", spectatorLogin); + } - await HideWidgetAsync(spectatorLogin); + return Task.CompletedTask; } public IEnumerable GetLoginsOfPlayersSpectatingTarget(string targetPlayerLogin) diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt b/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt deleted file mode 100644 index 5b11d141c..000000000 --- a/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/ResetCpTimes.mt b/src/Modules/SpectatorTargetInfoModule/Templates/ResetCpTimes.mt deleted file mode 100644 index d2b90dad5..000000000 --- a/src/Modules/SpectatorTargetInfoModule/Templates/ResetCpTimes.mt +++ /dev/null @@ -1,22 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt index a33521eb1..69ddb7736 100644 --- a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt +++ b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt @@ -50,7 +50,7 @@ size="{{ (h*3.4)*0.8 }} {{ h }}" textsize="{{ Theme.UI_FontSize*2 }}" textfont="{{ Font.Regular }}" - text="{{ RaceTime.FromMilliseconds(timeDifference) }}" + text='{{ timeDifference > 0 ? RaceTime.FromMilliseconds(timeDifference) : "000" }}' halign="center" valign="center2" /> From 5a3bdbf61cce838c76a482e329040f5169d17c1a Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Tue, 3 Sep 2024 19:38:18 +0200 Subject: [PATCH 11/73] Add team colors in spectator target info --- .../SpectatorTargetInfoEventController.cs | 5 ++ .../Interfaces/ISpectatorTargetInfoService.cs | 13 ++++ .../Services/SpectatorTargetInfoService.cs | 64 +++++++++++++++++-- .../SpectatorTargetInfoModule.cs | 12 ++-- .../Templates/SpectatorTargetInfo.mt | 5 +- .../SpectatorTargetInfoModule/info.toml | 3 + .../SpectatorTargetInfoServiceTests.cs | 8 +-- 7 files changed, 91 insertions(+), 19 deletions(-) diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs index d7cc6f1dd..2652a634d 100644 --- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs +++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs @@ -38,6 +38,10 @@ public class SpectatorTargetInfoEventController( public Task OnPlayerDisconnect(object sender, PlayerGbxEventArgs eventArgs) => spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(eventArgs.Login); + [Subscribe(GbxRemoteEvent.BeginMap)] + public Task OnBeginMap(object sender, MapGbxEventArgs eventArgs) => + spectatorTargetInfoService.UpdateIsTeamsModeAsync(); + [Subscribe(ModeScriptEvent.WayPoint)] public Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) => spectatorTargetInfoService.AddCheckpointAsync( @@ -50,6 +54,7 @@ public Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) public async Task OnNewRoundAsync(object sender, RoundEventArgs roundEventArgs) { await spectatorTargetInfoService.ClearCheckpointsAsync(); + await spectatorTargetInfoService.UpdateTeamInfoAsync(); await spectatorTargetInfoService.ResetWidgetForSpectatorsAsync(); } diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs index 4e3444d73..99aeb0da8 100644 --- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs @@ -1,10 +1,13 @@ using EvoSC.Common.Interfaces.Models; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; +using GbxRemoteNet.Structs; namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; public interface ISpectatorTargetInfoService { + public Task InitializeAsync(); + public Task GetOnlinePlayerByLoginAsync(string playerLogin); public Task AddCheckpointAsync(string playerLogin, int checkpointIndex, int checkpointTime); @@ -25,10 +28,14 @@ public interface ISpectatorTargetInfoService public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime); + public string GetTeamColorAsync(PlayerTeam team); + public int GetLastCheckpointIndexOfPlayer(string playerLogin); public Dictionary GetCheckpointTimes(); + public Task GetTeamInfoAsync(PlayerTeam team); + public Task ResetWidgetForSpectatorsAsync(); public Task SendWidgetAsync(IEnumerable playerLogins, IOnlinePlayer targetPlayer, int targetPlayerRank, int timeDifference); @@ -39,5 +46,11 @@ public interface ISpectatorTargetInfoService public Task HideWidgetAsync(string playerLogin); + public Task UpdateTeamInfoAsync(); + + public Task UpdateIsTeamsModeAsync(); + + public Task HideGameModeUiAsync(); + public Task AddFakePlayerAsync(); } diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index 2f962231c..435410a5f 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -1,13 +1,16 @@ using EvoSC.Common.Interfaces; using EvoSC.Common.Interfaces.Models; using EvoSC.Common.Interfaces.Services; +using EvoSC.Common.Interfaces.Themes; using EvoSC.Common.Services.Attributes; using EvoSC.Common.Services.Models; using EvoSC.Common.Util; +using EvoSC.Common.Util.MatchSettings; using EvoSC.Manialinks.Interfaces; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; +using GbxRemoteNet.Structs; using LinqToDB.Common; using Microsoft.Extensions.Logging; @@ -18,13 +21,18 @@ public class SpectatorTargetInfoService( IManialinkManager manialinks, IServerClient server, IPlayerManagerService playerManagerService, + IMatchSettingsService matchSettingsService, ISpectatorTargetInfoSettings settings, - ILogger logger) : ISpectatorTargetInfoService + IThemeManager theme, + ILogger logger +) : ISpectatorTargetInfoService { private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo"; private readonly Dictionary _checkpointTimes = new(); // cp-id -> CheckpointsGroup private readonly Dictionary _spectatorTargets = new(); // login -> IOnlinePlayer + private readonly Dictionary _teamInfos = new(); + private bool _isTeamsMode; /* * Spectator select new target -> update widget for them @@ -34,6 +42,13 @@ public class SpectatorTargetInfoService( * Re-send widget after map change */ + public async Task InitializeAsync() + { + await UpdateIsTeamsModeAsync(); + await UpdateTeamInfoAsync(); + await HideGameModeUiAsync(); + } + public Task GetOnlinePlayerByLoginAsync(string playerLogin) => playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(playerLogin)); @@ -146,6 +161,11 @@ public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime return targetCheckpointTime - leadingCheckpointTime; } + public string GetTeamColorAsync(PlayerTeam team) + { + return _isTeamsMode ? _teamInfos[team].RGB : (string)theme.Theme.UI_AccentPrimary; + } + public int GetLastCheckpointIndexOfPlayer(string playerLogin) { foreach (var (checkpointIndex, checkpointsGroup) in _checkpointTimes.Reverse()) @@ -164,6 +184,11 @@ public Dictionary GetCheckpointTimes() return _checkpointTimes; } + public async Task GetTeamInfoAsync(PlayerTeam team) + { + return await server.Remote.GetTeamInfoAsync((int)team + 1); + } + public async Task ResetWidgetForSpectatorsAsync() { foreach (var (spectatorLogin, targetPlayer) in _spectatorTargets) @@ -181,10 +206,20 @@ public async Task SendWidgetAsync(IEnumerable playerLogins, IOnlinePlaye } } - public Task SendWidgetAsync(string playerLogin, IOnlinePlayer targetPlayer, int targetPlayerRank, - int timeDifference) => - manialinks.SendManialinkAsync(playerLogin, WidgetTemplate, - new { settings, timeDifference, playerRank = targetPlayerRank, playerName = targetPlayer.NickName }); + public async Task SendWidgetAsync(string playerLogin, IOnlinePlayer targetPlayer, int targetPlayerRank, + int timeDifference) + { + await manialinks.SendManialinkAsync(playerLogin, WidgetTemplate, + new + { + settings, + timeDifference, + playerRank = targetPlayerRank, + playerName = targetPlayer.NickName, + playerTeam = targetPlayer.Team, + teamColorCode = new ColorUtils().Opacity(GetTeamColorAsync(targetPlayer.Team), 80) + }); + } public Task HideWidgetAsync() => manialinks.HideManialinkAsync(WidgetTemplate); @@ -192,6 +227,25 @@ public Task HideWidgetAsync() public Task HideWidgetAsync(string playerLogin) => manialinks.HideManialinkAsync(playerLogin, WidgetTemplate); + public async Task UpdateTeamInfoAsync() + { + _teamInfos[PlayerTeam.Team1] = await server.Remote.GetTeamInfoAsync((int)PlayerTeam.Team1); + _teamInfos[PlayerTeam.Team2] = await server.Remote.GetTeamInfoAsync((int)PlayerTeam.Team2); + } + + public async Task UpdateIsTeamsModeAsync() + { + _isTeamsMode = + await matchSettingsService.GetCurrentModeAsync() is DefaultModeScriptName.Teams + or DefaultModeScriptName.TmwtTeams; + logger.LogInformation("Team mode is {state}", _isTeamsMode ? "active" : "not active"); + } + + public Task HideGameModeUiAsync() + { + return Task.CompletedTask; //TODO: implement + } + public Task AddFakePlayerAsync() => //TODO: remove before mergin into master server.Remote.ConnectFakePlayerAsync(); } diff --git a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs index e6b45fd22..75cc8b7ab 100644 --- a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs +++ b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs @@ -5,13 +5,11 @@ namespace EvoSC.Modules.Official.SpectatorTargetInfoModule; [Module(IsInternal = true)] -public class SpectatorTargetInfoModule(ISpectatorTargetInfoService spectatorTargetInfoService) : EvoScModule +public class SpectatorTargetInfoModule(ISpectatorTargetInfoService spectatorTargetInfoService) + : EvoScModule, IToggleable { - // public async Task EnableAsync() - // { - // await spectatorTargetInfoService.HideNadeoSpectatorInfoAsync(); - // return spectatorTargetInfoService.SendManiaLinkAsync(); - // } + public Task EnableAsync() => + spectatorTargetInfoService.InitializeAsync(); - // public Task DisableAsync() => spectatorTargetInfoService.ShowNadeoSpectatorInfoAsync(); + public Task DisableAsync() => Task.CompletedTask; } diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt index 69ddb7736..f94690156 100644 --- a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt +++ b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt @@ -8,7 +8,9 @@ + + @@ -51,6 +53,7 @@ textsize="{{ Theme.UI_FontSize*2 }}" textfont="{{ Font.Regular }}" text='{{ timeDifference > 0 ? RaceTime.FromMilliseconds(timeDifference) : "000" }}' + textprefix="+" halign="center" valign="center2" /> @@ -73,7 +76,7 @@ diff --git a/src/Modules/SpectatorTargetInfoModule/info.toml b/src/Modules/SpectatorTargetInfoModule/info.toml index b3b1fdd7b..6d5d28388 100644 --- a/src/Modules/SpectatorTargetInfoModule/info.toml +++ b/src/Modules/SpectatorTargetInfoModule/info.toml @@ -4,3 +4,6 @@ title = "Spectator Target Info" summary = "Shows player info in spectator mode." version = "1.0.0" author = "Evo" + +[depends] +GameModeUiModule = "1.0.0" diff --git a/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs b/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs index 5b233c1a5..cc7a48413 100644 --- a/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs +++ b/tests/Modules/SpectatorTargetInfo.Tests/Services/SpectatorTargetInfoServiceTests.cs @@ -77,12 +77,8 @@ public async Task Gets_Login_By_Dedicated_Player_Id() public async Task Updates_Spectator_Target_With_Dedicated_Player_Id() { var spectatorTargetService = ServiceMock(); - _server.Remote.Setup(s => s.GetPlayerListAsync()) - .ReturnsAsync([ - new TmPlayerInfo { PlayerId = 22, Login = "UnitTest" } - ]); - await spectatorTargetService.UpdateSpectatorTargetAsync("*fakeplayer1*", 22); + await spectatorTargetService.SetSpectatorTargetLoginAsync("*fakeplayer1*", "UnitTest"); var spectatorOfPlayer = spectatorTargetService.GetLoginsOfPlayersSpectatingTarget("UnitTest").ToList(); Assert.Single(spectatorOfPlayer); @@ -95,7 +91,7 @@ public async Task Removes_Spectator_If_Target_Login_Is_Null() var spectatorTargetService = ServiceMock(); await spectatorTargetService.SetSpectatorTargetLoginAsync("*fakeplayer1*", "UnitTest"); - await spectatorTargetService.UpdateSpectatorTargetAsync("*fakeplayer1*", 1111); + await spectatorTargetService.SetSpectatorTargetLoginAsync("*fakeplayer1*", "SomeOtherPlayer"); var spectatorOfPlayer = spectatorTargetService.GetLoginsOfPlayersSpectatingTarget("UnitTest").ToList(); Assert.Empty(spectatorOfPlayer); From 4cf93fabfd54fb20fecaa115cc666acb1511be5f Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Sat, 7 Sep 2024 14:08:58 +0200 Subject: [PATCH 12/73] Implement change spec target --- .../Services/SpectatorTargetInfoService.cs | 7 +- .../Templates/SpectatorTargetInfo.mt | 188 ++++++++++++++++-- 2 files changed, 174 insertions(+), 21 deletions(-) diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index 435410a5f..7c43810eb 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -47,6 +47,9 @@ public async Task InitializeAsync() await UpdateIsTeamsModeAsync(); await UpdateTeamInfoAsync(); await HideGameModeUiAsync(); + + // var onlinePlayers = await playerManagerService.GetOnlinePlayersAsync(); + // onlinePlayers.Where(player => player.State == PlayerState.Spectating); } public Task GetOnlinePlayerByLoginAsync(string playerLogin) @@ -229,8 +232,8 @@ public Task HideWidgetAsync(string playerLogin) public async Task UpdateTeamInfoAsync() { - _teamInfos[PlayerTeam.Team1] = await server.Remote.GetTeamInfoAsync((int)PlayerTeam.Team1); - _teamInfos[PlayerTeam.Team2] = await server.Remote.GetTeamInfoAsync((int)PlayerTeam.Team2); + _teamInfos[PlayerTeam.Team1] = await GetTeamInfoAsync(PlayerTeam.Team1); + _teamInfos[PlayerTeam.Team2] = await GetTeamInfoAsync(PlayerTeam.Team2); } public async Task UpdateIsTeamsModeAsync() diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt index f94690156..eeea473a4 100644 --- a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt +++ b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt @@ -11,44 +11,44 @@ - - + + + + \ No newline at end of file From 63fd01ca3ccb02e5a093bb105691ac094da9c9f3 Mon Sep 17 00:00:00 2001 From: "Alex 'Braker' R." Date: Wed, 11 Sep 2024 20:20:09 +0200 Subject: [PATCH 13/73] Make spectator info module hide Nadeo counterpart --- src/EvoSC/InternalModules.cs | 4 +- .../Enums/GameModeUiComponents.cs | 20 ++ .../GameModeUiModule/GameModeUiModule.cs | 2 +- .../GameModeUiModule/GameModeUiModule.csproj | 2 - .../Interfaces/IGameModeUiModuleService.cs | 24 +- .../Models/GameModeUiComponentSettings.cs | 13 + .../Services/GameModeUiModuleService.cs | 282 ++++++++++-------- .../Localization.resx | 19 ++ .../Services/SpectatorTargetInfoService.cs | 15 +- .../SpectatorTargetInfoModule.csproj | 7 + .../Templates/SpectatorTargetInfo.mt | 2 +- .../SpectatorTargetInfoModule/info.toml | 2 +- .../Services/GameModeUiModuleServiceTests.cs | 3 +- .../SpectatorTargetInfoServiceTests.cs | 3 +- 14 files changed, 251 insertions(+), 147 deletions(-) create mode 100644 src/Modules/GameModeUiModule/Enums/GameModeUiComponents.cs create mode 100644 src/Modules/GameModeUiModule/Models/GameModeUiComponentSettings.cs create mode 100644 src/Modules/SpectatorTargetInfoModule/Localization.resx diff --git a/src/EvoSC/InternalModules.cs b/src/EvoSC/InternalModules.cs index 4b7f93cac..8d3acf6a7 100644 --- a/src/EvoSC/InternalModules.cs +++ b/src/EvoSC/InternalModules.cs @@ -53,7 +53,6 @@ public static class InternalModules typeof(LiveRankingModule), typeof(MatchRankingModule), typeof(ASayModule), - typeof(SpectatorTargetInfoModule), typeof(MapQueueModule), typeof(MapListModule), typeof(LocalRecordsModule), @@ -61,7 +60,8 @@ public static class InternalModules typeof(TeamSettingsModule), typeof(ServerManagementModule), typeof(TeamInfoModule), - typeof(GameModeUiModule) + typeof(GameModeUiModule), + typeof(SpectatorTargetInfoModule) ]; /// diff --git a/src/Modules/GameModeUiModule/Enums/GameModeUiComponents.cs b/src/Modules/GameModeUiModule/Enums/GameModeUiComponents.cs new file mode 100644 index 000000000..df2ec2bfd --- /dev/null +++ b/src/Modules/GameModeUiModule/Enums/GameModeUiComponents.cs @@ -0,0 +1,20 @@ +namespace EvoSC.Modules.Official.GameModeUiModule.Enums; + +public static class GameModeUiComponents +{ + public const string Chrono = "Race_Chrono"; + public const string RespawnHelper = "Race_RespawnHelper"; + public const string Checkpoint = "Race_Checkpoint"; + public const string LapsCounter = "Race_LapsCounter"; + public const string TimeGap = "Race_TimeGap"; + public const string ScoresTable = "Race_ScoresTable"; + public const string DisplayMessage = "Race_DisplayMessage"; + public const string Countdown = "Race_Countdown"; + public const string SpectatorBaseName = "Race_SpectatorBase_Name"; + public const string SpectatorBaseCommands = "Race_SpectatorBase_Commands"; + public const string Record = "Race_Record"; + public const string BigMessage = "Race_BigMessage"; + public const string BlockHelper = "Race_BlockHelper"; + public const string WarmUp = "Race_WarmUp"; + public const string BestRaceViewer = "Race_BestRaceViewer"; +} diff --git a/src/Modules/GameModeUiModule/GameModeUiModule.cs b/src/Modules/GameModeUiModule/GameModeUiModule.cs index 6f3146424..edd0ba130 100644 --- a/src/Modules/GameModeUiModule/GameModeUiModule.cs +++ b/src/Modules/GameModeUiModule/GameModeUiModule.cs @@ -7,7 +7,7 @@ namespace EvoSC.Modules.Official.GameModeUiModule; [Module(IsInternal = true)] public class GameModeUiModule(IGameModeUiModuleService gameModeUiModuleService) : EvoScModule, IToggleable { - public Task EnableAsync() => gameModeUiModuleService.ApplyConfigurationAsync(); + public Task EnableAsync() => gameModeUiModuleService.InitializeAsync(); public Task DisableAsync() => Task.CompletedTask; } diff --git a/src/Modules/GameModeUiModule/GameModeUiModule.csproj b/src/Modules/GameModeUiModule/GameModeUiModule.csproj index 5d11a594b..881d331e5 100644 --- a/src/Modules/GameModeUiModule/GameModeUiModule.csproj +++ b/src/Modules/GameModeUiModule/GameModeUiModule.csproj @@ -12,8 +12,6 @@ - ResXFileCodeGenerator - Localization.Designer.cs diff --git a/src/Modules/GameModeUiModule/Interfaces/IGameModeUiModuleService.cs b/src/Modules/GameModeUiModule/Interfaces/IGameModeUiModuleService.cs index 0774afd55..644d158d3 100644 --- a/src/Modules/GameModeUiModule/Interfaces/IGameModeUiModuleService.cs +++ b/src/Modules/GameModeUiModule/Interfaces/IGameModeUiModuleService.cs @@ -1,27 +1,33 @@ -namespace EvoSC.Modules.Official.GameModeUiModule.Interfaces; +using EvoSC.Modules.Official.GameModeUiModule.Models; + +namespace EvoSC.Modules.Official.GameModeUiModule.Interfaces; public interface IGameModeUiModuleService { + public Task InitializeAsync(); + /// /// Applies the configured UI modules property values. /// + /// /// - public Task ApplyConfigurationAsync(); + public Task ApplyConfigurationAsync(List componentSettingsList); + + public Task ApplyComponentSettingsAsync(GameModeUiComponentSettings componentSettings); + public Task ApplyAndSaveComponentSettingsAsync(GameModeUiComponentSettings componentSettings); /// /// Returns the configured UI modules properties as JSON string. /// /// - public Task GetUiModulesPropertiesJsonAsync(); + public string GetUiModulesPropertiesJson(List componentSettingsList); + + public List GetDefaultSettings(); /// /// Generates a UI properties object for the given values. /// - /// - /// - /// - /// - /// + /// /// - public Task GeneratePropertyObjectAsync(string uiModuleName, bool visible, double x, double y, double scale); + public dynamic GeneratePropertyObject(GameModeUiComponentSettings componentSettings); } diff --git a/src/Modules/GameModeUiModule/Models/GameModeUiComponentSettings.cs b/src/Modules/GameModeUiModule/Models/GameModeUiComponentSettings.cs new file mode 100644 index 000000000..354e6a6da --- /dev/null +++ b/src/Modules/GameModeUiModule/Models/GameModeUiComponentSettings.cs @@ -0,0 +1,13 @@ +namespace EvoSC.Modules.Official.GameModeUiModule.Models; + +public class GameModeUiComponentSettings(string name, bool visible, double x, double y, double scale) +{ + public string Name { get; init; } = name; + public bool Visible { get; set; } = visible; + public double X { get; set; } = x; + public double Y { get; set; } = y; + public double Scale { get; set; } = scale; + public bool UpdateVisible { get; set; } = true; + public bool UpdatePosition { get; set; } = true; + public bool UpdateScale { get; set; } = true; +} diff --git a/src/Modules/GameModeUiModule/Services/GameModeUiModuleService.cs b/src/Modules/GameModeUiModule/Services/GameModeUiModuleService.cs index 44ef2ba2c..8b80fdaa4 100644 --- a/src/Modules/GameModeUiModule/Services/GameModeUiModuleService.cs +++ b/src/Modules/GameModeUiModule/Services/GameModeUiModuleService.cs @@ -2,148 +2,176 @@ using EvoSC.Common.Services.Attributes; using EvoSC.Common.Services.Models; using EvoSC.Modules.Official.GameModeUiModule.Config; +using EvoSC.Modules.Official.GameModeUiModule.Enums; using EvoSC.Modules.Official.GameModeUiModule.Interfaces; +using EvoSC.Modules.Official.GameModeUiModule.Models; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace EvoSC.Modules.Official.GameModeUiModule.Services; -[Service(LifeStyle = ServiceLifeStyle.Transient)] -public class GameModeUiModuleService(IServerClient server, IGameModeUiModuleSettings settings) +[Service(LifeStyle = ServiceLifeStyle.Singleton)] +public class GameModeUiModuleService(IServerClient server, IGameModeUiModuleSettings settings, ILogger logger) : IGameModeUiModuleService { - public async Task ApplyConfigurationAsync() + private readonly List _componentSettings = []; + + public async Task InitializeAsync() + { + _componentSettings.AddRange(GetDefaultSettings()); + await ApplyConfigurationAsync(_componentSettings); + } + + public async Task ApplyConfigurationAsync(List componentSettingsList) { - var uiModuleProperties = await GetUiModulesPropertiesJsonAsync(); + var uiModuleProperties = GetUiModulesPropertiesJson(componentSettingsList); + logger.LogDebug("Applying UI properties: {json}", uiModuleProperties); await server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", uiModuleProperties); } - public async Task GetUiModulesPropertiesJsonAsync() + public async Task ApplyComponentSettingsAsync(GameModeUiComponentSettings componentSettings) { - return JsonConvert.SerializeObject(new - { - uimodules = new List - { - await GeneratePropertyObjectAsync( - "Race_Chrono", - settings.ChronoVisible, - settings.ChronoX, - settings.ChronoY, - settings.ChronoScale - ), - await GeneratePropertyObjectAsync( - "Race_RespawnHelper", - settings.RespawnHelperVisible, - settings.RespawnHelperX, - settings.RespawnHelperY, - settings.RespawnHelperScale - ), - await GeneratePropertyObjectAsync( - "Race_Checkpoint", - settings.CheckpointVisible, - settings.CheckpointX, - settings.CheckpointY, - settings.CheckpointScale - ), - await GeneratePropertyObjectAsync( - "Race_LapsCounter", - settings.LapsCounterVisible, - settings.LapsCounterX, - settings.LapsCounterY, - settings.LapsCounterScale - ), - await GeneratePropertyObjectAsync( - "Race_TimeGap", - settings.TimeGapVisible, - settings.TimeGapX, - settings.TimeGapY, - settings.TimeGapScale - ), - await GeneratePropertyObjectAsync( - "Race_ScoresTable", - settings.ScoresTableVisible, - settings.ScoresTableX, - settings.ScoresTableY, - settings.ScoresTableScale - ), - await GeneratePropertyObjectAsync( - "Race_DisplayMessage", - settings.DisplayMessageVisible, - settings.DisplayMessageX, - settings.DisplayMessageY, - settings.DisplayMessageScale - ), - await GeneratePropertyObjectAsync( - "Race_Countdown", - settings.CountdownVisible, - settings.CountdownX, - settings.CountdownY, - settings.CountdownScale - ), - await GeneratePropertyObjectAsync( - "Race_SpectatorBase_Name", - settings.SpectatorBaseNameVisible, - settings.SpectatorBaseNameX, - settings.SpectatorBaseNameY, - settings.SpectatorBaseNameScale - ), - await GeneratePropertyObjectAsync( - "Race_SpectatorBase_Commands", - settings.SpectatorBaseCommandsVisible, - settings.SpectatorBaseCommandsX, - settings.SpectatorBaseCommandsY, - settings.SpectatorBaseCommandsScale - ), - await GeneratePropertyObjectAsync( - "Race_Record", - settings.RecordVisible, - settings.RecordX, - settings.RecordY, - settings.RecordScale - ), - await GeneratePropertyObjectAsync( - "Race_BigMessage", - settings.BigMessageVisible, - settings.BigMessageX, - settings.BigMessageY, - settings.BigMessageScale - ), - await GeneratePropertyObjectAsync( - "Race_BlockHelper", - settings.BlockHelperVisible, - settings.BlockHelperX, - settings.BlockHelperY, - settings.BlockHelperScale - ), - await GeneratePropertyObjectAsync( - "Race_WarmUp", - settings.WarmUpVisible, - settings.WarmUpX, - settings.WarmUpY, - settings.WarmUpScale - ), - await GeneratePropertyObjectAsync( - "Race_BestRaceViewer", - settings.BestRaceViewerVisible, - settings.BestRaceViewerX, - settings.BestRaceViewerY, - settings.BestRaceViewerScale - ), - } - }); + await ApplyConfigurationAsync([componentSettings]); + } + + public async Task ApplyAndSaveComponentSettingsAsync(GameModeUiComponentSettings componentSettings) + { + //TODO: overwrite value in _componentSettings + await ApplyConfigurationAsync([componentSettings]); + } + + public List GetDefaultSettings() + { + return + [ + new GameModeUiComponentSettings( + GameModeUiComponents.Chrono, + settings.ChronoVisible, + settings.ChronoX, + settings.ChronoY, + settings.ChronoScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.RespawnHelper, + settings.RespawnHelperVisible, + settings.RespawnHelperX, + settings.RespawnHelperY, + settings.RespawnHelperScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.Checkpoint, + settings.CheckpointVisible, + settings.CheckpointX, + settings.CheckpointY, + settings.CheckpointScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.LapsCounter, + settings.LapsCounterVisible, + settings.LapsCounterX, + settings.LapsCounterY, + settings.LapsCounterScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.TimeGap, + settings.TimeGapVisible, + settings.TimeGapX, + settings.TimeGapY, + settings.TimeGapScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.ScoresTable, + settings.ScoresTableVisible, + settings.ScoresTableX, + settings.ScoresTableY, + settings.ScoresTableScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.DisplayMessage, + settings.DisplayMessageVisible, + settings.DisplayMessageX, + settings.DisplayMessageY, + settings.DisplayMessageScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.Countdown, + settings.CountdownVisible, + settings.CountdownX, + settings.CountdownY, + settings.CountdownScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.SpectatorBaseName, + settings.SpectatorBaseNameVisible, + settings.SpectatorBaseNameX, + settings.SpectatorBaseNameY, + settings.SpectatorBaseNameScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.SpectatorBaseCommands, + settings.SpectatorBaseCommandsVisible, + settings.SpectatorBaseCommandsX, + settings.SpectatorBaseCommandsY, + settings.SpectatorBaseCommandsScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.Record, + settings.RecordVisible, + settings.RecordX, + settings.RecordY, + settings.RecordScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.BigMessage, + settings.BigMessageVisible, + settings.BigMessageX, + settings.BigMessageY, + settings.BigMessageScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.BlockHelper, + settings.BlockHelperVisible, + settings.BlockHelperX, + settings.BlockHelperY, + settings.BlockHelperScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.WarmUp, + settings.WarmUpVisible, + settings.WarmUpX, + settings.WarmUpY, + settings.WarmUpScale + ), + new GameModeUiComponentSettings( + GameModeUiComponents.BestRaceViewer, + settings.BestRaceViewerVisible, + settings.BestRaceViewerX, + settings.BestRaceViewerY, + settings.BestRaceViewerScale + ) + ]; + } + + public string GetUiModulesPropertiesJson(List componentSettingsList) + { + var propertyObjects = componentSettingsList + .Select(uiElement => GeneratePropertyObject(uiElement)) + .ToList(); + + return JsonConvert.SerializeObject(new { uimodules = propertyObjects }); } - public Task GeneratePropertyObjectAsync(string uiModuleName, bool visible, double x, double y, - double scale) + public dynamic GeneratePropertyObject(GameModeUiComponentSettings componentSettings) { - return Task.FromResult(new + return new { - id = uiModuleName, - position = (double[]) [x, y], - visible, - scale, - position_update = true, - visible_update = true, - scale_update = true, - }); + id = componentSettings.Name, + position = (double[]) [componentSettings.X, componentSettings.Y], + visible = componentSettings.Visible, + scale = componentSettings.Scale, + position_update = componentSettings.UpdatePosition, + visible_update = componentSettings.UpdateVisible, + scale_update = componentSettings.UpdateScale, + }; } } diff --git a/src/Modules/SpectatorTargetInfoModule/Localization.resx b/src/Modules/SpectatorTargetInfoModule/Localization.resx new file mode 100644 index 000000000..02cf34b4b --- /dev/null +++ b/src/Modules/SpectatorTargetInfoModule/Localization.resx @@ -0,0 +1,19 @@ + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs index 7c43810eb..a7cd6b93a 100644 --- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs +++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs @@ -7,6 +7,9 @@ using EvoSC.Common.Util; using EvoSC.Common.Util.MatchSettings; using EvoSC.Manialinks.Interfaces; +using EvoSC.Modules.Official.GameModeUiModule.Enums; +using EvoSC.Modules.Official.GameModeUiModule.Interfaces; +using EvoSC.Modules.Official.GameModeUiModule.Models; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Config; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces; using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models; @@ -24,6 +27,7 @@ public class SpectatorTargetInfoService( IMatchSettingsService matchSettingsService, ISpectatorTargetInfoSettings settings, IThemeManager theme, + IGameModeUiModuleService gameModeUiModuleService, ILogger logger ) : ISpectatorTargetInfoService { @@ -244,9 +248,16 @@ await matchSettingsService.GetCurrentModeAsync() is DefaultModeScriptName.Teams logger.LogInformation("Team mode is {state}", _isTeamsMode ? "active" : "not active"); } - public Task HideGameModeUiAsync() + public async Task HideGameModeUiAsync() { - return Task.CompletedTask; //TODO: implement + var componentSettings = new GameModeUiComponentSettings( + GameModeUiComponents.SpectatorBaseName, + false, + 0.0, + 0.0, + 1.0 + ); + await gameModeUiModuleService.ApplyAndSaveComponentSettingsAsync(componentSettings); } public Task AddFakePlayerAsync() => //TODO: remove before mergin into master diff --git a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.csproj b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.csproj index 11821d888..1a6292958 100644 --- a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.csproj +++ b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.csproj @@ -5,14 +5,21 @@ enable enable EvoSC.Modules.Official.SpectatorTargetInfoModule + false + SpectatorTargetInfoModule + + + ResXFileCodeGenerator + Localization.Designer.cs + diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt index eeea473a4..73bf539b1 100644 --- a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt +++ b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt @@ -17,7 +17,7 @@