using Content.Client.Verbs.UI;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
+using Robust.Shared.Utility;
-namespace Content.Client.Administration.UI.CustomControls
+namespace Content.Client.Administration.UI.CustomControls;
+
+[GenerateTypedNameReferences]
+public sealed partial class PlayerListControl : BoxContainer
{
- [GenerateTypedNameReferences]
- public sealed partial class PlayerListControl : BoxContainer
- {
- private readonly AdminSystem _adminSystem;
+ private readonly AdminSystem _adminSystem;
- private List<PlayerInfo> _playerList = new();
- private readonly List<PlayerInfo> _sortedPlayerList = new();
+ private readonly IEntityManager _entManager;
+ private readonly IUserInterfaceManager _uiManager;
+
+ private PlayerInfo? _selectedPlayer;
- public event Action<PlayerInfo?>? OnSelectionChanged;
- public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
+ private List<PlayerInfo> _playerList = new();
+ private readonly List<PlayerInfo> _sortedPlayerList = new();
- public Func<PlayerInfo, string, string>? OverrideText;
- public Comparison<PlayerInfo>? Comparison;
+ public Comparison<PlayerInfo>? Comparison;
+ public Func<PlayerInfo, string, string>? OverrideText;
- private IEntityManager _entManager;
- private IUserInterfaceManager _uiManager;
+ public PlayerListControl()
+ {
+ _entManager = IoCManager.Resolve<IEntityManager>();
+ _uiManager = IoCManager.Resolve<IUserInterfaceManager>();
+ _adminSystem = _entManager.System<AdminSystem>();
+ RobustXamlLoader.Load(this);
+ // Fill the Option data
+ PlayerListContainer.ItemPressed += PlayerListItemPressed;
+ PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
+ PlayerListContainer.GenerateItem += GenerateButton;
+ PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
+ PopulateList(_adminSystem.PlayerList);
+ FilterLineEdit.OnTextChanged += _ => FilterList();
+ _adminSystem.PlayerListChanged += PopulateList;
+ BackgroundPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = new Color(32, 32, 40) };
+ }
- private PlayerInfo? _selectedPlayer;
+ public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
- public PlayerListControl()
- {
- _entManager = IoCManager.Resolve<IEntityManager>();
- _uiManager = IoCManager.Resolve<IUserInterfaceManager>();
- _adminSystem = _entManager.System<AdminSystem>();
- RobustXamlLoader.Load(this);
- // Fill the Option data
- PlayerListContainer.ItemPressed += PlayerListItemPressed;
- PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
- PlayerListContainer.GenerateItem += GenerateButton;
- PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
- PopulateList(_adminSystem.PlayerList);
- FilterLineEdit.OnTextChanged += _ => FilterList();
- _adminSystem.PlayerListChanged += PopulateList;
- BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)};
- }
+ public event Action<PlayerInfo?>? OnSelectionChanged;
- private void PlayerListNoItemSelected()
- {
- _selectedPlayer = null;
- OnSelectionChanged?.Invoke(null);
- }
+ private void PlayerListNoItemSelected()
+ {
+ _selectedPlayer = null;
+ OnSelectionChanged?.Invoke(null);
+ }
- private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
- {
- if (args == null || data is not PlayerListData {Info: var selectedPlayer})
- return;
+ private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
+ {
+ if (args == null || data is not PlayerListData { Info: var selectedPlayer })
+ return;
- if (selectedPlayer == _selectedPlayer)
- return;
+ if (selectedPlayer == _selectedPlayer)
+ return;
- if (args.Event.Function != EngineKeyFunctions.UIClick)
- return;
+ if (args.Event.Function != EngineKeyFunctions.UIClick)
+ return;
- OnSelectionChanged?.Invoke(selectedPlayer);
- _selectedPlayer = selectedPlayer;
+ OnSelectionChanged?.Invoke(selectedPlayer);
+ _selectedPlayer = selectedPlayer;
- // update label text. Only required if there is some override (e.g. unread bwoink count).
- if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
- label.Text = GetText(selectedPlayer);
- }
+ // update label text. Only required if there is some override (e.g. unread bwoink count).
+ if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
+ label.Text = GetText(selectedPlayer);
+ }
- private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
- {
- if (args == null || data is not PlayerListData { Info: var selectedPlayer })
- return;
+ private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
+ {
+ if (args == null || data is not PlayerListData { Info: var selectedPlayer })
+ return;
- if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
- return;
+ if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
+ return;
- _uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
- args.Handle();
- }
+ _uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
+ args.Handle();
+ }
- public void StopFiltering()
- {
- FilterLineEdit.Text = string.Empty;
- }
+ public void StopFiltering()
+ {
+ FilterLineEdit.Text = string.Empty;
+ }
- private void FilterList()
+ private void FilterList()
+ {
+ _sortedPlayerList.Clear();
+ foreach (var info in _playerList)
{
- _sortedPlayerList.Clear();
- foreach (var info in _playerList)
- {
- var displayName = $"{info.CharacterName} ({info.Username})";
- if (info.IdentityName != info.CharacterName)
- displayName += $" [{info.IdentityName}]";
- if (!string.IsNullOrEmpty(FilterLineEdit.Text)
- && !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
- continue;
- _sortedPlayerList.Add(info);
- }
-
- if (Comparison != null)
- _sortedPlayerList.Sort((a, b) => Comparison(a, b));
-
- PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
- if (_selectedPlayer != null)
- PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
+ var displayName = $"{info.CharacterName} ({info.Username})";
+ if (info.IdentityName != info.CharacterName)
+ displayName += $" [{info.IdentityName}]";
+ if (!string.IsNullOrEmpty(FilterLineEdit.Text)
+ && !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
+ continue;
+ _sortedPlayerList.Add(info);
}
- public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
- {
- players ??= _adminSystem.PlayerList;
+ if (Comparison != null)
+ _sortedPlayerList.Sort((a, b) => Comparison(a, b));
- _playerList = players.ToList();
- if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
- _selectedPlayer = null;
+ // Ensure pinned players are always at the top
+ _sortedPlayerList.Sort((a, b) => a.IsPinned != b.IsPinned && a.IsPinned ? -1 : 1);
- FilterList();
- }
+ PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
+ if (_selectedPlayer != null)
+ PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
+ }
- private string GetText(PlayerInfo info)
- {
- var text = $"{info.CharacterName} ({info.Username})";
- if (OverrideText != null)
- text = OverrideText.Invoke(info, text);
- return text;
- }
+ public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
+ {
+ players ??= _adminSystem.PlayerList;
- private void GenerateButton(ListData data, ListContainerButton button)
- {
- if (data is not PlayerListData { Info: var info })
- return;
-
- button.AddChild(new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Children =
- {
- new Label
- {
- ClipText = true,
- Text = GetText(info)
- }
- }
- });
-
- button.AddStyleClass(ListContainer.StyleClassListContainerButton);
- }
+ _playerList = players.ToList();
+ if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
+ _selectedPlayer = null;
+
+ FilterList();
}
- public record PlayerListData(PlayerInfo Info) : ListData;
+
+ private string GetText(PlayerInfo info)
+ {
+ var text = $"{info.CharacterName} ({info.Username})";
+ if (OverrideText != null)
+ text = OverrideText.Invoke(info, text);
+ return text;
+ }
+
+ private void GenerateButton(ListData data, ListContainerButton button)
+ {
+ if (data is not PlayerListData { Info: var info })
+ return;
+
+ var entry = new PlayerListEntry();
+ entry.Setup(info, OverrideText);
+ entry.OnPinStatusChanged += _ =>
+ {
+ FilterList();
+ };
+
+ button.AddChild(entry);
+ button.AddStyleClass(ListContainer.StyleClassListContainerButton);
+ }
}
+
+public record PlayerListData(PlayerInfo Info) : ListData;
using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.Afk;
+using Content.Server.Database;
using Content.Server.Discord;
using Content.Server.GameTicking;
using Content.Server.Players.RateLimiting;
using Content.Shared.Administration;
using Content.Shared.CCVar;
+using Content.Shared.GameTicking;
using Content.Shared.Mind;
using JetBrains.Annotations;
using Robust.Server.Player;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly SharedMindSystem _minds = default!;
[Dependency] private readonly IAfkManager _afkManager = default!;
+ [Dependency] private readonly IServerDbManager _dbManager = default!;
[Dependency] private readonly PlayerRateLimitManager _rateLimit = default!;
[GeneratedRegex(@"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")]
private string _footerIconUrl = string.Empty;
private string _avatarUrl = string.Empty;
private string _serverName = string.Empty;
- private readonly Dictionary<NetUserId, (string? id, string username, string description, string? characterName, GameRunLevel lastRunLevel)> _relayMessages = new();
+
+ private readonly
+ Dictionary<NetUserId, (string? id, string username, string description, string? characterName, GameRunLevel
+ lastRunLevel)> _relayMessages = new();
+
private Dictionary<NetUserId, string> _oldMessageIds = new();
private readonly Dictionary<NetUserId, Queue<string>> _messageQueues = new();
private readonly HashSet<NetUserId> _processingChannels = new();
private const string TooLongText = "... **(too long)**";
private int _maxAdditionalChars;
+ private readonly Dictionary<NetUserId, DateTime> _activeConversations = new();
public override void Initialize()
{
Subs.CVar(_config, CVars.GameHostName, OnServerNameChanged, true);
Subs.CVar(_config, CCVars.AdminAhelpOverrideClientName, OnOverrideChanged, true);
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("AHELP");
- _maxAdditionalChars = GenerateAHelpMessage("", "", true, _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"), _gameTicker.RunLevel, playedSound: false).Length;
+ var defaultParams = new AHelpMessageParams(
+ string.Empty,
+ string.Empty,
+ true,
+ _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"),
+ _gameTicker.RunLevel,
+ playedSound: false
+ );
+ _maxAdditionalChars = GenerateAHelpMessage(defaultParams).Length;
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameRunLevelChanged);
SubscribeNetworkEvent<BwoinkClientTypingUpdated>(OnClientTypingUpdated);
+ SubscribeLocalEvent<RoundRestartCleanupEvent>(_ => _activeConversations.Clear());
- _rateLimit.Register(
+ _rateLimit.Register(
RateLimitKey,
new RateLimitRegistration
{
_overrideClientName = obj;
}
- private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
+ private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
+ if (e.NewStatus == SessionStatus.Disconnected)
+ {
+ if (_activeConversations.TryGetValue(e.Session.UserId, out var lastMessageTime))
+ {
+ var timeSinceLastMessage = DateTime.Now - lastMessageTime;
+ if (timeSinceLastMessage > TimeSpan.FromMinutes(5))
+ {
+ _activeConversations.Remove(e.Session.UserId);
+ return; // Do not send disconnect message if timeout exceeded
+ }
+ }
+
+ // Check if the user has been banned
+ var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null);
+ if (ban != null)
+ {
+ var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason));
+ NotifyAdmins(e.Session, banMessage, PlayerStatusType.Banned);
+ _activeConversations.Remove(e.Session.UserId);
+ return;
+ }
+ }
+
+ // Notify all admins if a player disconnects or reconnects
+ var message = e.NewStatus switch
+ {
+ SessionStatus.Connected => Loc.GetString("bwoink-system-player-reconnecting"),
+ SessionStatus.Disconnected => Loc.GetString("bwoink-system-player-disconnecting"),
+ _ => null
+ };
+
+ if (message != null)
+ {
+ var statusType = e.NewStatus == SessionStatus.Connected
+ ? PlayerStatusType.Connected
+ : PlayerStatusType.Disconnected;
+ NotifyAdmins(e.Session, message, statusType);
+ }
+
if (e.NewStatus != SessionStatus.InGame)
return;
RaiseNetworkEvent(new BwoinkDiscordRelayUpdated(!string.IsNullOrWhiteSpace(_webhookUrl)), e.Session);
}
+ private void NotifyAdmins(ICommonSession session, string message, PlayerStatusType statusType)
+ {
+ if (!_activeConversations.ContainsKey(session.UserId))
+ {
+ // If the user is not part of an active conversation, do not notify admins.
+ return;
+ }
+
+ // Get the current timestamp
+ var timestamp = DateTime.Now.ToString("HH:mm:ss");
+ var roundTime = _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss");
+
+ // Determine the icon based on the status type
+ string icon = statusType switch
+ {
+ PlayerStatusType.Connected => ":green_circle:",
+ PlayerStatusType.Disconnected => ":red_circle:",
+ PlayerStatusType.Banned => ":no_entry:",
+ _ => ":question:"
+ };
+
+ // Create the message parameters for Discord
+ var messageParams = new AHelpMessageParams(
+ session.Name,
+ message,
+ true,
+ roundTime,
+ _gameTicker.RunLevel,
+ playedSound: true,
+ icon: icon
+ );
+
+ // Create the message for in-game with username
+ var color = statusType switch
+ {
+ PlayerStatusType.Connected => Color.Green.ToHex(),
+ PlayerStatusType.Disconnected => Color.Yellow.ToHex(),
+ PlayerStatusType.Banned => Color.Orange.ToHex(),
+ _ => Color.Gray.ToHex(),
+ };
+ var inGameMessage = $"[color={color}]{session.Name} {message}[/color]";
+
+ var bwoinkMessage = new BwoinkTextMessage(
+ userId: session.UserId,
+ trueSender: SystemUserId,
+ text: inGameMessage,
+ sentAt: DateTime.Now,
+ playSound: false
+ );
+
+ var admins = GetTargetAdmins();
+ foreach (var admin in admins)
+ {
+ RaiseNetworkEvent(bwoinkMessage, admin);
+ }
+
+ // Enqueue the message for Discord relay
+ if (_webhookUrl != string.Empty)
+ {
+ // if (!_messageQueues.ContainsKey(session.UserId))
+ // _messageQueues[session.UserId] = new Queue<string>();
+ //
+ // var escapedText = FormattedMessage.EscapeText(message);
+ // messageParams.Message = escapedText;
+ //
+ // var discordMessage = GenerateAHelpMessage(messageParams);
+ // _messageQueues[session.UserId].Enqueue(discordMessage);
+
+ var queue = _messageQueues.GetOrNew(session.UserId);
+ var escapedText = FormattedMessage.EscapeText(message);
+ messageParams.Message = escapedText;
+ var discordMessage = GenerateAHelpMessage(messageParams);
+ queue.Enqueue(discordMessage);
+ }
+ }
+
private void OnGameRunLevelChanged(GameRunLevelChangedEvent args)
{
// Don't make a new embed if we
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
- _sawmill.Log(LogLevel.Error, $"Discord returned bad status code when trying to get webhook data (perhaps the webhook URL is invalid?): {response.StatusCode}\nResponse: {content}");
+ _sawmill.Log(LogLevel.Error,
+ $"Discord returned bad status code when trying to get webhook data (perhaps the webhook URL is invalid?): {response.StatusCode}\nResponse: {content}");
return;
}
// Whether the message will become too long after adding these new messages
var tooLong = exists && messages.Sum(msg => Math.Min(msg.Length, MessageLengthCap) + "\n".Length)
- + existingEmbed.description.Length > DescriptionMax;
+ + existingEmbed.description.Length > DescriptionMax;
// If there is no existing embed, or it is getting too long, we create a new embed
if (!exists || tooLong)
if (lookup == null)
{
- _sawmill.Log(LogLevel.Error, $"Unable to find player for NetUserId {userId} when sending discord webhook.");
+ _sawmill.Log(LogLevel.Error,
+ $"Unable to find player for NetUserId {userId} when sending discord webhook.");
_relayMessages.Remove(userId);
return;
}
{
if (tooLong && existingEmbed.id != null)
{
- linkToPrevious = $"**[Go to previous embed of this round](https://discord.com/channels/{guildId}/{channelId}/{existingEmbed.id})**\n";
+ linkToPrevious =
+ $"**[Go to previous embed of this round](https://discord.com/channels/{guildId}/{channelId}/{existingEmbed.id})**\n";
}
else if (_oldMessageIds.TryGetValue(userId, out var id) && !string.IsNullOrEmpty(id))
{
- linkToPrevious = $"**[Go to last round's conversation with this player](https://discord.com/channels/{guildId}/{channelId}/{id})**\n";
+ linkToPrevious =
+ $"**[Go to last round's conversation with this player](https://discord.com/channels/{guildId}/{channelId}/{id})**\n";
}
}
GameRunLevel.PreRoundLobby => "\n\n:arrow_forward: _**Pre-round lobby started**_\n",
GameRunLevel.InRound => "\n\n:arrow_forward: _**Round started**_\n",
GameRunLevel.PostRound => "\n\n:stop_button: _**Post-round started**_\n",
- _ => throw new ArgumentOutOfRangeException(nameof(_gameTicker.RunLevel), $"{_gameTicker.RunLevel} was not matched."),
+ _ => throw new ArgumentOutOfRangeException(nameof(_gameTicker.RunLevel),
+ $"{_gameTicker.RunLevel} was not matched."),
};
existingEmbed.lastRunLevel = _gameTicker.RunLevel;
existingEmbed.description += $"\n{message}";
}
- var payload = GeneratePayload(existingEmbed.description, existingEmbed.username, existingEmbed.characterName);
+ var payload = GeneratePayload(existingEmbed.description,
+ existingEmbed.username,
+ existingEmbed.characterName);
// If there is no existing embed, create a new one
// Otherwise patch (edit) it
var content = await request.Content.ReadAsStringAsync();
if (!request.IsSuccessStatusCode)
{
- _sawmill.Log(LogLevel.Error, $"Discord returned bad status code when posting message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
+ _sawmill.Log(LogLevel.Error,
+ $"Discord returned bad status code when posting message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
_relayMessages.Remove(userId);
return;
}
var id = JsonNode.Parse(content)?["id"];
if (id == null)
{
- _sawmill.Log(LogLevel.Error, $"Could not find id in json-content returned from discord webhook: {content}");
+ _sawmill.Log(LogLevel.Error,
+ $"Could not find id in json-content returned from discord webhook: {content}");
_relayMessages.Remove(userId);
return;
}
if (!request.IsSuccessStatusCode)
{
var content = await request.Content.ReadAsStringAsync();
- _sawmill.Log(LogLevel.Error, $"Discord returned bad status code when patching message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
+ _sawmill.Log(LogLevel.Error,
+ $"Discord returned bad status code when patching message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
_relayMessages.Remove(userId);
return;
}
: $"pre-round lobby for round {_gameTicker.RoundId + 1}",
GameRunLevel.InRound => $"round {_gameTicker.RoundId}",
GameRunLevel.PostRound => $"post-round {_gameTicker.RoundId}",
- _ => throw new ArgumentOutOfRangeException(nameof(_gameTicker.RunLevel), $"{_gameTicker.RunLevel} was not matched."),
+ _ => throw new ArgumentOutOfRangeException(nameof(_gameTicker.RunLevel),
+ $"{_gameTicker.RunLevel} was not matched."),
};
return new WebhookPayload
protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs)
{
base.OnBwoinkTextMessage(message, eventArgs);
+ _activeConversations[message.UserId] = DateTime.Now;
var senderSession = eventArgs.SenderSession;
// TODO: Sanitize text?
string bwoinkText;
- if (senderAdmin is not null && senderAdmin.Flags == AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently.
+ if (senderAdmin is not null &&
+ senderAdmin.Flags ==
+ AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently.
{
bwoinkText = $"[color=purple]{senderSession.Name}[/color]";
}
{
string overrideMsgText;
// Doing the same thing as above, but with the override name. Theres probably a better way to do this.
- if (senderAdmin is not null && senderAdmin.Flags == AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently.
+ if (senderAdmin is not null &&
+ senderAdmin.Flags ==
+ AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently.
{
overrideMsgText = $"[color=purple]{_overrideClientName}[/color]";
}
overrideMsgText = $"{(message.PlaySound ? "" : "(S) ")}{overrideMsgText}: {escapedText}";
- RaiseNetworkEvent(new BwoinkTextMessage(message.UserId, senderSession.UserId, overrideMsgText, playSound: playSound), session.Channel);
+ RaiseNetworkEvent(new BwoinkTextMessage(message.UserId,
+ senderSession.UserId,
+ overrideMsgText,
+ playSound: playSound),
+ session.Channel);
}
else
RaiseNetworkEvent(msg, session.Channel);
{
str = str[..(DescriptionMax - _maxAdditionalChars - unameLength)];
}
+
var nonAfkAdmins = GetNonAfkAdmins();
- _messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(senderSession.Name, str, !personalChannel, _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"), _gameTicker.RunLevel, playedSound: playSound, noReceivers: nonAfkAdmins.Count == 0));
+ var messageParams = new AHelpMessageParams(
+ senderSession.Name,
+ str,
+ !personalChannel,
+ _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"),
+ _gameTicker.RunLevel,
+ playedSound: playSound,
+ noReceivers: nonAfkAdmins.Count == 0
+ );
+ _messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(messageParams));
}
if (admins.Count != 0 || sendsWebhook)
private IList<INetChannel> GetNonAfkAdmins()
{
return _adminManager.ActiveAdmins
- .Where(p => (_adminManager.GetAdminData(p)?.HasFlag(AdminFlags.Adminhelp) ?? false) && !_afkManager.IsAfk(p))
+ .Where(p => (_adminManager.GetAdminData(p)?.HasFlag(AdminFlags.Adminhelp) ?? false) &&
+ !_afkManager.IsAfk(p))
.Select(p => p.Channel)
.ToList();
}
.ToList();
}
- private static string GenerateAHelpMessage(string username, string message, bool admin, string roundTime, GameRunLevel roundState, bool playedSound, bool noReceivers = false)
+ private static string GenerateAHelpMessage(AHelpMessageParams parameters)
{
var stringbuilder = new StringBuilder();
- if (admin)
+ if (parameters.Icon != null)
+ stringbuilder.Append(parameters.Icon);
+ else if (parameters.IsAdmin)
stringbuilder.Append(":outbox_tray:");
- else if (noReceivers)
+ else if (parameters.NoReceivers)
stringbuilder.Append(":sos:");
else
stringbuilder.Append(":inbox_tray:");
- if(roundTime != string.Empty && roundState == GameRunLevel.InRound)
- stringbuilder.Append($" **{roundTime}**");
- if (!playedSound)
+ if (parameters.RoundTime != string.Empty && parameters.RoundState == GameRunLevel.InRound)
+ stringbuilder.Append($" **{parameters.RoundTime}**");
+ if (!parameters.PlayedSound)
stringbuilder.Append(" **(S)**");
- stringbuilder.Append($" **{username}:** ");
- stringbuilder.Append(message);
+
+ if (parameters.Icon == null)
+ stringbuilder.Append($" **{parameters.Username}:** ");
+ else
+ stringbuilder.Append($" **{parameters.Username}** ");
+ stringbuilder.Append(parameters.Message);
return stringbuilder.ToString();
}
}
-}
+ public sealed class AHelpMessageParams
+ {
+ public string Username { get; set; }
+ public string Message { get; set; }
+ public bool IsAdmin { get; set; }
+ public string RoundTime { get; set; }
+ public GameRunLevel RoundState { get; set; }
+ public bool PlayedSound { get; set; }
+ public bool NoReceivers { get; set; }
+ public string? Icon { get; set; }
+
+ public AHelpMessageParams(
+ string username,
+ string message,
+ bool isAdmin,
+ string roundTime,
+ GameRunLevel roundState,
+ bool playedSound,
+ bool noReceivers = false,
+ string? icon = null)
+ {
+ Username = username;
+ Message = message;
+ IsAdmin = isAdmin;
+ RoundTime = roundTime;
+ RoundState = roundState;
+ PlayedSound = playedSound;
+ NoReceivers = noReceivers;
+ Icon = icon;
+ }
+ }
+
+ public enum PlayerStatusType
+ {
+ Connected,
+ Disconnected,
+ Banned,
+ }
+}