Admin alerts now link players with tpto (#40472)

* Admin alerts now link players with tpto

* Add coords

* Slarti tweaks!

* He saw my minor spelling mistake - its over...
This commit is contained in:
beck-thompson
2025-10-26 19:40:07 -07:00
committed by GitHub
parent 5084fe456f
commit 82ab14da3a
6 changed files with 145 additions and 7 deletions

View File

@@ -31,6 +31,11 @@ internal sealed class ChatManager : IChatManager
// See server-side manager. This just exists for shared code. // See server-side manager. This just exists for shared code.
} }
public void SendAdminAlertNoFormatOrEscape(string message)
{
// See server-side manager. This just exists for shared code.
}
public void SendMessage(string text, ChatSelectChannel channel) public void SendMessage(string text, ChatSelectChannel channel)
{ {
var str = text.ToString(); var str = text.ToString();

View File

@@ -12,13 +12,16 @@ using Content.Shared.Database;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Players.PlayTimeTracking;
using Prometheus; using Prometheus;
using Robust.Server.GameObjects;
using Robust.Shared; using Robust.Shared;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Reflection; using Robust.Shared.Reflection;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Logs; namespace Content.Server.Administration.Logs;
@@ -338,7 +341,7 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
Players = players, Players = players,
}; };
DoAdminAlerts(players, message, impact); DoAdminAlerts(players, message, impact, handler);
if (preRound) if (preRound)
{ {
@@ -380,6 +383,34 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
return players; return players;
} }
/// <summary>
/// Get a list of coordinates from the <see cref="LogStringHandler"/>s values. Will transform all coordinate types
/// to map coordinates!
/// </summary>
/// <returns>A list of map coordinates that were found in the value input, can return an empty list.</returns>
private List<MapCoordinates> GetCoordinates(Dictionary<string, object?> values)
{
List<MapCoordinates> coordList = new();
EntityManager.TrySystem(out TransformSystem? transform);
foreach (var value in values.Values)
{
switch (value)
{
case EntityCoordinates entCords:
if (transform != null)
coordList.Add(transform.ToMapCoordinates(entCords));
continue;
case MapCoordinates mapCord:
coordList.Add(mapCord);
continue;
}
}
return coordList;
}
private void AddPlayer(List<AdminLogPlayer> players, Guid user, int logId) private void AddPlayer(List<AdminLogPlayer> players, Guid user, int logId)
{ {
// The majority of logs have a single player, or maybe two. Instead of allocating a List<AdminLogPlayer> and // The majority of logs have a single player, or maybe two. Instead of allocating a List<AdminLogPlayer> and
@@ -397,10 +428,11 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
}); });
} }
private void DoAdminAlerts(List<AdminLogPlayer> players, string message, LogImpact impact) private void DoAdminAlerts(List<AdminLogPlayer> players, string message, LogImpact impact, LogStringHandler handler)
{ {
var adminLog = false; var adminLog = false;
var logMessage = message; var logMessage = message;
var playerNetEnts = new List<(NetEntity, string)>();
foreach (var player in players) foreach (var player in players)
{ {
@@ -419,6 +451,8 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
("name", cachedInfo.CharacterName), ("name", cachedInfo.CharacterName),
("subtype", subtype)); ("subtype", subtype));
} }
if (cachedInfo != null && cachedInfo.NetEntity != null)
playerNetEnts.Add((cachedInfo.NetEntity.Value, cachedInfo.CharacterName));
} }
if (adminLog) if (adminLog)
@@ -442,7 +476,73 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
} }
if (adminLog) if (adminLog)
{
_chat.SendAdminAlert(logMessage); _chat.SendAdminAlert(logMessage);
if (CreateTpLinks(playerNetEnts, out var tpLinks))
_chat.SendAdminAlertNoFormatOrEscape(tpLinks);
var coords = GetCoordinates(handler.Values);
if (CreateCordLinks(coords, out var cordLinks))
_chat.SendAdminAlertNoFormatOrEscape(cordLinks);
}
}
/// <summary>
/// Creates a list of tpto command links of the given players
/// </summary>
private bool CreateTpLinks(List<(NetEntity NetEnt, string CharacterName)> players, out string outString)
{
outString = string.Empty;
if (players.Count == 0)
return false;
outString = Loc.GetString("admin-alert-tp-to-players-header");
for (var i = 0; i < players.Count; i++)
{
var player = players[i];
outString += $"[cmdlink=\"{EscapeText(player.CharacterName)}\" command=\"tpto {player.NetEnt}\"/]";
if (i < players.Count - 1)
outString += ", ";
}
return true;
}
/// <summary>
/// Creates a list of toto command links for the given map coordinates.
/// </summary>
private bool CreateCordLinks(List<MapCoordinates> cords, out string outString)
{
outString = string.Empty;
if (cords.Count == 0)
return false;
outString = Loc.GetString("admin-alert-tp-to-coords-header");
for (var i = 0; i < cords.Count; i++)
{
var cord = cords[i];
outString += $"[cmdlink=\"{cord.ToString()}\" command=\"tp {cord.X} {cord.Y} {cord.MapId}\"/]";
if (i < cords.Count - 1)
outString += ", ";
}
return true;
}
/// <summary>
/// Escape the given text to not allow breakouts of the cmdlink tags.
/// </summary>
private string EscapeText(string text)
{
return FormattedMessage.EscapeText(text).Replace("\"", "\\\"").Replace("'", "\\'");
} }
public async Task<List<SharedAdminLog>> All(LogFilter? filter = null, Func<List<SharedAdminLog>>? listProvider = null) public async Task<List<SharedAdminLog>> All(LogFilter? filter = null, Func<List<SharedAdminLog>>? listProvider = null)

