diff --git a/EvoSC.sln b/EvoSC.sln index 569a1ce65..95a966060 100644 --- a/EvoSC.sln +++ b/EvoSC.sln @@ -136,6 +136,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameModeUiModule", "src\Mod EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameModeUiModule.Tests", "tests\Modules\GameModeUiModule.Tests\GameModeUiModule.Tests.csproj", "{5B515690-0F0B-44D1-B644-3524A83A17CE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveRankingModule.Tests", "tests\Modules\LiveRankingModule.Tests\LiveRankingModule.Tests.csproj", "{4AA1890A-1423-4831-95B2-E29B9A7A58D1}" +EndProject @@ -411,6 +413,10 @@ Global {5B515690-0F0B-44D1-B644-3524A83A17CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B515690-0F0B-44D1-B644-3524A83A17CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B515690-0F0B-44D1-B644-3524A83A17CE}.Release|Any CPU.Build.0 = Release|Any CPU + {4AA1890A-1423-4831-95B2-E29B9A7A58D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -475,5 +481,6 @@ Global {F8C7FE5E-B389-4BA8-B0DF-6D9D0A9B0949} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A} {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} EndGlobalSection EndGlobal diff --git a/src/EvoSC.Common/Util/Manialinks/WidgetPosition.cs b/src/EvoSC.Common/Util/Manialinks/WidgetPosition.cs new file mode 100644 index 000000000..1dfe6772c --- /dev/null +++ b/src/EvoSC.Common/Util/Manialinks/WidgetPosition.cs @@ -0,0 +1,18 @@ +using EvoSC.Common.Util.EnumIdentifier; + +namespace EvoSC.Common.Util.Manialinks; + +public enum WidgetPosition +{ + [Identifier(Name = "left", NoPrefix = true)] + Left, + + [Identifier(Name = "right", NoPrefix = true)] + Right, + + [Identifier(Name = "center", NoPrefix = true)] + Center, + + [Identifier(Name = "", NoPrefix = true)] + Undefined, +} diff --git a/src/Modules/LiveRankingModule/Config/ILiveRankingSettings.cs b/src/Modules/LiveRankingModule/Config/ILiveRankingSettings.cs new file mode 100644 index 000000000..862e69272 --- /dev/null +++ b/src/Modules/LiveRankingModule/Config/ILiveRankingSettings.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; +using Config.Net; +using EvoSC.Common.Util.Manialinks; +using EvoSC.Modules.Attributes; + +namespace EvoSC.Modules.Official.LiveRankingModule.Config; + +[Settings] +public interface ILiveRankingSettings +{ + [Option(DefaultValue = 10), Description("Max of rows to show in the live ranking widget.")] + public int MaxWidgetRows { get; set; } + + [Option(DefaultValue = 63.0), Description("Specifies the Y position of the widget.")] + public double Y { get; set; } + + [Option(DefaultValue = 36.0), Description("Specifies the width of the widget.")] + public double Width { get; set; } + + [Option(DefaultValue = "right"), Description("Specifies on which side the widget is displayed.")] + public WidgetPosition Position { get; set; } +} diff --git a/src/Modules/LiveRankingModule/Controllers/LiveRankingEventController.cs b/src/Modules/LiveRankingModule/Controllers/LiveRankingEventController.cs index 5f8e1c9e4..98aac60cb 100644 --- a/src/Modules/LiveRankingModule/Controllers/LiveRankingEventController.cs +++ b/src/Modules/LiveRankingModule/Controllers/LiveRankingEventController.cs @@ -2,6 +2,7 @@ using EvoSC.Common.Controllers.Attributes; using EvoSC.Common.Events.Attributes; using EvoSC.Common.Interfaces.Controllers; +using EvoSC.Common.Models; using EvoSC.Common.Remote; using EvoSC.Common.Remote.EventArgsModels; using EvoSC.Modules.Official.LiveRankingModule.Interfaces; @@ -12,30 +13,38 @@ namespace EvoSC.Modules.Official.LiveRankingModule.Controllers; [Controller] public class LiveRankingEventController(ILiveRankingService service) : EvoScController { - [Subscribe(ModeScriptEvent.WayPoint)] - public Task OnPlayerWaypointAsync(object sender, WayPointEventArgs args) => service.OnPlayerWaypointAsync(args); - - [Subscribe(ModeScriptEvent.GiveUp)] - public Task OnPlayerGiveUpAsync(object sender, PlayerUpdateEventArgs args) => service.OnPlayerGiveupAsync(args); - - [Subscribe(ModeScriptEvent.StartRoundStart)] - public Task OnStartRoundAsync(object sender, RoundEventArgs args) => service.OnStartRoundAsync(args); - - [Subscribe(ModeScriptEvent.EndMapStart)] - public Task OnEndMapAsync(object sender, MapEventArgs args) => service.OnEndMapAsync(args); + [Subscribe(GbxRemoteEvent.BeginMap)] + public Task OnBeginMapAsync(object sender, MapGbxEventArgs args) + => service.DetectModeAndRequestScoreAsync(); + + [Subscribe(ModeScriptEvent.Scores)] + public async Task OnScoresAsync(object sender, ScoresEventArgs args) + { + if (args.Section is not (ModeScriptSection.EndRound or ModeScriptSection.Undefined)) + { + return; + } + + await service.MapScoresAndSendWidgetAsync(args); + } [Subscribe(ModeScriptEvent.PodiumStart)] - public Task OnPodiumStartAsync(object sender, PodiumEventArgs args) => service.OnPodiumStartAsync(args); + public Task OnPodiumStartAsync(object sender, PodiumEventArgs args) + => service.HideWidgetAsync(); - [Subscribe(ModeScriptEvent.EndRoundStart)] - public Task OnEndRoundAsync(object sender, RoundEventArgs args) => service.OnEndRoundAsync(args); - - [Subscribe(GbxRemoteEvent.BeginMatch)] - public Task OnBeginMatchAsync(object sender, EventArgs args) => service.OnBeginMatchAsync(); - - [Subscribe(GbxRemoteEvent.EndMatch)] - public Task OnEndMatchAsync(object sender, EndMatchGbxEventArgs args) => service.OnEndMatchAsync(args); - - [Subscribe(GbxRemoteEvent.PlayerConnect)] - public Task OnPlayerConnectAsync(object sender, PlayerConnectGbxEventArgs args) => service.SendManialinkAsync(); + [Subscribe(ModeScriptEvent.WayPoint)] + public async Task OnWayPointAsync(object sender, WayPointEventArgs args) + { + if (!args.IsEndLap) + { + return; + } + + if (await service.CurrentModeIsPointsBasedAsync()) + { + return; + } + + await service.RequestScoresAsync(); + } } diff --git a/src/Modules/LiveRankingModule/Interfaces/ILiveRankingService.cs b/src/Modules/LiveRankingModule/Interfaces/ILiveRankingService.cs index ab61ad8e1..c5615e3ad 100644 --- a/src/Modules/LiveRankingModule/Interfaces/ILiveRankingService.cs +++ b/src/Modules/LiveRankingModule/Interfaces/ILiveRankingService.cs @@ -1,92 +1,60 @@ -using EvoSC.Common.Remote.EventArgsModels; +using EvoSC.Common.Models.Callbacks; +using EvoSC.Common.Remote.EventArgsModels; using EvoSC.Modules.Official.LiveRankingModule.Models; -using GbxRemoteNet.Events; namespace EvoSC.Modules.Official.LiveRankingModule.Interfaces; public interface ILiveRankingService { /// - /// Called on when module is enabled + /// Determines if current mode is points based and requests scores. /// /// - Task OnEnableAsync(); + public Task DetectModeAndRequestScoreAsync(); /// - /// Called on when module is disabled + /// Requests scores from the game mode. /// /// - Task OnDisableAsync(); + public Task RequestScoresAsync(); /// - /// Called when a player passes a checkpoint. + /// Maps the scores and displays the widget. /// + /// /// - Task OnPlayerWaypointAsync(WayPointEventArgs args); + public Task MapScoresAndSendWidgetAsync(ScoresEventArgs scores); /// - /// Called when a player retires from the current round. + /// Maps the given ScoresEventArgs to LiveRankingPositions. /// + /// /// - Task OnPlayerGiveupAsync(PlayerUpdateEventArgs args); + public Task> MapScoresAsync(ScoresEventArgs scores); /// - /// Called when a new map starts. + /// Hides the live ranking widget for everyone. /// /// - Task OnBeginMapAsync(MapEventArgs args); + public Task HideWidgetAsync(); /// - /// Called when a map ends. + /// Returns whether the current mode is points based. /// /// - Task OnEndMapAsync(MapEventArgs args); + public Task CurrentModeIsPointsBasedAsync(); /// - /// Called when a round ends. + /// Determines whether a score should be displayed in the widget. /// + /// /// - Task OnEndRoundAsync(RoundEventArgs args); + public bool ScoreShouldBeDisplayed(PlayerScore score); /// - /// Called when a new round starts. + /// Converts a PlayerScore to a LiveRankingPosition object. /// + /// /// - Task OnStartRoundAsync(RoundEventArgs args); - - /// - /// Called when the podium sequence starts. - /// - /// - Task OnPodiumStartAsync(PodiumEventArgs args); - - /// - /// Sends a manialink. - /// - /// - Task SendManialinkAsync(); - - /// - /// Called when a new match starts. - /// - /// - Task OnBeginMatchAsync(); - - /// - /// Called when a match ends. - /// - /// - Task OnEndMatchAsync(EndMatchGbxEventArgs args); - - /// - /// Calculates and sets the diffs of given live ranking positions. - /// - /// - Task CalculateDiffsAsync(List rankings); - - /// - /// Resets the live ranking data. - /// - /// - Task ResetLiveRankingAsync(); + public LiveRankingPosition PlayerScoreToLiveRankingPosition(PlayerScore score); } diff --git a/src/Modules/LiveRankingModule/LiveRankingModule.cs b/src/Modules/LiveRankingModule/LiveRankingModule.cs index 3acf50cfd..243b9c967 100644 --- a/src/Modules/LiveRankingModule/LiveRankingModule.cs +++ b/src/Modules/LiveRankingModule/LiveRankingModule.cs @@ -7,8 +7,7 @@ namespace EvoSC.Modules.Official.LiveRankingModule; [Module(IsInternal = true)] public class LiveRankingModule(ILiveRankingService service) : EvoScModule, IToggleable { - public Task EnableAsync() => service.OnEnableAsync(); + public Task EnableAsync() => service.DetectModeAndRequestScoreAsync(); - // if no cleaning for the classes needed to be done, return here a completed task, otherwise clean the classes, and then complete the task. - public Task DisableAsync() => service.OnDisableAsync(); + public Task DisableAsync() => Task.CompletedTask; } diff --git a/src/Modules/LiveRankingModule/LiveRankingModule.csproj b/src/Modules/LiveRankingModule/LiveRankingModule.csproj index 086f1bd59..b77b78dd2 100644 --- a/src/Modules/LiveRankingModule/LiveRankingModule.csproj +++ b/src/Modules/LiveRankingModule/LiveRankingModule.csproj @@ -11,6 +11,10 @@ + + ResXFileCodeGenerator + Localization.Designer.cs + diff --git a/src/Modules/LiveRankingModule/Localization.resx b/src/Modules/LiveRankingModule/Localization.resx new file mode 100644 index 000000000..02cf34b4b --- /dev/null +++ b/src/Modules/LiveRankingModule/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/LiveRankingModule/Models/ExpandedLiveRankingPosition.cs b/src/Modules/LiveRankingModule/Models/ExpandedLiveRankingPosition.cs deleted file mode 100644 index cd2b166c9..000000000 --- a/src/Modules/LiveRankingModule/Models/ExpandedLiveRankingPosition.cs +++ /dev/null @@ -1,36 +0,0 @@ -using EvoSC.Common.Interfaces.Models; - -namespace EvoSC.Modules.Official.LiveRankingModule.Models; - -public record ExpandedLiveRankingPosition -{ - /// - /// The player. - /// - public required IOnlinePlayer Player { get; init; } - - /// - /// The time at the checkpoint. - /// - public required int CheckpointTime { get; init; } - - /// - /// The index of the checkpoint. - /// - public required int CheckpointIndex { get; init; } - - /// - /// Whether the player retreated from the round. - /// - public required bool IsDnf { get; init; } - - /// - /// Whether the player crossed the finish line. - /// - public required bool IsFinish { get; init; } - - /// - /// The difference in milliseconds of the players time to the leading players time. - /// - public int DiffToFirstPosition { get; set; } -} diff --git a/src/Modules/LiveRankingModule/Models/LiveRankingPosition.cs b/src/Modules/LiveRankingModule/Models/LiveRankingPosition.cs index 98d0a8764..9952cb07e 100644 --- a/src/Modules/LiveRankingModule/Models/LiveRankingPosition.cs +++ b/src/Modules/LiveRankingModule/Models/LiveRankingPosition.cs @@ -1,11 +1,3 @@ namespace EvoSC.Modules.Official.LiveRankingModule.Models; -/// -/// The live ranking position of a player. -/// -/// Account Id of the player. -/// The checkpoint time of the player. -/// The checkpoint index that was driven through. -/// Whether the player has given up or not. -/// Whether the player has finished. -public record LiveRankingPosition(string AccountId, int CpTime, int CpIndex, bool IsDnf, bool IsFinish); +public record LiveRankingPosition(string AccountId, string Name, int Position, int Time, int Points); diff --git a/src/Modules/LiveRankingModule/Models/LiveRankingStore.cs b/src/Modules/LiveRankingModule/Models/LiveRankingStore.cs deleted file mode 100644 index 96fcb09eb..000000000 --- a/src/Modules/LiveRankingModule/Models/LiveRankingStore.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Collections.Concurrent; -using EvoSC.Common.Interfaces.Services; - -namespace EvoSC.Modules.Official.LiveRankingModule.Models; - -internal class LiveRankingStore(IPlayerManagerService playerManager) -{ - private ConcurrentDictionary _curLiveRanking { get; set; } = new(); - private ConcurrentDictionary _prevLiveRanking { get; set; } = new(); - - private MatchInfo _matchInfo = new(); - - internal Task ResetLiveRankingsAsync() - { - _curLiveRanking.Clear(); - - return Task.CompletedTask; - } - - internal void RegisterTime(string accountId, int cpIndex, int cpTime, bool isFinish) - { - _prevLiveRanking = new ConcurrentDictionary(_curLiveRanking); - var liveRankingPosition = new LiveRankingPosition(accountId, cpTime, cpIndex, false, isFinish); - - _curLiveRanking.AddOrUpdate(accountId, _ => liveRankingPosition, (_, _) => liveRankingPosition); - } - - internal void RegisterPlayerGiveUp(string accountId) - { - _prevLiveRanking = new ConcurrentDictionary(_curLiveRanking); - - _curLiveRanking.AddOrUpdate(accountId, _ => new LiveRankingPosition(accountId, 0, 0, true, false), - (_, arg) => new LiveRankingPosition(accountId, arg.CpTime, arg.CpIndex, true, false)); - } - - /// - /// This sorts the live ranking based on the following criteria: - /// - DNFd players should always be at the bottom - /// - Players with a higher cpIndex should always be at the top - /// - Players with the faster CP time at the same CP index should be in a higher position - /// - internal static List SortLiveRanking( - IEnumerable positions) - { - return positions - .OrderBy(a => a.IsDnf) - .ThenByDescending(a => a.CheckpointIndex) - .ThenBy(a => a.CheckpointTime).ToList(); - } - - internal async Task> GetFullLiveRankingAsync() - { - List expandedLiveRanking = new(); - foreach (var rank in _curLiveRanking) - { - var player = await playerManager.GetOnlinePlayerAsync(rank.Value.AccountId); - expandedLiveRanking.Add(new ExpandedLiveRankingPosition - { - Player = player, - CheckpointTime = rank.Value.CpTime, - CheckpointIndex = rank.Value.CpIndex, - IsDnf = rank.Value.IsDnf, - IsFinish = rank.Value.IsFinish - }); - } - - var sortedExpandedLiveRanking = SortLiveRanking(expandedLiveRanking); - - return sortedExpandedLiveRanking; - } - - internal async Task> GetFullPreviousLiveRankingAsync() - { - List expandedLiveRanking = new(); - foreach (var rank in _prevLiveRanking) - { - var player = await playerManager.GetOnlinePlayerAsync(rank.Value.AccountId); - expandedLiveRanking.Add(new ExpandedLiveRankingPosition - { - Player = player, - CheckpointTime = rank.Value.CpTime, - CheckpointIndex = rank.Value.CpIndex, - IsDnf = rank.Value.IsDnf, - IsFinish = rank.Value.IsFinish - }); - } - - return expandedLiveRanking; - } - - internal void SetCurrentMap(string name) - { - _matchInfo.MapName = name; - } - - internal void SetWorldRecord(string wrHolder, string wrTime) - { - _matchInfo.WrHolderName = wrHolder; - _matchInfo.WrTime = wrTime; - } - - internal void IncreaseRoundCounter() - { - _matchInfo.NumRound++; - } - - internal void ResetRoundCounter() - { - _matchInfo.NumRound = 0; - } - - internal void IncreaseTrackCounter() - { - _matchInfo.NumTrack++; - } - - internal void ResetTrackCounter() - { - _matchInfo.NumTrack = 0; - } - - internal MatchInfo GetMatchInfo() - { - return _matchInfo; - } -} diff --git a/src/Modules/LiveRankingModule/Models/LiveRankingWidgetPosition.cs b/src/Modules/LiveRankingModule/Models/LiveRankingWidgetPosition.cs deleted file mode 100644 index b815f8b02..000000000 --- a/src/Modules/LiveRankingModule/Models/LiveRankingWidgetPosition.cs +++ /dev/null @@ -1,5 +0,0 @@ -using EvoSC.Common.Interfaces.Models; - -namespace EvoSC.Modules.Official.LiveRankingModule.Models; - -public record LiveRankingWidgetPosition(int Position, IPlayer Player, string Login, string Time, int CpIndex, bool IsFinish); diff --git a/src/Modules/LiveRankingModule/Models/MatchInfo.cs b/src/Modules/LiveRankingModule/Models/MatchInfo.cs deleted file mode 100644 index 1e6f62f87..000000000 --- a/src/Modules/LiveRankingModule/Models/MatchInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace EvoSC.Modules.Official.LiveRankingModule.Models; - -public class MatchInfo -{ - /// - /// The name of the map. - /// - public string MapName { get; set; } - /// - /// The number of the round. - /// - public int NumRound { get; set; } - /// - /// The number of the map. - /// - public int NumTrack { get; set; } - /// - /// Name of world record holder. - /// - public string WrHolderName { get; set; } - /// - /// Current world record time. - /// - public string WrTime { get; set; } -} diff --git a/src/Modules/LiveRankingModule/Services/LiveRankingService.cs b/src/Modules/LiveRankingModule/Services/LiveRankingService.cs index 7da1770ec..131063c7d 100644 --- a/src/Modules/LiveRankingModule/Services/LiveRankingService.cs +++ b/src/Modules/LiveRankingModule/Services/LiveRankingService.cs @@ -1,259 +1,87 @@ -using System.Globalization; -using EvoSC.Common.Interfaces; +using EvoSC.Common.Interfaces; using EvoSC.Common.Interfaces.Services; +using EvoSC.Common.Models.Callbacks; using EvoSC.Common.Remote.EventArgsModels; 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.LiveRankingModule.Config; using EvoSC.Modules.Official.LiveRankingModule.Interfaces; using EvoSC.Modules.Official.LiveRankingModule.Models; -using GbxRemoteNet.Events; -using Microsoft.Extensions.Logging; namespace EvoSC.Modules.Official.LiveRankingModule.Services; [Service(LifeStyle = ServiceLifeStyle.Singleton)] -public class LiveRankingService(ILogger logger, IManialinkManager manialinkManager, - IServerClient client, IPlayerManagerService playerManager) - : ILiveRankingService +public class LiveRankingService( + IManialinkManager manialinkManager, + IServerClient server, + ILiveRankingSettings settings, + IPlayerManagerService playerManager, + IMatchSettingsService matchSettingsService +) : ILiveRankingService { - private const int ShowRows = 4; + private const string WidgetTemplate = "LiveRankingModule.LiveRanking"; + private bool _isPointsBased; - private readonly LiveRankingStore _liveRankingStore = new(playerManager); - private bool _isRoundsMode; - - public async Task OnEnableAsync() + public async Task DetectModeAndRequestScoreAsync() { - logger.LogTrace("LiveRankingModule enabled"); - await CheckAndSetRoundsMode(); - await HideNadeoScoreboardAsync(); - if (_isRoundsMode) - { - await _liveRankingStore.ResetLiveRankingsAsync(); - _liveRankingStore.ResetRoundCounter(); - _liveRankingStore.IncreaseRoundCounter(); - _liveRankingStore.IncreaseTrackCounter(); - - await manialinkManager.SendPersistentManialinkAsync("LiveRankingModule.LiveRanking", - await GetWidgetDataAsync()); - } - - await Task.CompletedTask; + _isPointsBased = await matchSettingsService.GetCurrentModeAsync() is not DefaultModeScriptName.TimeAttack; + await RequestScoresAsync(); } - public async Task OnDisableAsync() - { - logger.LogTrace("LiveRankingModule disabled"); - await manialinkManager.HideManialinkAsync("LiveRankingModule.LiveRanking"); - await Task.CompletedTask; - } + public Task RequestScoresAsync() + => server.Remote.TriggerModeScriptEventArrayAsync("Trackmania.GetScores"); - public async Task OnPlayerWaypointAsync(WayPointEventArgs args) + public async Task MapScoresAndSendWidgetAsync(ScoresEventArgs scores) { - await CheckAndSetRoundsMode(); - if (_isRoundsMode) - { - logger.LogTrace("Player crossed a checkpoint: {ArgsAccountId} - RoundsMode: {IsRoundsMode}", - args.AccountId, _isRoundsMode); - - _liveRankingStore.RegisterTime(args.AccountId, args.CheckpointInRace, args.RaceTime, args.IsEndRace); - - await manialinkManager.SendPersistentManialinkAsync("LiveRankingModule.LiveRanking", - await GetWidgetDataAsync()); - } + await manialinkManager.SendPersistentManialinkAsync(WidgetTemplate, + new { settings, isPointsBased = _isPointsBased, scores = await MapScoresAsync(scores) }); } - private async Task GetWidgetDataAsync() + public Task> MapScoresAsync(ScoresEventArgs scores) { - var currentRanking = (await _liveRankingStore.GetFullLiveRankingAsync()).Take(ShowRows).ToList(); - await CalculateDiffsAsync(currentRanking); - var widgetCurrentRanking = GetLiveRankingForWidget(currentRanking); - - return new { rankings = widgetCurrentRanking, }; - } - - public Task CalculateDiffsAsync(List rankings) - { - if (rankings.Count > 0) - { - var firstRanking = rankings.First(); - foreach (var ranking in rankings) - { - ranking.DiffToFirstPosition = firstRanking.CheckpointTime - ranking.CheckpointTime; - } - } - - return Task.CompletedTask; + return Task.FromResult( + scores.Players.Take(settings.MaxWidgetRows) + .Where(score => score != null) + .OfType() + .Where(ScoreShouldBeDisplayed) + .Select(PlayerScoreToLiveRankingPosition) + ); } - public async Task OnPlayerGiveupAsync(PlayerUpdateEventArgs args) - { - await CheckAndSetRoundsMode(); - if (_isRoundsMode) - { - logger.LogTrace("Player gave up: {ArgsAccountId} - RoundsMode: {IsRoundsMode}", args.AccountId, - _isRoundsMode); - - _liveRankingStore.RegisterPlayerGiveUp(args.AccountId); + public Task HideWidgetAsync() + => manialinkManager.HideManialinkAsync(WidgetTemplate); - await manialinkManager.SendPersistentManialinkAsync("LiveRankingModule.LiveRanking", - await GetWidgetDataAsync()); - } - } + public Task CurrentModeIsPointsBasedAsync() + => Task.FromResult(_isPointsBased); - public async Task OnBeginMapAsync(MapEventArgs args) + public bool ScoreShouldBeDisplayed(PlayerScore score) { - logger.LogTrace("Map starts: {MapName}, IsRounds: {IsRoundsMode}", args.Map.Name, _isRoundsMode); - await CheckAndSetRoundsMode(); - if (!_isRoundsMode) - { - await Task.CompletedTask; - } - else + if (_isPointsBased) { - _liveRankingStore.ResetRoundCounter(); - _liveRankingStore.IncreaseRoundCounter(); - _liveRankingStore.IncreaseTrackCounter(); - await _liveRankingStore.ResetLiveRankingsAsync(); + return score.MatchPoints > 0; } - } - public async Task OnEndMapAsync(MapEventArgs args) - { - await CheckAndSetRoundsMode(); - logger.LogTrace("Map ends: {MapName} - RoundsMode: {IsRoundsMode}", args.Map.Name, _isRoundsMode); - if (_isRoundsMode) - { - await _liveRankingStore.ResetLiveRankingsAsync(); - await manialinkManager.HideManialinkAsync("LiveRankingModule.LiveRanking"); - await manialinkManager.HideManialinkAsync("LiveRankingModule.MatchInfo"); - } + return score.BestRaceTime > 0; } - public async Task ResetLiveRankingAsync() + public LiveRankingPosition PlayerScoreToLiveRankingPosition(PlayerScore score) { - await _liveRankingStore.ResetLiveRankingsAsync(); - } + var player = playerManager.GetPlayerAsync(score.AccountId).Result; + var nickname = score.Name; - public async Task OnStartRoundAsync(RoundEventArgs args) - { - logger.LogTrace("Round {ArgsCount} starts - RoundsMode: {IsRoundsMode}", args.Count, _isRoundsMode); - await _liveRankingStore.ResetLiveRankingsAsync(); - await manialinkManager.SendPersistentManialinkAsync("LiveRankingModule.LiveRanking", - await GetWidgetDataAsync()); - } - - public async Task SendManialinkAsync() - { - await manialinkManager.SendPersistentManialinkAsync("LiveRankingModule.LiveRanking", - await GetWidgetDataAsync()); - } - - public async Task OnEndRoundAsync(RoundEventArgs args) - { - logger.LogTrace("Round {ArgsCount} ends - RoundsMode: {IsRoundsMode}", args.Count, _isRoundsMode); - var liveRanking = await _liveRankingStore.GetFullLiveRankingAsync(); - var nbFinished = liveRanking.FindAll(x => x.IsFinish); - if (nbFinished.Count > 0) + if (player != null) { - logger.LogTrace("MatchInfo Rounds before: {ArgsCount}", _liveRankingStore.GetMatchInfo().NumRound); - _liveRankingStore.IncreaseRoundCounter(); - logger.LogTrace("MatchInfo Rounds after: {ArgsCount}", _liveRankingStore.GetMatchInfo().NumRound); + nickname = player.NickName; } - } - public async Task OnBeginMatchAsync() - { - await SendManialinkAsync(); - } - - public async Task OnEndMatchAsync(EndMatchGbxEventArgs args) - { - await HideManialinkAsync(); - } - - public async Task OnPodiumStartAsync(PodiumEventArgs args) - { - await CheckAndSetRoundsMode(); - await HideManialinkAsync(); - } - - public async Task HideNadeoScoreboardAsync() - { - var hudSettings = new List - { - @"{ - ""uimodules"": [ - { - ""id"": ""Rounds_SmallScoresTable"", - ""visible"": false, - ""visible_update"": true - } - ] -}" - }; - - await client.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", hudSettings.ToArray()); - } - - private async Task HideManialinkAsync() - { - await manialinkManager.HideManialinkAsync("LiveRankingModule.LiveRanking"); - } - - private string FormatTime(int time, bool isDelta) - { - TimeSpan ts = TimeSpan.FromMilliseconds(time); - - if (isDelta) - { - return $"+ {Math.Abs(ts.Seconds)}.{ts.ToString("fff", CultureInfo.InvariantCulture)}"; - } - - if (time > 60_000) - { - return $"{ts.ToString(@"mm\:ss\.fff", CultureInfo.InvariantCulture)}"; - } - - return $"{ts.ToString(@"ss\.fff", CultureInfo.InvariantCulture)}"; - } - - private List GetLiveRankingForWidget(List liveRanking) - { - return liveRanking.Select(RankingToTime).ToList(); - } - - private LiveRankingWidgetPosition RankingToTime(ExpandedLiveRankingPosition ranking, int i) - { - var formattedTime = "DNF"; - - if (ranking.IsDnf) - { - return new LiveRankingWidgetPosition(i + 1, ranking.Player, ranking.Player.GetLogin(), formattedTime, - ranking.CheckpointIndex + 1, ranking.IsFinish); - } - - var isDeltaTime = i > 0; - var timeToFormat = isDeltaTime ? ranking.DiffToFirstPosition : ranking.CheckpointTime; - formattedTime = FormatTime(timeToFormat, isDeltaTime); - - return new LiveRankingWidgetPosition(i + 1, ranking.Player, ranking.Player.GetLogin(), formattedTime, - ranking.CheckpointIndex + 1, ranking.IsFinish); - } - - private async Task CheckAndSetRoundsMode() - { - List validModes = new List - { - "Trackmania/TM_Rounds_Online.Script.txt", "Trackmania/TM_Cup_Online.Script.txt" - }; - var scriptInfo = await client.Remote.GetModeScriptInfoAsync(); - _isRoundsMode = validModes.Contains(scriptInfo.Name); - } - - public MatchInfo GetMatchInfo() - { - return _liveRankingStore.GetMatchInfo(); + return new LiveRankingPosition( + score.AccountId, + nickname, + score.Rank, + score.BestRaceTime, + score.MatchPoints + ); } } diff --git a/src/Modules/LiveRankingModule/Templates/Components/LiveRankingRecordRow.mt b/src/Modules/LiveRankingModule/Templates/Components/LiveRankingRecordRow.mt new file mode 100644 index 000000000..4565e05b2 --- /dev/null +++ b/src/Modules/LiveRankingModule/Templates/Components/LiveRankingRecordRow.mt @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Modules/LiveRankingModule/Templates/Components/LiveRankingStyles.mt b/src/Modules/LiveRankingModule/Templates/Components/LiveRankingStyles.mt new file mode 100644 index 000000000..e0cb07bbf --- /dev/null +++ b/src/Modules/LiveRankingModule/Templates/Components/LiveRankingStyles.mt @@ -0,0 +1,19 @@ + + + +