diff --git a/EvoSC.sln b/EvoSC.sln
index 2ec61fa13..767003d64 100644
--- a/EvoSC.sln
+++ b/EvoSC.sln
@@ -84,7 +84,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatchTrackerModule", "src\M
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatchReadyModule", "src\Modules\MatchReadyModule\MatchReadyModule.csproj", "{0538B9AB-B556-45BF-8230-53087BA9D353}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scoreboard", "src\Modules\Scoreboard\Scoreboard.csproj", "{CD032D37-3BC8-4DE8-8C5B-45A0DE36932E}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScoreboardModule", "src\Modules\ScoreboardModule\ScoreboardModule.csproj", "{CD032D37-3BC8-4DE8-8C5B-45A0DE36932E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NextMapModule", "src\Modules\NextMapModule\NextMapModule.csproj", "{64688FA7-136C-4BB9-B716-4E96DD0AA82F}"
EndProject
diff --git a/src/EvoSC/EvoSC.csproj b/src/EvoSC/EvoSC.csproj
index 844ecefc8..86b39b074 100644
--- a/src/EvoSC/EvoSC.csproj
+++ b/src/EvoSC/EvoSC.csproj
@@ -34,7 +34,7 @@
-
+
diff --git a/src/EvoSC/InternalModules.cs b/src/EvoSC/InternalModules.cs
index a7fa2c43b..5b5c3b2f7 100644
--- a/src/EvoSC/InternalModules.cs
+++ b/src/EvoSC/InternalModules.cs
@@ -20,7 +20,7 @@
using EvoSC.Modules.Official.OpenPlanetModule;
using EvoSC.Modules.Official.Player;
using EvoSC.Modules.Official.PlayerRecords;
-using EvoSC.Modules.Official.Scoreboard;
+using EvoSC.Modules.Official.ScoreboardModule;
using EvoSC.Modules.Official.ServerManagementModule;
using EvoSC.Modules.Official.SetName;
using EvoSC.Modules.Official.SpectatorCamModeModule;
diff --git a/src/Modules/Scoreboard/Controllers/ScoreboardCommandsController.cs b/src/Modules/Scoreboard/Controllers/ScoreboardCommandsController.cs
deleted file mode 100644
index e36e18395..000000000
--- a/src/Modules/Scoreboard/Controllers/ScoreboardCommandsController.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using EvoSC.Commands.Attributes;
-using EvoSC.Commands.Interfaces;
-using EvoSC.Common.Controllers;
-using EvoSC.Common.Controllers.Attributes;
-using EvoSC.Modules.Official.Scoreboard.Interfaces;
-
-namespace EvoSC.Modules.Official.Scoreboard.Controllers;
-
-[Controller]
-public class ScoreboardCommandsController(IScoreboardService scoreboardService) : EvoScController
-{
- [ChatCommand("scoreboard", "[Command.ShowScoreboard]")]
- public async Task ShowScoreboardAsync()
- {
- await scoreboardService.ShowScoreboardAsync(Context.Player);
- }
-}
diff --git a/src/Modules/Scoreboard/Controllers/ScoreboardEventController.cs b/src/Modules/Scoreboard/Controllers/ScoreboardEventController.cs
deleted file mode 100644
index 466a5545f..000000000
--- a/src/Modules/Scoreboard/Controllers/ScoreboardEventController.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using EvoSC.Common.Controllers;
-using EvoSC.Common.Controllers.Attributes;
-using EvoSC.Common.Events.Attributes;
-using EvoSC.Common.Interfaces.Controllers;
-using EvoSC.Common.Remote;
-using EvoSC.Common.Remote.EventArgsModels;
-using EvoSC.Modules.Official.MatchManagerModule.Events;
-using EvoSC.Modules.Official.MatchManagerModule.Events.EventArgObjects;
-using EvoSC.Modules.Official.Scoreboard.Interfaces;
-using GbxRemoteNet.Events;
-
-namespace EvoSC.Modules.Official.Scoreboard.Controllers;
-
-[Controller]
-public class ScoreboardEventController(IScoreboardService scoreboardService) : EvoScController
-{
- [Subscribe(GbxRemoteEvent.BeginMap)]
- public async Task OnBeginMapAsync(object sender, MapGbxEventArgs args)
- {
- await scoreboardService.LoadAndSendRequiredAdditionalInfoAsync();
- await scoreboardService.ShowScoreboardToAllAsync();
- }
-
- [Subscribe(MatchSettingsEvent.MatchSettingsLoaded)]
- public async Task OnMatchSettingsLoadedAsync(object sender, MatchSettingsLoadedEventArgs args)
- {
- await scoreboardService.LoadAndSendRequiredAdditionalInfoAsync();
- await scoreboardService.ShowScoreboardToAllAsync();
- }
-
- [Subscribe(ModeScriptEvent.StartRoundStart)]
- public async Task OnRoundStartAsync(object sender, RoundEventArgs args)
- {
- scoreboardService.SetCurrentRound(args.Count);
- await scoreboardService.SendRequiredAdditionalInfoAsync();
- }
-}
diff --git a/src/Modules/Scoreboard/Controllers/ScoreboardManialinkController.cs b/src/Modules/Scoreboard/Controllers/ScoreboardManialinkController.cs
deleted file mode 100644
index 70be12211..000000000
--- a/src/Modules/Scoreboard/Controllers/ScoreboardManialinkController.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using EvoSC.Common.Controllers.Attributes;
-using EvoSC.Manialinks;
-using EvoSC.Modules.Official.Scoreboard.Interfaces;
-
-namespace EvoSC.Modules.Official.Scoreboard.Controllers;
-
-[Controller]
-public class ScoreboardManialinkController(IScoreboardService scoreboardService) : ManialinkController
-{
- public Task ResendScoreboardAsync() => scoreboardService.ShowScoreboardAsync(Context.Player);
-}
diff --git a/src/Modules/Scoreboard/Interfaces/IScoreboardService.cs b/src/Modules/Scoreboard/Interfaces/IScoreboardService.cs
deleted file mode 100644
index 2e6a4d11f..000000000
--- a/src/Modules/Scoreboard/Interfaces/IScoreboardService.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using EvoSC.Common.Interfaces.Models;
-
-namespace EvoSC.Modules.Official.Scoreboard.Interfaces;
-
-public interface IScoreboardService
-{
- ///
- /// Sends the scoreboard manialink to all players.
- ///
- public Task ShowScoreboardToAllAsync();
-
- ///
- /// Sends the scoreboard manialink to a specific players.
- ///
- public Task ShowScoreboardAsync(IPlayer playerLogin);
-
- ///
- /// Hide the default game scoreboard.
- ///
- public Task HideNadeoScoreboardAsync();
-
- ///
- /// Shows the default game scoreboard.
- ///
- public Task ShowNadeoScoreboardAsync();
-
- ///
- /// Sends the manialink with additional values used by the scoreboard.
- ///
- public Task SendRequiredAdditionalInfoAsync();
-
- ///
- /// Refreshes the additionally required data and sends the manialink.
- ///
- public Task LoadAndSendRequiredAdditionalInfoAsync();
-
- ///
- /// Sets the current round.
- ///
- public void SetCurrentRound(int round);
-}
diff --git a/src/Modules/Scoreboard/Interfaces/IScoreboardTrackerService.cs b/src/Modules/Scoreboard/Interfaces/IScoreboardTrackerService.cs
deleted file mode 100644
index c00c7d63a..000000000
--- a/src/Modules/Scoreboard/Interfaces/IScoreboardTrackerService.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace EvoSC.Modules.Official.Scoreboard.Interfaces;
-
-public interface IScoreboardTrackerService
-{
- public int RoundsPerMap { get; set; }
-
- public int PointsLimit { get; set; }
-
- public int CurrentRound { get; set; }
-
- public int MaxPlayers { get; set; }
-}
diff --git a/src/Modules/Scoreboard/ScoreboardModule.cs b/src/Modules/Scoreboard/ScoreboardModule.cs
deleted file mode 100644
index 95bf6804a..000000000
--- a/src/Modules/Scoreboard/ScoreboardModule.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using EvoSC.Modules.Attributes;
-using EvoSC.Modules.Interfaces;
-using EvoSC.Modules.Official.Scoreboard.Interfaces;
-
-namespace EvoSC.Modules.Official.Scoreboard;
-
-[Module(IsInternal = true)]
-public class ScoreboardModule(IScoreboardService scoreboardService) : EvoScModule, IToggleable
-{
- public Task EnableAsync()
- {
- scoreboardService.LoadAndSendRequiredAdditionalInfoAsync();
- scoreboardService.HideNadeoScoreboardAsync();
-
- return scoreboardService.ShowScoreboardToAllAsync();
- }
-
- public Task DisableAsync()
- {
- return scoreboardService.ShowNadeoScoreboardAsync();
- }
-}
diff --git a/src/Modules/Scoreboard/Services/ScoreboardTrackerService.cs b/src/Modules/Scoreboard/Services/ScoreboardTrackerService.cs
deleted file mode 100644
index bbf52a0e6..000000000
--- a/src/Modules/Scoreboard/Services/ScoreboardTrackerService.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using EvoSC.Common.Services.Attributes;
-using EvoSC.Common.Services.Models;
-using EvoSC.Modules.Official.Scoreboard.Interfaces;
-
-namespace EvoSC.Modules.Official.Scoreboard.Services;
-
-[Service(LifeStyle = ServiceLifeStyle.Singleton)]
-public class ScoreboardTrackerService : IScoreboardTrackerService
-{
- private int _roundsPerMap = -1;
- private readonly object _roundsPerMapLock = new();
-
- private int _pointsLimit = -1;
- private readonly object _pointsLimitLock = new();
-
- private int _currentRound = -1;
- private readonly object _currentRoundLock = new();
-
- private int _maxPlayers = -1;
- private readonly object _maxPlayersLock = new();
-
- public int RoundsPerMap
- {
- get
- {
- lock (_roundsPerMapLock)
- {
- return _roundsPerMap;
- }
- }
-
- set
- {
- lock (_roundsPerMapLock)
- {
- _roundsPerMap = value;
- }
- }
- }
-
- public int PointsLimit
- {
- get
- {
- lock (_pointsLimitLock)
- {
- return _pointsLimit;
- }
- }
-
- set
- {
- lock (_pointsLimitLock)
- {
- _pointsLimit = value;
- }
- }
- }
-
- public int CurrentRound
- {
- get
- {
- lock (_currentRoundLock)
- {
- return _currentRound;
- }
- }
-
- set
- {
- lock (_currentRoundLock)
- {
- _currentRound = value;
- }
- }
- }
-
- public int MaxPlayers
- {
- get
- {
- lock (_maxPlayersLock)
- {
- return _maxPlayers;
- }
- }
-
- set
- {
- lock (_maxPlayersLock)
- {
- _maxPlayers = value;
- }
- }
- }
-}
diff --git a/src/Modules/Scoreboard/Templates/Components/BackgroundBox.mt b/src/Modules/Scoreboard/Templates/Components/BackgroundBox.mt
deleted file mode 100644
index 55fbe511d..000000000
--- a/src/Modules/Scoreboard/Templates/Components/BackgroundBox.mt
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Modules/Scoreboard/Templates/Components/PlayerRow/CustomLabelBackground.mt b/src/Modules/Scoreboard/Templates/Components/PlayerRow/CustomLabelBackground.mt
deleted file mode 100644
index c21412bdf..000000000
--- a/src/Modules/Scoreboard/Templates/Components/PlayerRow/CustomLabelBackground.mt
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Modules/Scoreboard/Templates/Components/PlayerRow/Framemodel.mt b/src/Modules/Scoreboard/Templates/Components/PlayerRow/Framemodel.mt
deleted file mode 100644
index 37cdafd0d..000000000
--- a/src/Modules/Scoreboard/Templates/Components/PlayerRow/Framemodel.mt
+++ /dev/null
@@ -1,195 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Modules/Scoreboard/Templates/Components/PlayerRow/PlayerRowBackground.mt b/src/Modules/Scoreboard/Templates/Components/PlayerRow/PlayerRowBackground.mt
deleted file mode 100644
index ad4154156..000000000
--- a/src/Modules/Scoreboard/Templates/Components/PlayerRow/PlayerRowBackground.mt
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Modules/Scoreboard/Templates/Components/PlayerRow/PointsBox.mt b/src/Modules/Scoreboard/Templates/Components/PlayerRow/PointsBox.mt
deleted file mode 100644
index fcabef295..000000000
--- a/src/Modules/Scoreboard/Templates/Components/PlayerRow/PointsBox.mt
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Modules/Scoreboard/Templates/Components/PlayerRow/PositionBox.mt b/src/Modules/Scoreboard/Templates/Components/PlayerRow/PositionBox.mt
deleted file mode 100644
index 9af6a75eb..000000000
--- a/src/Modules/Scoreboard/Templates/Components/PlayerRow/PositionBox.mt
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Modules/Scoreboard/Templates/Components/ScoreboardHeader.mt b/src/Modules/Scoreboard/Templates/Components/ScoreboardHeader.mt
deleted file mode 100644
index 3ddbd19e8..000000000
--- a/src/Modules/Scoreboard/Templates/Components/ScoreboardHeader.mt
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Modules/Scoreboard/Templates/Components/Scrollbar.mt b/src/Modules/Scoreboard/Templates/Components/Scrollbar.mt
deleted file mode 100644
index 61d55dddb..000000000
--- a/src/Modules/Scoreboard/Templates/Components/Scrollbar.mt
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Modules/Scoreboard/Themes/DefaultScoreboardTheme.cs b/src/Modules/Scoreboard/Themes/DefaultScoreboardTheme.cs
deleted file mode 100644
index e9a88b652..000000000
--- a/src/Modules/Scoreboard/Themes/DefaultScoreboardTheme.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using EvoSC.Common.Interfaces.Themes;
-using EvoSC.Common.Themes;
-using EvoSC.Common.Themes.Attributes;
-using EvoSC.Common.Util;
-
-namespace EvoSC.Modules.Official.Scoreboard.Themes;
-
-[Theme(Name = "Scoreboard", Description = "Default theme for the scoreboard.")]
-public class DefaultScoreboardTheme(IThemeManager theme) : Theme
-{
- private readonly dynamic _theme = theme.Theme;
-
- public override Task ConfigureAsync()
- {
- Set("ScoreboardModule.BackgroundBox.BgHeader").To(_theme.UI_BgPrimary);
- Set("ScoreboardModule.BackgroundBox.BgHeaderGrad").To(ColorUtils.Darken(_theme.UI_BgPrimary));
- Set("ScoreboardModule.BackgroundBox.BgList").To(_theme.UI_BgHighlight);
-
- Set("ScoreboardModule.ScoreboardHeader.Text").To(_theme.UI_TextPrimary);
- Set("ScoreboardModule.ScoreboardHeader.Logo").To(_theme.UI_LogoLight);
-
- Set("ScoreboardModule.ClubTag.Bg").To(_theme.UI_BgHighlight);
-
- Set("ScoreboardModule.PlayerRow.Text").To(_theme.UI_TextPrimary);
- Set("ScoreboardModule.PlayerRow.CustomLabelBackground.Bg").To(_theme.Black);
-
- Set("ScoreboardModule.PlayerRow.PlayerActions.BgHighlight").To(_theme.UI_BgHighlight);
-
- Set("ScoreboardModule.PlayerRow.PlayerRowBackground.Bg").To(ColorUtils.Lighten(_theme.UI_BgHighlight));
- Set("ScoreboardModule.PlayerRow.PlayerRowBackground.BgHighlight").To(ColorUtils.SetLightness(_theme.UI_BgHighlight, 70));
-
- Set("ScoreboardModule.PlayerRow.PointsBox.Bg").To(ColorUtils.SetLightness(_theme.UI_BgHighlight, 70));
- Set("ScoreboardModule.PlayerRow.PointsBox.Text").To(ColorUtils.SetLightness(_theme.UI_BgHighlight, 20));
-
- Set("ScoreboardModule.PlayerRow.PositionBox.Bg").To(_theme.UI_BgHighlight);
-
- Set("ScoreboardModule.PlayerRow.FrameModel.Bg").To(_theme.UI_BgHighlight);
-
- Set("ScoreboardModule.PlayerRow.FrameModel.Text").To(_theme.UI_TextPrimary);
- Set("ScoreboardModule.PlayerRow.FrameModel.BgRow").To(_theme.UI_BgHighlight);
- Set("ScoreboardModule.PlayerRow.FrameModel.TextRoundPoints").To(_theme.UI_TextSecondary);
-
- Set("ScoreboardModule.Scoreboard.BgPosition").To(_theme.UI_BgHighlight);
-
- Set("ScoreboardModule.Settings.Text").To(_theme.UI_TextPrimary);
-
- return Task.CompletedTask;
- }
-}
diff --git a/src/Modules/ScoreboardModule/Config/IScoreboardSettings.cs b/src/Modules/ScoreboardModule/Config/IScoreboardSettings.cs
new file mode 100644
index 000000000..60b50627b
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Config/IScoreboardSettings.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using Config.Net;
+using EvoSC.Modules.Attributes;
+
+namespace EvoSC.Modules.Official.ScoreboardModule.Config;
+
+[Settings]
+public interface IScoreboardSettings
+{
+ [Option(DefaultValue = 160.0), Description("Sets the width of the scoreboard.")]
+ public double Width { get; }
+
+ [Option(DefaultValue = 80.0), Description("Sets the height of the scoreboard.")]
+ public double Height { get; }
+}
diff --git a/src/Modules/ScoreboardModule/Controllers/ScoreboardEventController.cs b/src/Modules/ScoreboardModule/Controllers/ScoreboardEventController.cs
new file mode 100644
index 000000000..9943a7af6
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Controllers/ScoreboardEventController.cs
@@ -0,0 +1,28 @@
+using EvoSC.Common.Controllers;
+using EvoSC.Common.Controllers.Attributes;
+using EvoSC.Common.Events.Attributes;
+using EvoSC.Common.Interfaces.Controllers;
+using EvoSC.Common.Remote;
+using EvoSC.Modules.Official.ScoreboardModule.Interfaces;
+using GbxRemoteNet.Events;
+
+namespace EvoSC.Modules.Official.ScoreboardModule.Controllers;
+
+[Controller]
+public class ScoreboardEventController(
+ IScoreboardService scoreboardService,
+ IScoreboardNicknamesService nicknamesService
+)
+ : EvoScController
+{
+ [Subscribe(GbxRemoteEvent.PlayerConnect)]
+ public Task OnPlayerConnectAsync(object sender, PlayerGbxEventArgs args) =>
+ nicknamesService.AddNicknameByLoginAsync(args.Login);
+
+ [Subscribe(GbxRemoteEvent.BeginMap)]
+ public async Task OnBeginMapAsync(object sender, MapGbxEventArgs args)
+ {
+ await nicknamesService.LoadNicknamesAsync();
+ await scoreboardService.SendScoreboardAsync();
+ }
+}
diff --git a/src/Modules/ScoreboardModule/Interfaces/IScoreboardNicknamesService.cs b/src/Modules/ScoreboardModule/Interfaces/IScoreboardNicknamesService.cs
new file mode 100644
index 000000000..a3023558b
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Interfaces/IScoreboardNicknamesService.cs
@@ -0,0 +1,50 @@
+namespace EvoSC.Modules.Official.ScoreboardModule.Interfaces;
+
+public interface IScoreboardNicknamesService
+{
+ ///
+ /// Gets the online player by login and then sets their custom nickname in the repo.
+ ///
+ ///
+ ///
+ public Task AddNicknameByLoginAsync(string login);
+
+ ///
+ /// Clears the nicknames repo.
+ ///
+ ///
+ public Task ClearNicknamesAsync();
+
+ ///
+ /// Gets all online players and sets their custom nicknames in the repo.
+ ///
+ ///
+ public Task LoadNicknamesAsync();
+
+ ///
+ /// Sends the manialink containing the nicknames in the repo.
+ ///
+ ///
+ public Task SendNicknamesManialinkAsync();
+
+ ///
+ /// Converts the nickname repo to a ManiaScript array.
+ ///
+ ///
+ ///
+ public string ToManiaScriptArray(Dictionary nicknameMap);
+
+ ///
+ /// Converts an entry of the nickname repo to a ManiaScript array entry.
+ ///
+ ///
+ ///
+ public string ToManiaScriptArrayEntry(KeyValuePair loginNickname);
+
+ ///
+ /// Escapes a nickname to be safely inserted into a XMl comment.
+ ///
+ ///
+ ///
+ public string EscapeNickname(string nickname);
+}
diff --git a/src/Modules/ScoreboardModule/Interfaces/IScoreboardService.cs b/src/Modules/ScoreboardModule/Interfaces/IScoreboardService.cs
new file mode 100644
index 000000000..9dc5c0624
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Interfaces/IScoreboardService.cs
@@ -0,0 +1,19 @@
+namespace EvoSC.Modules.Official.ScoreboardModule.Interfaces;
+
+public interface IScoreboardService
+{
+ ///
+ /// Sends the scoreboard manialink to all players.
+ ///
+ public Task SendScoreboardAsync();
+
+ ///
+ /// Hide the default game scoreboard.
+ ///
+ public Task HideNadeoScoreboardAsync();
+
+ ///
+ /// Shows the default game scoreboard.
+ ///
+ public Task ShowNadeoScoreboardAsync();
+}
diff --git a/src/Modules/Scoreboard/Localization.resx b/src/Modules/ScoreboardModule/Localization.resx
similarity index 100%
rename from src/Modules/Scoreboard/Localization.resx
rename to src/Modules/ScoreboardModule/Localization.resx
diff --git a/src/Modules/ScoreboardModule/ScoreboardModule.cs b/src/Modules/ScoreboardModule/ScoreboardModule.cs
new file mode 100644
index 000000000..6d8235ca3
--- /dev/null
+++ b/src/Modules/ScoreboardModule/ScoreboardModule.cs
@@ -0,0 +1,20 @@
+using EvoSC.Modules.Attributes;
+using EvoSC.Modules.Interfaces;
+using EvoSC.Modules.Official.ScoreboardModule.Interfaces;
+
+namespace EvoSC.Modules.Official.ScoreboardModule;
+
+[Module(IsInternal = true)]
+public class ScoreboardModule(IScoreboardService scoreboardService, IScoreboardNicknamesService nicknamesService)
+ : EvoScModule, IToggleable
+{
+ public async Task EnableAsync()
+ {
+ await nicknamesService.LoadNicknamesAsync();
+ await scoreboardService.HideNadeoScoreboardAsync();
+ await scoreboardService.SendScoreboardAsync();
+ }
+
+ public Task DisableAsync() =>
+ scoreboardService.ShowNadeoScoreboardAsync();
+}
diff --git a/src/Modules/Scoreboard/Scoreboard.csproj b/src/Modules/ScoreboardModule/ScoreboardModule.csproj
similarity index 93%
rename from src/Modules/Scoreboard/Scoreboard.csproj
rename to src/Modules/ScoreboardModule/ScoreboardModule.csproj
index b1ff40dea..456dbbf3f 100644
--- a/src/Modules/Scoreboard/Scoreboard.csproj
+++ b/src/Modules/ScoreboardModule/ScoreboardModule.csproj
@@ -4,7 +4,7 @@
net8.0
enable
enable
- EvoSC.Modules.Official.Scoreboard
+ EvoSC.Modules.Official.ScoreboardModule
diff --git a/src/Modules/ScoreboardModule/Services/ScoreboardNicknamesService.cs b/src/Modules/ScoreboardModule/Services/ScoreboardNicknamesService.cs
new file mode 100644
index 000000000..7619688c8
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Services/ScoreboardNicknamesService.cs
@@ -0,0 +1,68 @@
+using EvoSC.Common.Interfaces.Services;
+using EvoSC.Common.Services.Attributes;
+using EvoSC.Common.Services.Models;
+using EvoSC.Common.Util;
+using EvoSC.Manialinks.Interfaces;
+using EvoSC.Modules.Official.ScoreboardModule.Interfaces;
+
+namespace EvoSC.Modules.Official.ScoreboardModule.Services;
+
+[Service(LifeStyle = ServiceLifeStyle.Singleton)]
+public class ScoreboardNicknamesService(
+ IPlayerManagerService playerManagerService,
+ IManialinkManager manialinkManager
+) : IScoreboardNicknamesService
+{
+ private readonly Dictionary _nicknames = new();
+
+ public async Task AddNicknameByLoginAsync(string login)
+ {
+ var player = await playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(login));
+
+ if (player.NickName == player.UbisoftName)
+ {
+ return;
+ }
+
+ _nicknames[login] = player.NickName;
+ }
+
+ public Task ClearNicknamesAsync()
+ {
+ _nicknames.Clear();
+
+ return Task.CompletedTask;
+ }
+
+ public async Task LoadNicknamesAsync()
+ {
+ var onlinePlayers = await playerManagerService.GetOnlinePlayersAsync();
+ foreach (var player in onlinePlayers.Where(player => player.NickName != player.UbisoftName))
+ {
+ _nicknames[player.GetLogin()] = player.NickName;
+ }
+ }
+
+ public Task SendNicknamesManialinkAsync() =>
+ manialinkManager.SendPersistentManialinkAsync("ScoreboardModule.PlayerNicknames",
+ new { nicknames = ToManiaScriptArray(_nicknames) });
+
+ public string ToManiaScriptArray(Dictionary nicknameMap)
+ {
+ var entriesList = nicknameMap.Select(ToManiaScriptArrayEntry).ToList();
+ var joinedEntries = string.Join(",\n", entriesList);
+
+ return $"[{joinedEntries}]";
+ }
+
+ public string ToManiaScriptArrayEntry(KeyValuePair loginNickname)
+ {
+ return $"\"{loginNickname.Key}\" => \"{EscapeNickname(loginNickname.Value)}\"";
+ }
+
+ public string EscapeNickname(string nickname)
+ {
+ return nickname.Replace("-->", "-\u2192", StringComparison.OrdinalIgnoreCase)
+ .Replace("\"", "\\\"", StringComparison.OrdinalIgnoreCase);
+ }
+}
diff --git a/src/Modules/ScoreboardModule/Services/ScoreboardService.cs b/src/Modules/ScoreboardModule/Services/ScoreboardService.cs
new file mode 100644
index 000000000..941f496fc
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Services/ScoreboardService.cs
@@ -0,0 +1,58 @@
+using EvoSC.Common.Interfaces;
+using EvoSC.Common.Services.Attributes;
+using EvoSC.Common.Services.Models;
+using EvoSC.Manialinks.Interfaces;
+using EvoSC.Modules.Official.GameModeUiModule.Enums;
+using EvoSC.Modules.Official.GameModeUiModule.Interfaces;
+using EvoSC.Modules.Official.ScoreboardModule.Config;
+using EvoSC.Modules.Official.ScoreboardModule.Interfaces;
+
+namespace EvoSC.Modules.Official.ScoreboardModule.Services;
+
+[Service(LifeStyle = ServiceLifeStyle.Transient)]
+public class ScoreboardService(
+ IManialinkManager manialinks,
+ IServerClient server,
+ IScoreboardNicknamesService nicknamesService,
+ IScoreboardSettings settings,
+ IGameModeUiModuleService gameModeUiModuleService
+)
+ : IScoreboardService
+{
+ private const string ScoreboardTemplate = "ScoreboardModule.Scoreboard";
+
+ public async Task SendScoreboardAsync()
+ {
+ await manialinks.SendPersistentManialinkAsync(ScoreboardTemplate, await GetDataAsync());
+ await nicknamesService.SendNicknamesManialinkAsync();
+ }
+
+ private async Task GetDataAsync()
+ {
+ var currentNextMaxPlayers = await server.Remote.GetMaxPlayersAsync();
+ var currentNextMaxSpectators = await server.Remote.GetMaxSpectatorsAsync();
+
+ return new
+ {
+ settings, MaxPlayers = currentNextMaxPlayers.CurrentValue + currentNextMaxSpectators.CurrentValue
+ };
+ }
+
+ public Task HideNadeoScoreboardAsync() =>
+ gameModeUiModuleService.ApplyComponentSettingsAsync(
+ GameModeUiComponents.ScoresTable,
+ false,
+ 0.0,
+ 0.0,
+ 1.0
+ );
+
+ public Task ShowNadeoScoreboardAsync() =>
+ gameModeUiModuleService.ApplyComponentSettingsAsync(
+ GameModeUiComponents.ScoresTable,
+ true,
+ 0.0,
+ 0.0,
+ 1.0
+ );
+}
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Body/Legend.mt b/src/Modules/ScoreboardModule/Templates/Components/Body/Legend.mt
new file mode 100644
index 000000000..b0ad6e0a8
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Body/Legend.mt
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Header/HeaderBackground.mt b/src/Modules/ScoreboardModule/Templates/Components/Header/HeaderBackground.mt
new file mode 100644
index 000000000..78adc18e1
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Header/HeaderBackground.mt
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Header/HeaderContent.mt b/src/Modules/ScoreboardModule/Templates/Components/Header/HeaderContent.mt
new file mode 100644
index 000000000..728922584
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Header/HeaderContent.mt
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Header/Logo.mt b/src/Modules/ScoreboardModule/Templates/Components/Header/Logo.mt
new file mode 100644
index 000000000..c1a8ad19a
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Header/Logo.mt
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Row/CustomLabelBackground.mt b/src/Modules/ScoreboardModule/Templates/Components/Row/CustomLabelBackground.mt
new file mode 100644
index 000000000..74cf71d74
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Row/CustomLabelBackground.mt
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Row/Flag.mt b/src/Modules/ScoreboardModule/Templates/Components/Row/Flag.mt
new file mode 100644
index 000000000..a53876481
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Row/Flag.mt
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Row/Framemodel.mt b/src/Modules/ScoreboardModule/Templates/Components/Row/Framemodel.mt
new file mode 100644
index 000000000..e38ae2fbe
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Row/Framemodel.mt
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Modules/Scoreboard/Templates/Components/PlayerRow/PlayerActions.mt b/src/Modules/ScoreboardModule/Templates/Components/Row/PlayerActions.mt
similarity index 59%
rename from src/Modules/Scoreboard/Templates/Components/PlayerRow/PlayerActions.mt
rename to src/Modules/ScoreboardModule/Templates/Components/Row/PlayerActions.mt
index 58ad3f4de..45d13b8a1 100644
--- a/src/Modules/Scoreboard/Templates/Components/PlayerRow/PlayerActions.mt
+++ b/src/Modules/ScoreboardModule/Templates/Components/Row/PlayerActions.mt
@@ -2,36 +2,36 @@
-
-
+
-
+
+ focusareacolor1="{{ Theme.UI_HeaderBg }}"
+ focusareacolor2="{{ Theme.ScoreboardModule_Background_Hover_Color }}"
+ textcolor="{{ Theme.ScoreboardModule_Text_Color }}"/>
+ focusareacolor1="{{ Theme.UI_HeaderBg }}"
+ focusareacolor2="{{ Theme.ScoreboardModule_Background_Hover_Color }}"
+ textcolor="{{ Theme.ScoreboardModule_Text_Color }}"/>
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Row/PlayerRowBackground.mt b/src/Modules/ScoreboardModule/Templates/Components/Row/PlayerRowBackground.mt
new file mode 100644
index 000000000..8499ed831
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Row/PlayerRowBackground.mt
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Modules/ScoreboardModule/Templates/Components/Row/PositionBox.mt b/src/Modules/ScoreboardModule/Templates/Components/Row/PositionBox.mt
new file mode 100644
index 000000000..9286387b3
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/Row/PositionBox.mt
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Modules/ScoreboardModule/Templates/Components/ScoreboardBg.mt b/src/Modules/ScoreboardModule/Templates/Components/ScoreboardBg.mt
new file mode 100644
index 000000000..edf86e156
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/ScoreboardBg.mt
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/src/Modules/ScoreboardModule/Templates/Components/ScoreboardBody.mt b/src/Modules/ScoreboardModule/Templates/Components/ScoreboardBody.mt
new file mode 100644
index 000000000..403d4b037
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/ScoreboardBody.mt
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Modules/ScoreboardModule/Templates/Components/ScoreboardHeader.mt b/src/Modules/ScoreboardModule/Templates/Components/ScoreboardHeader.mt
new file mode 100644
index 000000000..1f726082d
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/Components/ScoreboardHeader.mt
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Modules/Scoreboard/Templates/Components/Settings/Form.mt b/src/Modules/ScoreboardModule/Templates/Components/Settings/Form.mt
similarity index 100%
rename from src/Modules/Scoreboard/Templates/Components/Settings/Form.mt
rename to src/Modules/ScoreboardModule/Templates/Components/Settings/Form.mt
diff --git a/src/Modules/Scoreboard/Templates/Components/Settings/Wrapper.mt b/src/Modules/ScoreboardModule/Templates/Components/Settings/Wrapper.mt
similarity index 100%
rename from src/Modules/Scoreboard/Templates/Components/Settings/Wrapper.mt
rename to src/Modules/ScoreboardModule/Templates/Components/Settings/Wrapper.mt
diff --git a/src/Modules/ScoreboardModule/Templates/PlayerNicknames.mt b/src/Modules/ScoreboardModule/Templates/PlayerNicknames.mt
new file mode 100644
index 000000000..5a252187d
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Templates/PlayerNicknames.mt
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/src/Modules/Scoreboard/Templates/RoundsInfo.mt b/src/Modules/ScoreboardModule/Templates/RoundsInfo.mt
similarity index 100%
rename from src/Modules/Scoreboard/Templates/RoundsInfo.mt
rename to src/Modules/ScoreboardModule/Templates/RoundsInfo.mt
diff --git a/src/Modules/Scoreboard/Templates/Scoreboard.mt b/src/Modules/ScoreboardModule/Templates/Scoreboard.mt
similarity index 55%
rename from src/Modules/Scoreboard/Templates/Scoreboard.mt
rename to src/Modules/ScoreboardModule/Templates/Scoreboard.mt
index ed59bcc16..5a21a0915 100644
--- a/src/Modules/Scoreboard/Templates/Scoreboard.mt
+++ b/src/Modules/ScoreboardModule/Templates/Scoreboard.mt
@@ -1,84 +1,80 @@
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
-
+
+
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
@@ -115,7 +111,6 @@
declare Integer ScrollIndex;
declare Integer MaxPlayers;
declare CMlFrame RowsFrame;
- declare Boolean SettingsVisible;
declare Text[Integer] PositionColors;
Boolean ShouldShowPointsBox() {
@@ -125,50 +120,50 @@
}
Void UpdateScoreboardLayout() {
- declare persistent Boolean SB_Setting_ShowClubTags for LocalUser = True;
- declare persistent Boolean SB_Setting_ShowFlags for LocalUser = True;
- declare shouldShowPoints = ShouldShowPointsBox();
- declare Real flagWidth = {{ rowInnerHeight }} * 2.0;
- declare Real innerSpacing = {{ innerSpacing }} * 1.0;
+ UpdateLegend(ShouldShowPointsBox());
+ }
+
+ Text StripLeadingZeroes(Text timeString) {
+ return TL::RegexReplace("^[0.:]+", timeString, "", "");
+ }
+
+ Text StyleTime(Text timeString) {
+ declare mutedTextColor = "{{ Color.ToTextColor(Theme.UI_TextMuted) }}";
+ declare primaryTextColor = "{{ Color.ToTextColor(Theme.ScoreboardModule_Text_Color) }}";
+
+ if(timeString == "0:00.000") {
+ return mutedTextColor ^ "0" ^ timeString;
+ }
+
+ declare endPart = StripLeadingZeroes(timeString);
+ declare startPart = TL::Replace(timeString, endPart, "");
- foreach(Control in RowsFrame.Controls){
- declare Real offset = 0.0;
- declare playerRow = (Control as CMlFrame);
- declare pointsBoxFrame = (playerRow.GetFirstChild("points_box") as CMlFrame);
- declare flagQuad = (playerRow.GetFirstChild("flag") as CMlQuad);
- declare clubQuad = (playerRow.GetFirstChild("club_bg") as CMlQuad);
- declare clubLabel = (playerRow.GetFirstChild("club") as CMlLabel);
- declare nameLabel = (playerRow.GetFirstChild("name") as CMlLabel);
- declare pointsLabel = (playerRow.GetFirstChild("points") as CMlLabel);
-
- pointsBoxFrame.Visible = shouldShowPoints;
- pointsLabel.Visible = shouldShowPoints;
-
- if(SB_Setting_ShowFlags){
- flagQuad.RelativePosition_V3.X = offset;
- flagQuad.Show();
- offset += flagWidth;
- }else{
- flagQuad.Hide();
- }
-
- if(SB_Setting_ShowClubTags){
- clubQuad.RelativePosition_V3.X = offset;
- clubLabel.RelativePosition_V3.X = offset + (flagWidth / 2.0);
- clubQuad.Show();
- clubLabel.Show();
- offset += flagWidth;
- }else{
- clubQuad.Hide();
- clubLabel.Hide();
- }
-
- nameLabel.RelativePosition_V3.X = offset + innerSpacing;
+ return mutedTextColor ^ startPart ^ primaryTextColor ^ endPart;
+ }
+
+ Text StylePoints(Text points, Integer padding) {
+ declare mutedTextColor = "{{ Color.ToTextColor(Theme.UI_TextMuted) }}";
+ declare primaryTextColor = "{{ Color.ToTextColor(Theme.ScoreboardModule_Text_Color) }}";
+ declare out = mutedTextColor;
+
+ for(i, 1, padding - TL::Length(points)){
+ out ^= "0";
}
+
+ if(points == "0"){
+ return out ^ points;
+ }
+
+ return out ^ primaryTextColor ^ points;
}
- Text StripLeadingZeroes(Text input) {
- return TL::RegexReplace("^[0.:]+", input, "", "");
+ Text GetPlayerBestTimeStyled(CSmScore score) {
+ if(score.BestRaceTimes.count == 0){
+ return StyleTime("0:00.000");
+ }
+
+ declare bestTime = score.BestRaceTimes[score.BestRaceTimes.count - 1];
+ return StyleTime(TL::TimeToText(bestTime, True, True));
}
Integer[CSmScore] GetSortedScores() {
@@ -199,39 +194,11 @@
Void SetCountryFlag(CMlQuad flagQuad, Text login){
if(login != "" && !TL::StartsWith("*fakeplayer", login)){
flagQuad.ImageUrl = "file://ZoneFlags/Login/" ^ login ^ "/country";
- flagQuad.ModulateColor = <1.0, 1.0, 1.0>;
flagQuad.Opacity = 1.0;
}else{
- flagQuad.ImageUrl = "file://Media/Manialinks/Nadeo/TMNext/Menus/Common/Common_Flag_Mask.dds";
- flagQuad.ModulateColor = <0.0, 0.0, 0.0>;
- flagQuad.Opacity = 0.25;
- }
- }
-
- Void SetCustomLabel(CMlFrame playerRow, Text value, Text hexColor){
- declare customLabel = (playerRow.GetFirstChild("custom_label") as CMlLabel);
- declare customGradientFrame = (playerRow.GetFirstChild("custom_gradient") as CMlFrame);
-
- customLabel.Value = value;
- customLabel.TextColor = CL::HexToRgb(hexColor);
-
- Page.GetClassChildren("modulate", customGradientFrame, True);
- foreach(Control in Page.GetClassChildren_Result){
- (Control as CMlQuad).ModulateColor = customLabel.TextColor;
- }
- Page.GetClassChildren("set", customGradientFrame, True);
- foreach(Control in Page.GetClassChildren_Result){
- (Control as CMlQuad).BgColor = customLabel.TextColor;
+ flagQuad.ImageUrl = "file://ZoneFlags/World";
+ flagQuad.Opacity = 1.0;
}
-
- customGradientFrame.Show();
- }
-
- Void HideCustomLabel(CMlFrame playerRow){
- declare customLabel = (playerRow.GetFirstChild("custom_label") as CMlLabel);
- declare customGradientFrame = (playerRow.GetFirstChild("custom_gradient") as CMlFrame);
- customLabel.Value = "";
- customGradientFrame.Hide();
}
Void UpdateScoreAndPoints(CSmScore Score, CMlFrame playerRow, Integer position){
@@ -250,11 +217,14 @@
playerScore <=> Score;
declare scoreLabel = (playerRow.GetFirstChild("score") as CMlLabel);
+ declare bestTimeLabel = (playerRow.GetFirstChild("best_time") as CMlLabel);
declare specDisconnectedLabel = (playerRow.GetFirstChild("spec_disconnected_label") as CMlLabel);
- declare pointsLabel = (playerRow.GetFirstChild("points") as CMlLabel);
declare roundPointsLabel = (playerRow.GetFirstChild("round_points") as CMlLabel);
declare customLabel = (playerRow.GetFirstChild("custom_label") as CMlLabel);
+ scoreLabel.Value = "";
+ roundPointsLabel.Value = "";
+
if (!(CustomPointsEnabled && CurrentScoreMode != C_Mode_Trophy)) {
HideCustomLabel(playerRow);
}
@@ -279,17 +249,17 @@
}
} else if (CurrentScoreMode == C_Mode_Points) {
customLabel.Value = "";
- pointsLabel.Value = TL::ToText(Score.Points);
+ scoreLabel.Value = TL::ToText(Score.Points);
colorizePosition = Score.Points > 0;
- if(Score.PrevRaceTimes.count > 0 && Score.PrevRaceTimes[Score.PrevRaceTimes.count - 1] > 0){
- scoreLabel.Value = TL::TimeToText(Score.PrevRaceTimes[Score.PrevRaceTimes.count - 1], True, True);
+ if(Score.BestRaceTimes.count > 0 && Score.BestRaceTimes[Score.BestRaceTimes.count - 1] > 0){
+ bestTimeLabel.Value = TL::TimeToText(Score.BestRaceTimes[Score.BestRaceTimes.count - 1], True, True);
}else{
declare CSmPlayer::ESpawnStatus Race_ScoresTable_SpawnStatus for Score = CSmPlayer::ESpawnStatus::NotSpawned;
if(Race_ScoresTable_SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned && PlayerIsConnected && !Race_ScoresTable_IsSpectator){
- scoreLabel.Value = "DNF";
+ roundPointsLabel.Value = "DNF";
}else{
- scoreLabel.Value = "";
+ roundPointsLabel.Value = "";
}
}
@@ -326,11 +296,11 @@
} else if (CurrentScoreMode == C_Mode_Laps && Score.BestRaceTimes.count > 0) {
customLabel.Value = "";
scoreLabel.Value = TL::TimeToText(Score.BestRaceTimes[Score.BestRaceTimes.count - 1], True, True);
- pointsLabel.Value = ""^Score.BestRaceTimes.count;
+ scoreLabel.Value = ""^Score.BestRaceTimes.count;
} else if (CurrentScoreMode == C_Mode_RaceProgression) {
customLabel.Value = "";
declare netread Int2 Net_TMxSM_ScoresTable_RaceProgression for Score;
- pointsLabel.Value = ""^Net_TMxSM_ScoresTable_RaceProgression.X;
+ scoreLabel.Value = ""^Net_TMxSM_ScoresTable_RaceProgression.X;
if (Net_TMxSM_ScoresTable_RaceProgression.Y > 0) {
scoreLabel.Value = TL::TimeToText(Net_TMxSM_ScoresTable_RaceProgression.Y, True, True);
colorizePosition = True;
@@ -345,29 +315,35 @@
scoreLabel.Value = "0:00.000";
}
- scoreLabel.Value = StripLeadingZeroes(scoreLabel.Value);
+ if(ShouldShowPointsBox()){
+ scoreLabel.Value = StylePoints(scoreLabel.Value, 3);
+ bestTimeLabel.Value = GetPlayerBestTimeStyled(Score);
+ }else{
+ scoreLabel.Value = StyleTime(scoreLabel.Value);
+ bestTimeLabel.Value = "";
+ }
declare positionBox = (playerRow.GetFirstChild("position_box") as CMlFrame);
declare playerRowBg = (playerRow.GetFirstChild("player_row_bg") as CMlFrame);
- if(PositionColors.existskey(position) && colorizePosition){
- declare positionColor = PositionColors[position];
- SetPositionBackgroundColor(positionBox, CL::HexToRgb(positionColor));
- SetPlayerHighlightColor(playerRowBg, CL::HexToRgb(positionColor));
- }else{
- SetPositionBackgroundColor(positionBox, CL::HexToRgb("{{ Theme.ScoreboardModule_Scoreboard_BgPosition }}"));
- SetPlayerHighlightColor(playerRowBg, CL::HexToRgb("{{ Theme.ScoreboardModule_Scoreboard_BgPosition }}"));
+ if({{ Theme.ScoreboardModule_PositionBox_ShowAccent }}){
+ if(PositionColors.existskey(position) && colorizePosition){
+ declare positionColor = PositionColors[position];
+ SetPositionBoxColor(positionBox, CL::HexToRgb(positionColor));
+ }else{
+ SetPositionBoxColor(positionBox, CL::HexToRgb("{{ Theme.UI_AccentPrimary }}"));
+ }
}
if (PlayerIsConnected) {
//connected
if(Race_ScoresTable_IsSpectator){
- specDisconnectedLabel.Value = "";
+ specDisconnectedLabel.Value = "{{ Icons.VideoCamera }}";
}else{
specDisconnectedLabel.Value = "";
}
}else{
//disconnected
- specDisconnectedLabel.Value = "";
+ specDisconnectedLabel.Value = "{{ Icons.UserTimes }}";
}
//align items
@@ -375,27 +351,19 @@
declare x = scoreLabel.RelativePosition_V3.X;
if(scoreLabel.Value != ""){
- offset += scoreLabel.ComputeWidth(scoreLabel.Value) + {{ innerSpacing }};
+ offset += scoreLabel.ComputeWidth(scoreLabel.Value) + {{ columnSpacing / 2.0 }};
}
customLabel.RelativePosition_V3.X = x - offset;
if(customLabel.Value != ""){
- offset += customLabel.ComputeWidth(customLabel.Value) + {{ innerSpacing }};
+ offset += customLabel.ComputeWidth(customLabel.Value) + {{ columnSpacing / 2.0 }};
}
roundPointsLabel.RelativePosition_V3.X = x - offset;
if(roundPointsLabel.Value != ""){
- offset += roundPointsLabel.ComputeWidth(roundPointsLabel.Value) + {{ innerSpacing }};
+ offset += roundPointsLabel.ComputeWidth(roundPointsLabel.Value) + {{ columnSpacing / 2.0 }};
}
specDisconnectedLabel.RelativePosition_V3.X = x - offset;
}
- Void SetMapAndAuthorName() {
- declare mapNameLabel <=> (Page.MainFrame.GetFirstChild("map_name") as CMlLabel);
- declare authorName <=> (Page.MainFrame.GetFirstChild("author_name") as CMlLabel);
-
- mapNameLabel.Value = Map.MapName;
- authorName.Value = Map.AuthorNickName;
- }
-
Text GetRecordText() {
declare Integer SB_PointsLimit for UI = -2;
@@ -410,47 +378,26 @@
return "AUTHOR TIME | " ^ TL::TimeToText(Map.TMObjective_AuthorTime, True, True);
}
- Void UpdateHeaderInfo() {
- declare subTextLabel <=> (Page.MainFrame.GetFirstChild("sub_text") as CMlLabel);
- declare roundLabel <=> (Page.MainFrame.GetFirstChild("round_label") as CMlLabel);
-
- subTextLabel.Value = GetRecordText();
-
- declare Owner <=> MV_Utils::GetOwner(This);
+ Void UpdateScrollSize(Integer playerRowsFilled) {
+ declare filledHeight = playerRowsFilled * {{ rowHeight + rowSpacing }};
+ declare contentHeight = {{ settings.Height - headerHeight - legendHeight }};
- if (CurrentScoreMode == C_Mode_BestTime || CurrentScoreMode == C_Mode_PrevTime){
- declare timeLimit = RaceHelpers::GetTimeLimit(Teams[0]);
- roundLabel.Value = "TIME LIMIT | ";
- if(timeLimit <= 0){
- roundLabel.Value ^= "UNLIMITED";
- }else{
- roundLabel.Value ^= TL::TimeToText(timeLimit);
- }
- }else if (CurrentScoreMode == C_Mode_LapTime || CurrentScoreMode == C_Mode_Laps){
- declare Integer LapCurrent = -1;
- if(Owner != Null){
- declare Integer LapCurrent = RaceHelpers::GetPlayerLap(Owner);
- }
- declare LapsTotal = RaceHelpers::GetLapsNb(Teams[0]);
- roundLabel.Value = TL::Compose("%1 | %2 OF %3", _("|Race|Lap"), TL::ToText(LapCurrent), TL::ToText(LapsTotal));
- }else if (CurrentScoreMode == C_Mode_Points) {
- declare Integer SB_CurrentRound for UI = 0;
- declare Integer SB_RoundsPerMap for UI = 0;
- roundLabel.Value = TL::Compose("ROUND | %1 OF %2", TL::ToText(SB_CurrentRound), TL::ToText(SB_RoundsPerMap));
+ if(filledHeight > contentHeight) {
+ RowsFrame.ScrollMax.Y = (filledHeight - contentHeight) * 1.0;
}else{
- roundLabel.Value = "";
+ RowsFrame.ScrollMax.Y = 0.0;
}
-
- SetMapAndAuthorName();
+
+ PlayerRowsFilled = playerRowsFilled;
}
- Void UpdateScrollSize(Integer playerRowsFilled) {
- declare scrollN = 0;
- if(playerRowsFilled >= 8){
- scrollN = playerRowsFilled - 8;
+ Text GetNickname(CUser user) {
+ declare Text[Text] EvoSC_Player_Nicknames for UI = [];
+ if(EvoSC_Player_Nicknames.existskey(user.Login)){
+ return EvoSC_Player_Nicknames[user.Login];
}
- RowsFrame.ScrollMax = <0.0, {{ rowHeight + rowSpacing }} * scrollN * 1.0>;
- PlayerRowsFilled = playerRowsFilled;
+
+ return user.Name;
}
Void UpdateScoreTable() {
@@ -466,16 +413,13 @@
declare CSmPlayer::ESpawnStatus Race_ScoresTable_SpawnStatus for Player.Score = CSmPlayer::ESpawnStatus::NotSpawned;
Race_ScoresTable_SpawnStatus = Player.SpawnStatus;
}
-
+
declare cursor = 0;
- //declare startFill = ML::Max(ScrollIndex - PlayerRowsVisible, 0);
- //declare endFill = ML::Min(ScrollIndex + PlayerRowsVisible * 2, MaxPlayers - 1);
foreach(Score => Weight in GetSortedScores()){
- //if(cursor < startFill || cursor > endFill){
- // cursor += 1;
- // continue;
- //}
+ if(!RowsFrame.Controls.existskey(cursor)){
+ continue;
+ }
declare persistent Boolean SB_Setting_ShowSpectators for LocalUser = True;
declare persistent Boolean SB_Setting_ShowDisconnected for LocalUser = True;
@@ -493,24 +437,18 @@
continue;
}
}
-
+
declare playerRow = (RowsFrame.Controls[cursor] as CMlFrame);
- declare positionLabel = (playerRow.GetFirstChild("position") as CMlLabel);
- declare clubBg = (playerRow.GetFirstChild("club_bg") as CMlQuad);
declare clubLabel = (playerRow.GetFirstChild("club") as CMlLabel);
declare nameLabel = (playerRow.GetFirstChild("name") as CMlLabel);
declare flagQuad = (playerRow.GetFirstChild("flag") as CMlQuad);
- declare scoreLabel = (playerRow.GetFirstChild("score") as CMlLabel);
- declare pointsBoxFrame = (playerRow.GetFirstChild("points_box") as CMlFrame);
+ declare positionBoxFrame = (playerRow.GetFirstChild("position_box") as CMlFrame);
- positionLabel.Value = (cursor + 1) ^ "";
+ SetPlayerRank(positionBoxFrame, cursor + 1);
+ nameLabel.Value = GetNickname(Score.User);
clubLabel.Value = Score.User.ClubTag;
- nameLabel.Value = Score.User.Name;
-
- if(clubLabel.Value != ""){
- clubBg.Opacity = 0.95;
- }else{
- clubBg.Opacity = 0.25;
+ if(clubLabel.Value == ""){
+ clubLabel.Value = "-";
}
declare Boolean CustomLabelVisible for playerRow = False;
@@ -521,14 +459,6 @@
SetCountryFlag(flagQuad, Score.User.Login);
}
- if(ShouldShowPointsBox()){
- scoreLabel.RelativePosition_V3.X = pointsBoxFrame.RelativePosition_V3.X - {{ innerSpacing }};
- pointsBoxFrame.Show();
- }else{
- scoreLabel.RelativePosition_V3.X = {{ w - padding - innerSpacing }};
- pointsBoxFrame.Hide();
- }
-
playerRow.Show();
cursor += 1;
@@ -536,26 +466,15 @@
//Hide remaining rows
for(i, cursor, {{ MaxPlayers - 1 }}){
+ if(!RowsFrame.Controls.existskey(i)){
+ continue;
+ }
+
declare playerRow = (RowsFrame.Controls[i] as CMlFrame);
playerRow.Hide();
}
-
- UpdateHeaderInfo();
UpdateScrollSize(cursor);
}
-
- Void ToggleShowSettings() {
- declare wrapperInnerFrame <=> (Page.MainFrame.GetFirstChild("rows_inner") as CMlFrame);
- declare y = 0.0;
- SettingsVisible = !SettingsVisible;
-
- if(SettingsVisible) {
- y = {{ h + padding }} * -1.0;
- }
-
- declare targetState = "";
- AnimMgr.Add(wrapperInnerFrame, targetState, 320, CAnimManager::EAnimManagerEasing::ExpInOut);
- }
-->
@@ -572,16 +491,18 @@
RowsFrame.DisablePreload = True;
RowsFrame.ScrollGridSnap = True;
RowsFrame.ScrollMin = <0.0, 0.0>;
- RowsFrame.ScrollMax = <0.0, {{ MaxPlayers * (rowHeight + rowSpacing) - h }} * 1.0>;
RowsFrame.ScrollGrid = <0.0, {{ rowHeight + rowSpacing }} * 1.0>;
MaxPlayers = {{ MaxPlayers }};
- PlayerRowsVisible = {{ VisiblePlayers }};
+ PlayerRowsVisible = 0;
PlayerRowsFilled = -1;
CurrentScoreMode = -1;
- SettingsVisible = False;
- {! string.Join("\n", PositionColors.Select(pc => $"PositionColors[{pc.Key}] = \"{pc.Value}\";")) !}
+ PositionColors = [
+ 1 => "{{ Theme.Gold }}",
+ 2 => "{{ Theme.Silver }}",
+ 3 => "{{ Theme.Bronze }}"
+ ];
***
*** OnLoop ***
@@ -599,13 +520,6 @@
}
***
- *** OnMouseClick ***
- ***
- if (Event.Control.ControlId == "settings_icon") {
- ToggleShowSettings();
- }
- ***
-
*** OnScriptExecutionFinished ***
***
sleep(5000);
diff --git a/src/Modules/ScoreboardModule/Themes/DefaultScoreboardTheme.cs b/src/Modules/ScoreboardModule/Themes/DefaultScoreboardTheme.cs
new file mode 100644
index 000000000..c98d37b49
--- /dev/null
+++ b/src/Modules/ScoreboardModule/Themes/DefaultScoreboardTheme.cs
@@ -0,0 +1,52 @@
+using EvoSC.Common.Interfaces.Themes;
+using EvoSC.Common.Themes;
+using EvoSC.Common.Themes.Attributes;
+
+namespace EvoSC.Modules.Official.ScoreboardModule.Themes;
+
+[Theme(Name = "Scoreboard", Description = "Default theme for the scoreboard.")]
+public class DefaultScoreboardTheme(IThemeManager theme) : Theme
+{
+ private readonly dynamic _theme = theme.Theme;
+
+ public override Task ConfigureAsync()
+ {
+ Set("ScoreboardModule.Text_Color").To(_theme.UI_TextPrimary);
+
+ Set("ScoreboardModule.Logo_URL").To("file://Media/Manialinks/Nadeo/Trackmania/Menus/TMLogo.dds");
+ Set("ScoreboardModule.Logo_Width").To("20.0");
+ Set("ScoreboardModule.Logo_Height").To("10.0");
+
+ Set("ScoreboardModule.Background_Opacity").To("0.0");
+ Set("ScoreboardModule.Background_Image").To("");
+
+ Set("ScoreboardModule.Background_Header_Color").To(_theme.UI_HeaderBg);
+ Set("ScoreboardModule.Background_Header_Opacity").To("0.95");
+
+ Set("ScoreboardModule.Background_Legend_Color").To(_theme.UI_HeaderBg);
+ Set("ScoreboardModule.Background_Legend_Opacity").To("1.0");
+ Set("ScoreboardModule.Background_Legend_Text_Color").To(_theme.UI_TextPrimary);
+ Set("ScoreboardModule.Background_Legend_Text_Opacity").To("0.75");
+
+ Set("ScoreboardModule.Background_Row_Color").To(_theme.UI_BgPrimary);
+ Set("ScoreboardModule.Background_Row_Opacity").To("0.9");
+
+ Set("ScoreboardModule.Background_Hover_Color").To(_theme.UI_BgHighlight);
+ Set("ScoreboardModule.Background_Hover_Opacity").To("0.9");
+
+ Set("ScoreboardModule.PositionBox_ShowAccent").To("True");
+ Set("ScoreboardModule.PositionBox_Color").To(_theme.UI_AccentSecondary);
+ Set("ScoreboardModule.PositionBox_Opacity").To("1.0");
+ Set("ScoreboardModule.PositionBox_TextColor").To(_theme.UI_TextSecondary);
+ Set("ScoreboardModule.PositionBox_TextOpacity").To("1.0");
+
+ Set("ScoreboardModule.GainedPoints.Color").To(_theme.UI_AccentPrimary);
+
+ Set("ScoreboardModule.Background_Row_Flag_AlphaMaskUrl").To("file://Media/Manialinks/Nadeo/Trackmania/Menus/Common/Common_Flag_Mask.dds");
+
+ Set("ScoreboardModule.FinalistColor").To("");
+ Set("ScoreboardModule.WinnerColor").To("");
+
+ return Task.CompletedTask;
+ }
+}
diff --git a/src/Modules/ScoreboardModule/info.toml b/src/Modules/ScoreboardModule/info.toml
new file mode 100644
index 000000000..f7ad59d0d
--- /dev/null
+++ b/src/Modules/ScoreboardModule/info.toml
@@ -0,0 +1,9 @@
+[info]
+name = "ScoreboardModule"
+title = "Scoreboard Module"
+summary = "Custom EvoSC Scoreboards."
+version = "1.0.0"
+author = "Evo"
+
+[dependencies]
+GameModeUiModule = "1.0.0"