diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs
index 3667507aa0..c201aed9d1 100644
--- a/Content.Client/Entry/IgnoredComponents.cs
+++ b/Content.Client/Entry/IgnoredComponents.cs
@@ -110,6 +110,7 @@ namespace Content.Client.Entry
"Lung",
"BatteryDischarger",
"Apc",
+ "IntrinsicUI",
"PowerProvider",
"ApcPowerReceiver",
"Cable",
diff --git a/Content.Server/UserInterface/ActivatableUIComponent.cs b/Content.Server/UserInterface/ActivatableUIComponent.cs
index f55626884b..5f29aaa023 100644
--- a/Content.Server/UserInterface/ActivatableUIComponent.cs
+++ b/Content.Server/UserInterface/ActivatableUIComponent.cs
@@ -1,18 +1,8 @@
-using System;
-using Content.Shared.Instruments;
-using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Reflection;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Enums;
-using Robust.Shared.Player;
-using Robust.Shared.Network;
-using Robust.Shared.IoC;
-using Robust.Shared.Utility;
using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.Manager.Attributes;
-using Robust.Shared.ViewVariables;
+
namespace Content.Server.UserInterface
{
diff --git a/Content.Server/UserInterface/IntrinsicUIComponent.cs b/Content.Server/UserInterface/IntrinsicUIComponent.cs
new file mode 100644
index 0000000000..02bfa6db40
--- /dev/null
+++ b/Content.Server/UserInterface/IntrinsicUIComponent.cs
@@ -0,0 +1,56 @@
+using Content.Shared.Actions.ActionTypes;
+using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Shared.Reflection;
+using Robust.Shared.Serialization;
+
+namespace Content.Server.UserInterface;
+
+[RegisterComponent]
+public sealed class IntrinsicUIComponent : Component, ISerializationHooks
+{
+ ///
+ /// List of UIs and their actions that this entity has.
+ ///
+ [ViewVariables, DataField("uis", required: true)]
+ public List UIs = new();
+
+ void ISerializationHooks.AfterDeserialization()
+ {
+ foreach (var ui in UIs)
+ {
+ ui.AfterDeserialization();
+ }
+ }
+}
+
+[DataDefinition]
+public struct IntrinsicUIEntry
+{
+ [ViewVariables]
+ public Enum? Key { get; set; }
+
+ ///
+ /// The BUI key that this intrinsic UI should open.
+ ///
+ [DataField("key", readOnly: true, required: true)]
+ private string _keyRaw = default!;
+
+ ///
+ /// The action used for this BUI.
+ ///
+ [DataField("toggleAction", required: true)]
+ public InstantAction ToggleAction = new();
+
+ public void AfterDeserialization()
+ {
+ var reflectionManager = IoCManager.Resolve();
+ if (reflectionManager.TryParseEnumReference(_keyRaw, out var key))
+ Key = key;
+
+ if (ToggleAction.Event is ToggleIntrinsicUIEvent ev)
+ {
+ ev.Key = Key;
+ }
+ }
+}
diff --git a/Content.Server/UserInterface/IntrinsicUISystem.cs b/Content.Server/UserInterface/IntrinsicUISystem.cs
new file mode 100644
index 0000000000..bf9a0a6264
--- /dev/null
+++ b/Content.Server/UserInterface/IntrinsicUISystem.cs
@@ -0,0 +1,89 @@
+using Content.Server.Actions;
+using Content.Shared.Actions;
+using Content.Shared.Toggleable;
+using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Shared.Reflection;
+using Robust.Shared.Serialization;
+
+namespace Content.Server.UserInterface;
+
+public sealed class IntrinsicUISystem : EntitySystem
+{
+ [Dependency] private readonly ActionsSystem _actionsSystem = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnGetActions);
+ SubscribeLocalEvent(OnActionToggle);
+ }
+
+ private void OnActionToggle(EntityUid uid, IntrinsicUIComponent component, ToggleIntrinsicUIEvent args)
+ {
+ args.Handled = InteractUI(uid, args.Key, component);
+ }
+
+ private void OnGetActions(EntityUid uid, IntrinsicUIComponent component, ComponentStartup args)
+ {
+ if (!TryComp(uid, out var actions))
+ return;
+
+ foreach (var entry in component.UIs)
+ {
+ _actionsSystem.AddAction(uid, entry.ToggleAction, null, actions);
+ }
+ }
+
+ public bool InteractUI(EntityUid uid, Enum? key, IntrinsicUIComponent? iui = null, ActorComponent? actor = null)
+ {
+ if (!Resolve(uid, ref iui, ref actor))
+ return false;
+
+ if (key is null)
+ {
+ Logger.ErrorS("bui", $"Entity {ToPrettyString(uid)} has an invalid intrinsic UI.");
+ }
+
+ var ui = GetUIOrNull(uid, key, iui);
+
+ if (ui is null)
+ {
+ Logger.ErrorS("bui", $"Couldn't get UI {key} on {ToPrettyString(uid)}");
+ return false;
+ }
+
+ var attempt = new IntrinsicUIOpenAttemptEvent(uid, key);
+ RaiseLocalEvent(uid, attempt, false);
+ if (attempt.Cancelled) return false;
+
+ ui.Toggle(actor.PlayerSession);
+ return true;
+ }
+
+ private BoundUserInterface? GetUIOrNull(EntityUid uid, Enum? key, IntrinsicUIComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return null;
+
+ return key is null ? null : uid.GetUIOrNull(key);
+ }
+}
+
+[UsedImplicitly]
+public sealed class ToggleIntrinsicUIEvent : PerformActionEvent
+{
+ [ViewVariables]
+ public Enum? Key { get; set; }
+}
+
+// Competing with ActivatableUI for horrible event names.
+public sealed class IntrinsicUIOpenAttemptEvent : CancellableEntityEventArgs
+{
+ public EntityUid User { get; }
+ public Enum? Key { get; }
+ public IntrinsicUIOpenAttemptEvent(EntityUid who, Enum? key)
+ {
+ User = who;
+ Key = key;
+ }
+}
diff --git a/Resources/Locale/en-US/robotics/ai-actions.ftl b/Resources/Locale/en-US/robotics/ai-actions.ftl
new file mode 100644
index 0000000000..940f574679
--- /dev/null
+++ b/Resources/Locale/en-US/robotics/ai-actions.ftl
@@ -0,0 +1,10 @@
+action-name-show-solar-console = Solar Control Interface
+action-description-show-solar-console = View a solar control interface.
+action-name-show-communications-console = Communications Interface
+action-description-show-communications-console = View a communications interface.
+action-name-show-radar-console = Mass Scanner Interface
+action-description-show-radar-console = View a mass scanner interface.
+action-name-show-cargo-console = Cargo Ordering Interface
+action-description-show-cargo-console = View a cargo ordering interface.
+action-name-show-crew-monitoring-console = Crew Monitoring Interface.
+action-description-crew-monitoring-console = View a crew monitoring interface.
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index ae326af4cc..dd8267a7b9 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -35,3 +35,133 @@
- type: Access
groups:
- AllAccess
+ - type: UserInterface
+ interfaces:
+ - key: enum.SolarControlConsoleUiKey.Key
+ type: SolarControlConsoleBoundUserInterface
+ - key: enum.CommunicationsConsoleUiKey.Key
+ type: CommunicationsConsoleBoundUserInterface
+ - key: enum.RadarConsoleUiKey.Key
+ type: RadarConsoleBoundUserInterface
+ - key: enum.CargoConsoleUiKey.Key
+ type: CargoConsoleBoundUserInterface
+ - key: enum.CrewMonitoringUIKey.Key
+ type: CrewMonitoringBoundUserInterface
+ - type: IntrinsicUI
+ uis:
+ - key: enum.SolarControlConsoleUiKey.Key
+ toggleAction:
+ name: action-name-show-solar-console
+ description: action-description-show-solar-console
+ icon: Structures/Machines/parts.rsi/box_0.png
+ iconOn: Structures/Machines/parts.rsi/box_2.png
+ keywords: [ "AI", "console", "interface" ]
+ priority: -10
+ event: !type:ToggleIntrinsicUIEvent
+ - key: enum.CommunicationsConsoleUiKey.Key
+ toggleAction:
+ name: action-name-show-communications-console
+ description: action-description-show-communications-console
+ icon: Structures/Machines/parts.rsi/box_0.png
+ iconOn: Structures/Machines/parts.rsi/box_2.png
+ keywords: [ "AI", "console", "interface" ]
+ priority: -10
+ event: !type:ToggleIntrinsicUIEvent
+ - key: enum.RadarConsoleUiKey.Key
+ toggleAction:
+ name: action-name-show-radar-console
+ description: action-description-show-radar-console
+ icon: Structures/Machines/parts.rsi/box_0.png
+ iconOn: Structures/Machines/parts.rsi/box_2.png
+ keywords: [ "AI", "console", "interface" ]
+ priority: -10
+ event: !type:ToggleIntrinsicUIEvent
+ - key: enum.CargoConsoleUiKey.Key
+ toggleAction:
+ name: action-name-show-cargo-console
+ description: action-description-show-cargo-console
+ icon: Structures/Machines/parts.rsi/box_0.png
+ iconOn: Structures/Machines/parts.rsi/box_2.png
+ keywords: [ "AI", "console", "interface" ]
+ priority: -10
+ event: !type:ToggleIntrinsicUIEvent
+ - key: enum.CrewMonitoringUIKey.Key
+ toggleAction:
+ name: action-name-show-crew-monitoring-console
+ description: action-description-crew-monitoring-console
+ icon: Structures/Machines/parts.rsi/box_0.png
+ iconOn: Structures/Machines/parts.rsi/box_2.png
+ keywords: [ "AI", "console", "interface" ]
+ priority: -10
+ event: !type:ToggleIntrinsicUIEvent
+ - type: SolarControlConsole # look ma i AM the computer!
+ - type: CommunicationsConsole
+ - type: RadarConsole
+ - type: CargoConsole
+ - type: CargoOrderDatabase
+ - type: GalacticMarket # wow this kinda sucks.
+ products:
+ - MedicalSupplies
+ - MedicalChemistrySupplies
+ - EmergencyExplosive
+ - EmergencyFire
+ - EmergencyInternals
+ - EmergencyRadiation
+ - EmergencyInflatablewall
+ - ArmorySmg
+ - ArmoryShotgun
+ - SecurityArmor
+ - SecurityRiot
+ - SecurityLaser
+ - SecurityHelmet
+ - SecuritySupplies
+ - SecurityNonLethal
+ - SecurityRestraints
+ - HydroponicsTools
+ - HydroponicsSeeds
+ - HydroponicsSeedsExotic
+ - LivestockMonkeyCube
+ - LivestockCow
+ - LivestockChicken
+ - LivestockDuck
+ - LivestockGoat
+ - FoodPizza
+ - ServiceJanitorial
+ - ServiceLightsReplacement
+ - ServiceSmokeables
+ - ServiceCustomSmokable
+ - ServiceBureaucracy
+ - ServicePersonnel
+ - EngineeringCableLv
+ - EngineeringCableMv
+ - EngineeringCableHv
+ - EngineeringCableBulk
+ - EngineAmeShielding
+ - EngineAmeJar
+ - EngineAmeControl
+ - EngineSolar
+ - FunPlushies
+ - FunArtSupplies
+ - FunInstruments
+ - FunBoardGames
+ - MaterialSteel
+ - MaterialGlass
+ - MaterialPlastic
+ - MaterialPlasteel
+ - MaterialPlasma
+ - EngineSingularityEmitter
+ - EngineSingularityCollector
+ - EngineSingularityGenerator
+ - EngineSingularityContainment
+ - EngineParticleAccelerator
+ - ShuttleThruster
+ - ShuttleGyroscope
+ - AtmosphericsAir
+ - AtmosphericsOxygen
+ - AtmosphericsNitrogen
+ - AtmosphericsCarbonDioxide
+ - type: CrewMonitoringConsole
+ - type: DeviceNetworkComponent
+ deviceNetId: Wireless
+ - type: WirelessNetworkConnection
+ range: 500