diff --git a/Content.Client/Administration/AdminQuickInfoSystem.cs b/Content.Client/Administration/AdminQuickInfoSystem.cs index 55e1979a99..846dd3f0f0 100644 --- a/Content.Client/Administration/AdminQuickInfoSystem.cs +++ b/Content.Client/Administration/AdminQuickInfoSystem.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Content.Client.Administration.Systems; using Content.Client.Stylesheets; using Content.Shared.Administration; @@ -59,7 +59,7 @@ internal sealed partial class AdminQuickInfoSystem : EntitySystem public void OpenPopupFor(NetEntity[] entities) { - var vBox = new VBox(); + var vBox = new VBox() { SeparationOverride = 6 };; var popup = new Popup { Children = @@ -74,9 +74,19 @@ internal sealed partial class AdminQuickInfoSystem : EntitySystem }, }, }; - + var first = true; foreach (var entity in entities) { + if (!first) + { + vBox.AddChild(new PanelContainer + { + StyleClasses = { StyleClass.LowDivider }, + HorizontalExpand = true, + }); + first = false; + } + var playerInfo = _adminSystem.PlayerList.FirstOrDefault(p => p.NetEntity == entity); var control = new InfoControl(this, entity, playerInfo); popup.OnPopupHide += () => control.Unsubscribe(); @@ -92,19 +102,21 @@ internal sealed partial class AdminQuickInfoSystem : EntitySystem { private readonly AdminQuickInfoSystem _system; private readonly NetEntity _entity; - private readonly PlayerInfo? _playerInfo; + private readonly bool _activeControl; + private PlayerInfo? _playerInfo; private QuickInfoShared.SingleEntityInfo? _response; private readonly RichTextLabel _contents = new(); private ILocalizationManager Loc => _system.Loc; + private AdminSystem Admin => _system._adminSystem; public InfoControl(AdminQuickInfoSystem system, NetEntity entity, PlayerInfo? playerInfo) { _system = system; _entity = entity; _playerInfo = playerInfo; - + _activeControl = (playerInfo != null); AddChild(_contents); system.EntityResponseReceived += OnEntityResponseReceived; @@ -119,6 +131,7 @@ internal sealed partial class AdminQuickInfoSystem : EntitySystem return; _response = ev; + _playerInfo ??= Admin.PlayerList.FirstOrDefault(p => p.SessionId == ev.LastPlayer); Rebuild(); } @@ -131,6 +144,10 @@ internal sealed partial class AdminQuickInfoSystem : EntitySystem private void Rebuild() { var sb = new FormattedStringBuilder(); + if (!_activeControl) + { + sb.AppendMarkupLine(Loc.GetString("admin-quick-info-inactive")); + } if (_playerInfo != null) { diff --git a/Content.Server/Administration/Systems/AdminQuickInfoSystem.cs b/Content.Server/Administration/Systems/AdminQuickInfoSystem.cs index 625781441e..4233af59bb 100644 --- a/Content.Server/Administration/Systems/AdminQuickInfoSystem.cs +++ b/Content.Server/Administration/Systems/AdminQuickInfoSystem.cs @@ -1,6 +1,9 @@ -using System.Linq; +using System.Linq; using Content.Server.Administration.Managers; using Content.Shared.Administration; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Robust.Shared.Network; namespace Content.Server.Administration.Systems; @@ -33,10 +36,17 @@ public sealed partial class AdminQuickInfoSystem : EntitySystem var responses = ev.Entities.Select(e => { if (!TryGetEntity(e, out var ent)) - return new QuickInfoShared.SingleEntityInfo(e, false, "", ""); + return new QuickInfoShared.SingleEntityInfo(e, false, "", "", null); + NetUserId? lastPlayer = null; + + // Check the last mind that was attached to the entity and get its userid. + if (TryComp(ent, out var comp) && TryComp(comp.LastMind, out var mindComp)) + { + lastPlayer = mindComp.UserId; + } var metadata = MetaData(ent.Value); - return new QuickInfoShared.SingleEntityInfo(e, true, metadata.EntityName, metadata.EntityPrototype?.ID); + return new QuickInfoShared.SingleEntityInfo(e, true, metadata.EntityName, metadata.EntityPrototype?.ID, lastPlayer); }) .ToArray(); diff --git a/Content.Server/Administration/Systems/BwoinkSystem.NameLinks.cs b/Content.Server/Administration/Systems/BwoinkSystem.NameLinks.cs index c56daebd45..720f6d227e 100644 --- a/Content.Server/Administration/Systems/BwoinkSystem.NameLinks.cs +++ b/Content.Server/Administration/Systems/BwoinkSystem.NameLinks.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Content.Shared.Administration; @@ -50,8 +50,11 @@ public sealed partial class BwoinkSystem var q = EntityQueryEnumerator(); var list = new List(); - while (q.MoveNext(out var entity, out _)) + while (q.MoveNext(out var entity, out var comp)) { + if (comp.LastMind is null) + continue; + list.Add(new NameMatchOption(Name(entity), entity)); } diff --git a/Content.Server/Mind/MindSystem.cs b/Content.Server/Mind/MindSystem.cs index 434b59711c..c63710e6fc 100644 --- a/Content.Server/Mind/MindSystem.cs +++ b/Content.Server/Mind/MindSystem.cs @@ -262,6 +262,7 @@ public sealed partial class MindSystem : SharedMindSystem if (entity != null) { component!.Mind = mindId; + component.LastMind = mindId; component.HasMind = true; mind.OwnedEntity = entity; mind.OriginalOwnedEntity ??= GetNetEntity(mind.OwnedEntity); diff --git a/Content.Shared/Administration/QuickInfoShared.cs b/Content.Shared/Administration/QuickInfoShared.cs index 61b0dd99a7..91be59ea32 100644 --- a/Content.Shared/Administration/QuickInfoShared.cs +++ b/Content.Shared/Administration/QuickInfoShared.cs @@ -1,4 +1,5 @@ -using Robust.Shared.Serialization; +using Robust.Shared.Network; +using Robust.Shared.Serialization; namespace Content.Shared.Administration; @@ -27,5 +28,5 @@ public static class QuickInfoShared } [Serializable, NetSerializable] - public sealed record SingleEntityInfo(NetEntity Entity, bool Exists, string Name, string? Prototype); + public sealed record SingleEntityInfo(NetEntity Entity, bool Exists, string Name, string? Prototype, NetUserId? LastPlayer); } diff --git a/Content.Shared/Mind/Components/MindContainerComponent.cs b/Content.Shared/Mind/Components/MindContainerComponent.cs index 7355550b95..856a48de73 100644 --- a/Content.Shared/Mind/Components/MindContainerComponent.cs +++ b/Content.Shared/Mind/Components/MindContainerComponent.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using Robust.Shared.GameStates; namespace Content.Shared.Mind.Components; @@ -11,22 +10,31 @@ namespace Content.Shared.Mind.Components; public sealed partial class MindContainerComponent : Component { /// - /// The mind controlling this mob. Can be null. + /// The mind controlling this mob. Can be null. /// [DataField, AutoNetworkedField] public EntityUid? Mind; /// - /// True if we have a mind, false otherwise. + /// True if we have a mind, false otherwise. /// [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)] public bool HasMind; /// - /// Whether the mind will be put on a ghost after this component is shutdown. + /// Whether the mind will be put on a ghost after this component is shutdown. /// [DataField] public bool GhostOnShutdown = true; + + /// + /// Last mind that had control of this mob. If null, it was never controlled by a player. + /// + /// + /// Because minds only get networked to their owners, this field will be on client unless the last mind was the one belonging to the local client. + /// + [DataField, AutoNetworkedField] + public EntityUid? LastMind; } /// diff --git a/Resources/Locale/en-US/administration/ui/quick-info.ftl b/Resources/Locale/en-US/administration/ui/quick-info.ftl index 0c3577d694..f7e0ac020b 100644 --- a/Resources/Locale/en-US/administration/ui/quick-info.ftl +++ b/Resources/Locale/en-US/administration/ui/quick-info.ftl @@ -2,6 +2,7 @@ admin-quick-info-entity = Entity: [color=white]{ESCAPE($name)}[/color] ([color=cyan]{ESCAPE($prototype)}[/color], [color=lime]{ESCAPE($entity)}[/color]) admin-quick-info-entity-missing = Entity {ESCAPE($entity)} no longer exists admin-quick-info-no-prototype = (No prototype) +admin-quick-info-inactive = [color=yellow]No longer player-controlled.[/color] Last player: admin-quick-info-username = Username: [color=white]{ESCAPE($username)}[/color] (playtime: [color=white]{ESCAPE($playtime)}[/color]) admin-quick-info-character = Character: [color=white]{ESCAPE($character)}[/color]