diff --git a/Content.Client/ContextMenu/UI/EntityMenuElement.cs b/Content.Client/ContextMenu/UI/EntityMenuElement.cs index cdd171a6e7..8a8070aecc 100644 --- a/Content.Client/ContextMenu/UI/EntityMenuElement.cs +++ b/Content.Client/ContextMenu/UI/EntityMenuElement.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Client.Administration.Managers; using Content.Client.Administration.Systems; +using Content.Client.UserInterface; using Content.Shared.Administration; using Content.Shared.IdentityManagement; using Robust.Client.GameObjects; @@ -8,7 +9,7 @@ using Robust.Client.Player; namespace Content.Client.ContextMenu.UI { - public sealed partial class EntityMenuElement : ContextMenuElement + public sealed partial class EntityMenuElement : ContextMenuElement, IEntityControl { [Dependency] private readonly IClientAdminManager _adminManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; @@ -117,5 +118,7 @@ namespace Content.Client.ContextMenu.UI Text = GetEntityDescription(entity.Value); } } + + EntityUid? IEntityControl.UiEntity => Entity; } } diff --git a/Content.Client/Gameplay/GameplayStateBase.cs b/Content.Client/Gameplay/GameplayStateBase.cs index 87fe257cd0..788a4e5dff 100644 --- a/Content.Client/Gameplay/GameplayStateBase.cs +++ b/Content.Client/Gameplay/GameplayStateBase.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; using System.Linq; using System.Numerics; using Content.Client.Clickable; -using Content.Client.ContextMenu.UI; +using Content.Client.UserInterface; +using Content.Shared.Input; using Robust.Client.ComponentTrees; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -11,10 +10,13 @@ using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.State; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Containers; +using Robust.Shared.Console; using Robust.Shared.Input; +using Robust.Shared.Input.Binding; using Robust.Shared.Map; +using Robust.Shared.Players; using Robust.Shared.Timing; namespace Content.Client.Gameplay @@ -34,28 +36,36 @@ namespace Content.Client.Gameplay [Dependency] protected readonly IUserInterfaceManager UserInterfaceManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IViewVariablesManager _vvm = default!; + [Dependency] private readonly IConsoleHost _conHost = default!; private ClickableEntityComparer _comparer = default!; - private (ViewVariablesPath? path, string[] segments) ResolveVVHoverObject(string path) + private (ViewVariablesPath? path, string[] segments) ResolveVvHoverObject(string path) { - // VVs the currently hovered entity. For a nifty vv keybinding you can use: - // - // /bind v command "vv /c/enthover" - // /svbind - // - // Though you probably want to include a modifier like alt, as otherwise this would open VV even when typing - // a message into chat containing the letter v. - var segments = path.Split('/'); + var uid = RecursivelyFindUiEntity(UserInterfaceManager.CurrentlyHovered); + var netUid = _entityManager.GetNetEntity(uid); + return (netUid != null ? new ViewVariablesInstancePath(netUid) : null, segments); + } - EntityUid? uid = null; - if (UserInterfaceManager.CurrentlyHovered is IViewportControl vp && _inputManager.MouseScreenPosition.IsValid) - uid = GetClickedEntity(vp.PixelToMap(_inputManager.MouseScreenPosition.Position)); - else if (UserInterfaceManager.CurrentlyHovered is EntityMenuElement element) - uid = element.Entity; + private EntityUid? RecursivelyFindUiEntity(Control? control) + { + if (control == null) + return null; - return (uid != null ? new ViewVariablesInstancePath(uid) : null, segments); + switch (control) + { + case IViewportControl vp: + if (_inputManager.MouseScreenPosition.IsValid) + return GetClickedEntity(vp.PixelToMap(_inputManager.MouseScreenPosition.Position)); + return null; + case SpriteView sprite: + return sprite.Entity; + case IEntityControl ui: + return ui.UiEntity; + } + + return RecursivelyFindUiEntity(control.Parent); } private IEnumerable? ListVVHoverPaths(string[] segments) @@ -65,15 +75,25 @@ namespace Content.Client.Gameplay protected override void Startup() { - _vvm.RegisterDomain("enthover", ResolveVVHoverObject, ListVVHoverPaths); + _vvm.RegisterDomain("enthover", ResolveVvHoverObject, ListVVHoverPaths); _inputManager.KeyBindStateChanged += OnKeyBindStateChanged; _comparer = new ClickableEntityComparer(); + CommandBinds.Builder + .Bind(ContentKeyFunctions.InspectEntity, new PointerInputCmdHandler(HandleInspect, outsidePrediction: true)) + .Register(); } protected override void Shutdown() { _vvm.UnregisterDomain("enthover"); _inputManager.KeyBindStateChanged -= OnKeyBindStateChanged; + CommandBinds.Unregister(); + } + + private bool HandleInspect(ICommonSession? session, EntityCoordinates coords, EntityUid uid) + { + _conHost.ExecuteCommand($"vv /c/enthover"); + return true; } public EntityUid? GetClickedEntity(MapCoordinates coordinates) diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 963571abe7..63809f88c1 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -33,6 +33,7 @@ namespace Content.Client.Input common.AddFunction(ContentKeyFunctions.ZoomOut); common.AddFunction(ContentKeyFunctions.ZoomIn); common.AddFunction(ContentKeyFunctions.ResetZoom); + common.AddFunction(ContentKeyFunctions.InspectEntity); // Not in engine, because engine cannot check for sanbox/admin status before starting placement. common.AddFunction(ContentKeyFunctions.EditorCopyObject); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index d1e4c3bbcd..ba77c43160 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -192,6 +192,7 @@ namespace Content.Client.Options.UI.Tabs AddButton(EngineKeyFunctions.ShowDebugConsole); AddButton(EngineKeyFunctions.ShowDebugMonitors); AddButton(EngineKeyFunctions.HideUI); + AddButton(ContentKeyFunctions.InspectEntity); foreach (var control in _keyControls.Values) { diff --git a/Content.Client/UserInterface/Controls/ListContainer.cs b/Content.Client/UserInterface/Controls/ListContainer.cs index eefeb19d40..d1bb103926 100644 --- a/Content.Client/UserInterface/Controls/ListContainer.cs +++ b/Content.Client/UserInterface/Controls/ListContainer.cs @@ -344,7 +344,7 @@ public sealed class ListContainer : Control } } -public sealed class ListContainerButton : ContainerButton +public sealed class ListContainerButton : ContainerButton, IEntityControl { public readonly ListData Data; // public PanelContainer Background; @@ -359,6 +359,8 @@ public sealed class ListContainerButton : ContainerButton // PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(55, 55, 68)} // }); } + + public EntityUid? UiEntity => (Data as EntityListData)?.Uid; } #region Data diff --git a/Content.Client/UserInterface/Controls/SlotControl.cs b/Content.Client/UserInterface/Controls/SlotControl.cs index 4caabaa788..b0a2198443 100644 --- a/Content.Client/UserInterface/Controls/SlotControl.cs +++ b/Content.Client/UserInterface/Controls/SlotControl.cs @@ -8,7 +8,7 @@ using Robust.Shared.Input; namespace Content.Client.UserInterface.Controls { [Virtual] - public abstract class SlotControl : Control + public abstract class SlotControl : Control, IEntityControl { public static int DefaultButtonSize = 64; @@ -20,7 +20,7 @@ namespace Content.Client.UserInterface.Controls public TextureButton StorageButton { get; } public CooldownGraphic CooldownDisplay { get; } - public EntityUid? Entity => SpriteView.Sprite?.Owner; + public EntityUid? Entity => SpriteView.Entity; private bool _slotNameSet; @@ -232,5 +232,7 @@ namespace Content.Client.UserInterface.Controls ButtonRect.Texture = Theme.ResolveTextureOrNull(_buttonTexturePath)?.Texture; HighlightRect.Texture = Theme.ResolveTextureOrNull(_highlightTexturePath)?.Texture; } + + EntityUid? IEntityControl.UiEntity => Entity; } } diff --git a/Content.Client/UserInterface/IEntityControl.cs b/Content.Client/UserInterface/IEntityControl.cs new file mode 100644 index 0000000000..5bf997607c --- /dev/null +++ b/Content.Client/UserInterface/IEntityControl.cs @@ -0,0 +1,10 @@ +namespace Content.Client.UserInterface; + +/// +/// Simple interface that indicates that the given control is associated with some entity. +/// This is primarily intended to be used with VV, so that you can easily open the VV window to examine an entity. +/// +public interface IEntityControl +{ + EntityUid? UiEntity { get; } +} diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs index 2af079ccce..e4521cecaa 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs @@ -19,7 +19,7 @@ using Direction = Robust.Shared.Maths.Direction; namespace Content.Client.UserInterface.Systems.Actions.Controls; -public sealed class ActionButton : Control +public sealed class ActionButton : Control, IEntityControl { private IEntityManager? _entities; @@ -197,7 +197,7 @@ public sealed class ActionButton : Control private void UpdateItemIcon() { if (!Actions.TryGetActionData(ActionId, out var action) || - action is not { EntityIcon: { } entity } || + action is not {EntityIcon: { } entity} || !Entities.HasComponent(entity)) { _bigItemSpriteView.Visible = false; @@ -344,7 +344,7 @@ public sealed class ActionButton : Control public void Depress(GUIBoundKeyEventArgs args, bool depress) { // action can still be toggled if it's allowed to stay selected - if (!Actions.TryGetActionData(ActionId, out var action) || action is not { Enabled: true }) + if (!Actions.TryGetActionData(ActionId, out var action) || action is not {Enabled: true}) return; if (_depressed && !depress) @@ -401,4 +401,6 @@ public sealed class ActionButton : Control SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal); } + + EntityUid? IEntityControl.UiEntity => ActionId; } diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 57b0493eb9..c50531e295 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -112,5 +112,6 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction Vote9 = "Vote9"; public static readonly BoundKeyFunction EditorCopyObject = "EditorCopyObject"; public static readonly BoundKeyFunction EditorFlipObject = "EditorFlipObject"; + public static readonly BoundKeyFunction InspectEntity = "InspectEntity"; } } diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index e2ff7bc411..10db378ab4 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -159,6 +159,7 @@ ui-options-function-editor-copy-object = Copy ui-options-function-open-abilities-menu = Open action menu ui-options-function-show-debug-console = Open Console ui-options-function-show-debug-monitors = Show Debug Monitors +ui-options-function-inspect-entity = Inspect Entity ui-options-function-hide-ui = Hide UI ui-options-function-hotbar1 = Hotbar slot 1 diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 4ce5d84e8c..e3e7a09657 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -237,6 +237,10 @@ binds: - function: ShowDebugConsole type: State key: Tilde +- function: InspectEntity + type: State + key: v + mod1: Alt - function: MouseMiddle type: State key: MouseMiddle