diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 35feb13d51..12f78f19dc 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -216,6 +216,8 @@ namespace Content.Client.Entry "Log", "Hoe", "Seed", + "ActivatableUI", + "ActivatableUIRequiresPower", "BotanySharp", "PlantSampleTaker", "Internals", diff --git a/Content.Server/Access/Components/IdCardConsoleComponent.cs b/Content.Server/Access/Components/IdCardConsoleComponent.cs index 9ce2433ba1..61eefd2922 100644 --- a/Content.Server/Access/Components/IdCardConsoleComponent.cs +++ b/Content.Server/Access/Components/IdCardConsoleComponent.cs @@ -21,8 +21,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Access.Components { [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate, IInteractUsing, IBreakAct + public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IInteractUsing, IBreakAct { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -207,17 +206,6 @@ namespace Content.Server.Access.Components UserInterface?.SetState(newState); } - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return; - } - if (!Powered) return; - - UserInterface?.Open(actor.PlayerSession); - } - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { var item = eventArgs.Using; diff --git a/Content.Server/Atmos/Components/BaseComputerUserInterfaceComponent.cs b/Content.Server/Atmos/Components/BaseComputerUserInterfaceComponent.cs deleted file mode 100644 index 5d03b258b5..0000000000 --- a/Content.Server/Atmos/Components/BaseComputerUserInterfaceComponent.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using Content.Server.Power.Components; -using Content.Server.UserInterface; -using Content.Shared.ActionBlocker; -using Content.Shared.Interaction; -using Content.Shared.Popups; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.ViewVariables; - -namespace Content.Server.GameObjects.Components -{ - /// - /// This component is used as a base class for classes like SolarControlConsoleComponent. - /// These components operate the server-side logic for the "primary UI" of a computer. - /// That means showing the UI when a user activates it, for example. - /// - public abstract class BaseComputerUserInterfaceComponent : Component - { - protected readonly object UserInterfaceKey; - - [ViewVariables] protected BoundUserInterface? UserInterface => Owner.GetUIOrNull(UserInterfaceKey); - [ViewVariables] public bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered; - - public BaseComputerUserInterfaceComponent(object key) - { - UserInterfaceKey = key; - } - - protected override void Initialize() - { - base.Initialize(); - - if (UserInterface != null) - UserInterface.OnReceiveMessage += OnReceiveUIMessageCallback; - } - - /// - /// Internal callback used to grab session and session attached entity before any more work is done. - /// This is so that sessionEntity is always available to checks up and down the line. - /// - private void OnReceiveUIMessageCallback(ServerBoundUserInterfaceMessage obj) - { - var session = obj.Session; - var sessionEntity = session.AttachedEntity; - if (sessionEntity == null) - return; // No session entity, so we're probably not able to touch this. - OnReceiveUnfilteredUserInterfaceMessage(obj, sessionEntity); - } - - /// - /// Override this to handle messages from the UI before filtering them. - /// Calling base is necessary if you want this class to have any meaning. - /// - protected void OnReceiveUnfilteredUserInterfaceMessage(ServerBoundUserInterfaceMessage obj, IEntity sessionEntity) - { - // "Across all computers" "anti-cheats" ought to be put here or at some parent level (BaseDeviceUserInterfaceComponent?) - // Determine some facts about the session. - // Powered? - if (!Powered) - { - sessionEntity.PopupMessageCursor(Loc.GetString("base-computer-ui-component-not-powered")); - return; // Not powered, so this computer should probably do nothing. - } - // Can we interact? - if (!EntitySystem.Get().CanInteract(sessionEntity.Uid)) - { - sessionEntity.PopupMessageCursor(Loc.GetString("base-computer-ui-component-cannot-interact")); - return; - } - // Good to go! - OnReceiveUserInterfaceMessage(obj); - } - - /// - /// Override this to handle messages from the UI. - /// Calling base is unnecessary. - /// These messages will automatically be blocked if the user shouldn't be able to access this computer, or if the computer has lost power. - /// - protected virtual void OnReceiveUserInterfaceMessage(ServerBoundUserInterfaceMessage obj) - { - // Nothing! - } - - [Obsolete("Component Messages are deprecated, use Entity Events instead.")] - public override void HandleMessage(ComponentMessage message, IComponent? component) - { -#pragma warning disable 618 - base.HandleMessage(message, component); -#pragma warning restore 618 - switch (message) - { - case PowerChangedMessage powerChanged: - PowerReceiverOnOnPowerStateChanged(powerChanged); - break; - } - } - - private void PowerReceiverOnOnPowerStateChanged(PowerChangedMessage e) - { - if (!e.Powered) - { - // We need to kick off users who are using it when it loses power. - UserInterface?.CloseAll(); - // Now alert subclass. - ComputerLostPower(); - } - } - - /// - /// Override this if you want the computer to do something when it loses power (i.e. reset state) - /// All UIs should have been closed by the time this is called. - /// Calling base is unnecessary. - /// - public virtual void ComputerLostPower() - { - } - - /// - /// This is called from ComputerUIActivatorSystem. - /// Override this to add additional activation conditions of some sort. - /// Calling base runs standard activation logic. - /// *This remains inside the component for overridability.* - /// - public virtual void ActivateThunk(ActivateInWorldEvent eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - if (!Powered) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("base-computer-ui-component-not-powered")); - return; - } - - UserInterface?.Open(actor.PlayerSession); - } - } -} diff --git a/Content.Server/Cargo/Components/CargoConsoleComponent.cs b/Content.Server/Cargo/Components/CargoConsoleComponent.cs index 5725c4f28a..60dd83f226 100644 --- a/Content.Server/Cargo/Components/CargoConsoleComponent.cs +++ b/Content.Server/Cargo/Components/CargoConsoleComponent.cs @@ -19,8 +19,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Cargo.Components { [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class CargoConsoleComponent : SharedCargoConsoleComponent, IActivate + public class CargoConsoleComponent : SharedCargoConsoleComponent { [Dependency] private readonly IMapManager _mapManager = default!; @@ -203,18 +202,6 @@ namespace Content.Server.Cargo.Components } } - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return; - } - if (!Powered) - return; - - UserInterface?.Open(actor.PlayerSession); - } - private void UpdateUIState() { if (_bankAccount == null || !Owner.IsValid()) diff --git a/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs b/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs index 79192ee579..58aed70422 100644 --- a/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs +++ b/Content.Server/Chemistry/TileReactions/CleanTileReaction.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Server.Cleanable; using Content.Server.Coordinates.Helpers; -using Content.Server.GameObjects.Components; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; diff --git a/Content.Server/Communications/CommunicationsConsoleComponent.cs b/Content.Server/Communications/CommunicationsConsoleComponent.cs index cfd856c99b..117d24e691 100644 --- a/Content.Server/Communications/CommunicationsConsoleComponent.cs +++ b/Content.Server/Communications/CommunicationsConsoleComponent.cs @@ -21,8 +21,7 @@ using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.Communications { [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate + public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IChatManager _chatManager = default!; @@ -121,23 +120,5 @@ namespace Content.Server.Communications break; } } - - public void OpenUserInterface(IPlayerSession session) - { - UserInterface?.Open(session); - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - return; -/* - if (!Powered) - { - return; - } -*/ - OpenUserInterface(actor.PlayerSession); - } } } diff --git a/Content.Server/Computer/ComputerUIActivatorSystem.cs b/Content.Server/Computer/ComputerUIActivatorSystem.cs deleted file mode 100644 index d3fa2fa7f5..0000000000 --- a/Content.Server/Computer/ComputerUIActivatorSystem.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Server.GameObjects.Components; -using Content.Shared.Interaction; -using JetBrains.Annotations; -using Robust.Shared.GameObjects; - -namespace Content.Server.Computer -{ - [UsedImplicitly] - internal sealed class ComputerUIActivatorSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(HandleActivate); - } - - private void HandleActivate(EntityUid uid, BaseComputerUserInterfaceComponent component, ActivateInWorldEvent args) - { - component.ActivateThunk(args); - } - } -} diff --git a/Content.Server/Construction/Conditions/WirePanel.cs b/Content.Server/Construction/Conditions/WirePanel.cs index 7f9d12a9ae..859c5af53c 100644 --- a/Content.Server/Construction/Conditions/WirePanel.cs +++ b/Content.Server/Construction/Conditions/WirePanel.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Content.Server.GameObjects.Components; using Content.Server.WireHacking; using Content.Shared.Construction; using Content.Shared.Examine; diff --git a/Content.Server/Instruments/InstrumentComponent.cs b/Content.Server/Instruments/InstrumentComponent.cs index 77bcf6eea2..56d97f5c6f 100644 --- a/Content.Server/Instruments/InstrumentComponent.cs +++ b/Content.Server/Instruments/InstrumentComponent.cs @@ -25,27 +25,11 @@ namespace Content.Server.Instruments { [RegisterComponent] - [ComponentReference(typeof(IActivate))] public class InstrumentComponent - : SharedInstrumentComponent, - IDropped, - IHandSelected, - IHandDeselected, - IActivate, - IUse, - IThrown + : SharedInstrumentComponent { private InstrumentSystem _instrumentSystem = default!; - /// - /// The client channel currently playing the instrument, or null if there's none. - /// - [ViewVariables] - private IPlayerSession? _instrumentPlayer; - - [DataField("handheld")] - private bool _handheld; - [ViewVariables] private bool _playing = false; @@ -115,11 +99,7 @@ namespace Content.Server.Instruments } } - /// - /// Whether the instrument is an item which can be held or not. - /// - [ViewVariables] - public bool Handheld => _handheld; + public IPlayerSession? InstrumentPlayer => Owner.GetComponentOrNull()?.CurrentSingleUser; /// /// Whether the instrument is currently playing or not. @@ -135,41 +115,12 @@ namespace Content.Server.Instruments } } - public IPlayerSession? InstrumentPlayer - { - get => _instrumentPlayer; - private set - { - Playing = false; - - if (_instrumentPlayer != null) - _instrumentPlayer.PlayerStatusChanged -= OnPlayerStatusChanged; - - _instrumentPlayer = value; - - if (value != null) - _instrumentPlayer!.PlayerStatusChanged += OnPlayerStatusChanged; - } - } - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(InstrumentUiKey.Key); - private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) - { - if (e.Session != _instrumentPlayer || e.NewStatus != SessionStatus.Disconnected) return; - InstrumentPlayer = null; - Clean(); - } - protected override void Initialize() { base.Initialize(); - if (UserInterface != null) - { - UserInterface.OnClosed += UserInterfaceOnClosed; - } - _instrumentSystem = EntitySystem.Get(); } @@ -190,7 +141,7 @@ namespace Content.Server.Instruments switch (message) { case InstrumentMidiEventMessage midiEventMsg: - if (!Playing || session != _instrumentPlayer || InstrumentPlayer == null) return; + if (!Playing || session != InstrumentPlayer || InstrumentPlayer == null) return; var send = true; @@ -237,12 +188,12 @@ namespace Content.Server.Instruments _lastSequencerTick = Math.Max(maxTick, minTick); break; case InstrumentStartMidiMessage startMidi: - if (session != _instrumentPlayer) + if (session != InstrumentPlayer) break; Playing = true; break; case InstrumentStopMidiMessage stopMidi: - if (session != _instrumentPlayer) + if (session != InstrumentPlayer) break; Playing = false; Clean(); @@ -250,99 +201,20 @@ namespace Content.Server.Instruments } } - private void Clean() + public void Clean() { + if (Playing) + { +#pragma warning disable 618 + SendNetworkMessage(new InstrumentStopMidiMessage()); +#pragma warning restore 618 + } Playing = false; _lastSequencerTick = 0; _batchesDropped = 0; _laggedBatches = 0; } - void IDropped.Dropped(DroppedEventArgs eventArgs) - { - Clean(); -#pragma warning disable 618 - SendNetworkMessage(new InstrumentStopMidiMessage()); -#pragma warning restore 618 - InstrumentPlayer = null; - UserInterface?.CloseAll(); - } - - void IThrown.Thrown(ThrownEventArgs eventArgs) - { - Clean(); -#pragma warning disable 618 - SendNetworkMessage(new InstrumentStopMidiMessage()); -#pragma warning restore 618 - InstrumentPlayer = null; - UserInterface?.CloseAll(); - } - - void IHandSelected.HandSelected(HandSelectedEventArgs eventArgs) - { - if (eventArgs.User == null || !eventArgs.User.TryGetComponent(out ActorComponent? actor)) - return; - - var session = actor.PlayerSession; - - if (session.Status != SessionStatus.InGame) return; - - InstrumentPlayer = session; - } - - void IHandDeselected.HandDeselected(HandDeselectedEventArgs eventArgs) - { - Clean(); -#pragma warning disable 618 - SendNetworkMessage(new InstrumentStopMidiMessage()); -#pragma warning restore 618 - UserInterface?.CloseAll(); - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (Handheld) - return; - - InteractInstrument(eventArgs.User); - } - - bool IUse.UseEntity(UseEntityEventArgs eventArgs) - { - InteractInstrument(eventArgs.User); - return false; - } - - private void InteractInstrument(IEntity user) - { - if (!user.TryGetComponent(out ActorComponent? actor)) return; - - if ((!Handheld && InstrumentPlayer != null) - || (Handheld && actor.PlayerSession != InstrumentPlayer) - || !EntitySystem.Get().CanInteract(user.Uid)) return; - - InstrumentPlayer = actor.PlayerSession; - OpenUserInterface(InstrumentPlayer); - - return; - } - - private void UserInterfaceOnClosed(IPlayerSession player) - { - if (Handheld || player != InstrumentPlayer) return; - - Clean(); - InstrumentPlayer = null; -#pragma warning disable 618 - SendNetworkMessage(new InstrumentStopMidiMessage()); -#pragma warning restore 618 - } - - private void OpenUserInterface(IPlayerSession session) - { - UserInterface?.Toggle(session); - } - public override void Update(float delta) { base.Update(delta); @@ -350,37 +222,22 @@ namespace Content.Server.Instruments var maxMidiLaggedBatches = _instrumentSystem.MaxMidiLaggedBatches; var maxMidiBatchDropped = _instrumentSystem.MaxMidiBatchesDropped; - if (_instrumentPlayer != null - && (_instrumentPlayer.AttachedEntityUid == null - || !EntitySystem.Get().CanInteract(_instrumentPlayer.AttachedEntityUid.Value))) - { - InstrumentPlayer = null; - Clean(); - UserInterface?.CloseAll(); - } - if ((_batchesDropped >= maxMidiBatchDropped || _laggedBatches >= maxMidiLaggedBatches) && InstrumentPlayer != null && _respectMidiLimits) { var mob = InstrumentPlayer.AttachedEntity; -#pragma warning disable 618 - SendNetworkMessage(new InstrumentStopMidiMessage()); -#pragma warning restore 618 - Playing = false; - + // Just in case + Clean(); UserInterface?.CloseAll(); if (mob != null) { EntitySystem.Get().TryParalyze(mob.Uid, TimeSpan.FromSeconds(1)); - Clean(); Owner.PopupMessage(mob, "instrument-component-finger-cramps-max-message"); } - - InstrumentPlayer = null; } _timer += delta; diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs index e3aba6b264..049a9c93d6 100644 --- a/Content.Server/Instruments/InstrumentSystem.cs +++ b/Content.Server/Instruments/InstrumentSystem.cs @@ -1,5 +1,6 @@ using Content.Shared; using Content.Shared.CCVar; +using Content.Server.UserInterface; using JetBrains.Annotations; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; @@ -20,6 +21,8 @@ namespace Content.Server.Instruments _cfg.OnValueChanged(CCVars.MaxMidiEventsPerBatch, OnMaxMidiEventsPerBatchChanged, true); _cfg.OnValueChanged(CCVars.MaxMidiBatchesDropped, OnMaxMidiBatchesDroppedChanged, true); _cfg.OnValueChanged(CCVars.MaxMidiLaggedBatches, OnMaxMidiLaggedBatchesChanged, true); + + SubscribeLocalEvent(InstrumentNeedsClean); } public int MaxMidiEventsPerSecond { get; private set; } @@ -47,6 +50,11 @@ namespace Content.Server.Instruments MaxMidiEventsPerSecond = obj; } + private void InstrumentNeedsClean(EntityUid uid, InstrumentComponent component, ActivatableUIPlayerChangedEvent ev) + { + component.Clean(); + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Server/Objectives/Conditions/StealCondition.cs b/Content.Server/Objectives/Conditions/StealCondition.cs index 93de56b9a0..0f1ec48b77 100644 --- a/Content.Server/Objectives/Conditions/StealCondition.cs +++ b/Content.Server/Objectives/Conditions/StealCondition.cs @@ -1,6 +1,5 @@ using System; using Content.Server.Containers; -using Content.Server.GameObjects; using Content.Server.Objectives.Interfaces; using JetBrains.Annotations; using Robust.Shared.Containers; diff --git a/Content.Server/Power/Components/ActivatableUIRequiresPowerComponent.cs b/Content.Server/Power/Components/ActivatableUIRequiresPowerComponent.cs new file mode 100644 index 0000000000..ed361de718 --- /dev/null +++ b/Content.Server/Power/Components/ActivatableUIRequiresPowerComponent.cs @@ -0,0 +1,12 @@ +using System; +using Robust.Shared.GameObjects; + +namespace Content.Server.Power.Components +{ + [RegisterComponent] + public class ActivatableUIRequiresPowerComponent : Component + { + public override string Name => "ActivatableUIRequiresPower"; + } +} + diff --git a/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs b/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs new file mode 100644 index 0000000000..f14f6c6dcf --- /dev/null +++ b/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs @@ -0,0 +1,53 @@ +using System.Linq; +using Content.Shared; +using Content.Shared.CCVar; +using Content.Shared.ActionBlocker; +using Content.Shared.Hands; +using Content.Shared.Popups; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Content.Shared.Throwing; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Helpers; +using Content.Server.Power.Components; +using Content.Server.UserInterface; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Localization; +using Robust.Shared.GameObjects; +using Robust.Shared.Network; +using Robust.Shared.IoC; + +namespace Content.Server.Power.EntitySystems +{ + [UsedImplicitly] + internal sealed class ActivatableUIRequiresPowerSystem : EntitySystem + { + [Dependency] private readonly ActivatableUISystem _activatableUISystem = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnActivate); + SubscribeLocalEvent(OnPowerChanged); + } + + private void OnActivate(EntityUid uid, ActivatableUIRequiresPowerComponent component, ActivatableUIOpenAttemptEvent args) + { + if (args.Cancelled) return; + if (EntityManager.TryGetComponent(uid, out var power) && !power.Powered) + { + args.User.PopupMessageCursor(Loc.GetString("base-computer-ui-component-not-powered")); + args.Cancel(); + } + } + + private void OnPowerChanged(EntityUid uid, ActivatableUIRequiresPowerComponent component, PowerChangedEvent args) + { + if (!args.Powered) + _activatableUISystem.CloseAll(uid); + } + } +} diff --git a/Content.Server/Solar/Components/SolarControlConsoleComponent.cs b/Content.Server/Solar/Components/SolarControlConsoleComponent.cs index a0a0aef54d..b37f7565db 100644 --- a/Content.Server/Solar/Components/SolarControlConsoleComponent.cs +++ b/Content.Server/Solar/Components/SolarControlConsoleComponent.cs @@ -1,7 +1,7 @@ using System; using Content.Shared.Solar; using Content.Server.Solar.EntitySystems; -using Content.Server.GameObjects.Components; +using Content.Server.UserInterface; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -11,45 +11,8 @@ using Robust.Shared.Maths; namespace Content.Server.Solar.Components { [RegisterComponent] - [ComponentReference(typeof(BaseComputerUserInterfaceComponent))] - public class SolarControlConsoleComponent : BaseComputerUserInterfaceComponent + public class SolarControlConsoleComponent : Component { public override string Name => "SolarControlConsole"; - - [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - - private PowerSolarSystem _powerSolarSystem = default!; - - public SolarControlConsoleComponent() : base(SolarControlConsoleUiKey.Key) { } - - protected override void Initialize() - { - base.Initialize(); - _powerSolarSystem = _entitySystemManager.GetEntitySystem(); - } - - public void UpdateUIState() - { - UserInterface?.SetState(new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun)); - } - - protected override void OnReceiveUserInterfaceMessage(ServerBoundUserInterfaceMessage obj) - { - switch (obj.Message) - { - case SolarControlConsoleAdjustMessage msg: - if (double.IsFinite(msg.Rotation)) - { - _powerSolarSystem.TargetPanelRotation = msg.Rotation.Reduced(); - } - if (double.IsFinite(msg.AngularVelocity)) - { - var degrees = msg.AngularVelocity.Degrees; - degrees = Math.Clamp(degrees, -PowerSolarSystem.MaxPanelVelocityDegrees, PowerSolarSystem.MaxPanelVelocityDegrees); - _powerSolarSystem.TargetPanelVelocity = Angle.FromDegrees(degrees); - } - break; - } - } } } diff --git a/Content.Server/Solar/EntitySystems/PowerSolarControlConsoleSystem.cs b/Content.Server/Solar/EntitySystems/PowerSolarControlConsoleSystem.cs index 2f9728130b..429d763d68 100644 --- a/Content.Server/Solar/EntitySystems/PowerSolarControlConsoleSystem.cs +++ b/Content.Server/Solar/EntitySystems/PowerSolarControlConsoleSystem.cs @@ -1,6 +1,12 @@ +using System; using Content.Server.Solar.Components; +using Content.Server.UserInterface; +using Content.Shared.Solar; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Server.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Maths; namespace Content.Server.Solar.EntitySystems { @@ -10,22 +16,53 @@ namespace Content.Server.Solar.EntitySystems [UsedImplicitly] internal sealed class PowerSolarControlConsoleSystem : EntitySystem { + [Dependency] private PowerSolarSystem _powerSolarSystem = default!; + /// /// Timer used to avoid updating the UI state every frame (which would be overkill) /// private float _updateTimer; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUIMessage); + } + public override void Update(float frameTime) { _updateTimer += frameTime; if (_updateTimer >= 1) { _updateTimer -= 1; + var state = new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun); foreach (var component in EntityManager.EntityQuery()) { - component.UpdateUIState(); + component.Owner.GetUIOrNull(SolarControlConsoleUiKey.Key)?.SetState(state); } } } + + private void OnUIMessage(EntityUid uid, SolarControlConsoleComponent component, ServerBoundUserInterfaceMessage obj) + { + if (component.Deleted) return; + switch (obj.Message) + { + case SolarControlConsoleAdjustMessage msg: + if (double.IsFinite(msg.Rotation)) + { + _powerSolarSystem.TargetPanelRotation = msg.Rotation.Reduced(); + } + if (double.IsFinite(msg.AngularVelocity)) + { + var degrees = msg.AngularVelocity.Degrees; + degrees = Math.Clamp(degrees, -PowerSolarSystem.MaxPanelVelocityDegrees, PowerSolarSystem.MaxPanelVelocityDegrees); + _powerSolarSystem.TargetPanelVelocity = Angle.FromDegrees(degrees); + } + break; + } + } + } } diff --git a/Content.Server/UserInterface/ActivatableUIComponent.cs b/Content.Server/UserInterface/ActivatableUIComponent.cs new file mode 100644 index 0000000000..be030dfffe --- /dev/null +++ b/Content.Server/UserInterface/ActivatableUIComponent.cs @@ -0,0 +1,60 @@ +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 +{ + [RegisterComponent] + public class ActivatableUIComponent : Component, + ISerializationHooks + { + public override string Name => "ActivatableUI"; + + [ViewVariables] + public Enum? Key { get; set; } + + [ViewVariables] public BoundUserInterface? UserInterface => (Key != null) ? Owner.GetUIOrNull(Key) : null; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("inHandsOnly")] + public bool InHandsOnly { get; set; } = false; + + [ViewVariables] + [DataField("singleUser")] + public bool SingleUser { get; set; } = false; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("adminOnly")] + public bool AdminOnly { get; set; } = false; + + [DataField("key", readOnly: true, required: true)] + private string _keyRaw = default!; + + /// + /// 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] + public IPlayerSession? CurrentSingleUser; + + void ISerializationHooks.AfterDeserialization() + { + var reflectionManager = IoCManager.Resolve(); + if (reflectionManager.TryParseEnumReference(_keyRaw, out var key)) + Key = key; + } + } +} + diff --git a/Content.Server/UserInterface/ActivatableUISystem.cs b/Content.Server/UserInterface/ActivatableUISystem.cs new file mode 100644 index 0000000000..0649769802 --- /dev/null +++ b/Content.Server/UserInterface/ActivatableUISystem.cs @@ -0,0 +1,154 @@ +using System.Linq; +using Content.Shared; +using Content.Shared.CCVar; +using Content.Shared.ActionBlocker; +using Content.Shared.Hands; +using Content.Shared.Popups; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Content.Shared.Throwing; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Helpers; +using Content.Server.Administration.Managers; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Localization; +using Robust.Shared.GameObjects; +using Robust.Shared.Network; +using Robust.Shared.IoC; + +namespace Content.Server.UserInterface +{ + [UsedImplicitly] + internal sealed class ActivatableUISystem : EntitySystem + { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnActivate); + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent((uid, aui, _) => CloseAll(uid, aui)); + SubscribeLocalEvent((uid, aui, _) => CloseAll(uid, aui)); + // *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it + SubscribeLocalEvent(OnParentChanged); + SubscribeLocalEvent(OnUIClose); + } + + private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args) + { + if (args.Handled) return; + if (component.InHandsOnly) return; + args.Handled = InteractUI(args.User, component); + } + + private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) + { + if (args.Handled) return; + args.Handled = InteractUI(args.User, component); + } + + private void OnParentChanged(EntityUid uid, ActivatableUIComponent aui, ref EntParentChangedMessage args) + { + CloseAll(uid, aui); + } + + private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args) + { + if (args.Session != component.CurrentSingleUser) return; + if (args.UiKey != component.Key) return; + SetCurrentSingleUser(uid, null, component); + } + + private bool InteractUI(IEntity user, ActivatableUIComponent aui) + { + if (!user.TryGetComponent(out ActorComponent? actor)) return false; + + if (aui.AdminOnly && !_adminManager.IsAdmin(actor.PlayerSession)) return false; + + if (!_actionBlockerSystem.CanInteract(user.Uid)) + { + user.PopupMessageCursor(Loc.GetString("base-computer-ui-component-cannot-interact")); + return true; + } + + var ui = aui.UserInterface; + if (ui == null) return false; + + if (aui.SingleUser && (aui.CurrentSingleUser != null) && (actor.PlayerSession != aui.CurrentSingleUser)) + { + // 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 (ui.SubscribedSessions.Count != 0) return false; + } + + // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. + // This is so that stuff can require further conditions (like power). + var oae = new ActivatableUIOpenAttemptEvent(user); + RaiseLocalEvent(aui.OwnerUid, oae, false); + if (oae.Cancelled) return false; + + SetCurrentSingleUser(aui.OwnerUid, actor.PlayerSession, aui); + ui.Toggle(actor.PlayerSession); + return true; + } + + public void SetCurrentSingleUser(EntityUid uid, IPlayerSession? v, ActivatableUIComponent? aui = null) + { + if (!Resolve(uid, ref aui)) + return; + if (!aui.SingleUser) + return; + + aui.CurrentSingleUser = v; + + RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent(), false); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var component in EntityManager.EntityQuery(true)) + { + var ui = component.UserInterface; + if (ui == null) continue; + // Done to skip an allocation on anything that's not in use. + if (ui.SubscribedSessions.Count == 0) continue; + // Must ToList in order to close things safely. + foreach (var session in ui.SubscribedSessions.ToArray()) + { + if (session.AttachedEntityUid == null || !_actionBlockerSystem.CanInteract(session.AttachedEntityUid.Value)) + { + ui.Close(session); + } + } + } + } + + public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null) + { + if (!Resolve(uid, ref aui, false)) return; + aui.UserInterface?.CloseAll(); + } + } + + public class ActivatableUIOpenAttemptEvent : CancellableEntityEventArgs + { + public IEntity User { get; } + public ActivatableUIOpenAttemptEvent(IEntity who) + { + User = who; + } + } + + public class ActivatableUIPlayerChangedEvent : EntityEventArgs + { + } +} diff --git a/Resources/Prototypes/Entities/Objects/Fun/instruments.yml b/Resources/Prototypes/Entities/Objects/Fun/instruments.yml index 6d891bc4b6..217f1437b5 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/instruments.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/instruments.yml @@ -5,7 +5,10 @@ description: That's an instrument. components: - type: Instrument - handheld: true + - type: ActivatableUI + inHandsOnly: true + singleUser: true + key: enum.InstrumentUiKey.Key - type: UserInterface interfaces: - key: enum.InstrumentUiKey.Key diff --git a/Resources/Prototypes/Entities/Structures/Furniture/instruments.yml b/Resources/Prototypes/Entities/Structures/Furniture/instruments.yml index c4b1058eee..a8db507061 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/instruments.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/instruments.yml @@ -5,7 +5,10 @@ abstract: true components: - type: Instrument - handheld: false + - type: ActivatableUI + inHandsOnly: false + singleUser: true + key: enum.InstrumentUiKey.Key - type: InteractionOutline - type: Rotatable rotateWhileAnchored: true diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index e7114ab462..1dbf7f9e4c 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -126,6 +126,9 @@ - type: AccessReader access: [["HeadOfPersonnel"]] - type: IdCardConsole + - type: ActivatableUI + key: enum.IdCardConsoleUiKey.Key + - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - key: enum.IdCardConsoleUiKey.Key @@ -177,6 +180,9 @@ key: generic_key screen: comm - type: CommunicationsConsole + - type: ActivatableUI + key: enum.CommunicationsConsoleUiKey.Key + - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - key: enum.CommunicationsConsoleUiKey.Key @@ -200,6 +206,9 @@ key: generic_key screen: solar_screen - type: SolarControlConsole + - type: ActivatableUI + key: enum.SolarControlConsoleUiKey.Key + - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - key: enum.SolarControlConsoleUiKey.Key @@ -278,6 +287,9 @@ # - AtmosphericsWaterVapor # - AtmosphericsPlasma # - AtmosphericsTritium + - type: ActivatableUI + key: enum.CargoConsoleUiKey.Key + - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - key: enum.CargoConsoleUiKey.Key