Make quick info links in the ahelp window more robust (#44373)

* Store last player whose mind was in container

* Send last player who controlled the entity's session ID along with other quick info

* No more spam

* Display last player info if there is no active player on entity

* Better looking separator

* Better looking separator but I didnt forget to ctrl s this time

* newline

* Address reviews

* cleaning

* Tiny tweaks

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: Connor Huffine <chuffine@gmail.com>
This commit is contained in:
Doru991 2026-07-08 11:32:08 +03:00 • committed by GitHub
parent f983f04487
commit 252b5f9b09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 57 additions and 16 deletions

View file

@ -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)
{

View file

@ -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<MindContainerComponent>(ent, out var comp) && TryComp<MindComponent>(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();

View file

@ -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<MindContainerComponent>();
var list = new List<NameMatchOption>();
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));
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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
{
/// <summary>
/// The mind controlling this mob. Can be null.
/// The mind controlling this mob. Can be null.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Mind;
/// <summary>
/// True if we have a mind, false otherwise.
/// True if we have a mind, false otherwise.
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)]
public bool HasMind;
/// <summary>
/// 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.
/// </summary>
[DataField]
public bool GhostOnShutdown = true;
/// <summary>
/// Last mind that had control of this mob. If null, it was never controlled by a player.
/// </summary>
/// <remarks>
/// Because minds only get networked to their owners, this field will be <see cref="EntityUid.Invalid"/> on client unless the last mind was the one belonging to the local client.
/// </remarks>
[DataField, AutoNetworkedField]
public EntityUid? LastMind;
}
/// <summary>

View file

@ -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]