Add AFK tracking system.
Allows us to see which players have not done any input and can be considered AFK.
This commit is contained in:
103
Content.Server/Afk/AfkManager.cs
Normal file
103
Content.Server/Afk/AfkManager.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.CCVar;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Afk
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks AFK (away from keyboard) status for players.
|
||||
/// </summary>
|
||||
/// <seealso cref="CCVars.AfkTime"/>
|
||||
public interface IAfkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Check whether this player is currently AFK.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to check.</param>
|
||||
/// <returns>True if the player is AFK, false otherwise.</returns>
|
||||
bool IsAfk(IPlayerSession player);
|
||||
|
||||
/// <summary>
|
||||
/// Resets AFK status for the player as if they just did an action and are definitely not AFK.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to set AFK status for.</param>
|
||||
void PlayerDidAction(IPlayerSession player);
|
||||
|
||||
void Initialize();
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public class AfkManager : IAfkManager, IEntityEventSubscriber
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IConsoleHost _consoleHost = default!;
|
||||
|
||||
private readonly Dictionary<IPlayerSession, TimeSpan> _lastActionTimes = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// Connecting, console commands and input commands all reset AFK status.
|
||||
|
||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||
_consoleHost.AnyCommandExecuted += ConsoleHostOnAnyCommandExecuted;
|
||||
|
||||
_entityManager.EventBus.SubscribeSessionEvent<FullInputCmdMessage>(
|
||||
EventSource.Network,
|
||||
this,
|
||||
HandleInputCmd);
|
||||
}
|
||||
|
||||
public void PlayerDidAction(IPlayerSession player)
|
||||
{
|
||||
if (player.Status == SessionStatus.Disconnected)
|
||||
// Make sure we don't re-add to the dictionary if the player is disconnected now.
|
||||
return;
|
||||
|
||||
_lastActionTimes[player] = _gameTiming.RealTime;
|
||||
}
|
||||
|
||||
public bool IsAfk(IPlayerSession player)
|
||||
{
|
||||
if (!_lastActionTimes.TryGetValue(player, out var time))
|
||||
// Some weird edge case like disconnected clients. Just say true I guess.
|
||||
return true;
|
||||
|
||||
var timeOut = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.AfkTime));
|
||||
return _gameTiming.RealTime - time > timeOut;
|
||||
}
|
||||
|
||||
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Disconnected)
|
||||
{
|
||||
_lastActionTimes.Remove(e.Session);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerDidAction(e.Session);
|
||||
}
|
||||
|
||||
private void ConsoleHostOnAnyCommandExecuted(IConsoleShell shell, string commandname, string argstr, string[] args)
|
||||
{
|
||||
if (shell.Player is IPlayerSession player)
|
||||
PlayerDidAction(player);
|
||||
}
|
||||
|
||||
private void HandleInputCmd(FullInputCmdMessage msg, EntitySessionEventArgs args)
|
||||
{
|
||||
PlayerDidAction((IPlayerSession) args.SenderSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Content.Server/Afk/IsAfkCommand.cs
Normal file
36
Content.Server/Afk/IsAfkCommand.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Afk
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class IsAfkCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "isafk";
|
||||
public string Description => "Checks if a specified player is AFK";
|
||||
public string Help => "Usage: isafk <playerName>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||
var afkManager = IoCManager.Resolve<IAfkManager>();
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteError("Need one argument");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!playerManager.TryGetSessionByUsername(args[0], out var player))
|
||||
{
|
||||
shell.WriteError("Unable to find that player");
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(afkManager.IsAfk(player) ? "They are indeed AFK" : "They are not AFK");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Afk;
|
||||
using Content.Server.AI.Utility;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.WorldState;
|
||||
@@ -92,6 +93,7 @@ namespace Content.Server.Entry
|
||||
IoCManager.Resolve<IPDAUplinkManager>().Initialize();
|
||||
IoCManager.Resolve<IAdminManager>().Initialize();
|
||||
IoCManager.Resolve<INpcBehaviorManager>().Initialize();
|
||||
IoCManager.Resolve<IAfkManager>().Initialize();
|
||||
_euiManager.Initialize();
|
||||
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Afk;
|
||||
using Content.Server.AI.Utility;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.WorldState;
|
||||
@@ -62,6 +63,7 @@ namespace Content.Server.IoC
|
||||
IoCManager.Register<IVoteManager, VoteManager>();
|
||||
IoCManager.Register<INpcBehaviorManager, NpcBehaviorManager>();
|
||||
IoCManager.Register<IPlayerLocator, PlayerLocator>();
|
||||
IoCManager.Register<IAfkManager, AfkManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,5 +382,15 @@ namespace Content.Shared.CCVar
|
||||
|
||||
public static readonly CVarDef<int> ChatMaxMessageLength =
|
||||
CVarDef.Create("chat.max_message_length", 1000, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* AFK
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// How long a client can go without any input before being considered AFK.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> AfkTime =
|
||||
CVarDef.Create("afk.time", 60f, CVar.SERVERONLY);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user