From caf34be616d153401a7fe40fbe932934b3c9daed Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 23 Sep 2024 19:28:42 +1200 Subject: [PATCH] Entity menu lookup changes (#32395) --- Content.Client/Verbs/VerbSystem.cs | 175 ++++++++++++++--------------- Content.Shared/CCVar/CCVars.cs | 6 + 2 files changed, 93 insertions(+), 88 deletions(-) diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs index f84389195f..f592303d28 100644 --- a/Content.Client/Verbs/VerbSystem.cs +++ b/Content.Client/Verbs/VerbSystem.cs @@ -1,9 +1,9 @@ using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Numerics; using Content.Client.Examine; using Content.Client.Gameplay; using Content.Client.Popups; +using Content.Shared.CCVar; using Content.Shared.Examine; using Content.Shared.Tag; using Content.Shared.Verbs; @@ -13,6 +13,7 @@ using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Client.State; +using Robust.Shared.Configuration; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Utility; @@ -30,11 +31,10 @@ namespace Content.Client.Verbs [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; - /// - /// When a user right clicks somewhere, how large is the box we use to get entities for the context menu? - /// - public const float EntityMenuLookupSize = 0.25f; + private float _lookupSize; /// /// These flags determine what entities the user can see on the context menu. @@ -43,128 +43,127 @@ namespace Content.Client.Verbs public Action? OnVerbsResponse; - private List _entities = new(); - public override void Initialize() { base.Initialize(); SubscribeNetworkEvent(HandleVerbResponse); + Subs.CVar(_cfg, CCVars.GameEntityMenuLookup, OnLookupChanged, true); + } + + private void OnLookupChanged(float val) + { + _lookupSize = val; } /// - /// Get all of the entities in an area for displaying on the context menu. + /// Get all of the entities in an area for displaying on the context menu. /// - public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List? result) + /// True if any entities were found. + public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List? entities) { - result = null; + entities = null; - if (_stateManager.CurrentState is not GameplayStateBase gameScreenBase) + if (_stateManager.CurrentState is not GameplayStateBase) return false; - var player = _playerManager.LocalEntity; - if (player == null) + if (_playerManager.LocalEntity is not { } player) return false; // If FOV drawing is disabled, we will modify the visibility option to ignore visiblity checks. - var visibility = _eyeManager.CurrentEye.DrawFov - ? Visibility - : Visibility | MenuVisibility.NoFov; + var visibility = _eyeManager.CurrentEye.DrawFov ? Visibility : Visibility | MenuVisibility.NoFov; - var ev = new MenuVisibilityEvent() + var ev = new MenuVisibilityEvent { TargetPos = targetPos, Visibility = visibility, }; - RaiseLocalEvent(player.Value, ref ev); + RaiseLocalEvent(player, ref ev); visibility = ev.Visibility; - // Get entities - _entities.Clear(); - var entitiesUnderMouse = _tree.QueryAabb(targetPos.MapId, Box2.CenteredAround(targetPos.Position, new Vector2(EntityMenuLookupSize, EntityMenuLookupSize))); - bool Predicate(EntityUid e) => e == player; + // Initially, we include all entities returned by a sprite area lookup + var box = Box2.CenteredAround(targetPos.Position, new Vector2(_lookupSize, _lookupSize)); + var queryResult = _tree.QueryAabb(targetPos.MapId, box); + entities = new List(queryResult.Count); + foreach (var ent in queryResult) + { + entities.Add(ent.Uid); + } + + // If we're in a container list all other entities in it. + // E.g., allow players in lockers to examine / interact with other entities in the same locker + if (_containers.TryGetContainingContainer((player, null), out var container)) + { + // Only include the container contents when clicking near it. + if (entities.Contains(container.Owner) + || _containers.TryGetOuterContainer(container.Owner, Transform(container.Owner), out var outer) + && entities.Contains(outer.Owner)) + { + // The container itself might be in some other container, so it might not have been added by the + // sprite tree lookup. + if (!entities.Contains(container.Owner)) + entities.Add(container.Owner); + + // TODO Context Menu + // This might miss entities in some situations. E.g., one of the contained entities entity in it, that + // itself has another entity attached to it, then we should be able to "see" that entity. + // E.g., if a security guard is on a segway and gets thrown in a locker, this wouldn't let you see the guard. + foreach (var ent in container.ContainedEntities) + { + if (!entities.Contains(ent)) + entities.Add(ent); + } + } + } + + if ((visibility & MenuVisibility.InContainer) != 0) + { + // This is inefficient, but I'm lazy and CBF implementing my own recursive container method. Note that + // this might actually fail to add the contained children of some entities in the menu. E.g., an entity + // with a large sprite aabb, but small broadphase might appear in the menu, but have its children added + // by this. + var flags = LookupFlags.All & ~LookupFlags.Sensors; + foreach (var e in _lookup.GetEntitiesInRange(targetPos, _lookupSize, flags: flags)) + { + if (!entities.Contains(e)) + entities.Add(e); + } + } // Do we have to do FoV checks? if ((visibility & MenuVisibility.NoFov) == 0) { - TryComp(player.Value, out ExaminerComponent? examiner); - - foreach (var ent in entitiesUnderMouse) + TryComp(player, out ExaminerComponent? examiner); + for (var i = entities.Count - 1; i >= 0; i--) { - if (_examine.CanExamine(player.Value, targetPos, Predicate, ent.Uid, examiner)) - _entities.Add(ent.Uid); - } - } - else - { - foreach (var ent in entitiesUnderMouse) - { - _entities.Add(ent.Uid); + if (!_examine.CanExamine(player, targetPos, e => e == player, entities[i], examiner)) + entities.RemoveSwap(i); } } - // If we're in a container list all other entities in it. - if (_containers.TryGetContainingContainer(player.Value, out var container)) - { - foreach (var ent in container.ContainedEntities) - { - if (ent == player.Value || _entities.Contains(ent)) - continue; + if ((visibility & MenuVisibility.Invisible) != 0) + return entities.Count != 0; - if ((visibility & MenuVisibility.NoFov) == 0x0 || _examine.CanExamine(player.Value, targetPos, examined: ent)) - { - _entities.Add(ent); - } - } + for (var i = entities.Count - 1; i >= 0; i--) + { + if (_tagSystem.HasTag(entities[i], "HideContextMenu")) + entities.RemoveSwap(i); } - if (_entities.Count == 0) - return false; + // Unless we added entities in containers, every entity should already have a visible sprite due to + // the fact that we used the sprite tree query. + if (container == null && (visibility & MenuVisibility.InContainer) == 0) + return entities.Count != 0; - if (visibility == MenuVisibility.All) + var spriteQuery = GetEntityQuery(); + for (var i = entities.Count - 1; i >= 0; i--) { - result = new (_entities); - return true; + if (!spriteQuery.TryGetComponent(entities[i], out var spriteComponent) || !spriteComponent.Visible) + entities.RemoveSwap(i); } - // remove any entities in containers - if ((visibility & MenuVisibility.InContainer) == 0) - { - for (var i = _entities.Count - 1; i >= 0; i--) - { - var entity = _entities[i]; - - if (ContainerSystem.IsInSameOrTransparentContainer(player.Value, entity)) - continue; - - _entities.RemoveSwap(i); - } - } - - // remove any invisible entities - if ((visibility & MenuVisibility.Invisible) == 0) - { - var spriteQuery = GetEntityQuery(); - - for (var i = _entities.Count - 1; i >= 0; i--) - { - var entity = _entities[i]; - - if (!spriteQuery.TryGetComponent(entity, out var spriteComponent) || - !spriteComponent.Visible || - _tagSystem.HasTag(entity, "HideContextMenu")) - { - _entities.RemoveSwap(i); - } - } - } - - if (_entities.Count == 0) - return false; - - result = new(_entities); - return true; + return entities.Count != 0; } /// diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index d6d8bafa0e..26101c7537 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -430,6 +430,12 @@ namespace Content.Shared.CCVar public static readonly CVarDef ContrabandExamine = CVarDef.Create("game.contraband_examine", true, CVar.SERVER | CVar.REPLICATED); + /// + /// Size of the lookup area for adding entities to the context menu + /// + public static readonly CVarDef GameEntityMenuLookup = + CVarDef.Create("game.entity_menu_lookup", 0.25f, CVar.CLIENTONLY | CVar.ARCHIVE); + /* * Discord */