Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

276 spec info #313

Merged
merged 78 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
676e475
Update current map design to fit mockup
araszka Aug 20, 2024
f669ff6
Update next map widget design
araszka Aug 20, 2024
fe7f07c
Center author name in next map widget
araszka Aug 20, 2024
1f3da08
Merge remote-tracking branch 'origin/master' into 277-current-map-red…
araszka Aug 24, 2024
7dd5d85
Show correct diffs in spectator target info and update design
araszka Aug 24, 2024
9b94fc4
Stash
araszka Aug 26, 2024
face066
Add SpectatorTargetInfo tests
araszka Sep 1, 2024
8795740
Add more spectator target info tests
araszka Sep 1, 2024
3b4644d
Send updated widget to spectators if target player changes or is updated
araszka Sep 1, 2024
05c6614
Reset spectator info widgets on new round and hide on podium start
araszka Sep 1, 2024
31e8358
Remove spectators on disconnect and do not update widget if spec targ…
araszka Sep 1, 2024
5a3bdbf
Add team colors in spectator target info
araszka Sep 3, 2024
4cf93fa
Implement change spec target
araszka Sep 7, 2024
63fd01c
Make spectator info module hide Nadeo counterpart
araszka Sep 11, 2024
485d64a
Add request current spectator target after controller start to show w…
araszka Sep 11, 2024
e6a3914
Add custom cam mode widget
araszka Sep 14, 2024
51ed44f
Improve spectator info pad/keyboard detections
araszka Sep 14, 2024
635b3fe
Improve showing/hiding of spectator target info
araszka Sep 14, 2024
1066869
Simplify spectator cam mode module
araszka Sep 15, 2024
06531c4
Add dropup to select cam mode
araszka Sep 15, 2024
68fade0
Add tests for SpectatorCamModeModule
araszka Sep 15, 2024
9eb15f3
Make cam mode menu hidden by default
araszka Sep 15, 2024
c34949f
Improve detection of spectator target and simplify code
araszka Sep 18, 2024
897133e
Use 1 default rank in spectator target
araszka Sep 18, 2024
dce72d3
Fix existing spectator target tests
araszka Sep 18, 2024
751e83a
Remove redundant spec target reporting
araszka Sep 18, 2024
3cf1b98
Add tests for spectator target manialink controller
araszka Sep 18, 2024
e2bf309
Add doc comments for ISpectatorTargetInfoService
araszka Sep 20, 2024
7bbd370
Clean up GameModeUiModule
araszka Sep 21, 2024
ffb3dd6
Remove unnecessary code and add another overload to GameModeUiModuleS…
araszka Sep 21, 2024
6a0bada
Use GameModeUiModule in Scoreboard
araszka Sep 21, 2024
abd2d0f
Load GameModeUiModule before any other module else depndency might fail
araszka Sep 21, 2024
ef652c4
Add SpectatorTargetEventControllerTests
araszka Sep 21, 2024
9cd5692
Add tests for SpectatorTargetInfoService
araszka Sep 21, 2024
e677517
Add tests for CheckpointGroup
araszka Sep 21, 2024
ae3f0db
Fix TeamInfoEventControllerTests
araszka Sep 21, 2024
d262fa5
Merge remote-tracking branch 'origin/master' into 276-spec-info
araszka Sep 21, 2024
0400dc5
Merge fix
araszka Sep 21, 2024
eae31c1
Revert "Merge fix"
araszka Sep 21, 2024
328d6a9
Update SpectatorTargetInfoServiceTests
araszka Sep 21, 2024
77e599f
Remove commented out code from tests
araszka Sep 21, 2024
87a6685
Convert constants to static readonly properties
araszka Sep 21, 2024
c9cd6f9
Replace Moq with EvoSC.Testing reference
araszka Sep 21, 2024
f0e7f13
Fix dependencies in csproj
araszka Sep 21, 2024
7108c29
Add async suffix to methods
araszka Sep 21, 2024
8cfebb9
Add async suffix
araszka Sep 21, 2024
9fa18f1
Use Find instead of FirstOrDefault on List
araszka Sep 21, 2024
bdc18d0
Add curly braces
araszka Sep 21, 2024
0177d13
Fix spectator target tests
araszka Sep 21, 2024
e2b6fc9
Rename tests project to fit module name
araszka Sep 21, 2024
5bb6510
Update src/EvoSC.Common/Interfaces/Models/IPlayer.cs
araszka Sep 29, 2024
c7b685a
Add property comments to GameModeUiComponentSettings
araszka Sep 29, 2024
dbac768
Remove debug code
araszka Sep 29, 2024
9cf59fb
Make GameModeUiModuleService transient lifecycle
araszka Sep 29, 2024
99f2b96
Change log level in SpectatorTargetInfoService
araszka Sep 29, 2024
d8a66e6
Move maniascript of spectator mode to own file
araszka Sep 29, 2024
28df54d
Move SpectatorTargetInfo maniascript to own file
araszka Sep 29, 2024
c14d11b
Follow AAA principle in tests
araszka Sep 29, 2024
92ce113
Fix tests
araszka Sep 29, 2024
f468a93
Add reference to script
araszka Sep 29, 2024
31fa165
Fox show cam mode widget in warmups and do not reset on each new round
araszka Sep 30, 2024
732d691
Make cam mode selection equal width
araszka Sep 30, 2024
2d2c9ce
Prevent script error when changing spec target with one player on server
araszka Sep 30, 2024
1bceaa1
Always format time the same way in spec info
araszka Sep 30, 2024
0d508c7
Add warmup round mode script events
araszka Sep 30, 2024
f5efb78
Reset spectator target info on new warmup rounds
araszka Sep 30, 2024
ebc1916
Resend spectator target info to spectators on new rounds
araszka Sep 30, 2024
c9adf6e
Only show spec info and cam selection if UI sequence "playing"
araszka Sep 30, 2024
69ebdad
Merge remote-tracking branch 'origin/master' into 276-spec-info
araszka Sep 30, 2024
4bf996f
Remove controller
araszka Sep 30, 2024
8940dda
Merge remote-tracking branch 'origin/master' into 276-spec-info
araszka Oct 4, 2024
f2187b6
Fix spec previous player
araszka Oct 6, 2024
6ed3793
Add mutex to spec target info service to prevent race conditions
araszka Oct 6, 2024
c9452cb
Get time difference for spec info as absolute number
araszka Oct 6, 2024
d20ca8c
Add time attack support for spectator target info
araszka Oct 6, 2024
fdad06b
Add more tests for spec info
araszka Oct 6, 2024
933264f
Suppress non-nullable warning
araszka Oct 6, 2024
8faa996
Merge branch 'master' into 276-spec-info
araszka Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ public class SpectatorTargetInfoEventController(ISpectatorTargetInfoService spec
{
[Subscribe(GbxRemoteEvent.PlayerDisconnect)]
public Task OnPlayerDisconnectAsync(object sender, PlayerGbxEventArgs eventArgs) =>
spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(eventArgs.Login);
spectatorTargetInfoService.RemovePlayerAsync(eventArgs.Login);

[Subscribe(GbxRemoteEvent.BeginMap)]
public Task OnBeginMapAsync(object sender, MapGbxEventArgs eventArgs) =>
spectatorTargetInfoService.UpdateIsTeamsModeAsync();
public async Task OnBeginMapAsync(object sender, MapGbxEventArgs eventArgs)
{
await spectatorTargetInfoService.DetectIsTeamsModeAsync();
await spectatorTargetInfoService.DetectIsTimeAttackModeAsync();
}

[Subscribe(ModeScriptEvent.WayPoint)]
public Task OnWayPointAsync(object sender, WayPointEventArgs wayPointEventArgs) =>
Expand All @@ -44,4 +47,16 @@ public async Task OnNewWarmUpRoundAsync(object sender, WarmUpRoundEventArgs roun
await spectatorTargetInfoService.FetchAndCacheTeamInfoAsync();
await spectatorTargetInfoService.ResetWidgetForSpectatorsAsync();
}

[Subscribe(ModeScriptEvent.WarmUpStart)]
public Task OnWarmUpStartAsync(object sender, EventArgs args) =>
spectatorTargetInfoService.UpdateIsTimeAttackModeAsync(true);

[Subscribe(ModeScriptEvent.WarmUpEnd)]
public Task OnWarmUpEndAsync(object sender, EventArgs args) =>
spectatorTargetInfoService.DetectIsTimeAttackModeAsync();

[Subscribe(ModeScriptEvent.GiveUp)]
public Task OnPlayerGiveUpAsync(object sender, PlayerUpdateEventArgs args) =>
spectatorTargetInfoService.ClearCheckpointsAsync(args.Login);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public async Task ReportSpectatorTargetAsync(string targetLogin)
}
else
{
await spectatorTargetInfoService.RemovePlayerFromSpectatorsListAsync(spectatorLogin);
await spectatorTargetInfoService.RemovePlayerAsync(spectatorLogin);
await spectatorTargetInfoService.HideSpectatorInfoWidgetAsync(spectatorLogin);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public interface ISpectatorTargetInfoService
/// </summary>
/// <returns></returns>
public Task ClearCheckpointsAsync();

/// <summary>
/// Clears all registered checkpoint times of the given player.
/// </summary>
/// <returns></returns>
public Task ClearCheckpointsAsync(string playerLogin);

/// <summary>
/// Retrieve an IOnlinePlayer instance by their login.
Expand Down Expand Up @@ -59,9 +65,9 @@ public interface ISpectatorTargetInfoService
/// <summary>
/// Remove a player from the spectators list.
/// </summary>
/// <param name="spectatorLogin"></param>
/// <param name="playerLogin"></param>
/// <returns></returns>
public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin);
public Task RemovePlayerAsync(string playerLogin);

/// <summary>
/// Gets the logins of a players spectating the given target.
Expand Down Expand Up @@ -98,6 +104,12 @@ public interface ISpectatorTargetInfoService
/// <returns></returns>
public Dictionary<int, CheckpointsGroup> GetCheckpointTimes();

/// <summary>
/// Returns the current spectator targets.
/// </summary>
/// <returns></returns>
public Dictionary<string, IOnlinePlayer> GetSpectatorTargets();

/// <summary>
/// Resets the widget for all spectating players.
/// </summary>
Expand Down Expand Up @@ -166,10 +178,23 @@ public interface ISpectatorTargetInfoService
public Task FetchAndCacheTeamInfoAsync();

/// <summary>
/// Updates whether team mode is active or not.
/// Updates whether team mode is active.
/// </summary>
/// <returns></returns>
public Task DetectIsTeamsModeAsync();

/// <summary>
/// Detects whether time attack mode is active.
/// </summary>
/// <returns></returns>
public Task DetectIsTimeAttackModeAsync();

/// <summary>
/// Manually sets active state of time attack mode.
/// </summary>
/// <param name="isTimeAttack"></param>
/// <returns></returns>
public Task UpdateIsTeamsModeAsync();
public Task UpdateIsTimeAttackModeAsync(bool isTimeAttack);

/// <summary>
/// Hides the default game mode UI.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ public int GetRank(string playerLogin)
{
return rank;
}

rank++;
}

