Restrict examine/context-menu usage when incapacitated. (#5416)

* wip

* update in-view checks

* cleanup
This commit is contained in:
Leon Friedrich
2021-11-22 09:40:09 +13:00
committed by GitHub
parent fda85b4b6b
commit bec1f99f1f
4 changed files with 85 additions and 41 deletions

View File

@@ -46,6 +46,7 @@ namespace Content.Client.ContextMenu.UI
[Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!;
private readonly VerbSystem _verbSystem; private readonly VerbSystem _verbSystem;
private readonly ExamineSystem _examineSystem;
/// <summary> /// <summary>
/// This maps the currently displayed entities to the actual GUI elements. /// This maps the currently displayed entities to the actual GUI elements.
@@ -60,6 +61,7 @@ namespace Content.Client.ContextMenu.UI
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_verbSystem = verbSystem; _verbSystem = verbSystem;
_examineSystem = EntitySystem.Get<ExamineSystem>();
_cfg.OnValueChanged(CCVars.EntityMenuGroupingType, OnGroupingChanged, true); _cfg.OnValueChanged(CCVars.EntityMenuGroupingType, OnGroupingChanged, true);
@@ -157,10 +159,9 @@ namespace Content.Client.ContextMenu.UI
var coords = args.Coordinates.ToMap(_entityManager); var coords = args.Coordinates.ToMap(_entityManager);
if (!_verbSystem.TryGetEntityMenuEntities(coords, out var entities)) if (_verbSystem.TryGetEntityMenuEntities(coords, out var entities))
return false;
OpenRootMenu(entities); OpenRootMenu(entities);
return true; return true;
} }
@@ -183,7 +184,7 @@ namespace Content.Client.ContextMenu.UI
foreach (var entity in Elements.Keys.ToList()) foreach (var entity in Elements.Keys.ToList())
{ {
if (entity.Deleted || !ignoreFov && !player.InRangeUnOccluded(entity)) if (entity.Deleted || !ignoreFov && !_examineSystem.CanExamine(player, entity))
RemoveEntity(entity); RemoveEntity(entity);
} }
} }

View File