View File

@@ -160,14 +160,20 @@ internal sealed partial class ChatManager : IChatManager
public void SendAdminAlert(string message) public void SendAdminAlert(string message)
{ {
var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message",
("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message))); ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message)));
ChatMessageToMany(ChatChannel.AdminAlert, message, wrappedMessage, default, false, true, clients); SendAdminAlertNoFormatOrEscape(wrappedMessage);
} }
public void SendAdminAlertNoFormatOrEscape(string message)
{
var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
ChatMessageToMany(ChatChannel.AdminAlert, message, message, default, false, true, clients);
}
public void SendAdminAlert(EntityUid player, string message) public void SendAdminAlert(EntityUid player, string message)
{ {
var mindSystem = _entityManager.System<SharedMindSystem>(); var mindSystem = _entityManager.System<SharedMindSystem>();

View File

@@ -256,11 +256,14 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
var logImpact = (alertMinExplosionIntensity > -1 && totalIntensity >= alertMinExplosionIntensity) var logImpact = (alertMinExplosionIntensity > -1 && totalIntensity >= alertMinExplosionIntensity)
? LogImpact.Extreme ? LogImpact.Extreme
: LogImpact.High; : LogImpact.High;
_adminLogger.Add(LogType.Explosion, logImpact, if (posFound)
$"{ToPrettyString(user.Value):user} caused {ToPrettyString(uid):entity} to explode ({typeId}) at Pos:{(posFound ? $"{gridPos:coordinates}" : "[Grid or Map not found]")} with intensity {totalIntensity} slope {slope}"); _adminLogger.Add(LogType.Explosion, logImpact, $"{ToPrettyString(user.Value):user} caused {ToPrettyString(uid):entity} to explode ({typeId}) at Pos:{gridPos:coordinates} with intensity {totalIntensity} slope {slope}");
else
_adminLogger.Add(LogType.Explosion, logImpact, $"{ToPrettyString(user.Value):user} caused {ToPrettyString(uid):entity} to explode ({typeId}) at Pos:[Grid or Map not found] with intensity {totalIntensity} slope {slope}");
} }
} }
/// <summary> /// <summary>
/// Queue an explosion, with a specified epicenter and set of starting tiles. /// Queue an explosion, with a specified epicenter and set of starting tiles.
/// </summary> /// </summary>

View File

@@ -3,6 +3,28 @@ namespace Content.Shared.Chat;
public interface ISharedChatManager public interface ISharedChatManager
{ {
void Initialize(); void Initialize();
/// <summary>
/// Send an admin alert to the admin chat channel.
/// </summary>
/// <param name="message">The message to send.</param>
void SendAdminAlert(string message); void SendAdminAlert(string message);
/// <summary>
/// Send an admin alert to the admin chat channel specifically about the given player.
/// Will include info extra like their antag status and name.
/// </summary>
/// <param name="player">The player that the message is about.</param>
/// <param name="message">The message to send.</param>
void SendAdminAlert(EntityUid player, string message); void SendAdminAlert(EntityUid player, string message);
/// <summary>
/// This is a dangerous function! Only pass in property escaped text.
/// See: <see cref="SendAdminAlert(string)"/>
/// <br/><br/>
/// Use this for things that need to be unformatted (like tpto links) but ensure that everything else
/// is formated properly. If it's not, players could sneak in ban links or other nasty commands that the admins
/// could clink on.
/// </summary>
void SendAdminAlertNoFormatOrEscape(string message);
} }

View File

@@ -2,3 +2,5 @@
admin-alert-ipintel-blocked = {$player} was rejected from joining due to their IP having a {TOSTRING($percent, "P2")} confidence of being a VPN/Datacenter. admin-alert-ipintel-blocked = {$player} was rejected from joining due to their IP having a {TOSTRING($percent, "P2")} confidence of being a VPN/Datacenter.
admin-alert-ipintel-warning = {$player} IP has a {TOSTRING($percent, "P2")} confidence of being a VPN/Datacenter. Please watch them. admin-alert-ipintel-warning = {$player} IP has a {TOSTRING($percent, "P2")} confidence of being a VPN/Datacenter. Please watch them.
admin-alert-antag-label = {$message} [ANTAG: {$name}, {$subtype}] admin-alert-antag-label = {$message} [ANTAG: {$name}, {$subtype}]
admin-alert-tp-to-players-header = Players:{" "}
admin-alert-tp-to-coords-header = Coords:{" "}