diff --git a/Content.Client/ContextMenu/UI/EntityMenuPresenter.cs b/Content.Client/ContextMenu/UI/EntityMenuPresenter.cs index c5838f69cd..83f1e33e50 100644 --- a/Content.Client/ContextMenu/UI/EntityMenuPresenter.cs +++ b/Content.Client/ContextMenu/UI/EntityMenuPresenter.cs @@ -6,8 +6,6 @@ using Content.Client.Verbs; using Content.Client.Viewport; using Content.Shared.CCVar; using Content.Shared.Input; -using Content.Shared.Interaction.Helpers; -using Content.Shared.Verbs; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; @@ -121,8 +119,12 @@ namespace Content.Client.ContextMenu.UI } // do some other server-side interaction? - if (args.Function == EngineKeyFunctions.Use || args.Function == ContentKeyFunctions.AltActivateItemInWorld || args.Function == ContentKeyFunctions.Point || - args.Function == ContentKeyFunctions.TryPullObject || args.Function == ContentKeyFunctions.MovePulledObject) + if (args.Function == EngineKeyFunctions.Use || + args.Function == ContentKeyFunctions.ActivateItemInWorld || + args.Function == ContentKeyFunctions.AltActivateItemInWorld || + args.Function == ContentKeyFunctions.Point || + args.Function == ContentKeyFunctions.TryPullObject || + args.Function == ContentKeyFunctions.MovePulledObject) { var inputSys = _systemManager.GetEntitySystem(); @@ -142,11 +144,6 @@ namespace Content.Client.ContextMenu.UI args.Handle(); return; } - - if (_itemSlotManager.OnButtonPressed(args, entity)) - { - _verbSystem.CloseAllMenus(); - } } private bool HandleOpenEntityMenu(in PointerInputCmdHandler.PointerInputCmdArgs args) diff --git a/Content.Client/Interactable/InteractionSystem.cs b/Content.Client/Interactable/InteractionSystem.cs index bba19b092d..0103ece58c 100644 --- a/Content.Client/Interactable/InteractionSystem.cs +++ b/Content.Client/Interactable/InteractionSystem.cs @@ -1,9 +1,25 @@ +using Content.Client.Storage; using Content.Shared.Interaction; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; namespace Content.Client.Interactable { public sealed class InteractionSystem : SharedInteractionSystem { + public override bool CanAccessViaStorage(EntityUid user, EntityUid target) + { + if (!EntityManager.TryGetEntity(target, out var entity)) + return false; + if (!entity.TryGetContainer(out var container)) + return false; + + if (!EntityManager.TryGetComponent(container.Owner.Uid, out ClientStorageComponent storage)) + return false; + + // we don't check if the user can access the storage entity itself. This should be handed by the UI system. + return storage.UIOpen; + } } } diff --git a/Content.Client/Storage/ClientStorageComponent.cs b/Content.Client/Storage/ClientStorageComponent.cs index ffefa476c3..a74836279d 100644 --- a/Content.Client/Storage/ClientStorageComponent.cs +++ b/Content.Client/Storage/ClientStorageComponent.cs @@ -38,6 +38,7 @@ namespace Content.Client.Storage private int StorageSizeUsed; private int StorageCapacityMax; private StorageWindow? _window; + public bool UIOpen => _window?.IsOpen ?? false; public override IReadOnlyList StoredEntities => _storedEntities; diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 781484aca0..5b74ecba43 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -7,6 +7,7 @@ using Content.Server.CombatMode; using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Pulling; +using Content.Server.Storage.Components; using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; using Content.Shared.Database; @@ -98,6 +99,27 @@ namespace Content.Server.Interaction return true; } + + public override bool CanAccessViaStorage(EntityUid user, EntityUid target) + { + if (!EntityManager.TryGetEntity(target, out var entity)) + return false; + + if (!entity.TryGetContainer(out var container)) + return false; + + if (!EntityManager.TryGetComponent(container.Owner.Uid, out ServerStorageComponent storage)) + return false; + + if (storage.Storage?.ID != container.ID) + return false; + + if (!EntityManager.TryGetComponent(user, out ActorComponent actor)) + return false; + + // we don't check if the user can access the storage entity itself. This should be handed by the UI system. + return storage.SubscribedSessions.Contains(actor.PlayerSession); + } #endregion /// @@ -188,9 +210,6 @@ namespace Content.Server.Interaction if (!EntityManager.TryGetEntity(uid, out var used)) return false; - if (user.IsInContainer()) - return false; - InteractionActivate(user, used); return true; } @@ -286,15 +305,13 @@ namespace Content.Server.Interaction /// Whether to use default or alternative interactions (usually as a result of /// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat /// interaction. Having an item in the active hand also disables alternative interactions. - public async void UserInteraction(IEntity user, EntityCoordinates coordinates, EntityUid clickedUid, bool altInteract = false ) + public async void UserInteraction(IEntity user, EntityCoordinates coordinates, EntityUid clickedUid, bool altInteract = false) { // TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms? if (!altInteract && user.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode) { - DoAttack(user, coordinates, false, clickedUid); return; - } if (!ValidateInteractAndFace(user, coordinates)) @@ -307,7 +324,8 @@ namespace Content.Server.Interaction EntityManager.TryGetEntity(clickedUid, out var target); // Check if interacted entity is in the same container, the direct child, or direct parent of the user. - if (target != null && !user.IsInSameOrParentContainer(target)) + // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) + if (target != null && !user.IsInSameOrParentContainer(target) && !CanAccessViaStorage(user.Uid, target.Uid)) { Logger.WarningS("system.interaction", $"User entity named {user.Name} clicked on object {target.Name} that isn't the parent, child, or in the same container"); @@ -449,7 +467,7 @@ namespace Content.Server.Interaction EntityManager.TryGetEntity(targetUid, out targetEnt); // Check if interacted entity is in the same container, the direct child, or direct parent of the user. - if (targetEnt != null && !user.IsInSameOrParentContainer(targetEnt)) + if (targetEnt != null && !user.IsInSameOrParentContainer(targetEnt) && !CanAccessViaStorage(user.Uid, targetEnt.Uid)) { Logger.WarningS("system.interaction", $"User entity named {user.Name} clicked on object {targetEnt.Name} that isn't the parent, child, or in the same container"); diff --git a/Content.Server/Storage/Components/ServerStorageComponent.cs b/Content.Server/Storage/Components/ServerStorageComponent.cs index 54fa795d79..11fe60b80a 100644 --- a/Content.Server/Storage/Components/ServerStorageComponent.cs +++ b/Content.Server/Storage/Components/ServerStorageComponent.cs @@ -31,6 +31,7 @@ using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Players; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Server.Storage.Components @@ -47,7 +48,8 @@ namespace Content.Server.Storage.Components private const string LoggerName = "Storage"; - private Container? _storage; + public Container? Storage; + private readonly Dictionary _sizeCache = new(); [DataField("occludesLight")] @@ -77,7 +79,7 @@ namespace Content.Server.Storage.Components public SoundSpecifier StorageSoundCollection { get; set; } = new SoundCollectionSpecifier("storageRustle"); [ViewVariables] - public override IReadOnlyList? StoredEntities => _storage?.ContainedEntities; + public override IReadOnlyList? StoredEntities => Storage?.ContainedEntities; [ViewVariables(VVAccess.ReadWrite)] public bool OccludesLight @@ -86,7 +88,7 @@ namespace Content.Server.Storage.Components set { _occludesLight = value; - if (_storage != null) _storage.OccludesLight = value; + if (Storage != null) Storage.OccludesLight = value; } } @@ -120,12 +122,12 @@ namespace Content.Server.Storage.Components { _storageUsed = 0; - if (_storage == null) + if (Storage == null) { return; } - foreach (var entity in _storage.ContainedEntities) + foreach (var entity in Storage.ContainedEntities) { var item = entity.GetComponent(); _storageUsed += item.Size; @@ -173,18 +175,18 @@ namespace Content.Server.Storage.Components /// true if the entity was inserted, false otherwise public bool Insert(IEntity entity) { - return CanInsert(entity) && _storage?.Insert(entity) == true; + return CanInsert(entity) && Storage?.Insert(entity) == true; } public override bool Remove(IEntity entity) { EnsureInitialCalculated(); - return _storage?.Remove(entity) == true; + return Storage?.Remove(entity) == true; } public void HandleEntityMaybeInserted(EntInsertedIntoContainerMessage message) { - if (message.Container != _storage) + if (message.Container != Storage) { return; } @@ -206,7 +208,7 @@ namespace Content.Server.Storage.Components public void HandleEntityMaybeRemoved(EntRemovedFromContainerMessage message) { - if (message.Container != _storage) + if (message.Container != Storage) { return; } @@ -324,9 +326,9 @@ namespace Content.Server.Storage.Components return; } - if (_storage == null) + if (Storage == null) { - Logger.WarningS(LoggerName, $"{nameof(UpdateClientInventory)} called with null {nameof(_storage)}"); + Logger.WarningS(LoggerName, $"{nameof(UpdateClientInventory)} called with null {nameof(Storage)}"); return; } @@ -381,10 +383,39 @@ namespace Content.Server.Storage.Components #pragma warning restore 618 } + CloseNestedInterfaces(session); + if (SubscribedSessions.Count == 0) UpdateStorageVisualization(); } + /// + /// If the user has nested-UIs open (e.g., PDA UI open when pda is in a backpack), close them. + /// + /// + public void CloseNestedInterfaces(IPlayerSession session) + { + if (StoredEntities == null) + return; + + foreach (var entity in StoredEntities) + { + if (_entityManager.TryGetComponent(entity.Uid, out ServerStorageComponent storageComponent)) + { + DebugTools.Assert(storageComponent != this, $"Storage component contains itself!? Entity: {OwnerUid}"); + storageComponent.UnsubscribeSession(session); + } + + if (_entityManager.TryGetComponent(entity.Uid, out ServerUserInterfaceComponent uiComponent)) + { + foreach (var ui in uiComponent.Interfaces) + { + ui.Close(session); + } + } + } + } + private void HandlePlayerSessionChangeEvent(object? obj, SessionStatusEventArgs sessionStatus) { Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) handled a status change in player session (UID {sessionStatus.Session.AttachedEntityUid})."); @@ -400,8 +431,8 @@ namespace Content.Server.Storage.Components base.Initialize(); // ReSharper disable once StringLiteralTypo - _storage = Owner.EnsureContainer("storagebase"); - _storage.OccludesLight = _occludesLight; + Storage = Owner.EnsureContainer("storagebase"); + Storage.OccludesLight = _occludesLight; UpdateStorageVisualization(); } @@ -437,7 +468,7 @@ namespace Content.Server.Storage.Components break; } - if (!Owner.EntityManager.TryGetEntity(remove.EntityUid, out var entity) || _storage?.Contains(entity) == false) + if (!Owner.EntityManager.TryGetEntity(remove.EntityUid, out var entity) || Storage?.Contains(entity) == false) { break; } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index ed4f8cfb51..f004646cdc 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -13,12 +13,14 @@ using Content.Shared.Throwing; using Content.Shared.Timing; using Content.Shared.Verbs; using JetBrains.Annotations; +using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics; +using Robust.Shared.Players; using Robust.Shared.Random; using Robust.Shared.Serialization; @@ -33,7 +35,6 @@ namespace Content.Shared.Interaction public abstract class SharedInteractionSystem : EntitySystem { [Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!; - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly SharedVerbSystem _verbSystem = default!; [Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!; @@ -454,6 +455,11 @@ namespace Content.Shared.Interaction if (!InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true)) return; + // Check if interacted entity is in the same container, the direct child, or direct parent of the user. + // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) + if (!user.IsInSameOrParentContainer(used) && !CanAccessViaStorage(user.Uid, used.Uid)) + return; + var activateMsg = new ActivateInWorldEvent(user, used); RaiseLocalEvent(used.Uid, activateMsg); if (activateMsg.Handled) @@ -529,17 +535,9 @@ namespace Content.Shared.Interaction public void AltInteract(IEntity user, IEntity target) { // Get list of alt-interact verbs - GetAlternativeVerbsEvent getVerbEvent = new(user, target); - RaiseLocalEvent(target.Uid, getVerbEvent); - - foreach (var verb in getVerbEvent.Verbs) - { - if (verb.Disabled) - continue; - - _verbSystem.ExecuteVerb(verb, user.Uid, target.Uid); - break; - } + var verbs = _verbSystem.GetLocalVerbs(target, user, VerbType.Alternative)[VerbType.Alternative]; + if (verbs.Any()) + _verbSystem.ExecuteVerb(verbs.First(), user.Uid, target.Uid); } #endregion @@ -733,6 +731,13 @@ namespace Content.Shared.Interaction } } #endregion + + /// + /// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This + /// checks if the user can access the item in these situations. + /// + public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target); + #endregion } diff --git a/Content.Shared/Item/ItemSystem.cs b/Content.Shared/Item/ItemSystem.cs index 8d88b30d63..821d749849 100644 --- a/Content.Shared/Item/ItemSystem.cs +++ b/Content.Shared/Item/ItemSystem.cs @@ -26,8 +26,10 @@ namespace Content.Shared.Item verb.Act = () => args.Hands.TryPutInActiveHandOrAny(args.Target); verb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"; - // if the item already in the user's inventory, change the text - if (args.Target.TryGetContainer(out var container) && container.Owner == args.User) + // if the item already in a container (that is not the same as the user's), then change the text. + // this occurs when the item is in their inventory or in an open backpack + args.User.TryGetContainer(out var userContainer); + if (args.Target.TryGetContainer(out var container) && container != userContainer) verb.Text = Loc.GetString("pick-up-verb-get-data-text-inventory"); else verb.Text = Loc.GetString("pick-up-verb-get-data-text"); diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 8861c1a74e..a6771c4fd7 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; +using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -10,6 +13,8 @@ namespace Content.Shared.Verbs public abstract class SharedVerbSystem : EntitySystem { [Dependency] private readonly SharedAdminLogSystem _logSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; /// /// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This @@ -19,30 +24,61 @@ namespace Content.Shared.Verbs { Dictionary> verbs = new(); + // accessibility checks + bool canAccess = false; + if (force || target == user) + canAccess = true; + else if (_interactionSystem.InRangeUnobstructed(user, target, ignoreInsideBlocker: true)) + { + if (user.IsInSameOrParentContainer(target)) + canAccess = true; + else + // the item might be in a backpack that the user has open + canAccess = _interactionSystem.CanAccessViaStorage(user.Uid, target.Uid); + } + + // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually + // call ActionBlocker checks, just cache it for the verb request. + var canInteract = force || _actionBlockerSystem.CanInteract(user.Uid); + + IEntity? @using = null; + if (user.TryGetComponent(out SharedHandsComponent? hands) && (force || _actionBlockerSystem.CanUse(user.Uid))) + { + hands.TryGetActiveHeldEntity(out @using); + + // Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used". + // This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging + // their sprite. + if (@using != null && @using.TryGetComponent(out var pull)) + { + @using = IoCManager.Resolve().GetEntity(pull.BlockingEntity); + } + } + if ((verbTypes & VerbType.Interaction) == VerbType.Interaction) { - GetInteractionVerbsEvent getVerbEvent = new(user, target, force); + GetInteractionVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target.Uid, getVerbEvent); verbs.Add(VerbType.Interaction, getVerbEvent.Verbs); } if ((verbTypes & VerbType.Activation) == VerbType.Activation) { - GetActivationVerbsEvent getVerbEvent = new(user, target, force); + GetActivationVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target.Uid, getVerbEvent); verbs.Add(VerbType.Activation, getVerbEvent.Verbs); } if ((verbTypes & VerbType.Alternative) == VerbType.Alternative) { - GetAlternativeVerbsEvent getVerbEvent = new(user, target, force); + GetAlternativeVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target.Uid, getVerbEvent); verbs.Add(VerbType.Alternative, getVerbEvent.Verbs); } if ((verbTypes & VerbType.Other) == VerbType.Other) { - GetOtherVerbsEvent getVerbEvent = new(user, target, force); + GetOtherVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess); RaiseLocalEvent(target.Uid, getVerbEvent); verbs.Add(VerbType.Other, getVerbEvent.Verbs); } diff --git a/Content.Shared/Verbs/VerbEvents.cs b/Content.Shared/Verbs/VerbEvents.cs index 9a832b41d1..49c89ce283 100644 --- a/Content.Shared/Verbs/VerbEvents.cs +++ b/Content.Shared/Verbs/VerbEvents.cs @@ -5,9 +5,7 @@ using Content.Shared.Hands.Components; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; -using Robust.Shared.IoC; using Content.Shared.Interaction; -using System.Collections.Immutable; namespace Content.Shared.Verbs { @@ -17,10 +15,18 @@ namespace Content.Shared.Verbs public readonly EntityUid EntityUid; public readonly VerbType Type; - public RequestServerVerbsEvent(EntityUid entityUid, VerbType type) + /// + /// If the target item is inside of some storage (e.g., backpack), this is the entity that owns that item + /// slot. Needed for validating that the user can access the target item. + /// + public readonly EntityUid? SlotOwner; + + + public RequestServerVerbsEvent(EntityUid entityUid, VerbType type, EntityUid? slotOwner = null) { EntityUid = entityUid; Type = type; + SlotOwner = slotOwner; } } @@ -75,7 +81,9 @@ namespace Content.Shared.Verbs /// public class GetInteractionVerbsEvent : GetVerbsEvent { - public GetInteractionVerbsEvent(IEntity user, IEntity target, bool force=false) : base(user, target, force) { } + public GetInteractionVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands, + bool canInteract, bool canAccess) : base(user, target, @using, hands, canInteract, canAccess) { } + } /// @@ -89,7 +97,8 @@ namespace Content.Shared.Verbs /// public class GetActivationVerbsEvent : GetVerbsEvent { - public GetActivationVerbsEvent(IEntity user, IEntity target, bool force=false) : base(user, target, force) { } + public GetActivationVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands, + bool canInteract, bool canAccess) : base(user, target, @using, hands, canInteract, canAccess) { } } /// @@ -101,7 +110,8 @@ namespace Content.Shared.Verbs /// public class GetAlternativeVerbsEvent : GetVerbsEvent { - public GetAlternativeVerbsEvent(IEntity user, IEntity target, bool force=false) : base(user, target, force) { } + public GetAlternativeVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands, + bool canInteract, bool canAccess) : base(user, target, @using, hands, canInteract, canAccess) { } } /// @@ -113,7 +123,8 @@ namespace Content.Shared.Verbs /// public class GetOtherVerbsEvent : GetVerbsEvent { - public GetOtherVerbsEvent(IEntity user, IEntity target, bool force=false) : base(user, target, force) { } + public GetOtherVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands, + bool canInteract, bool canAccess) : base(user, target, @using, hands, canInteract, canAccess) { } } /// @@ -124,7 +135,7 @@ namespace Content.Shared.Verbs /// /// Event output. Set of verbs that can be executed. /// - public SortedSet Verbs = new(); + public readonly SortedSet Verbs = new(); /// /// Can the user physically access the target? @@ -133,17 +144,17 @@ namespace Content.Shared.Verbs /// This is a combination of and /// . /// - public bool CanAccess; + public readonly bool CanAccess = false; /// /// The entity being targeted for the verb. /// - public IEntity Target; + public readonly IEntity Target; /// /// The entity that will be "performing" the verb. /// - public IEntity User; + public readonly IEntity User; /// /// Can the user physically interact? @@ -153,7 +164,7 @@ namespace Content.Shared.Verbs /// to check this, it prevents it from having to be repeatedly called by each individual system that might /// contribute a verb. /// - public bool CanInteract; + public readonly bool CanInteract; /// /// The User's hand component. @@ -161,7 +172,7 @@ namespace Content.Shared.Verbs /// /// This may be null if the user has no hands. /// - public SharedHandsComponent? Hands; + public readonly SharedHandsComponent? Hands; /// /// The entity currently being held by the active hand. @@ -170,34 +181,21 @@ namespace Content.Shared.Verbs /// This is only ever not null when is true and the user /// has hands. /// - public IEntity? Using; + public readonly IEntity? Using; - public GetVerbsEvent(IEntity user, IEntity target, bool force=false) + // for eventual removal of IEntity. + public EntityUid UserUid => User.Uid; + public EntityUid TargetUid => Target.Uid; + public EntityUid? UsingUid => Using?.Uid; + + public GetVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands, bool canInteract, bool canAccess) { User = user; Target = target; - - CanAccess = force || (Target == User) || user.IsInSameOrParentContainer(target) && - EntitySystem.Get().InRangeUnobstructed(user, target, ignoreInsideBlocker: true); - - // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually - // call ActionBlocker checks, just cache it for the verb request. - var actionBlockerSystem = EntitySystem.Get(); - CanInteract = force || actionBlockerSystem.CanInteract(user.Uid); - - if (!user.TryGetComponent(out Hands) || - !actionBlockerSystem.CanUse(user.Uid)) - return; - - Hands.TryGetActiveHeldEntity(out Using); - - // Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used". - // This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging - // their sprite. - if (Using != null && Using.TryGetComponent(out var pull)) - { - Using = IoCManager.Resolve().GetEntity(pull.BlockingEntity); - } + Using = @using; + Hands = hands; + CanAccess = canAccess; + CanInteract = canInteract; } } }