return rank;
}

public bool ForgetPlayer(string playerLogin) =>
(from checkpointData in this
where checkpointData.player.GetLogin() == playerLogin
select this.Remove(checkpointData)
).FirstOrDefault();
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ ILogger<SpectatorTargetInfoService> logger
private const string ReportTargetTemplate = "SpectatorTargetInfoModule.ReportSpecTarget";
private const string WidgetTemplate = "SpectatorTargetInfoModule.SpectatorTargetInfo";

private readonly object _checkpointTimesMutex = new();
private readonly object _spectatorTargetsMutex = new();
private readonly Dictionary<int, CheckpointsGroup> _checkpointTimes = new(); // cp-id -> CheckpointsGroup
private readonly Dictionary<string, IOnlinePlayer> _spectatorTargets = new(); // login -> IOnlinePlayer
private readonly Dictionary<PlayerTeam, TmTeamInfo> _teamInfos = new();
private bool _isTimeAttackMode;
private bool _isTeamsMode;

public async Task InitializeAsync()
{
await UpdateIsTeamsModeAsync();
await DetectIsTeamsModeAsync();
await DetectIsTimeAttackModeAsync();
await FetchAndCacheTeamInfoAsync();
await SendReportSpectatorTargetManialinkAsync();
await HideGameModeUiAsync();
Expand All @@ -53,32 +57,61 @@ public async Task AddCheckpointAsync(string playerLogin, int checkpointIndex, in
{
var player = await GetOnlinePlayerByLoginAsync(playerLogin);
var newCheckpointData = new CheckpointData(player, checkpointTime);
CheckpointsGroup checkpointsGroup = [];

if (!_checkpointTimes.TryGetValue(checkpointIndex, out var checkpointGroup))
lock (_checkpointTimesMutex)
{
checkpointGroup = [];
_checkpointTimes.Add(checkpointIndex, checkpointGroup);
}
if (_checkpointTimes.TryGetValue(checkpointIndex, out var existingCheckpointGroup))
{
checkpointsGroup = existingCheckpointGroup;
}

checkpointGroup.Add(newCheckpointData);
_checkpointTimes[checkpointIndex] = checkpointGroup;
checkpointsGroup.Add(newCheckpointData);
_checkpointTimes[checkpointIndex] = checkpointsGroup;
}

var spectatorLogins = GetLoginsOfPlayersSpectatingTarget(player).ToList();
if (spectatorLogins.IsNullOrEmpty())
{
return;
}

var leadingCheckpointData = checkpointGroup.First();
var leadingCheckpointData = checkpointsGroup.First();
var timeDifference = GetTimeDifference(leadingCheckpointData.time, newCheckpointData.time);

await SendSpectatorInfoWidgetAsync(spectatorLogins, player, checkpointGroup.GetRank(playerLogin),
timeDifference);
await SendSpectatorInfoWidgetAsync(
spectatorLogins,
player,
checkpointsGroup.GetRank(playerLogin),
timeDifference
);
}

public Task ClearCheckpointsAsync()
{
_checkpointTimes.Clear();
lock (_checkpointTimesMutex)
{
_checkpointTimes.Clear();
}

return Task.CompletedTask;
}

public Task ClearCheckpointsAsync(string playerLogin)
{
if (!_isTimeAttackMode)
{
//New round event is going to clear the entries.
return Task.CompletedTask;
}

lock (_checkpointTimesMutex)
{
foreach (var checkpointGroup in _checkpointTimes.Values)
{
checkpointGroup.ForgetPlayer(playerLogin);
}
}

return Task.CompletedTask;
}
Expand All @@ -101,12 +134,15 @@ public Task ClearCheckpointsAsync()

var targetPlayer = await GetOnlinePlayerByLoginAsync(targetLogin);

if (_spectatorTargets.TryGetValue(spectatorLogin, out var target) && target == targetPlayer)
lock (_spectatorTargetsMutex)
{
return null; //Player is already spectating target
}
if (_spectatorTargets.TryGetValue(spectatorLogin, out var target) && target == targetPlayer)
{
return null; //Player is already spectating target
}

_spectatorTargets[spectatorLogin] = targetPlayer;
_spectatorTargets[spectatorLogin] = targetPlayer;
}

