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
*/