Antag Rolebans (#35966)

Co-authored-by: beck-thompson <beck314159@hotmail.com>
Co-authored-by: Hannah Giovanna Dawson <karakkaraz@gmail.com>
This commit is contained in:
Errant
2025-09-17 23:59:07 +02:00
committed by GitHub
parent e1ba33814b
commit b692b6e33e
33 changed files with 898 additions and 283 deletions

View File

@@ -26,24 +26,25 @@ namespace Content.Server.Administration.Managers;
public sealed partial class BanManager : IBanManager, IPostInjectInit
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly ServerDbEntryManager _entryManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
[Dependency] private readonly ServerDbEntryManager _entryManager = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly UserDbDataManager _userDbData = default!;
private ISawmill _sawmill = default!;
public const string SawmillId = "admin.bans";
public const string JobPrefix = "Job:";
public const string PrefixAntag = "Antag:";
public const string PrefixJob = "Job:";
private readonly Dictionary<ICommonSession, List<ServerRoleBanDef>> _cachedRoleBans = new();
// Cached ban exemption flags are used to handle
@@ -91,30 +92,6 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
_cachedBanExemptions.Remove(player);
}
private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
{
banDef = await _db.AddServerRoleBanAsync(banDef);
if (banDef.UserId != null
&& _playerManager.TryGetSessionById(banDef.UserId, out var player)
&& _cachedRoleBans.TryGetValue(player, out var cachedBans))
{
cachedBans.Add(banDef);
}
return true;
}
public HashSet<string>? GetRoleBans(NetUserId playerUserId)
{
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
return null;
return _cachedRoleBans.TryGetValue(session, out var roleBans)
? roleBans.Select(banDef => banDef.Role).ToHashSet()
: null;
}
public void Restart()
{
// Clear out players that have disconnected.
@@ -232,23 +209,54 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
#endregion
#region Job Bans
#region Role Bans
// If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin.
// Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset.
public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
public async void CreateRoleBan<T>(
NetUserId? target,
string? targetUsername,
NetUserId? banningAdmin,
(IPAddress, int)? addressRange,
ImmutableTypedHwid? hwid,
ProtoId<T> role,
uint? minutes,
NoteSeverity severity,
string reason,
DateTimeOffset timeOfBan
) where T : class, IPrototype
{
if (!_prototypeManager.TryIndex(role, out JobPrototype? _))
string encodedRole;
// TODO: Note that it's possible to clash IDs here between a job and an antag. The refactor that introduced
// this check has consciously avoided refactoring Job and Antag prototype.
// Refactor Job- and Antag- Prototype to introduce a common RolePrototype, which will fix this possible clash.
//TODO remove this check as part of the above refactor
if (_prototypeManager.HasIndex<JobPrototype>(role) && _prototypeManager.HasIndex<AntagPrototype>(role))
{
throw new ArgumentException($"Invalid role '{role}'", nameof(role));
_sawmill.Error($"Creating role ban for {role}: cannot create role ban, role is both JobPrototype and AntagPrototype.");
return;
}
role = string.Concat(JobPrefix, role);
DateTimeOffset? expires = null;
if (minutes > 0)
// Don't trust the input: make sure the job or antag actually exists.
if (_prototypeManager.HasIndex<JobPrototype>(role))
encodedRole = PrefixJob + role;
else if (_prototypeManager.HasIndex<AntagPrototype>(role))
encodedRole = PrefixAntag + role;
else
{
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
_sawmill.Error($"Creating role ban for {role}: cannot create role ban, role is not a JobPrototype or an AntagPrototype.");
return;
}
DateTimeOffset? expires = null;
if (minutes > 0)
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
_systems.TryGetEntitySystem(out GameTicker? ticker);
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
@@ -266,21 +274,34 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
severity,
banningAdmin,
null,
role);
encodedRole);
if (!await AddRoleBan(banDef))
{
_chat.SendAdminAlert(Loc.GetString("cmd-roleban-existing", ("target", targetUsername ?? "null"), ("role", role)));
return;
}
var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires));
_chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length)));
if (target != null && _playerManager.TryGetSessionById(target.Value, out var session))
{
if (target is not null && _playerManager.TryGetSessionById(target.Value, out var session))
SendRoleBans(session);
}
private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
{
banDef = await _db.AddServerRoleBanAsync(banDef);
if (banDef.UserId != null
&& _playerManager.TryGetSessionById(banDef.UserId, out var player)
&& _cachedRoleBans.TryGetValue(player, out var cachedBans))
{
cachedBans.Add(banDef);
}
return true;
}
public async Task<string> PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
@@ -319,32 +340,109 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
}
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId)
{
return GetRoleBans<JobPrototype>(playerUserId, PrefixJob);
}
public HashSet<ProtoId<AntagPrototype>>? GetAntagBans(NetUserId playerUserId)
{
return GetRoleBans<AntagPrototype>(playerUserId, PrefixAntag);
}
private HashSet<ProtoId<T>>? GetRoleBans<T>(NetUserId playerUserId, string prefix) where T : class, IPrototype
{
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
return null;
if (!_cachedRoleBans.TryGetValue(session, out var roleBans))
return GetRoleBans<T>(session, prefix);
}
private HashSet<ProtoId<T>>? GetRoleBans<T>(ICommonSession playerSession, string prefix) where T : class, IPrototype
{
if (!_cachedRoleBans.TryGetValue(playerSession, out var roleBans))
return null;
return roleBans
.Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
.Select(ban => new ProtoId<JobPrototype>(ban.Role[JobPrefix.Length..]))
.Where(ban => ban.Role.StartsWith(prefix, StringComparison.Ordinal))
.Select(ban => new ProtoId<T>(ban.Role[prefix.Length..]))
.ToHashSet();
}
#endregion
public HashSet<string>? GetRoleBans(NetUserId playerUserId)
{
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
return null;
return _cachedRoleBans.TryGetValue(session, out var roleBans)
? roleBans.Select(banDef => banDef.Role).ToHashSet()
: null;
}
public bool IsRoleBanned(ICommonSession player, List<ProtoId<JobPrototype>> jobs)
{
return IsRoleBanned(player, jobs, PrefixJob);
}
public bool IsRoleBanned(ICommonSession player, List<ProtoId<AntagPrototype>> antags)
{
return IsRoleBanned(player, antags, PrefixAntag);
}
private bool IsRoleBanned<T>(ICommonSession player, List<ProtoId<T>> roles, string prefix) where T : class, IPrototype
{
var bans = GetRoleBans(player.UserId);
if (bans is null || bans.Count == 0)
return false;
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
foreach (var role in roles)
{
if (bans.Contains(prefix + role))
return true;
}
return false;
}
public void SendRoleBans(ICommonSession pSession)
{
var roleBans = _cachedRoleBans.GetValueOrDefault(pSession) ?? new List<ServerRoleBanDef>();
var jobBans = GetRoleBans<JobPrototype>(pSession, PrefixJob);
var jobBansList = new List<string>(jobBans?.Count ?? 0);
if (jobBans is not null)
{
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
foreach (var encodedId in jobBans)
{
jobBansList.Add(encodedId.ToString().Replace(PrefixJob, ""));
}
}
var antagBans = GetRoleBans<AntagPrototype>(pSession, PrefixAntag);
var antagBansList = new List<string>(antagBans?.Count ?? 0);
if (antagBans is not null)
{
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
foreach (var encodedId in antagBans)
{
antagBansList.Add(encodedId.ToString().Replace(PrefixAntag, ""));
}
}
var bans = new MsgRoleBans()
{
Bans = roleBans.Select(o => o.Role).ToList()
JobBans = jobBansList,
AntagBans = antagBansList,
};
_sawmill.Debug($"Sent rolebans to {pSession.Name}");
_sawmill.Debug($"Sent role bans to {pSession.Name}");
_netManager.ServerSendMessage(bans, pSession.Channel);
}
#endregion
public void PostInject()
{
_sawmill = _logManager.GetSawmill(SawmillId);