Skip to content

Commit

Permalink
276 spec info (#313)
Browse files Browse the repository at this point in the history
Co-authored-by: snixtho <snixtho@users.noreply.github.com>
  • Loading branch information
araszka and snixtho authored Oct 7, 2024
1 parent 8f7dedb commit c7f6e4d
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 73 deletions.
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

0 comments on commit c7f6e4d

Please sign in to comment.