]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Admin alerts now link players with tpto (#40472)
authorbeck-thompson <107373427+beck-thompson@users.noreply.github.com>
Mon, 27 Oct 2025 02:40:07 +0000 (19:40 -0700)
committerGitHub <noreply@github.com>
Mon, 27 Oct 2025 02:40:07 +0000 (02:40 +0000)
* Admin alerts now link players with tpto

* Add coords

* Slarti tweaks!

* He saw my minor spelling mistake - its over...

Content.Client/Chat/Managers/ChatManager.cs
Content.Server/Administration/Logs/AdminLogManager.cs
Content.Server/Chat/Managers/ChatManager.cs
Content.Server/Explosion/EntitySystems/ExplosionSystem.cs
Content.Shared/Chat/ISharedChatManager.cs
Resources/Locale/en-US/administration/admin-alerts.ftl

index 68707e021c5c6a0f42c2fcb6e677d4836f39ee80..1b66bf8732d14a5f9f34f145adb9a98726a9ae96 100644 (file)
@@ -31,6 +31,11 @@ internal sealed class ChatManager : IChatManager
         // 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)
     {
         var str = text.ToString();
index e7682cf5595b69a6e14674ee2675ee4d05ba69be..2587d4b8f918a15828cdb7405d1bdee500bd3854 100644 (file)
@@ -12,13 +12,16 @@ using Content.Shared.Database;
 using Content.Shared.Mind;
 using Content.Shared.Players.PlayTimeTracking;
 using Prometheus;
+using Robust.Server.GameObjects;
 using Robust.Shared;
 using Robust.Shared.Configuration;
+using Robust.Shared.Map;
 using Robust.Shared.Network;
 using Robust.Shared.Player;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Reflection;
 using Robust.Shared.Timing;
+using Robust.Shared.Utility;
 
 namespace Content.Server.Administration.Logs;
 
@@ -338,7 +341,7 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
             Players = players,
         };
 
-        DoAdminAlerts(players, message, impact);
+        DoAdminAlerts(players, message, impact, handler);
 
         if (preRound)
         {
@@ -380,6 +383,34 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
         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)
     {
         // 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 logMessage = message;
+        var playerNetEnts = new List<(NetEntity, string)>();
 
         foreach (var player in players)
         {
@@ -419,6 +451,8 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
                         ("name", cachedInfo.CharacterName),
                         ("subtype", subtype));
                 }
+                if (cachedInfo != null && cachedInfo.NetEntity != null)
+                    playerNetEnts.Add((cachedInfo.NetEntity.Value, cachedInfo.CharacterName));
             }
 
             if (adminLog)
@@ -442,7 +476,73 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa
         }
 
         if (adminLog)
+        {
             _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)
index c62a10ada362c995e4394bd56c8d1f639c856bbf..f90e286d9ed86906e9a752de7bf5037af6e38f14 100644 (file)
@@ -160,14 +160,20 @@ internal sealed partial class ChatManager : IChatManager
 
     public void SendAdminAlert(string message)
     {
-        var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
-
         var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-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)
     {
         var mindSystem = _entityManager.System<SharedMindSystem>();
index 67dbe97b290f009406164a38e7dc12d444cf870b..198db3eca135dabde73ad1005104d62e46f5ab57 100644 (file)
@@ -256,11 +256,14 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
             var logImpact = (alertMinExplosionIntensity > -1 && totalIntensity >= alertMinExplosionIntensity)
                 ? LogImpact.Extreme
                 : LogImpact.High;
-            _adminLogger.Add(LogType.Explosion, logImpact,
-                $"{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}");
+            if (posFound)
+                _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>
     ///     Queue an explosion, with a specified epicenter and set of starting tiles.
     /// </summary>
index 39c1d85dd25017409e8c843e63ccb17330f4c3c7..76fb4fbea8571cee45746dd8beea3d71f2b0710f 100644 (file)
@@ -3,6 +3,28 @@ namespace Content.Shared.Chat;
 public interface ISharedChatManager
 {
     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);
+
+    /// <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);
+
+    /// <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);
 }
index 931c3766a7ece7c381747c1c1c26d1d34b28642b..512c654650b49fa8368f7b9f833a6d922ec610a4 100644 (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-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-tp-to-players-header = Players:{" "}
+admin-alert-tp-to-coords-header = Coords:{" "}