logger.LogTrace("Updated spectator target {spectatorLogin} -> {targetLogin}.", spectatorLogin,
targetLogin);
Expand All @@ -123,25 +159,41 @@ public async Task SetSpectatorTargetAndSendAsync(string spectatorLogin, string t
}
}

public Task RemovePlayerFromSpectatorsListAsync(string spectatorLogin)
public Task RemovePlayerAsync(string playerLogin)
{
if (_spectatorTargets.Remove(spectatorLogin))
lock (_spectatorTargetsMutex)
{
logger.LogTrace("Removed spectator {spectatorLogin}.", spectatorLogin);
if (_spectatorTargets.Remove(playerLogin))
{
//Player was spectator
logger.LogTrace("Removed spectator {spectatorLogin}.", playerLogin);

return Task.CompletedTask;
}

//Player is driver, get all spectators
var spectatorLoginsToRemove = _spectatorTargets.Where(kv => kv.Value.GetLogin() == playerLogin)
.Select(kv => kv.Key);

foreach (var spectatorLogin in spectatorLoginsToRemove)
{
_spectatorTargets.Remove(spectatorLogin);
}
}

return Task.CompletedTask;
}

public IEnumerable<string> GetLoginsOfPlayersSpectatingTarget(IOnlinePlayer targetPlayer)
{
return _spectatorTargets.Where(specTarget => specTarget.Value.AccountId == targetPlayer.AccountId)
return GetSpectatorTargets()
.Where(specTarget => specTarget.Value.AccountId == targetPlayer.AccountId)
.Select(specTarget => specTarget.Key);
}

