diff --git a/Content.Server/UserInterface/ActivatableUIComponent.cs b/Content.Server/UserInterface/ActivatableUIComponent.cs
index 5f29aaa023..25b1a3ee67 100644
--- a/Content.Server/UserInterface/ActivatableUIComponent.cs
+++ b/Content.Server/UserInterface/ActivatableUIComponent.cs
@@ -33,6 +33,22 @@ namespace Content.Server.UserInterface
[DataField("verbText")]
public string VerbText = "ui-verb-toggle-open";
+ ///
+ /// Whether you need a hand to operate this UI. The hand does not need to be free, you just need to have one.
+ ///
+ ///
+ /// This should probably be true for most machines & computers, but there will still be UIs that represent a
+ /// more generic interaction / configuration that might not require hands.
+ ///
+ [DataField("requireHands")]
+ public bool RequireHands = true;
+
+ ///
+ /// Whether spectators (non-admin ghosts) should be allowed to view this UI.
+ ///
+ [DataField("allowSpectator")]
+ public bool AllowSpectator = 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
diff --git a/Content.Server/UserInterface/ActivatableUISystem.cs b/Content.Server/UserInterface/ActivatableUISystem.cs
index c5e85d3d84..2278a4cc86 100644
--- a/Content.Server/UserInterface/ActivatableUISystem.cs
+++ b/Content.Server/UserInterface/ActivatableUISystem.cs
@@ -1,8 +1,7 @@
using Content.Server.Administration.Managers;
using Content.Server.Ghost.Components;
-using Content.Shared.Actions;
-using Content.Shared.Actions.ActionTypes;
using Content.Shared.Hands;
+using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
@@ -28,12 +27,25 @@ namespace Content.Server.UserInterface
// *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it
SubscribeLocalEvent(OnParentChanged);
SubscribeLocalEvent(OnUIClose);
+ SubscribeLocalEvent(OnBoundInterfaceInteractAttempt);
SubscribeLocalEvent>(AddOpenUiVerb);
SubscribeLocalEvent(OnActionPerform);
}
+ private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
+ {
+ if (!TryComp(ev.Target, out ActivatableUIComponent? comp))
+ return;
+
+ if (!comp.RequireHands)
+ return;
+
+ if (!TryComp(ev.Sender.AttachedEntity, out SharedHandsComponent? hands) || hands.Hands.Count == 0)
+ ev.Cancel();
+ }
+
private void OnActionPerform(EntityUid uid, ServerUserInterfaceComponent component, OpenUiActionEvent args)
{
if (args.Handled || args.Key == null)
@@ -57,7 +69,7 @@ namespace Content.Server.UserInterface
if (component.InHandsOnly && args.Using != uid)
return;
- if (!args.CanInteract && !HasComp(args.User))
+ if (!args.CanInteract && (!component.AllowSpectator || !HasComp(args.User)))
return;
ActivationVerb verb = new();
diff --git a/Resources/Prototypes/Entities/Structures/Furniture/instruments.yml b/Resources/Prototypes/Entities/Structures/Furniture/instruments.yml
index ac4ce701ea..0ae142426f 100644
--- a/Resources/Prototypes/Entities/Structures/Furniture/instruments.yml
+++ b/Resources/Prototypes/Entities/Structures/Furniture/instruments.yml
@@ -6,6 +6,7 @@
components:
- type: Instrument
- type: ActivatableUI
+ allowSpectator: false # otherwise they can play client-side music
inHandsOnly: false
singleUser: true
verbText: verb-instrument-openui