diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index b72a7c4eb3..fd732009e9 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -87,7 +87,6 @@ public abstract partial class SharedHandsSystem /// /// /// - public void RemoveHands(EntityUid uid, HandsComponent? handsComp = null) { if (!Resolve(uid, ref handsComp)) @@ -137,6 +136,43 @@ public abstract partial class SharedHandsSystem return false; } + public bool TryGetActiveHand(Entity entity, [NotNullWhen(true)] out Hand? hand) + { + if (!Resolve(entity, ref entity.Comp, false)) + { + hand = null; + return false; + } + + hand = entity.Comp.ActiveHand; + return hand != null; + } + + public bool TryGetActiveItem(Entity entity, [NotNullWhen(true)] out EntityUid? item) + { + if (!TryGetActiveHand(entity, out var hand)) + { + item = null; + return false; + } + + item = hand.HeldEntity; + return item != null; + } + + public Hand? GetActiveHand(Entity entity) + { + if (!Resolve(entity, ref entity.Comp)) + return null; + + return entity.Comp.ActiveHand; + } + + public EntityUid? GetActiveItem(Entity entity) + { + return GetActiveHand(entity)?.HeldEntity; + } + /// /// Enumerate over hands, starting with the currently active hand. /// @@ -227,9 +263,17 @@ public abstract partial class SharedHandsSystem return true; } - public bool IsHolding(EntityUid uid, EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null) + public bool IsHolding(Entity entity, [NotNullWhen(true)] EntityUid? item) + { + return IsHolding(entity, item, out _, entity); + } + + public bool IsHolding(EntityUid uid, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null) { inHand = null; + if (entity == null) + return false; + if (!Resolve(uid, ref handsComp, false)) return false; diff --git a/Content.Shared/UserInterface/ActivatableUIComponent.cs b/Content.Shared/UserInterface/ActivatableUIComponent.cs index 136a1f82cf..3f83816b7d 100644 --- a/Content.Shared/UserInterface/ActivatableUIComponent.cs +++ b/Content.Shared/UserInterface/ActivatableUIComponent.cs @@ -4,22 +4,26 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations; namespace Content.Shared.UserInterface { - [RegisterComponent, NetworkedComponent] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class ActivatableUIComponent : Component { [DataField(required: true, customTypeSerializer: typeof(EnumSerializer))] - public Enum? Key { get; set; } = default!; + public Enum? Key; + + /// + /// Whether the item must be held in one of the user's hands to work. + /// This is ignored unless is true. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public bool InHandsOnly; + + [DataField] + public bool SingleUser; [ViewVariables(VVAccess.ReadWrite)] [DataField] - public bool InHandsOnly { get; set; } = false; - - [DataField] - public bool SingleUser { get; set; } = false; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public bool AdminOnly { get; set; } = false; + public bool AdminOnly; [DataField] public LocId VerbText = "ui-verb-toggle-open"; @@ -38,16 +42,15 @@ namespace Content.Shared.UserInterface /// /// Entities that are required to open this UI. /// - [DataField("allowedItems")] - [ViewVariables(VVAccess.ReadWrite)] - public EntityWhitelist? AllowedItems = null; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? RequiredItems; /// - /// Whether you can activate this ui with activateinhand or not + /// If true, then this UI can only be opened via verbs. I.e., normal interactions/activations will not open + /// the UI. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public bool RightClickOnly; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool VerbOnly; /// /// Whether spectators (non-admin ghosts) should be allowed to view this UI. @@ -57,17 +60,18 @@ namespace Content.Shared.UserInterface public bool AllowSpectator = true; /// - /// Whether the UI should close when the item is deselected due to a hand swap or drop + /// Whether the item must be in the user's currently selected/active hand. + /// This is ignored unless is true. /// [ViewVariables(VVAccess.ReadWrite)] [DataField] - public bool CloseOnHandDeselect = true; + public bool RequireActiveHand = true; /// /// The client channel currently using the object, or null if there's none/not single user. /// NOTE: DO NOT DIRECTLY SET, USE ActivatableUISystem.SetCurrentSingleUser /// - [ViewVariables] + [DataField, AutoNetworkedField] public EntityUid? CurrentSingleUser; } } diff --git a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs index 64099c9573..b8a815c7a8 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs @@ -20,12 +20,19 @@ public sealed partial class ActivatableUISystem { _cell.SetPowerCellDrawEnabled(uid, false); - if (HasComp(uid) && - TryComp(uid, out var activatable) && - activatable.Key != null) + if (!HasComp(uid) || + !TryComp(uid, out ActivatableUIComponent? activatable)) { - _uiSystem.CloseUi(uid, activatable.Key); + return; } + + if (activatable.Key == null) + { + Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}"); + return; + } + + _uiSystem.CloseUi(uid, activatable.Key); } private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIOpenedEvent args) @@ -55,9 +62,15 @@ public sealed partial class ActivatableUISystem /// public void CheckUsage(EntityUid uid, ActivatableUIComponent? active = null, ActivatableUIRequiresPowerCellComponent? component = null, PowerCellDrawComponent? draw = null) { - if (!Resolve(uid, ref component, ref draw, ref active, false) || active.Key == null) + if (!Resolve(uid, ref component, ref draw, ref active, false)) return; + if (active.Key == null) + { + Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}"); + return; + } + if (_cell.HasActivatableCharge(uid)) return; diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index 94271cc681..5d408012bd 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -3,11 +3,11 @@ using Content.Shared.Administration.Managers; using Content.Shared.Ghost; using Content.Shared.Hands; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Verbs; -using Robust.Shared.Player; +using Robust.Shared.Containers; namespace Content.Shared.UserInterface; @@ -17,23 +17,31 @@ public sealed partial class ActivatableUISystem : EntitySystem [Dependency] private readonly ActionBlockerSystem _blockerSystem = default!; [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + + private readonly List _toClose = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnActivate); - SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnHandDeselected); - SubscribeLocalEvent((uid, aui, _) => CloseAll(uid, aui)); - // *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it - SubscribeLocalEvent(OnParentChanged); + SubscribeLocalEvent(OnHandUnequipped); SubscribeLocalEvent(OnUIClose); + SubscribeLocalEvent>(GetActivationVerb); + SubscribeLocalEvent>(GetVerb); + + // TODO ActivatableUI + // Add UI-user component, and listen for user container changes. + // I.e., should lose a computer UI if a player gets shut into a locker. + SubscribeLocalEvent(OnGotInserted); + SubscribeLocalEvent(OnGotRemoved); + SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); - - SubscribeLocalEvent>(AddOpenUiVerb); - SubscribeLocalEvent(OnActionPerform); InitializePower(); @@ -59,25 +67,54 @@ public sealed partial class ActivatableUISystem : EntitySystem args.Handled = _uiSystem.TryToggleUi(uid, args.Key, args.Performer); } - private void AddOpenUiVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent args) + + private void GetActivationVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent args) + { + if (component.VerbOnly || !ShouldAddVerb(uid, component, args)) + return; + + args.Verbs.Add(new ActivationVerb + { + // TODO VERBS add "open UI" icon + Act = () => InteractUI(args.User, uid, component), + Text = Loc.GetString(component.VerbText) + }); + } + + private void GetVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent args) + { + if (!component.VerbOnly || !ShouldAddVerb(uid, component, args)) + return; + + args.Verbs.Add(new Verb + { + // TODO VERBS add "open UI" icon + Act = () => InteractUI(args.User, uid, component), + Text = Loc.GetString(component.VerbText) + }); + } + + private bool ShouldAddVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent args) where T : Verb { if (!args.CanAccess) - return; + return false; - if (component.RequireHands && args.Hands == null) - return; + if (component.RequireHands) + { + if (args.Hands == null) + return false; - if (component.InHandsOnly && args.Using != uid) - return; + if (component.InHandsOnly) + { + if (!_hands.IsHolding(args.User, uid, out var hand, args.Hands)) + return false; - if (!args.CanInteract && (!component.AllowSpectator || !HasComp(args.User))) - return; + if (component.RequireActiveHand && args.Hands.ActiveHand != hand) + return false; + } + } - ActivationVerb verb = new(); - verb.Act = () => InteractUI(args.User, uid, component); - verb.Text = Loc.GetString(component.VerbText); - // TODO VERBS add "open UI" icon? - args.Verbs.Add(verb); + return args.CanInteract || component.AllowSpectator && HasComp(args.User); } private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args) @@ -85,24 +122,10 @@ public sealed partial class ActivatableUISystem : EntitySystem if (args.Handled) return; - if (component.InHandsOnly) + if (component.VerbOnly) return; - if (component.AllowedItems != null) - return; - - args.Handled = InteractUI(args.User, uid, component); - } - - private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) - { - if (args.Handled) - return; - - if (component.RightClickOnly) - return; - - if (component.AllowedItems != null) + if (component.RequiredItems != null) return; args.Handled = InteractUI(args.User, uid, component); @@ -110,15 +133,19 @@ public sealed partial class ActivatableUISystem : EntitySystem private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args) { - if (args.Handled) return; - if (component.AllowedItems == null) return; - if (!component.AllowedItems.IsValid(args.Used, EntityManager)) return; - args.Handled = InteractUI(args.User, uid, component); - } + if (args.Handled) + return; - private void OnParentChanged(EntityUid uid, ActivatableUIComponent aui, ref EntParentChangedMessage args) - { - CloseAll(uid, aui); + if (component.VerbOnly) + return; + + if (component.RequiredItems == null) + return; + + if (!component.RequiredItems.IsValid(args.Used, EntityManager)) + return; + + args.Handled = InteractUI(args.User, uid, component); } private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args) @@ -148,22 +175,33 @@ public sealed partial class ActivatableUISystem : EntitySystem if (!_blockerSystem.CanInteract(user, uiEntity) && (!aui.AllowSpectator || !HasComp(user))) return false; - if (aui.RequireHands && !HasComp(user)) - return false; + if (aui.RequireHands) + { + if (!TryComp(user, out HandsComponent? hands)) + return false; + + if (aui.InHandsOnly) + { + if (!_hands.IsHolding(user, uiEntity, out var hand, hands)) + return false; + + if (aui.RequireActiveHand && hands.ActiveHand != hand) + return false; + } + } if (aui.AdminOnly && !_adminManager.IsAdmin(user)) return false; if (aui.SingleUser && aui.CurrentSingleUser != null && user != aui.CurrentSingleUser) { - string message = Loc.GetString("machine-already-in-use", ("machine", uiEntity)); + var message = Loc.GetString("machine-already-in-use", ("machine", uiEntity)); _popupSystem.PopupEntity(message, uiEntity, user); - // If we get here, supposedly, the object is in use. - // Check with BUI that it's ACTUALLY in use just in case. - // Since this could brick the object if it goes wrong. if (_uiSystem.IsUiOpen(uiEntity, aui.Key)) - return false; + return true; + + Log.Error($"Activatable UI has user without being opened? Entity: {ToPrettyString(uiEntity)}. User: {aui.CurrentSingleUser}, Key: {aui.Key}"); } // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. @@ -199,26 +237,77 @@ public sealed partial class ActivatableUISystem : EntitySystem return; aui.CurrentSingleUser = user; + Dirty(uid, aui); RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent()); } public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null) { - if (!Resolve(uid, ref aui, false) || aui.Key == null) + if (!Resolve(uid, ref aui, false)) return; + if (aui.Key == null) + { + Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}"); + return; + } + _uiSystem.CloseUi(uid, aui.Key); } - private void OnHandDeselected(EntityUid uid, ActivatableUIComponent? aui, HandDeselectedEvent args) + private void OnHandDeselected(Entity ent, ref HandDeselectedEvent args) { - if (!Resolve(uid, ref aui, false)) + if (ent.Comp.RequireHands && ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand) + CloseAll(ent, ent); + } + + private void OnHandUnequipped(Entity ent, ref GotUnequippedHandEvent args) + { + if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) + CloseAll(ent, ent); + } + + private void OnGotInserted(Entity ent, ref EntGotInsertedIntoContainerMessage args) + { + CheckAccess((ent, ent)); + } + + private void OnGotRemoved(Entity ent, ref EntGotRemovedFromContainerMessage args) + { + CheckAccess((ent, ent)); + } + + public void CheckAccess(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) return; - if (!aui.CloseOnHandDeselect) + if (ent.Comp.Key == null) + { + Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}"); return; + } - CloseAll(uid, aui); + foreach (var user in _uiSystem.GetActors(ent.Owner, ent.Comp.Key)) + { + if (!_container.IsInSameOrParentContainer(user, ent) + && !_interaction.CanAccessViaStorage(user, ent)) + { + _toClose.Add(user); + continue; + + } + + if (!_interaction.InRangeUnobstructed(user, ent)) + _toClose.Add(user); + } + + foreach (var user in _toClose) + { + _uiSystem.CloseUi(ent.Owner, ent.Comp.Key, user); + } + + _toClose.Clear(); } } diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml index 02ec58bd67..0c2ded620e 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml @@ -372,8 +372,7 @@ - type: MeleeSpeech - type: ActivatableUI key: enum.MeleeSpeechUiKey.Key - closeOnHandDeselect: false - rightClickOnly: true + verbOnly: true - type: Actions - type: UserInterface interfaces: diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml index ada46c5d02..a832bea741 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml @@ -16,7 +16,7 @@ - type: AccessReader - type: ActivatableUI key: enum.DoorElectronicsConfigurationUiKey.Key - allowedItems: + requiredItems: tags: - DoorElectronicsConfigurator - type: UserInterface diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/war_declarator.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/war_declarator.yml index c0ce4cba18..78009f1042 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/war_declarator.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/war_declarator.yml @@ -14,7 +14,7 @@ - type: ActivatableUI inHandsOnly: true singleUser: true - closeOnHandDeselect: false + requireActiveHand: false key: enum.WarDeclaratorUiKey.Key - type: UserInterface interfaces: diff --git a/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml b/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml index e7c0b14beb..170751766b 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml @@ -17,7 +17,7 @@ - type: ActivatableUI key: enum.ForensicScannerUiKey.Key inHandsOnly: true - closeOnHandDeselect: false + requireActiveHand: false - type: UserInterface interfaces: enum.ForensicScannerUiKey.Key: diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index e1efc03993..472d0ecbe1 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -86,7 +86,6 @@ - type: ActivatableUI key: enum.PdaUiKey.Key singleUser: true - closeOnHandDeselect: false - type: UserInterface interfaces: enum.PdaUiKey.Key: diff --git a/Resources/Prototypes/Entities/Objects/Misc/books.yml b/Resources/Prototypes/Entities/Objects/Misc/books.yml index e44e981a6c..25e6bb9f94 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/books.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/books.yml @@ -22,7 +22,6 @@ contentSize: 12000 - type: ActivatableUI key: enum.PaperUiKey.Key - closeOnHandDeselect: false - type: UserInterface interfaces: enum.PaperUiKey.Key: diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index 0c87459164..5fa341c976 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -18,7 +18,6 @@ - type: PaperLabelType - type: ActivatableUI key: enum.PaperUiKey.Key - closeOnHandDeselect: false requireHands: false - type: UserInterface interfaces: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml index f30e1cf633..19a0b36ee9 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml @@ -17,7 +17,6 @@ - type: ActivatableUIRequiresPowerCell - type: ActivatableUI key: enum.CrewMonitoringUIKey.Key - closeOnHandDeselect: false - type: UserInterface interfaces: enum.CrewMonitoringUIKey.Key: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml index 4f8b6f2936..c01aaa84a9 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml @@ -17,7 +17,6 @@ storedRotation: -90 - type: ActivatableUI key: enum.HealthAnalyzerUiKey.Key - closeOnHandDeselect: false - type: UserInterface interfaces: enum.HealthAnalyzerUiKey.Key: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml index 4250d9ccdd..bf0a3be4e5 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml @@ -9,7 +9,7 @@ state: icon - type: ActivatableUI key: enum.AnomalyScannerUiKey.Key - closeOnHandDeselect: false + requireActiveHand: false inHandsOnly: true - type: UserInterface interfaces: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Service/barber.yml b/Resources/Prototypes/Entities/Objects/Specific/Service/barber.yml index d7b0a566e1..251331919e 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Service/barber.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Service/barber.yml @@ -10,7 +10,8 @@ - type: MagicMirror - type: ActivatableUI key: enum.MagicMirrorUiKey.Key - closeOnHandDeselect: true + inHandsOnly: true + requireActiveHand: true - type: UserInterface interfaces: enum.MagicMirrorUiKey.Key: diff --git a/Resources/Prototypes/Entities/Objects/Specific/atmos.yml b/Resources/Prototypes/Entities/Objects/Specific/atmos.yml index acf5f6f240..c67e7ff8c3 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/atmos.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/atmos.yml @@ -13,7 +13,7 @@ - type: ActivatableUI inHandsOnly: true singleUser: true - closeOnHandDeselect: false + requireActiveHand: false key: enum.GasAnalyzerUiKey.Key - type: UserInterface interfaces: diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index b6c7c49855..d63e1f0aa9 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -67,7 +67,7 @@ - type: ActivatableUI key: enum.AccessOverriderUiKey.Key requireHands: true - closeOnHandDeselect: false + requireActiveHand: false singleUser: true - type: ItemSlots - type: ContainerContainer