public int GetTimeDifference(int leadingCheckpointTime, int targetCheckpointTime)
{
return targetCheckpointTime - leadingCheckpointTime;
return int.Abs(targetCheckpointTime - leadingCheckpointTime);
}

public string GetTeamColor(PlayerTeam team)
Expand All @@ -152,7 +204,8 @@ public string GetTeamColor(PlayerTeam team)
public int GetLastCheckpointIndexOfPlayer(IOnlinePlayer player)
{
var playerLogin = player.GetLogin();
foreach (var (checkpointIndex, checkpointsGroup) in _checkpointTimes.Reverse())

foreach (var (checkpointIndex, checkpointsGroup) in GetCheckpointTimes().Reverse())
{
if (checkpointsGroup.GetPlayerCheckpointData(playerLogin) != null)
{
Expand All @@ -163,12 +216,25 @@ public int GetLastCheckpointIndexOfPlayer(IOnlinePlayer player)
return -1;
}

public Dictionary<int, CheckpointsGroup> GetCheckpointTimes() =>
_checkpointTimes;
public Dictionary<int, CheckpointsGroup> GetCheckpointTimes()
{
lock (_checkpointTimesMutex)
{
return _checkpointTimes;
}
}

public Dictionary<string, IOnlinePlayer> GetSpectatorTargets()
{
lock (_spectatorTargetsMutex)
{
return _spectatorTargets;
}
}

public async Task ResetWidgetForSpectatorsAsync()
{
foreach (var (spectatorLogin, targetPlayer) in _spectatorTargets)
foreach (var (spectatorLogin, targetPlayer) in GetSpectatorTargets())
{
var widgetData = GetWidgetData(targetPlayer, 1, 0);
await SendSpectatorInfoWidgetAsync(spectatorLogin, targetPlayer, widgetData);
Expand All @@ -182,7 +248,7 @@ public async Task SendSpectatorInfoWidgetAsync(string spectatorLogin, IOnlinePla
var targetRank = 1;
var timeDifference = 0;

if (_checkpointTimes.TryGetValue(checkpointIndex, out var checkpointsGroup))
if (GetCheckpointTimes().TryGetValue(checkpointIndex, out var checkpointsGroup))
{
var leadingCpData = checkpointsGroup.First();
var targetCpData = checkpointsGroup.GetPlayerCheckpointData(targetLogin);
Expand Down Expand Up @@ -236,13 +302,22 @@ public async Task FetchAndCacheTeamInfoAsync()
_teamInfos[PlayerTeam.Team2] = await server.Remote.GetTeamInfoAsync((int)PlayerTeam.Team2 + 1);
}

public async Task UpdateIsTeamsModeAsync()
public async Task DetectIsTeamsModeAsync()
{
_isTeamsMode =
await matchSettingsService.GetCurrentModeAsync() is DefaultModeScriptName.Teams
or DefaultModeScriptName.TmwtTeams;
_isTeamsMode = await matchSettingsService.GetCurrentModeAsync() is DefaultModeScriptName.Teams
or DefaultModeScriptName.TmwtTeams;
}

logger.LogInformation("Team mode is {state}", _isTeamsMode ? "active" : "not active");
public async Task DetectIsTimeAttackModeAsync()
{
_isTimeAttackMode = await matchSettingsService.GetCurrentModeAsync() is DefaultModeScriptName.TimeAttack;
}

public Task UpdateIsTimeAttackModeAsync(bool isTimeAttack)
{
_isTimeAttackMode = isTimeAttack;

return Task.CompletedTask;
}

public Task HideGameModeUiAsync() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Void FocusPlayer(CSmPlayer _Player) {
Void SpecPrevious(CMlLabel button, Boolean focus){
AnimatePop(button);
declare CSmPlayer target <=> GetNextSpawnedPlayer();
if(target == Null && focus){
if(target != Null && focus){
FocusPlayer(target);
}
}
Expand Down
Loading
Loading