diff --git a/EvoSC.sln b/EvoSC.sln
index 8820dabb7..2ec61fa13 100644
--- a/EvoSC.sln
+++ b/EvoSC.sln
@@ -142,7 +142,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamChatModule", "src/Modul
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamChatModule.Tests", "tests\Modules\TeamChatModule.Tests\TeamChatModule.Tests.csproj", "{7BD60D6E-7B7E-4771-87C0-7F98FC82F990}"
EndProject
-
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpectatorTargetInfoModule.Tests", "tests\Modules\SpectatorTargetInfoModule.Tests\SpectatorTargetInfoModule.Tests.csproj", "{E4BF17BE-A517-4D3C-8DCA-DA99A100EBFE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpectatorCamModeModule", "src\Modules\SpectatorCamModeModule\SpectatorCamModeModule.csproj", "{E9806703-6E24-4F05-A728-A04F7EB31749}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpectatorCamModeModule.Tests", "tests\Modules\SpectatorCamModeModule.Tests\SpectatorCamModeModule.Tests.csproj", "{09A88256-8008-4085-A8E6-CA6DEFAC63E3}"
+EndProject
@@ -430,6 +435,18 @@ Global
{7BD60D6E-7B7E-4771-87C0-7F98FC82F990}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BD60D6E-7B7E-4771-87C0-7F98FC82F990}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BD60D6E-7B7E-4771-87C0-7F98FC82F990}.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
+ {E9806703-6E24-4F05-A728-A04F7EB31749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E9806703-6E24-4F05-A728-A04F7EB31749}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E9806703-6E24-4F05-A728-A04F7EB31749}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E9806703-6E24-4F05-A728-A04F7EB31749}.Release|Any CPU.Build.0 = Release|Any CPU
+ {09A88256-8008-4085-A8E6-CA6DEFAC63E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {09A88256-8008-4085-A8E6-CA6DEFAC63E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {09A88256-8008-4085-A8E6-CA6DEFAC63E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {09A88256-8008-4085-A8E6-CA6DEFAC63E3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -497,5 +514,8 @@ Global
{4AA1890A-1423-4831-95B2-E29B9A7A58D1} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A}
{DDB0A249-BD1C-4556-BFE1-362B17EDA874} = {DC47658A-F421-4BA4-B617-090A7DFB3900}
{7BD60D6E-7B7E-4771-87C0-7F98FC82F990} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A}
+ {E4BF17BE-A517-4D3C-8DCA-DA99A100EBFE} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A}
+ {E9806703-6E24-4F05-A728-A04F7EB31749} = {DC47658A-F421-4BA4-B617-090A7DFB3900}
+ {09A88256-8008-4085-A8E6-CA6DEFAC63E3} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A}
EndGlobalSection
EndGlobal
diff --git a/src/EvoSC/EvoSC.csproj b/src/EvoSC/EvoSC.csproj
index 12ca2d98b..844ecefc8 100644
--- a/src/EvoSC/EvoSC.csproj
+++ b/src/EvoSC/EvoSC.csproj
@@ -49,6 +49,7 @@
+
diff --git a/src/EvoSC/InternalModules.cs b/src/EvoSC/InternalModules.cs
index 94a8df479..a7fa2c43b 100644
--- a/src/EvoSC/InternalModules.cs
+++ b/src/EvoSC/InternalModules.cs
@@ -23,6 +23,7 @@
using EvoSC.Modules.Official.Scoreboard;
using EvoSC.Modules.Official.ServerManagementModule;
using EvoSC.Modules.Official.SetName;
+using EvoSC.Modules.Official.SpectatorCamModeModule;
using EvoSC.Modules.Official.SpectatorTargetInfoModule;
using EvoSC.Modules.Official.TeamChatModule;
using EvoSC.Modules.Official.TeamInfoModule;
@@ -37,6 +38,7 @@ public static class InternalModules
public static readonly Type[] Modules =
[
//typeof(ExampleModule),
+ typeof(GameModeUiModule),
typeof(PlayerModule),
typeof(MapsModule),
typeof(WorldRecordModule),
@@ -55,6 +57,7 @@ public static class InternalModules
typeof(MatchRankingModule),
typeof(ASayModule),
typeof(SpectatorTargetInfoModule),
+ typeof(SpectatorCamModeModule),
typeof(MapQueueModule),
typeof(MapListModule),
typeof(LocalRecordsModule),
@@ -62,7 +65,6 @@ public static class InternalModules
typeof(TeamSettingsModule),
typeof(ServerManagementModule),
typeof(TeamInfoModule),
- typeof(GameModeUiModule),
typeof(TeamChatModule)
];
diff --git a/src/Modules/GameModeUiModule/Enums/GameModeUiComponents.cs b/src/Modules/GameModeUiModule/Enums/GameModeUiComponents.cs
new file mode 100644
index 000000000..2d871da86
--- /dev/null
+++ b/src/Modules/GameModeUiModule/Enums/GameModeUiComponents.cs
@@ -0,0 +1,20 @@
+namespace EvoSC.Modules.Official.GameModeUiModule.Enums;
+
+public static class GameModeUiComponents
+{
+ public static readonly string Chrono = "Race_Chrono";
+ public static readonly string RespawnHelper = "Race_RespawnHelper";
+ public static readonly string Checkpoint = "Race_Checkpoint";
+ public static readonly string LapsCounter = "Race_LapsCounter";
+ public static readonly string TimeGap = "Race_TimeGap";
+ public static readonly string ScoresTable = "Race_ScoresTable";
+ public static readonly string DisplayMessage = "Race_DisplayMessage";
+ public static readonly string Countdown = "Race_Countdown";
+ public static readonly string SpectatorBaseName = "Race_SpectatorBase_Name";
+ public static readonly string SpectatorBaseCommands = "Race_SpectatorBase_Commands";
+ public static readonly string Record = "Race_Record";
+ public static readonly string BigMessage = "Race_BigMessage";
+ public static readonly string BlockHelper = "Race_BlockHelper";
+ public static readonly string WarmUp = "Race_WarmUp";
+ public static readonly string BestRaceViewer = "Race_BestRaceViewer";
+}
diff --git a/src/Modules/GameModeUiModule/GameModeUiModule.cs b/src/Modules/GameModeUiModule/GameModeUiModule.cs
index 6f3146424..e89eaa919 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.ApplyComponentSettingsAsync(gameModeUiModuleService.GetDefaultSettings());
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..eb2f5dcad 100644
--- a/src/Modules/GameModeUiModule/Interfaces/IGameModeUiModuleService.cs
+++ b/src/Modules/GameModeUiModule/Interfaces/IGameModeUiModuleService.cs
@@ -1,27 +1,50 @@
-namespace EvoSC.Modules.Official.GameModeUiModule.Interfaces;
+using EvoSC.Modules.Official.GameModeUiModule.Models;
+
+namespace EvoSC.Modules.Official.GameModeUiModule.Interfaces;
public interface IGameModeUiModuleService
{
///
- /// Applies the configured UI modules property values.
+ /// Applies the given game mode component settings collection.
///
+ ///
///
- public Task ApplyConfigurationAsync();
+ public Task ApplyComponentSettingsAsync(IEnumerable componentSettingsList);
///
- /// Returns the configured UI modules properties as JSON string.
+ /// Applies the given game mode component settings.
///
+ ///
///
- public Task GetUiModulesPropertiesJsonAsync();
+ public Task ApplyComponentSettingsAsync(GameModeUiComponentSettings componentSettings);
///
- /// Generates a UI properties object for the given values.
+ /// Applies the given game mode component settings.
///
- ///
+ ///
///
///
///
///
///
- public Task GeneratePropertyObjectAsync(string uiModuleName, bool visible, double x, double y, double scale);
+ public Task ApplyComponentSettingsAsync(string name, bool visible, double x, double y, double scale);
+
+ ///
+ /// Returns the configured UI modules properties as JSON string.
+ ///
+ ///
+ public string GetUiModulesPropertiesJson(IEnumerable componentSettingsList);
+
+ ///
+ /// Generates a UI properties object for the given values.
+ ///
+ ///
+ ///
+ public dynamic GeneratePropertyObject(GameModeUiComponentSettings componentSettings);
+
+ ///
+ /// Gets the default game mode component settings collection.
+ ///
+ ///
+ public List GetDefaultSettings();
}
diff --git a/src/Modules/GameModeUiModule/Models/GameModeUiComponentSettings.cs b/src/Modules/GameModeUiModule/Models/GameModeUiComponentSettings.cs
new file mode 100644
index 000000000..b001de740
--- /dev/null
+++ b/src/Modules/GameModeUiModule/Models/GameModeUiComponentSettings.cs
@@ -0,0 +1,44 @@
+namespace EvoSC.Modules.Official.GameModeUiModule.Models;
+
+public class GameModeUiComponentSettings(string name, bool visible, double x, double y, double scale)
+{
+ ///
+ /// The Name/ID of the game mode UI component.
+ ///
+ public string Name { get; init; } = name;
+
+ ///
+ /// Sets whether the component should be visible.
+ ///
+ public bool Visible { get; set; } = visible;
+
+ ///
+ /// Sets the X position of the component.
+ ///
+ public double X { get; set; } = x;
+
+ ///
+ /// Sets the Y position of the component.
+ ///
+ public double Y { get; set; } = y;
+
+ ///
+ /// Sets the scale of the component.
+ ///
+ public double Scale { get; set; } = scale;
+
+ ///
+ /// Sets whether the visibility of the component should be overwritten.
+ ///
+ public bool UpdateVisible { get; set; } = true;
+
+ ///
+ /// Sets whether the position of the component should be overwritten.
+ ///
+ public bool UpdatePosition { get; set; } = true;
+
+ ///
+ /// Sets whether the scale of the component should be overwritten.
+ ///
+ public bool UpdateScale { get; set; } = true;
+}
diff --git a/src/Modules/GameModeUiModule/Services/GameModeUiModuleService.cs b/src/Modules/GameModeUiModule/Services/GameModeUiModuleService.cs
index 44ef2ba2c..8f87e5578 100644
--- a/src/Modules/GameModeUiModule/Services/GameModeUiModuleService.cs
+++ b/src/Modules/GameModeUiModule/Services/GameModeUiModuleService.cs
@@ -2,7 +2,9 @@
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 Newtonsoft.Json;
namespace EvoSC.Modules.Official.GameModeUiModule.Services;
@@ -11,139 +13,148 @@ namespace EvoSC.Modules.Official.GameModeUiModule.Services;
public class GameModeUiModuleService(IServerClient server, IGameModeUiModuleSettings settings)
: IGameModeUiModuleService
{
- public async Task ApplyConfigurationAsync()
+ public Task ApplyComponentSettingsAsync(IEnumerable componentSettingsList) =>
+ server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties",
+ GetUiModulesPropertiesJson(componentSettingsList));
+
+ public Task ApplyComponentSettingsAsync(GameModeUiComponentSettings componentSettings) =>
+ ApplyComponentSettingsAsync([componentSettings]);
+
+ public Task ApplyComponentSettingsAsync(string name, bool visible, double x, double y, double scale) =>
+ ApplyComponentSettingsAsync([new GameModeUiComponentSettings(name, visible, x, y, scale)]);
+
+ public string GetUiModulesPropertiesJson(IEnumerable componentSettingsList)
{
- var uiModuleProperties = await GetUiModulesPropertiesJsonAsync();
- await server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", uiModuleProperties);
+ var propertyObjects = componentSettingsList
+ .Select(uiElement => GeneratePropertyObject(uiElement))
+ .ToList();
+
+ return JsonConvert.SerializeObject(new { uimodules = propertyObjects });
}
- public async Task GetUiModulesPropertiesJsonAsync()
+ public dynamic GeneratePropertyObject(GameModeUiComponentSettings componentSettings)
{
- return JsonConvert.SerializeObject(new
+ return 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
- ),
- }
- });
+ 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,
+ };
}
- public Task GeneratePropertyObjectAsync(string uiModuleName, bool visible, double x, double y,
- double scale)
+ public List GetDefaultSettings()
{
- return Task.FromResult(new
- {
- id = uiModuleName,
- position = (double[]) [x, y],
- visible,
- scale,
- position_update = true,
- visible_update = true,
- scale_update = true,
- });
+ 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
+ )
+ ];
}
}
diff --git a/src/Modules/Scoreboard/Scoreboard.csproj b/src/Modules/Scoreboard/Scoreboard.csproj
index 7e97db700..b1ff40dea 100644
--- a/src/Modules/Scoreboard/Scoreboard.csproj
+++ b/src/Modules/Scoreboard/Scoreboard.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Modules/Scoreboard/Services/ScoreboardService.cs b/src/Modules/Scoreboard/Services/ScoreboardService.cs
index d8e79f91b..4df4f2992 100644
--- a/src/Modules/Scoreboard/Services/ScoreboardService.cs
+++ b/src/Modules/Scoreboard/Services/ScoreboardService.cs
@@ -5,23 +5,33 @@
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.Scoreboard.Interfaces;
namespace EvoSC.Modules.Official.Scoreboard.Services;
[Service(LifeStyle = ServiceLifeStyle.Transient)]
-public class ScoreboardService(IManialinkManager manialinks, IServerClient server,
- IMatchSettingsService matchSettingsService, IScoreboardTrackerService scoreboardTracker, IThemeManager themes)
+public class ScoreboardService(
+ IManialinkManager manialinks,
+ IServerClient server,
+ IMatchSettingsService matchSettingsService,
+ IScoreboardTrackerService scoreboardTracker,
+ IGameModeUiModuleService gameModeUiModuleService,
+ IThemeManager themes
+)
: IScoreboardService
{
+ private const string ScoreboardTemplate = "Scoreboard.Scoreboard";
+
public async Task ShowScoreboardToAllAsync()
{
- await manialinks.SendPersistentManialinkAsync("Scoreboard.Scoreboard", await GetDataAsync());
+ await manialinks.SendPersistentManialinkAsync(ScoreboardTemplate, await GetDataAsync());
}
public async Task ShowScoreboardAsync(IPlayer playerLogin)
{
- await manialinks.SendManialinkAsync(playerLogin, "Scoreboard.Scoreboard", await GetDataAsync());
+ await manialinks.SendManialinkAsync(playerLogin, ScoreboardTemplate, await GetDataAsync());
}
private Task GetDataAsync()
@@ -30,49 +40,18 @@ private Task GetDataAsync()
{
scoreboardTracker.MaxPlayers,
scoreboardTracker.RoundsPerMap,
- PositionColors = new Dictionary { { 1, themes.Theme.Gold }, { 2, themes.Theme.Silver }, { 3, themes.Theme.Bronze } },
+ PositionColors = new Dictionary
+ {
+ { 1, themes.Theme.Gold }, { 2, themes.Theme.Silver }, { 3, themes.Theme.Bronze }
+ },
});
}
- public async Task HideNadeoScoreboardAsync()
- {
- var hudSettings = new List
- {
- @"{
- ""uimodules"": [
- {
- ""id"": ""Race_ScoresTable"",
- ""position"": [-50,0],
- ""scale"": 1,
- ""visible"": false,
- ""visible_update"": true
- }
- ]
-}"
- };
+ public Task HideNadeoScoreboardAsync() =>
+ gameModeUiModuleService.ApplyComponentSettingsAsync(GameModeUiComponents.ScoresTable, false, 0, 0, 1);
- await server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", hudSettings.ToArray());
- }
-
- public async Task ShowNadeoScoreboardAsync()
- {
- var hudSettings = new List
- {
- @"{
- ""uimodules"": [
- {
- ""id"": ""Race_ScoresTable"",
- ""position"": [-50,0],
- ""scale"": 1,
- ""visible"": true,
- ""visible_update"": true
- }
- ]
-}"
- };
-
- await server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", hudSettings.ToArray());
- }
+ public Task ShowNadeoScoreboardAsync() =>
+ gameModeUiModuleService.ApplyComponentSettingsAsync(GameModeUiComponents.ScoresTable, false, -50, 0, 1);
public async Task SendRequiredAdditionalInfoAsync()
{
@@ -88,14 +67,15 @@ public async Task LoadAndSendRequiredAdditionalInfoAsync()
{
return;
}
-
+
var roundsPerMap = -1;
var pointsLimit = -1;
-
+
if (settings.TryGetValue("S_RoundsPerMap", out var rounds))
{
roundsPerMap = (int)rounds;
}
+
if (settings.TryGetValue("S_PointsLimit", out var pointsLimitValue))
{
pointsLimit = (int)pointsLimitValue;
@@ -104,7 +84,7 @@ public async Task LoadAndSendRequiredAdditionalInfoAsync()
scoreboardTracker.MaxPlayers = (await server.Remote.GetMaxPlayersAsync()).CurrentValue;
scoreboardTracker.RoundsPerMap = roundsPerMap;
scoreboardTracker.PointsLimit = pointsLimit;
-
+
await SendRequiredAdditionalInfoAsync();
}
diff --git a/src/Modules/Scoreboard/info.toml b/src/Modules/Scoreboard/info.toml
index f3c9a1dae..d957e2580 100644
--- a/src/Modules/Scoreboard/info.toml
+++ b/src/Modules/Scoreboard/info.toml
@@ -4,3 +4,6 @@ title = "Scoreboard"
summary = "Custom EvoSC Scoreboards."
version = "1.0.0"
author = "Evo"
+
+[dependencies]
+GameModeUiModule = "1.0.0"
diff --git a/src/Modules/SpectatorCamModeModule/Config/ISpectatorCamModeSettings.cs b/src/Modules/SpectatorCamModeModule/Config/ISpectatorCamModeSettings.cs
new file mode 100644
index 000000000..d1e36d61c
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/Config/ISpectatorCamModeSettings.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+using Config.Net;
+using EvoSC.Common.Util.Manialinks;
+using EvoSC.Modules.Attributes;
+
+namespace EvoSC.Modules.Official.SpectatorCamModeModule.Config;
+
+[Settings]
+public interface ISpectatorCamModeSettings
+{
+ [Option(DefaultValue = "right"),
+ Description("Specifies the alignment of the widget, allowed values are: left, right and center.")]
+ public WidgetPosition Alignment { get; set; }
+
+ [Option(DefaultValue = 158.0), Description("Defines the horizontal position of the widget.")]
+ public double X { get; set; }
+
+ [Option(DefaultValue = -82.5), Description("Defines the vertical position of the widget.")]
+ public double Y { get; set; }
+}
diff --git a/src/Modules/SpectatorCamModeModule/Controllers/SpectatorCamModeEventController.cs b/src/Modules/SpectatorCamModeModule/Controllers/SpectatorCamModeEventController.cs
new file mode 100644
index 000000000..c253972b6
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/Controllers/SpectatorCamModeEventController.cs
@@ -0,0 +1,22 @@
+using EvoSC.Common.Controllers;
+using EvoSC.Common.Controllers.Attributes;
+using EvoSC.Common.Controllers.Context;
+using EvoSC.Common.Events.Attributes;
+using EvoSC.Common.Remote;
+using EvoSC.Common.Remote.EventArgsModels;
+using EvoSC.Modules.Official.SpectatorCamModeModule.Interfaces;
+
+namespace EvoSC.Modules.Official.SpectatorCamModeModule.Controllers;
+
+[Controller]
+public class SpectatorCamModeEventController(ISpectatorCamModeService camModeService)
+ : EvoScController
+{
+ [Subscribe(ModeScriptEvent.EndRoundStart)]
+ public Task OnEndRoundStartAsync(object sender, RoundEventArgs eventArgs) =>
+ camModeService.HideCamModeWidgetAsync();
+
+ [Subscribe(ModeScriptEvent.StartRoundStart)]
+ public Task OnStartRoundStartAsync(object sender, RoundEventArgs eventArgs) =>
+ camModeService.SendPersistentCamModeWidgetAsync();
+}
diff --git a/src/Modules/SpectatorCamModeModule/Interfaces/ISpectatorCamModeService.cs b/src/Modules/SpectatorCamModeModule/Interfaces/ISpectatorCamModeService.cs
new file mode 100644
index 000000000..7ffac03a6
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/Interfaces/ISpectatorCamModeService.cs
@@ -0,0 +1,30 @@
+using EvoSC.Modules.Official.GameModeUiModule.Models;
+
+namespace EvoSC.Modules.Official.SpectatorCamModeModule.Interfaces;
+
+public interface ISpectatorCamModeService
+{
+ ///
+ /// Sends a persistent cam mode selection widget.
+ ///
+ ///
+ public Task SendPersistentCamModeWidgetAsync();
+
+ ///
+ /// Hides the previously send widget.
+ ///
+ ///
+ public Task HideCamModeWidgetAsync();
+
+ ///
+ /// Hides the default UI provided by the game mode.
+ ///
+ ///
+ public Task HideGameModeUiAsync();
+
+ ///
+ /// Gets the settings to hide the default UI component.
+ ///
+ ///
+ public GameModeUiComponentSettings GetGameModeUiSettings();
+}
diff --git a/src/Modules/SpectatorCamModeModule/Localization.resx b/src/Modules/SpectatorCamModeModule/Localization.resx
new file mode 100644
index 000000000..02cf34b4b
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/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/SpectatorCamModeModule/Services/SpectatorCamModeService.cs b/src/Modules/SpectatorCamModeModule/Services/SpectatorCamModeService.cs
new file mode 100644
index 000000000..69212135b
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/Services/SpectatorCamModeService.cs
@@ -0,0 +1,41 @@
+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.GameModeUiModule.Models;
+using EvoSC.Modules.Official.SpectatorCamModeModule.Config;
+using EvoSC.Modules.Official.SpectatorCamModeModule.Interfaces;
+
+namespace EvoSC.Modules.Official.SpectatorCamModeModule.Services;
+
+[Service(LifeStyle = ServiceLifeStyle.Transient)]
+public class SpectatorCamModeService(
+ IManialinkManager manialinks,
+ IGameModeUiModuleService gameModeUiModuleService,
+ ISpectatorCamModeSettings settings
+)
+ : ISpectatorCamModeService
+{
+ private const string WidgetTemplate = "SpectatorCamModeModule.SpectatorMode";
+
+ public Task SendPersistentCamModeWidgetAsync() =>
+ manialinks.SendPersistentManialinkAsync(WidgetTemplate, new { settings });
+
+ public Task HideCamModeWidgetAsync() =>
+ manialinks.HideManialinkAsync(WidgetTemplate);
+
+ public Task HideGameModeUiAsync() =>
+ gameModeUiModuleService.ApplyComponentSettingsAsync(GetGameModeUiSettings());
+
+ public GameModeUiComponentSettings GetGameModeUiSettings()
+ {
+ return new GameModeUiComponentSettings(
+ GameModeUiComponents.SpectatorBaseCommands,
+ false,
+ 0.0,
+ 0.0,
+ 1.0
+ );
+ }
+}
diff --git a/src/Modules/SpectatorCamModeModule/SpectatorCamModeModule.cs b/src/Modules/SpectatorCamModeModule/SpectatorCamModeModule.cs
new file mode 100644
index 000000000..68fe0079c
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/SpectatorCamModeModule.cs
@@ -0,0 +1,17 @@
+using EvoSC.Modules.Attributes;
+using EvoSC.Modules.Interfaces;
+using EvoSC.Modules.Official.SpectatorCamModeModule.Interfaces;
+
+namespace EvoSC.Modules.Official.SpectatorCamModeModule;
+
+[Module(IsInternal = true)]
+public class SpectatorCamModeModule(ISpectatorCamModeService spectatorCamModeService) : EvoScModule, IToggleable
+{
+ public async Task EnableAsync()
+ {
+ await spectatorCamModeService.SendPersistentCamModeWidgetAsync();
+ await spectatorCamModeService.HideGameModeUiAsync();
+ }
+
+ public Task DisableAsync() => Task.CompletedTask;
+}
diff --git a/src/Modules/SpectatorCamModeModule/SpectatorCamModeModule.csproj b/src/Modules/SpectatorCamModeModule/SpectatorCamModeModule.csproj
new file mode 100644
index 000000000..fa7c64cbc
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/SpectatorCamModeModule.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net8.0
+ enable
+ enable
+ EvoSC.Modules.Official.SpectatorCamModeModule
+ false
+ SpectatorCamModeModule
+
+
+
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Localization.Designer.cs
+
+
+
diff --git a/src/Modules/SpectatorCamModeModule/Templates/Scripts/SpectatorMode.ms b/src/Modules/SpectatorCamModeModule/Templates/Scripts/SpectatorMode.ms
new file mode 100644
index 000000000..843927ebf
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/Templates/Scripts/SpectatorMode.ms
@@ -0,0 +1,302 @@
+
\ No newline at end of file
diff --git a/src/Modules/SpectatorCamModeModule/Templates/SpectatorMode.mt b/src/Modules/SpectatorCamModeModule/Templates/SpectatorMode.mt
new file mode 100644
index 000000000..41d138210
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/Templates/SpectatorMode.mt
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Modules/SpectatorCamModeModule/info.toml b/src/Modules/SpectatorCamModeModule/info.toml
new file mode 100644
index 000000000..8d6803e66
--- /dev/null
+++ b/src/Modules/SpectatorCamModeModule/info.toml
@@ -0,0 +1,9 @@
+[info]
+name = "SpectatorCamModeModule"
+title = "Spectator Cam Mode"
+summary = "Shows and allows changing the cam mode."
+version = "1.0.0"
+author = "Evo"
+
+[dependencies]
+GameModeUiModule = "1.0.0"
diff --git a/src/Modules/SpectatorTargetInfoModule/Config/ISpectatorTargetInfoSettings.cs b/src/Modules/SpectatorTargetInfoModule/Config/ISpectatorTargetInfoSettings.cs
new file mode 100644
index 000000000..f48adad55
--- /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 = -57.0), Description("Defines the vertical position of the widget.")]
+ public double Y { get; set; }
+}
diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs
index e6d35e4f3..5afc8f94b 100644
--- a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs
+++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoEventController.cs
@@ -1,31 +1,38 @@
using EvoSC.Common.Controllers;
using EvoSC.Common.Controllers.Attributes;
-using EvoSC.Common.Controllers.Context;
using EvoSC.Common.Events.Attributes;
+using EvoSC.Common.Interfaces.Controllers;
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;
[Controller]
-public class SpectatorTargetInfoEventController
- (ISpectatorTargetInfoService spectatorTargetInfoService) : EvoScController
+public class SpectatorTargetInfoEventController(ISpectatorTargetInfoService spectatorTargetInfoService)
+ : EvoScController
{
- [Subscribe(GbxRemoteEvent.PlayerConnect)]
- public Task OnPlayerConnectAsync(object x, PlayerConnectGbxEventArgs args) =>
- spectatorTargetInfoService.SendManiaLinkAsync(args.Login);
+ [Subscribe(GbxRemoteEvent.PlayerDisconnect)]
+ public Task OnPlayerDisconnectAsync(object sender, PlayerGbxEventArgs eventArgs) =>
+ spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(eventArgs.Login);
+
+ [Subscribe(GbxRemoteEvent.BeginMap)]
+ public Task OnBeginMapAsync(object sender, MapGbxEventArgs eventArgs) =>
+ spectatorTargetInfoService.UpdateIsTeamsModeAsync();
[Subscribe(ModeScriptEvent.WayPoint)]
public Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) =>
- spectatorTargetInfoService.ForwardCheckpointTimeToClientsAsync(wayPointEventArgs);
+ spectatorTargetInfoService.AddCheckpointAsync(
+ wayPointEventArgs.Login,
+ wayPointEventArgs.CheckpointInLap,
+ wayPointEventArgs.LapTime
+ );
[Subscribe(ModeScriptEvent.StartRoundStart)]
- public Task OnNewRoundAsync(object sender, RoundEventArgs roundEventArgs) =>
- spectatorTargetInfoService.ResetCheckpointTimesAsync();
-
- [Subscribe(ModeScriptEvent.GiveUp)]
- public Task OnPlayerGiveUpAsync(object sender, PlayerUpdateEventArgs playerUpdateEventArgs) =>
- spectatorTargetInfoService.ForwardDnfToClientsAsync(playerUpdateEventArgs);
+ public async Task OnNewRoundAsync(object sender, RoundEventArgs roundEventArgs)
+ {
+ await spectatorTargetInfoService.ClearCheckpointsAsync();
+ await spectatorTargetInfoService.FetchAndCacheTeamInfoAsync();
+ }
}
diff --git a/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoManialinkController.cs b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoManialinkController.cs
new file mode 100644
index 000000000..04b17fb77
--- /dev/null
+++ b/src/Modules/SpectatorTargetInfoModule/Controllers/SpectatorTargetInfoManialinkController.cs
@@ -0,0 +1,26 @@
+using EvoSC.Common.Controllers.Attributes;
+using EvoSC.Common.Util;
+using EvoSC.Manialinks;
+using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces;
+
+namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Controllers;
+
+[Controller]
+public class SpectatorTargetInfoManialinkController(ISpectatorTargetInfoService spectatorTargetInfoService)
+ : ManialinkController
+{
+ public async Task ReportSpectatorTargetAsync(string targetLogin)
+ {
+ var spectatorLogin = Context.Player.GetLogin();
+
+ if (targetLogin != "" && targetLogin != spectatorLogin)
+ {
+ await spectatorTargetInfoService.SetSpectatorTargetAndSendAsync(spectatorLogin, targetLogin);
+ }
+ else
+ {
+ await spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(spectatorLogin);
+ await spectatorTargetInfoService.HideSpectatorInfoWidgetAsync(spectatorLogin);
+ }
+ }
+}
diff --git a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs
index 32d2d9148..3be2de777 100644
--- a/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs
+++ b/src/Modules/SpectatorTargetInfoModule/Interfaces/ISpectatorTargetInfoService.cs
@@ -1,46 +1,179 @@
-using EvoSC.Common.Remote.EventArgsModels;
+using EvoSC.Common.Interfaces.Models;
+using EvoSC.Modules.Official.SpectatorTargetInfoModule.Models;
-namespace SpectatorTargetInfo.Interfaces;
+namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces;
public interface ISpectatorTargetInfoService
{
///
- /// Sends the manialink to all players.
+ /// Initialize the module on controller start.
///
- public Task SendManiaLinkAsync();
-
+ ///
+ public Task InitializeAsync();
+
///
- /// Sends the manialink to a specific player.
+ /// Register a new checkpoint time for the ongoing round.
///
- public Task SendManiaLinkAsync(string playerLogin);
-
+ ///
+ ///
+ ///
+ ///
+ public Task AddCheckpointAsync(string playerLogin, int checkpointIndex, int checkpointTime);
+
///
- /// Hides the manialink for all players.
+ /// Clears all registered checkpoint times.
///
- public Task HideManiaLinkAsync();
+ ///
+ public Task ClearCheckpointsAsync();
///
- /// Hides the default spectator info.
+ /// Retrieve an IOnlinePlayer instance by their login.
+ ///
+ ///
+ ///
+ public Task GetOnlinePlayerByLoginAsync(string playerLogin);
+
+ ///
+ /// Get the login of a player by their dedicated server ID.
///
- public Task HideNadeoSpectatorInfoAsync();
+ ///
+ ///
+ public Task GetLoginOfDedicatedPlayerAsync(int targetPlayerIdDedicated);
+
+ ///
+ /// Set the spectator target for a player.
+ ///
+ ///
+ ///
+ ///
+ public Task SetSpectatorTargetAsync(string spectatorLogin, string targetLogin);
///
- /// Shows the default spectator info.
+ /// Set the spectator target for a player and display the widget to them.
+ ///
+ ///
+ ///
+ ///
+ public Task SetSpectatorTargetAndSendAsync(string spectatorLogin, string targetLogin);
+
+ ///
+ /// Remove a player from the spectators list.
///
- public Task ShowNadeoSpectatorInfoAsync();
+ ///
+ ///
+ public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin);
+
+ ///
+ /// Gets the logins of a players spectating the given target.
+ ///
+ ///
+ ///
+ public IEnumerable GetLoginsOfPlayersSpectatingTarget(IOnlinePlayer targetPlayer);
+
+ ///
+ /// Calculates the time difference between two given times in milliseconds.
+ ///
+ ///
+ ///
+ ///
+ public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime);
+
+ ///
+ /// Gets the hex team color for the given team.
+ ///
+ ///
+ ///
+ public string GetTeamColor(PlayerTeam team);
+
+ ///
+ /// Gets the highest checkpoint ID the given player has reached in the ongoing round.
+ ///
+ ///
+ ///
+ public int GetLastCheckpointIndexOfPlayer(IOnlinePlayer player);
///
- /// Maps wayPointEventArgs and sends data to clients.
+ /// Returns the CheckpointGroups for all collected checkpoints.
+ ///
+ ///
+ public Dictionary GetCheckpointTimes();
+
+ ///
+ /// Resets the widget for all spectating players.
+ ///
+ ///
+ public Task ResetWidgetForSpectatorsAsync();
+
+ ///
+ /// Send the widget to the given player.
+ ///
+ ///
+ ///
+ ///
+ public Task SendSpectatorInfoWidgetAsync(string spectatorLogin, IOnlinePlayer targetPlayer);
+
+ ///
+ /// Send the widget to the given players.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task SendSpectatorInfoWidgetAsync(IEnumerable spectatorLogins, IOnlinePlayer targetPlayer, int targetPlayerRank, int timeDifference);
+
+ ///
+ /// Send the widget to the given player.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task SendSpectatorInfoWidgetAsync(string spectatorLogin, IOnlinePlayer targetPlayer, object widgetData);
+
+ ///
+ /// Gets the data that's passed to the widget.
///
- public Task ForwardCheckpointTimeToClientsAsync(WayPointEventArgs wayPointEventArgs);
+ ///
+ ///
+ ///
+ ///
+ public object GetWidgetData(IOnlinePlayer player, int rank, int timeDifference);
///
- /// Clears the checkpoint times for the clients.
+ /// Hides the widget for all players.
+ ///
+ ///
+ public Task HideSpectatorInfoWidgetAsync();
+
+ ///
+ /// Hides the widget for the given player.
+ ///
+ ///
+ ///
+ public Task HideSpectatorInfoWidgetAsync(string playerLogin);
+
+ ///
+ /// Sends the script responsible for reporting back the current spectator target from the player to the module.
///
- public Task ResetCheckpointTimesAsync();
+ ///
+ public Task SendReportSpectatorTargetManialinkAsync();
+
+ ///
+ /// Retrieve and cache the latest team infos.
+ ///
+ ///
+ public Task FetchAndCacheTeamInfoAsync();
///
- /// Sends players DNF to clients.
+ /// Updates whether team mode is active or not.
+ ///
+ ///
+ public Task UpdateIsTeamsModeAsync();
+
+ ///
+ /// Hides the default game mode UI.
///
- public Task ForwardDnfToClientsAsync(PlayerUpdateEventArgs playerUpdateEventArgs);
+ ///
+ public Task HideGameModeUiAsync();
}
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/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/CheckpointsGroup.cs b/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs
new file mode 100644
index 000000000..07f882846
--- /dev/null
+++ b/src/Modules/SpectatorTargetInfoModule/Models/CheckpointsGroup.cs
@@ -0,0 +1,26 @@
+using EvoSC.Common.Util;
+
+namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Models;
+
+public class CheckpointsGroup : List
+{
+ public CheckpointData? GetPlayerCheckpointData(string playerLogin)
+ {
+ return this.Find(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 rank;
+ }
+}
diff --git a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs
index 3ba7e5144..9af83f349 100644
--- a/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs
+++ b/src/Modules/SpectatorTargetInfoModule/Services/SpectatorTargetInfoService.cs
@@ -1,99 +1,250 @@
using EvoSC.Common.Interfaces;
+using EvoSC.Common.Interfaces.Models;
using EvoSC.Common.Interfaces.Services;
-using EvoSC.Common.Remote.EventArgsModels;
+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 SpectatorTargetInfo.Interfaces;
+using EvoSC.Modules.Official.GameModeUiModule.Enums;
+using EvoSC.Modules.Official.GameModeUiModule.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;
namespace EvoSC.Modules.Official.SpectatorTargetInfoModule.Services;
[Service(LifeStyle = ServiceLifeStyle.Singleton)]
-public class SpectatorTargetInfoService
- (IManialinkManager manialinks, IServerClient server, IPlayerManagerService playerManagerService) : ISpectatorTargetInfoService
+public class SpectatorTargetInfoService(
+ IManialinkManager manialinks,
+ IServerClient server,
+ IPlayerManagerService playerManagerService,
+ IMatchSettingsService matchSettingsService,
+ ISpectatorTargetInfoSettings settings,
+ IThemeManager theme,
+ IGameModeUiModuleService gameModeUiModuleService,
+ ILogger logger
+) : ISpectatorTargetInfoService
{
+ private const string ReportTargetTemplate = "SpectatorTargetInfoModule.ReportSpecTarget";
private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo";
- public async Task SendManiaLinkAsync() =>
- await manialinks.SendManialinkAsync(WidgetTemplate);
+ private readonly Dictionary _checkpointTimes = new(); // cp-id -> CheckpointsGroup
+ private readonly Dictionary _spectatorTargets = new(); // login -> IOnlinePlayer
+ private readonly Dictionary _teamInfos = new();
+ private bool _isTeamsMode;
+ public async Task InitializeAsync()
+ {
+ await UpdateIsTeamsModeAsync();
+ await FetchAndCacheTeamInfoAsync();
+ await SendReportSpectatorTargetManialinkAsync();
+ await HideGameModeUiAsync();
+ }
+
+ public Task GetOnlinePlayerByLoginAsync(string playerLogin)
+ => playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(playerLogin));
+
+ public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, int checkpointTime)
+ {
+ 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);
+ _checkpointTimes[checkpointIndex] = checkpointGroup;
- public async Task SendManiaLinkAsync(string playerLogin)
+ var spectatorLogins = GetLoginsOfPlayersSpectatingTarget(player).ToList();
+ if (spectatorLogins.IsNullOrEmpty())
+ {
+ return;
+ }
+
+ var leadingCheckpointData = checkpointGroup.First();
+ var timeDifference = GetTimeDifference(leadingCheckpointData.time, newCheckpointData.time);
+
+ await SendSpectatorInfoWidgetAsync(spectatorLogins, player, checkpointGroup.GetRank(playerLogin),
+ timeDifference);
+ }
+
+ public Task ClearCheckpointsAsync()
{
- var player = await playerManagerService.GetOnlinePlayerAsync(PlayerUtils.ConvertLoginToAccountId(playerLogin));
- await manialinks.SendManialinkAsync(player, WidgetTemplate);
+ _checkpointTimes.Clear();
+
+ return Task.CompletedTask;
}
- public async Task HideManiaLinkAsync() =>
- await manialinks.HideManialinkAsync(WidgetTemplate);
+ 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 Task HideNadeoSpectatorInfoAsync()
+ public async Task SetSpectatorTargetAsync(string spectatorLogin, string targetLogin)
{
- var hudSettings = new List
+ if (spectatorLogin == targetLogin)
{
- @"{""uimodules"": [
- {
- ""id"": ""Race_SpectatorBase_Name"",
- ""visible"": false,
- ""visible_update"": true,
- },
- {
- ""id"": ""Race_SpectatorBase_Commands"",
- ""visible"": true,
- ""visible_update"": true,
- }
- ]}"
- };
+ return null; //Can't spec yourself
+ }
+
+ var targetPlayer = await GetOnlinePlayerByLoginAsync(targetLogin);
+
+ if (_spectatorTargets.TryGetValue(spectatorLogin, out var target) && target == targetPlayer)
+ {
+ return null; //Player is already spectating target
+ }
+
+ _spectatorTargets[spectatorLogin] = targetPlayer;
- return server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", hudSettings.ToArray());
+ logger.LogTrace("Updated spectator target {spectatorLogin} -> {targetLogin}.", spectatorLogin,
+ targetLogin);
+
+ return targetPlayer;
}
- public Task ShowNadeoSpectatorInfoAsync()
+ public async Task SetSpectatorTargetAndSendAsync(string spectatorLogin, string targetLogin)
{
- var hudSettings = new List
+ var targetPlayer = await SetSpectatorTargetAsync(spectatorLogin, targetLogin);
+ if (targetPlayer != null)
{
- @"{""uimodules"": [
- {
- ""id"": ""Race_SpectatorBase_Name"",
- ""visible"": true,
- ""visible_update"": true,
- },
- {
- ""id"": ""Race_SpectatorBase_Commands"",
- ""visible"": true,
- ""visible_update"": true,
- }
- ]}"
- };
+ await SendSpectatorInfoWidgetAsync(spectatorLogin, targetPlayer);
+ }
+ }
+
+ public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin)
+ {
+ if (_spectatorTargets.Remove(spectatorLogin))
+ {
+ logger.LogTrace("Removed spectator {spectatorLogin}.", spectatorLogin);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public IEnumerable GetLoginsOfPlayersSpectatingTarget(IOnlinePlayer targetPlayer)
+ {
+ return _spectatorTargets.Where(specTarget => specTarget.Value.AccountId == targetPlayer.AccountId)
+ .Select(specTarget => specTarget.Key);
+ }
+
+ public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime)
+ {
+ return targetCheckpointTime - leadingCheckpointTime;
+ }
- return server.Remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", hudSettings.ToArray());
+ public string GetTeamColor(PlayerTeam team)
+ {
+ return _isTeamsMode ? _teamInfos[team].RGB : (string)theme.Theme.UI_AccentPrimary;
}
- public Task ForwardCheckpointTimeToClientsAsync(WayPointEventArgs wayPointEventArgs)
+ public int GetLastCheckpointIndexOfPlayer(IOnlinePlayer player)
{
- return manialinks.SendManialinkAsync("SpectatorTargetInfoModule.NewCpTime",
- new
+ var playerLogin = player.GetLogin();
+ foreach (var (checkpointIndex, checkpointsGroup) in _checkpointTimes.Reverse())
+ {
+ if (checkpointsGroup.GetPlayerCheckpointData(playerLogin) != null)
{
- accountId = wayPointEventArgs.AccountId,
- time = wayPointEventArgs.RaceTime,
- cpIndex = wayPointEventArgs.CheckpointInRace
- });
+ return checkpointIndex;
+ }
+ }
+
+ return -1;
+ }
+
+ public Dictionary GetCheckpointTimes() =>
+ _checkpointTimes;
+
+ public async Task ResetWidgetForSpectatorsAsync()
+ {
+ foreach (var (spectatorLogin, targetPlayer) in _spectatorTargets)
+ {
+ var widgetData = GetWidgetData(targetPlayer, 1, 0);
+ await SendSpectatorInfoWidgetAsync(spectatorLogin, targetPlayer, widgetData);
+ }
+ }
+
+ public async Task SendSpectatorInfoWidgetAsync(string spectatorLogin, IOnlinePlayer targetPlayer)
+ {
+ var targetLogin = targetPlayer.GetLogin();
+ var checkpointIndex = GetLastCheckpointIndexOfPlayer(targetPlayer);
+ var targetRank = 1;
+ var timeDifference = 0;
+
+ if (_checkpointTimes.TryGetValue(checkpointIndex, out var checkpointsGroup))
+ {
+ var leadingCpData = checkpointsGroup.First();
+ var targetCpData = checkpointsGroup.GetPlayerCheckpointData(targetLogin);
+ targetRank = checkpointsGroup.GetRank(targetLogin);
+ timeDifference = GetTimeDifference(leadingCpData.time, targetCpData?.time ?? 0);
+ }
+
+ var widgetData = GetWidgetData(targetPlayer, targetRank, timeDifference);
+ await SendSpectatorInfoWidgetAsync(spectatorLogin, targetPlayer, widgetData);
}
- public Task ResetCheckpointTimesAsync()
+ public async Task SendSpectatorInfoWidgetAsync(IEnumerable spectatorLogins, IOnlinePlayer targetPlayer,
+ int targetPlayerRank, int timeDifference)
{
- manialinks.HideManialinkAsync("SpectatorTargetInfoModule.NewCpTime");
- return manialinks.SendManialinkAsync("SpectatorTargetInfoModule.ResetCpTimes");
+ var widgetData = GetWidgetData(targetPlayer, targetPlayerRank, timeDifference);
+ foreach (var spectatorLogin in spectatorLogins)
+ {
+ await SendSpectatorInfoWidgetAsync(spectatorLogin, targetPlayer, widgetData);
+ }
}
- public Task ForwardDnfToClientsAsync(PlayerUpdateEventArgs playerUpdateEventArgs)
+ public Task SendSpectatorInfoWidgetAsync(string spectatorLogin, IOnlinePlayer targetPlayer, object widgetData) =>
+ manialinks.SendManialinkAsync(spectatorLogin, WidgetTemplate, widgetData);
+
+ public object GetWidgetData(IOnlinePlayer player, int rank, int timeDifference)
{
- return manialinks.SendManialinkAsync("SpectatorTargetInfoModule.NewCpTime", new
+ return new
{
- accountId = playerUpdateEventArgs.AccountId,
- time = 0,
- cpIndex = -1
- });
+ settings,
+ timeDifference,
+ playerRank = rank,
+ playerName = player.NickName,
+ playerTeam = player.Team,
+ playerLogin = player.GetLogin(),
+ teamColorCode = new ColorUtils().Opacity(GetTeamColor(player.Team), 80)
+ };
+ }
+
+ public Task HideSpectatorInfoWidgetAsync()
+ => manialinks.HideManialinkAsync(WidgetTemplate);
+
+ public Task HideSpectatorInfoWidgetAsync(string playerLogin)
+ => manialinks.HideManialinkAsync(playerLogin, WidgetTemplate);
+
+ public Task SendReportSpectatorTargetManialinkAsync() =>
+ manialinks.SendPersistentManialinkAsync(ReportTargetTemplate);
+
+ public async Task FetchAndCacheTeamInfoAsync()
+ {
+ _teamInfos[PlayerTeam.Team1] = await server.Remote.GetTeamInfoAsync((int)PlayerTeam.Team1 + 1);
+ _teamInfos[PlayerTeam.Team2] = await server.Remote.GetTeamInfoAsync((int)PlayerTeam.Team2 + 1);
+ }
+
+ 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() =>
+ gameModeUiModuleService.ApplyComponentSettingsAsync(GameModeUiComponents.SpectatorBaseName, false, 0, 0, 1);
}
diff --git a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs
index d96983417..75cc8b7ab 100644
--- a/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs
+++ b/src/Modules/SpectatorTargetInfoModule/SpectatorTargetInfoModule.cs
@@ -1,18 +1,15 @@
using EvoSC.Modules.Attributes;
using EvoSC.Modules.Interfaces;
-using SpectatorTargetInfo.Interfaces;
+using EvoSC.Modules.Official.SpectatorTargetInfoModule.Interfaces;
namespace EvoSC.Modules.Official.SpectatorTargetInfoModule;
[Module(IsInternal = true)]
-public class SpectatorTargetInfoModule(ISpectatorTargetInfoService spectatorTargetInfoService) : EvoScModule,
- IToggleable
+public class SpectatorTargetInfoModule(ISpectatorTargetInfoService spectatorTargetInfoService)
+ : EvoScModule, IToggleable
{
- public Task EnableAsync()
- {
- 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/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/NewCpTime.mt b/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt
deleted file mode 100644
index 49ecc4b76..000000000
--- a/src/Modules/SpectatorTargetInfoModule/Templates/NewCpTime.mt
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/ReportSpecTarget.mt b/src/Modules/SpectatorTargetInfoModule/Templates/ReportSpecTarget.mt
new file mode 100644
index 000000000..1fedbce88
--- /dev/null
+++ b/src/Modules/SpectatorTargetInfoModule/Templates/ReportSpecTarget.mt
@@ -0,0 +1,28 @@
+
+
+
+
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/Scripts/SpectatorTargetInfo.ms b/src/Modules/SpectatorTargetInfoModule/Templates/Scripts/SpectatorTargetInfo.ms
new file mode 100644
index 000000000..3bc6f9edc
--- /dev/null
+++ b/src/Modules/SpectatorTargetInfoModule/Templates/Scripts/SpectatorTargetInfo.ms
@@ -0,0 +1,143 @@
+
\ No newline at end of file
diff --git a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt
index 2728ff2bc..4d7e5dabf 100644
--- a/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt
+++ b/src/Modules/SpectatorTargetInfoModule/Templates/SpectatorTargetInfo.mt
@@ -1,280 +1,111 @@
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/Modules/SpectatorTargetInfoModule/info.toml b/src/Modules/SpectatorTargetInfoModule/info.toml
index b3b1fdd7b..9fc9498f6 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"
+
+[dependencies]
+GameModeUiModule = "1.0.0"
diff --git a/tests/Modules/GameModeUiModule.Tests/Services/GameModeUiModuleServiceTests.cs b/tests/Modules/GameModeUiModule.Tests/Services/GameModeUiModuleServiceTests.cs
index 0f2fadff6..c871b7eb9 100644
--- a/tests/Modules/GameModeUiModule.Tests/Services/GameModeUiModuleServiceTests.cs
+++ b/tests/Modules/GameModeUiModule.Tests/Services/GameModeUiModuleServiceTests.cs
@@ -2,6 +2,7 @@
using EvoSC.Common.Interfaces.Services;
using EvoSC.Modules.Official.GameModeUiModule.Config;
using EvoSC.Modules.Official.GameModeUiModule.Interfaces;
+using EvoSC.Modules.Official.GameModeUiModule.Models;
using EvoSC.Modules.Official.GameModeUiModule.Services;
using EvoSC.Testing;
using GbxRemoteNet.Interfaces;
@@ -27,10 +28,11 @@ private IGameModeUiModuleService UiModuleServiceMock()
[InlineData("UnitTest", true, 0.0, 0.0, 1.0)]
[InlineData("UnitTest", false, -160.0, 80.0, 2.0)]
[InlineData("UnitTest", false, 160.0, -80.0, 0.5)]
- public async Task Generates_Property_Object(string id, bool visible, double x, double y, double scale)
+ public Task Generates_Property_Object(string id, bool visible, double x, double y, double scale)
{
var uiModuleService = UiModuleServiceMock();
- var uiModulePropertyObject = await uiModuleService.GeneratePropertyObjectAsync(id, visible, x, y, scale);
+ var uiModulePropertyObject =
+ uiModuleService.GeneratePropertyObject(new GameModeUiComponentSettings(id, visible, x, y, scale));
var idProperty = uiModulePropertyObject.GetType().GetProperty("id");
Assert.Equal(id, idProperty.GetValue(uiModulePropertyObject, null));
@@ -55,25 +57,44 @@ public async Task Generates_Property_Object(string id, bool visible, double x, d
var scaleUpdateProperty = uiModulePropertyObject.GetType().GetProperty("scale_update");
Assert.True(scaleUpdateProperty.GetValue(uiModulePropertyObject, null));
+
+ return Task.CompletedTask;
}
[Fact]
- public async Task Gets_Ui_Module_Properties_As_Json()
+ public Task Gets_Ui_Module_Properties_As_Json()
{
var uiModuleService = UiModuleServiceMock();
- var uiModuleProperties = await uiModuleService.GetUiModulesPropertiesJsonAsync();
+ var uiModuleProperties = uiModuleService.GetUiModulesPropertiesJson(uiModuleService.GetDefaultSettings());
Assert.IsType(uiModuleProperties);
JToken.Parse(uiModuleProperties);
+
+ return Task.CompletedTask;
}
[Fact]
public async Task Applies_Ui_Modules_Configuration()
{
var uiModuleService = UiModuleServiceMock();
- var uiModuleProperties = await uiModuleService.GetUiModulesPropertiesJsonAsync();
+ var uiModuleProperties = uiModuleService.GetUiModulesPropertiesJson(uiModuleService.GetDefaultSettings());
+
+ await uiModuleService.ApplyComponentSettingsAsync(uiModuleService.GetDefaultSettings());
+
+ _server.Remote.Verify(
+ remote => remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", uiModuleProperties),
+ Times.Once
+ );
+ }
+
+ [Fact]
+ public async Task Creates_And_Applies_Ui_Settings_From_Single_Arguments()
+ {
+ var uiModuleService = UiModuleServiceMock();
+ var uiComponentSettings = new GameModeUiComponentSettings("UnitTest", true, 123.0, 123.0, 1.0);
+ var uiModuleProperties = uiModuleService.GetUiModulesPropertiesJson([uiComponentSettings]);
- await uiModuleService.ApplyConfigurationAsync();
+ await uiModuleService.ApplyComponentSettingsAsync("UnitTest", true, 123.0, 123.0, 1.0);
_server.Remote.Verify(
remote => remote.TriggerModeScriptEventArrayAsync("Common.UIModules.SetProperties", uiModuleProperties),
diff --git a/tests/Modules/SpectatorCamModeModule.Tests/Services/SpectatorCamModeServiceTests.cs b/tests/Modules/SpectatorCamModeModule.Tests/Services/SpectatorCamModeServiceTests.cs
new file mode 100644
index 000000000..6e2420dba
--- /dev/null
+++ b/tests/Modules/SpectatorCamModeModule.Tests/Services/SpectatorCamModeServiceTests.cs
@@ -0,0 +1,68 @@
+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.SpectatorCamModeModule.Config;
+using EvoSC.Modules.Official.SpectatorCamModeModule.Interfaces;
+using EvoSC.Modules.Official.SpectatorCamModeModule.Services;
+using Moq;
+using Xunit;
+
+namespace EvoSC.Modules.Official.SpectatorCamModeModule.Tests.Services;
+
+public class SpectatorCamModeServiceTests
+{
+ private readonly Mock _manialinkManager = new();
+ private readonly Mock _gameModeUiModule = new();
+ private readonly Mock _settings = new();
+
+ private ISpectatorCamModeService CamModeServiceMock()
+ {
+ return new SpectatorCamModeService(
+ _manialinkManager.Object,
+ _gameModeUiModule.Object,
+ _settings.Object
+ );
+ }
+
+ [Fact]
+ public async Task SendsPersistentCamModeWidget()
+ {
+ var camModeService = CamModeServiceMock();
+ await camModeService.SendPersistentCamModeWidgetAsync();
+
+ _manialinkManager.Verify(m =>
+ m.SendPersistentManialinkAsync("SpectatorCamModeModule.SpectatorMode", It.IsAny