@@ -6,6 +6,7 @@ using Content.Shared.Input;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
@@ -28,6 +29,7 @@ namespace Content.Client.Examine
{ {
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
public const string StyleClassEntityTooltip = "entity-tooltip"; public const string StyleClassEntityTooltip = "entity-tooltip";
@@ -52,18 +54,9 @@ namespace Content.Client.Examine
if (_examineTooltipOpen == null || !_examineTooltipOpen.Visible) return; if (_examineTooltipOpen == null || !_examineTooltipOpen.Visible) return;
if (_examinedEntity == null || _playerEntity == null) return; if (_examinedEntity == null || _playerEntity == null) return;
Ignored predicate = entity => entity == _playerEntity || entity == _examinedEntity; if (!CanExamine(_playerEntity, _examinedEntity))
if (_playerEntity.TryGetContainer(out var container))
{
predicate += entity => entity == container.Owner;
}
if (!InRangeUnOccluded(_playerEntity, _examinedEntity, ExamineRange, predicate))
{
CloseTooltip(); CloseTooltip();
} }
}
public override void Shutdown() public override void Shutdown()
{ {
@@ -71,6 +64,15 @@ namespace Content.Client.Examine
base.Shutdown(); base.Shutdown();
} }
public override bool CanExamine(IEntity examiner, MapCoordinates target, Ignored? predicate = null)
{
var b = _eyeManager.GetWorldViewbounds();
if (!b.Contains(target.Position))
return false;
return base.CanExamine(examiner, target, predicate);
}
private bool HandleExamine(ICommonSession? session, EntityCoordinates coords, EntityUid uid) private bool HandleExamine(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{ {
if (!uid.IsValid() || !EntityManager.TryGetEntity(uid, out var entity)) if (!uid.IsValid() || !EntityManager.TryGetEntity(uid, out var entity))

View File

@@ -3,22 +3,25 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Client.ContextMenu.UI; using Content.Client.ContextMenu.UI;
using Content.Client.Examine;
using Content.Client.Popups; using Content.Client.Popups;
using Content.Client.Verbs.UI; using Content.Client.Verbs.UI;
using Content.Client.Viewport;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.State;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using static Content.Shared.Interaction.SharedInteractionSystem;
namespace Content.Client.Verbs namespace Content.Client.Verbs
{ {
@@ -26,6 +29,8 @@ namespace Content.Client.Verbs
public sealed class VerbSystem : SharedVerbSystem public sealed class VerbSystem : SharedVerbSystem
{ {
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly ExamineSystem _examineSystem = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!; [Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -85,19 +90,27 @@ namespace Content.Client.Verbs
public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List<IEntity>? result) public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List<IEntity>? result)
{ {
result = null; result = null;
var player = _playerManager.LocalPlayer?.ControlledEntity;
if (_stateManager.CurrentState is not GameScreenBase gameScreenBase)
return false;
var player = _playerManager.LocalPlayer?.ControlledEntity;
if (player == null) if (player == null)
return false; return false;
// If FOV drawing is disabled, we will modify the visibility option to ignore visiblity checks.
var visibility = _eyeManager.CurrentEye.DrawFov var visibility = _eyeManager.CurrentEye.DrawFov
? Visibility ? Visibility
: Visibility | MenuVisibility.NoFov; : Visibility | MenuVisibility.NoFov;
// Check if we have LOS to the clicked-location. // Do we have to do FoV checks?
if ((visibility & MenuVisibility.NoFov) == 0 && if ((visibility & MenuVisibility.NoFov) == 0)
!player.InRangeUnOccluded(targetPos, range: ExamineSystemShared.ExamineRange)) {
var entitiesUnderMouse = gameScreenBase.GetEntitiesUnderPosition(targetPos);
Ignored? predicate = e => e == player || entitiesUnderMouse.Contains(e);
if (!_examineSystem.CanExamine(player, targetPos, predicate))
return false; return false;
}
// Get entities // Get entities
var entities = _entityLookup.GetEntitiesInRange(targetPos.MapId, targetPos.Position, EntityMenuLookupSize) var entities = _entityLookup.GetEntitiesInRange(targetPos.MapId, targetPos.Position, EntityMenuLookupSize)

View File

@@ -1,8 +1,9 @@
using System; using System;
using System.Linq; using System.Linq;
using Content.Shared.DragDrop; using Content.Shared.DragDrop;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.MobState.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -27,12 +28,30 @@ namespace Content.Shared.Examine
public abstract class ExamineSystemShared : EntitySystem public abstract class ExamineSystemShared : EntitySystem
{ {
/// <summary>
/// Examine range to use when the examiner is in critical condition.
/// </summary>
/// <remarks>
/// Detailed examinations are disabled while incapactiated. Ideally this should just be set equal to the
/// radius of the crit overlay that blackens most of the screen. The actual radius of that is defined
/// in a shader sooo... eh.
/// </remarks>
public const float CritExamineRange = 1.3f;
/// <summary>
/// Examine range to use when the examiner is dead. See <see cref="CritExamineRange"/>.
/// </summary>
public const float DeadExamineRange = 0.75f;
public const float ExamineRange = 16f; public const float ExamineRange = 16f;
public const float ExamineRangeSquared = ExamineRange * ExamineRange;
protected const float ExamineDetailsRange = 3f; protected const float ExamineDetailsRange = 3f;
private static bool IsInDetailsRange(IEntity examiner, IEntity entity) private bool IsInDetailsRange(IEntity examiner, IEntity entity)
{ {
// check if the mob is in ciritcal or dead
if (EntityManager.TryGetComponent(examiner.Uid, out MobStateComponent mobState) && mobState.IsIncapacitated())
return false;
if (entity.TryGetContainerMan(out var man) && man.Owner == examiner) if (entity.TryGetContainerMan(out var man) && man.Owner == examiner)
return true; return true;
@@ -41,38 +60,47 @@ namespace Content.Shared.Examine
} }
[Pure] [Pure]
protected static bool CanExamine(IEntity examiner, IEntity examined) public bool CanExamine(IEntity examiner, IEntity examined)
{
return CanExamine(examiner, examined.Transform.MapPosition,
entity => entity == examiner || entity == examined);
}
[Pure]
public virtual bool CanExamine(IEntity examiner, MapCoordinates target, Ignored? predicate = null)
{ {
if (!examiner.TryGetComponent(out ExaminerComponent? examinerComponent)) if (!examiner.TryGetComponent(out ExaminerComponent? examinerComponent))
{
return false; return false;
}
if (!examinerComponent.DoRangeCheck) if (!examinerComponent.DoRangeCheck)
{
return true; return true;
}
if (examiner.Transform.MapID != examined.Transform.MapID) if (examiner.Transform.MapID != target.MapId)
{
return false; return false;
}
Ignored predicate = entity => entity == examiner || entity == examined;
if (examiner.TryGetContainer(out var container))
{
predicate += entity => entity == container.Owner;
}
return InRangeUnOccluded( return InRangeUnOccluded(
examiner.Transform.MapPosition, examiner.Transform.MapPosition,
examined.Transform.MapPosition, target,
ExamineRange, GetExaminerRange(examiner.Uid),
predicate: predicate, predicate: predicate,
ignoreInsideBlocker: true); ignoreInsideBlocker: true);
} }
/// <summary>
/// Check if a given examiner is incapacitated. If yes, return a reduced examine range. Otherwise, return the deault range.
/// </summary>
public float GetExaminerRange(EntityUid examiner, MobStateComponent? mobState = null)
{
if (Resolve(examiner, ref mobState, logMissing: false))
{
if (mobState.IsDead())
return DeadExamineRange;
else if (mobState.IsCritical())
return CritExamineRange;
}
return ExamineRange;
}
public static bool InRangeUnOccluded(MapCoordinates origin, MapCoordinates other, float range, Ignored? predicate, bool ignoreInsideBlocker = true) public static bool InRangeUnOccluded(MapCoordinates origin, MapCoordinates other, float range, Ignored? predicate, bool ignoreInsideBlocker = true)
{ {
if (origin.MapId == MapId.Nullspace || if (origin.MapId == MapId.Nullspace ||